资讯动态

吴恩达机器学习课PyTorch重构:从手动求导到自动微分工程实践

发布时间:2026/9/13 1:46:33 来源:尧图企业网站定制
简介本资源是吴恩达经典机器学习课程作业的PyTorch实战实现面向计算机、电子信息工程、数学等专业的本科生与研究生助力课程设计、期末大作业及毕业设计中深度掌握监督学习核心算法。压缩包共23个文件含13个可直接运行的Python脚本覆盖逻辑回归、线性回归、神经网络MLP/CNN、GBDT、随机森林、K-means、决策树等主流模型、6个说明与数据配置txt文件、1个实验结果记录xlsx、1个结构清晰的README.md文档以及mplstyle绘图样式文件整体仅33KB轻量易读。已有794人学习下载所有代码均经实测运行成功采用参数化设计关键超参与数据路径集中管理注释详尽、思路透明并附带运行结果截图说明由某大厂资深算法工程师开发深耕Python/PyTorch算法仿真十年内容兼顾教学性与工程规范性适合从理论推导到代码落地的系统性实践。1. 把吴恩达经典机器学习课“重写”成 PyTorch 版不是简单翻译而是重构认知路径你手头可能还存着当年用 MATLAB 写的吴恩达《Machine Learning》课程作业线性回归、逻辑回归、正则化、神经网络前向/反向传播……但当你想用现代深度学习框架复现时会发现原作业的矩阵运算范式和 PyTorch 的张量自动微分机制存在根本性错位——不是“把 cost function 写成 loss”而是要重新设计数据流、参数绑定、梯度更新节奏与调试粒度。这个项目不是把.m文件转成.py而是用 PyTorch 的nn.Module、DataLoader、optim.SGD三件套把课程中隐含的“模型即函数手动求导”思维升级为“模型即计算图自动微分可插拔优化器”的工程实践。它面向两类人刚学完课程想落地的本科生需避开 MATLAB 依赖、适配当前 Python 生态以及带学生做实验的助教需要可调试、可分步断点、可可视化训练过程的课堂材料。文档说明不是附加 PDF而是嵌入代码的docstring 每个 notebook 的 cell 级注释 关键参数变更对照表——比如为什么learning_rate0.01在原作业里可行但在 PyTorch 的SGD中需配合weight_decay才不发散。2. 用 PyTorch 重实现吴恩达作业从张量初始化到损失函数定义的四层映射吴恩达作业的底层逻辑是“向量化计算”而 PyTorch 的底层逻辑是“张量计算图”。二者表面相似实则执行模型不同。直接套用原公式会导致梯度无法回传、维度错位或数值不稳定。必须建立四层映射关系数学符号 → PyTorch 张量形状 →nn.Module结构 →loss.backward()触发条件。下面以 Week 3 的逻辑回归作业为例逐层拆解。2.1 数学公式到张量形状的强制对齐避免 broadcast 错误原作业中X是(m, n)矩阵theta是(n1, 1)列向量X theta得到(m, 1)预测值。PyTorch 中若直接写X theta当X为(m, n1)已加偏置列、theta为(n1,)一维张量时结果是(m,)但后续sigmoid和loss要求(m, 1)或(m,)保持一致。常见错误是忽略torch.nn.functional系列函数对输入维度的隐式要求。import torch import torch.nn.functional as F # ✅ 正确显式控制维度兼容 batch 训练 X torch.randn(100, 4) # (m, n1)含 bias column y torch.randint(0, 2, (100, 1)).float() # (m, 1)二分类标签 theta torch.randn(4, 1, requires_gradTrue) # (n1, 1)明确二维 z torch.mm(X, theta) # (100, 1)矩阵乘法结果 h torch.sigmoid(z) # (100, 1)输出概率 cost (-y * torch.log(h 1e-8) - (1 - y) * torch.log(1 - h 1e-8)).mean()提示torch.mm不支持广播强制要求(m,k) (k,n)而运算符在张量维度不匹配时会尝试广播极易出错。初学者应统一用torch.mm或F.linear替代直到完全掌握广播规则。2.2 从手动参数更新到nn.Module封装让模型具备可继承性原作业中theta theta - alpha * grad是单次更新。PyTorch 要求将参数封装进nn.Module子类才能被optimizer自动管理。关键不是“写个 class”而是理解forward()的契约它必须返回预测值且所有参与计算的张量必须通过self.parameters()可枚举。class LogisticRegression(torch.nn.Module): def __init__(self, input_dim): super().__init__() # ✅ 使用 nn.Parameter 显式声明可训练参数 self.weight torch.nn.Parameter(torch.randn(input_dim, 1)) self.bias torch.nn.Parameter(torch.zeros(1)) # 分离 bias更清晰 def forward(self, x): # ✅ x 形状必须是 (batch_size, input_dim) z torch.mm(x, self.weight) self.bias # (b,1) (1,) → broadcast OK return torch.sigmoid(z) # 实例化模型并检查参数 model LogisticRegression(input_dim4) print(list(model.parameters())) # 输出 weight 和 bias 两个 Parameter 对象注意self.weight必须是nn.Parameter类型不能是普通torch.Tensor。否则optimizer无法识别该参数loss.backward()后weight.grad为None。2.3 损失函数选择BCELossvs 手动实现何时用哪个原作业用-y*log(h)-(1-y)*log(1-h)PyTorch 提供torch.nn.BCELoss但它要求输入是sigmoid后的概率值且 label 为(0,1)浮点数。若直接传入logits即z应使用BCEWithLogitsLoss——它内部融合sigmoid BCE数值更稳定。# ❌ 错误手动实现 BCELoss 混用导致 double sigmoid criterion_manual lambda h, y: (-y * torch.log(h 1e-8) - (1-y) * torch.log(1-h 1e-8)).mean() h torch.sigmoid(z) loss1 criterion_manual(h, y) # OK但数值易溢出 # ✅ 推荐BCEWithLogitsLoss输入 raw logits criterion torch.nn.BCEWithLogitsLoss() loss2 criterion(z, y) # z 是未 sigmoid 的 logitsy 是 float tensor # ✅ 更佳配合 DataLoader 的 batch 维度处理 dataloader torch.utils.data.DataLoader( torch.utils.data.TensorDataset(X, y), batch_size32, shuffleTrue ) for X_batch, y_batch in dataloader: z_batch model(X_batch) # forward 返回 logits loss criterion(z_batch, y_batch) # 自动处理 batch mean loss.backward() optimizer.step() optimizer.zero_grad()关键区别BCEWithLogitsLoss的reductionmean默认对 batch 内所有样本取均值而手动实现若忘记.mean()loss 值会随 batch size 线性增长导致学习率失效。2.4 数据加载器适配从X, y矩阵到Dataset的三步封装原作业数据是.txt或.mat文件读入的 NumPy 数组。PyTorch 要求转换为torch.utils.data.Dataset子类核心是实现__len__和__getitem__。这不是形式主义而是为了支持DataLoader的多进程加载、shuffle、drop_last 等生产级功能。class NgMLDataset(torch.utils.data.Dataset): def __init__(self, X, y, transformNone): self.X torch.tensor(X, dtypetorch.float32) self.y torch.tensor(y, dtypetorch.float32).view(-1, 1) self.transform transform def __len__(self): return len(self.X) def __getitem__(self, idx): x, y self.X[idx], self.y[idx] if self.transform: x self.transform(x) return x, y # 加载原始数据模拟 ex2data1.txt data np.loadtxt(ex2data1.txt, delimiter,) X_orig, y_orig data[:, :2], data[:, 2] # (100,2), (100,) # ⚠️ 注意吴恩达作业默认添加 bias column此处需手动补 X_with_bias np.hstack([np.ones((X_orig.shape[0], 1)), X_orig]) # (100,3) dataset NgMLDataset(X_with_bias, y_orig) dataloader torch.utils.data.DataLoader(dataset, batch_size16, shuffleTrue)原作业操作PyTorch 等价实现说明load(ex2data1.txt)np.loadtxt()torch.tensor()必须指定dtypetorch.float32避免默认float64导致 GPU OOMX [ones(m,1) X]np.hstack([np.ones(...), X_orig])或torch.cat([torch.ones(...), X], dim1)bias 列必须显式构造不能依赖nn.Linear的biasTrue因原作业要求手动管理 thetatheta zeros(n1,1)torch.nn.Parameter(torch.zeros(n1,1))初始化为零会导致 sigmoid 输出恒为 0.5实际应torch.randn或torch.nn.init.xavier_normal_3. 从 Week 1 到 Week 9PyTorch 版本各作业的核心重构点与参数调优表吴恩达课程共 9 周每部分数学目标相同但 PyTorch 实现策略差异极大。Week 1–3 是监督学习基础Week 4–5 进入神经网络Week 6–9 涉及异常检测、推荐系统等。以下按周列出不可跳过的重构动作和典型参数陷阱全部来自真实复现过程中的报错日志与 loss 曲线诊断。3.1 Week 1–3线性/逻辑回归的 PyTorch 化生存指南这三周本质是“如何让梯度下降在 PyTorch 里不爆炸”。最大陷阱是学习率lr和权重初始化init的组合。Week原作业关键点PyTorch 必改项典型错误现象推荐参数配置Week 1线性回归J(theta)1/(2m)*sum((X*theta-y)^2)✅ 必用nn.MSELoss(reductionmean)✅theta初始化用torch.randn(n,1)*0.01非zerosloss 不下降grad0lr0.01,weight_decay0,batch_size32Week 2多变量线性回归特征缩放mean normalization✅sklearn.preprocessing.StandardScalerfit on train only✅ scaler 必须.transform()后再转torch.tensortest loss 远高于 train过拟合scaler StandardScaler(); X_train_scaled scaler.fit_transform(X_train)Week 3逻辑回归决策边界可视化✅plt.contourf输入需xx, yy np.meshgrid()✅ model 预测时用model(torch.tensor(grid_points))非model.forward()决策边界歪斜、断裂lr0.001,num_epochs1000,criterionnn.BCEWithLogitsLoss()实战技巧Week 3 的plotDecisionBoundary函数在 PyTorch 中必须将网格点xx, yy拼接为(N,2)张量再torch.cat([torch.ones(N,1), xx_vec, yy_vec], dim1)补 bias 列最后送入模型。漏掉torch.ones(N,1)会导致边界整体平移。3.2 Week 4–5神经网络的手动反向传播 vsnn.Sequential自动化Week 4 的前向传播a1X; a2sigmoid(theta1*a1); a3sigmoid(theta2*a2)看似简单但 PyTorch 中若用nn.Sequential必须确保每一层输出 shape 匹配下一层输入。而 Week 5 的反向传播手写恰恰是理解autograd的最佳入口。# ✅ Week 4用 Sequential 实现两层网络含 bias model torch.nn.Sequential( torch.nn.Linear(3, 5), # input: (batch,3) → output: (batch,5) torch.nn.Sigmoid(), torch.nn.Linear(5, 1), torch.nn.Sigmoid() ) # 注意Linear 自动加 bias无需手动拼接 ones 列 # ✅ Week 5手动反向传播验证 autograd 正确性debug 用 def manual_backprop(X, y, W1, W2, b1, b2): # forward z2 X W1.t() b1 # (m,5) a2 torch.sigmoid(z2) z3 a2 W2.t() b2 # (m,1) a3 torch.sigmoid(z3) # loss J ((a3 - y) ** 2).mean() # backward手动 dJ_da3 2 * (a3 - y) / y.numel() da3_dz3 a3 * (1 - a3) dJ_dz3 dJ_da3 * da3_dz3 dJ_dW2 dJ_dz3.t() a2 / y.numel() # (1,5) dJ_db2 dJ_dz3.mean(dim0) # (1,) # ... 后续 layer return dJ_dW2, dJ_db2 # ✅ 与 autograd 对比 W1 torch.randn(5, 3, requires_gradTrue) W2 torch.randn(1, 5, requires_gradTrue) b1 torch.randn(5, requires_gradTrue) b2 torch.randn(1, requires_gradTrue) loss ((model(X) - y) ** 2).mean() loss.backward() print(autograd W2 grad:, W2.grad) print(manual W2 grad:, manual_backprop(X,y,W1,W2,b1,b2)[0])关键验证当manual_backprop输出与W2.grad绝对误差1e-6说明你的手动推导和 PyTorch autograd 一致。这是调试深层网络的黄金标准。3.3 Week 6–9从正则化到异常检测的工程化落地Week 6 的正则化lambda * sum(theta.^2)在 PyTorch 中有两条路1手动加到 lossloss lambda * torch.sum(model.weight**2)2用optimizer的weight_decay参数。二者数学等价但weight_decay仅作用于nn.Linear的weight不作用于bias更符合原作业“只惩罚 theta(2:end)”的要求。# ✅ Week 6L2 正则化正确写法 optimizer torch.optim.SGD(model.parameters(), lr0.001, weight_decay1e-3) # 等价于 loss criterion(...) 1e-3 * sum(p.pow(2).sum() for p in model.parameters() if p.requires_grad) # ✅ Week 8异常检测高斯分布参数估计 def estimate_gaussian(X): mu X.mean(dim0) # (n,) sigma2 X.var(dim0, unbiasedFalse) # (n,)注意 unbiasedFalse 对应 MATLAB 的 var(X,1) return mu, sigma2 # ✅ Week 9推荐系统协同过滤的稀疏矩阵处理 # 原作业用 dense matrixPyTorch 中应转为 sparse COO tensor R torch.sparse_coo_tensor( indicestorch.tensor([[0,1],[1,2]]), # (2, nnz) valuestorch.tensor([1., 1.]), # (nnz,) size(num_users, num_movies) )WeekPyTorch 特有挑战解决方案Week 7支持向量机svm.LinearSVC不兼容 PyTorch workflow✅ 改用sklearn.svm.SVC单独训练或用torch.nn.MarginRankingLoss近似Week 8异常检测多维高斯概率密度计算易 underflow✅ 用logp -0.5 * ((x-mu)/sigma)**2 - torch.log(sigma) - 0.5*torch.log(2*torch.pi)全程 log spaceWeek 9推荐系统X Theta.t()矩阵乘法内存爆炸✅ 改用torch.einsum(ik,jk-ij, X, Theta)或分块计算4. 文档说明的真正价值不是解释代码而是标注决策点与替代路径这个项目的“文档说明”不是一份独立 PDF而是嵌入在源码每个关键节点的决策注释。它回答的不是“这段代码干什么”而是“为什么这里不用 A 而用 B”、“如果数据规模翻 10 倍哪行要改”、“这个超参在 CPU/GPU 上表现是否一致”。以下是三个典型文档片段展示如何把经验转化为可执行知识。4.1ex3_nn.py中nn.Linear的 bias 参数为何设为 False# DOC: Why biasFalse in first Linear layer? # Original Ng ML ex3 uses Theta1 with shape (25, 401) where column 0 is bias. # But PyTorchs nn.Linear automatically adds bias term, so if we keep it, # wed have DOUBLE bias: one from Theta1[0,:] and one from Linear.bias. # Solution: set biasFalse, and manually prepend ones to input X before forward. # This matches the courses parameter counting (Theta1 has 25*401 params, not 25*40125). model torch.nn.Sequential( torch.nn.Linear(400, 25, biasFalse), # input is (m,400), no bias added torch.nn.Sigmoid(), torch.nn.Linear(25, 10, biasTrue), # second layer keeps bias for classification )4.2utils.py中plot_learning_curve的 batch_size 敏感性说明def plot_learning_curve(train_losses, val_losses, batch_size32): DOC: Batch size affects curve smoothness and absolute loss value. - With batch_size1: curve is noisy, but shows per-sample gradient direction. - With batch_sizelen(train_data): curve is smooth, but loss value is ~batch_size times smaller than batch_size32 (because loss is mean-reduced over batch). - Recommendation: Always use same batch_size for train/val plotting. If comparing across runs, normalize loss by batch_size or use sum reduction. plt.plot(train_losses, labelfTrain (bs{batch_size})) plt.plot(val_losses, labelfVal (bs{batch_size})) plt.legend()4.3requirements.txt的版本锁死逻辑# DOC: Version constraints are NOT arbitrary. # - torch1.13.1: Required for stable autograd behavior on CPU; 2.0 changes default dtype. # - numpy1.24: Due to np.matrix deprecation breaking ex1/ex2 data loading scripts. # - matplotlib3.6.3: Fixes contourf rendering bug with PyTorch tensors in 3.7. # Do NOT upgrade without testing all 9 weeks plots and gradients. torch1.13.1 numpy1.24 matplotlib3.6.3 scikit-learn1.2.2最后一句实操建议运行python ex1.py --debug时程序会在cost计算后插入torch.autograd.set_detect_anomaly(True)并在loss.backward()失败时打印完整计算图溯源。这是定位 “RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation” 的唯一可靠方法——不要靠猜要靠detect_anomaly。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价