
用PyTorch实现一个多层感知机MLP是我心里深度学习入门阶段最值得亲手走一遍的项目。它不涉及CNN的卷积、RNN的时序、Transformer的注意力只围绕全连接层、激活函数、损失函数和反向传播这四件事展开而这几件事恰恰是所有深度学习模型共用的骨架。做完这个项目你会真正掌握“定义模型—加载数据—训练—评估”这套在PyTorch里的核心工作流之后再接触任何复杂模型都会有底气去拆解和调试。无论你是刚接触PyTorch的小白还是想系统梳理神经网络基础的老手这篇从原理到代码再到坑点排查的完整记录都能给你一份可以直接参考的实操路线。1. 动手前的思路为什么MLP是第一个练手项目1.1 MLP是一个完整学习闭环的最小单元很多人学深度学习一上来就看CNN、Transformer卷积、多头注意力、残差连接这些名词堆在一起很容易被复杂结构淹没反而忽略了最本质的东西模型如何从输入得到输出损失如何计算梯度如何回传参数如何更新。多层感知机恰好把这一整套流程压缩到了最小规模。它没有卷积的局部连接没有循环的时序依赖没有注意力机制就是简单的全连接层堆叠。正因为结构单纯你才能清楚地看到每一层矩阵运算的参数从哪来、梯度经过哪些路径回到每个参数身上。把MLP彻底搞懂之后再去看ResNet里的残差跳连、Transformer里的LayerNorm你会发现它们都是在这个基础骨架上做局部优化核心逻辑并没有变。这个项目还逼着你走完一个“完整项目循环”数据准备、模型搭建、训练迭代、评估验证、结果可视化。这五个环节在任何一个实际任务里都缺一不可而MLP因为结构简单让你把注意力全部放在流程本身而不是被复杂模型的问题带走。1.2 跑通这个项目你掌握的是一套可迁移能力具体来说完成这个MLP项目后你会获得四方面能力。第一数据加载与预处理能力会用Dataset和DataLoader组装数据流知道为什么要把图像转成张量、为什么要归一化、为什么要打乱顺序。第二模型定义能力会用nn.Module组织网络结构理解forward里每一步计算和维度变化。第三训练循环能力能写出包含前向传播、损失计算、反向传播、参数更新的标准训练代码并且明白每个环节为什么必不可少。第四评估与调参能力会区分训练集和测试集的作用能通过曲线判断模型是没学够还是学过头。这四块能力是所有PyTorch项目的通用底座。以后你换一个目标检测数据集换一个Transformer模型外围的数据加载、训练评估代码几乎可以原样复用你要改的只是模型定义和输入输出适配。这就是为什么我强烈建议入门者先把MLP做扎实而不是急着去跑那些看着炫酷的大模型。1.3 适合什么基础的人能达到什么目标如果你是刚接触PyTorch的初学者建议照着本文流程把代码完整敲一遍重点观察代码结构和每个模块的职责先不求调出多么惊人的精度只要能把loss曲线降下来、测试集上看到一个合理准确率就算达标。如果你已经会写一些简单模型但遇到问题上手就慌这篇文章后半部分的训练经验和排查清单会更适合你里面整理了我实际踩过的坑和固定的排查顺序。如果你正在准备面试或复习深度学习基础也值得快速过一遍第2章把激活函数作用、交叉熵与Softmax的关系这些关键概念理清楚。2. 核心原理拆解MLP由哪些部件组成为什么要这样设计2.1 从单层感知机说起线性模型的天然天花板要理解多层感知机得先知道它要解决的问题在哪。单层感知机的形式很简单把输入向量和权重矩阵相乘加上偏置过一个激活函数。在二分类任务里它本质上是在特征空间中画一条直线或者一个超平面把两类样本分隔开。如果数据本身是线性可分的这条线就能解决问题但如果数据是非线性分布的单层感知机就完全无能为力。最经典的例子是异或问题四个样本点(0,0)和(1,1)是一类(0,1)和(1,0)是另一类。无论你怎么画直线都不可能把这两类完美分开。这个看似简单的问题直接暴露了线性模型的局限它只能做线性分割无法处理特征之间的非线性组合关系。要让模型具备非线性表达能力就必须引入多个神经元的分层组合这就是多层感知机出现的原因。多层结构的核心思想是先用前面的隐藏层把原始输入逐步变换到新的特征空间在这个空间里原来线性不可分的模式可能变得线性可分最后一层只要再做一个简单的线性分类即可。层数越深理论上可以表达的函数关系越复杂。这也是为什么MLP可以被视为所有现代深度学习模型的雏形。2.2 激活函数为什么不可或缺如果仅仅是多层线性变换堆叠那无论堆多少层最终等价于一次线性变换。道理很简单线性函数套线性函数结果还是线性函数。比如第一层输出是W1x加b1第二层再对结果做一次线性变换矩阵相乘后合并起来还是某个权重矩阵乘以输入加上某个偏置。换句话说没有非线性激活函数深层网络就失去了“深”的意义模型的表达能力不会随着层数增加而增强。激活函数的作用就是把非线性引进来让每一层都会对上一层的输出做一次非线性扭曲。看到负值时直接输出0这种简单的截断操作在数学上就足以让多层网络拥有拟合任意复杂函数的能力。实际工程里隐藏层选择有很多种我整理了一个对比表激活函数输出范围主要特点常见使用场景Sigmoid0到1平滑可导但两端梯度接近0容易梯度消失二分类输出层Tanh-1到1比Sigmoid梯度更强但仍存在饱和区隐藏层历史方案ReLU0到正无穷计算快正区间梯度恒为1有效缓解梯度消失隐藏层首选LeakyReLU负半轴保留小斜率避免ReLU的神经元死亡问题负值信息重要的场景输出层的选择取决于任务类型。做多分类时通常在最后一层接Softmax把输出转换成概率分布做回归时则不需要激活函数直接输出连续值。有一点要注意在PyTorch里如果你用的是nn.CrossEntropyLoss模型最后一层不需要手动加Softmax这个损失函数内部已经把Softmax和对数损失融合在一起。我见过不少新手在这里画蛇添足手动加一层Softmax之后发现loss很难收敛就是这个原因。2.3 前向传播、损失函数与反向传播训练到底在做什么把整个MLP的数学过程拆开看其实很清晰。假设输入是x第一层权重是W1偏置是b1那么第一隐藏层输出h1 relu(W1 x b1)。接着第二层权重W2和偏置b2得到h2 relu(W2 h1 b2)。最后输出层z W3 h2 b3。整个过程从输入到输出就是前向传播。它做的事情很直白把原始数据不断变换直到最后一层产生一个预测值。有了预测值就要算和真实标签之间的差距这个差距就是损失。在分类任务里交叉熵损失是常用选择它的数值越小说明预测分布和真实分布越接近。训练的目标就是找到一组参数让损失尽可能小。那怎么找呢用梯度下降计算损失对每个参数的偏导数也就是梯度然后让参数沿着梯度的反方向走一小步损失就会下降。这一小步的尺度就是学习率。反向传播做的就是高效计算梯度这件事。它利用链式法则从输出层开始一层一层的往回传播误差把损失对每一层参数的梯度都算出来。在早期框架里这些梯度要手动推导并编码非常痛苦。PyTorch的自动求导机制把这个过程完全自动化了当你执行loss.backward()时框架会遍历计算图自动算出每个需要梯度的参数的梯度值然后存到参数对应的grad属性上。之后optimizer.step()读取这些梯度完成参数更新。理解了这一整条链路你就会明白训练代码里为什么是那几行固定的操作也就能在模型不收敛时知道应该去查哪个环节。3. 一步一步用PyTorch实现MLP训练全流程3.1 环境准备与数据加载实现这个项目只需要最基本的深度学习环境。你需要安装PyTorch和torchvision数据分析方面用matplotlib画曲线就足够了。如果你有NVIDIA显卡可以把模型放到GPU上训练没有的话CPU上跑这个规模的MLP也完全没问题一开始不涉及复杂模型CPU慢不了多少。数据我建议用公开的手写数字数据集MNIST。它是图像分类里的“入门标准题”每张图片是28×28的灰度图共10个类别训练集有6万张测试集有1万张。选它有三个理由图片尺寸小适合MLP这种把像素展平做全连接的模型类别均衡且任务难度适当MLP在这样的数据集上很容易达到97%以上的准确率能直观看到效果下载和加载都方便torchvision一行代码就能搞定。数据加载的代码很简单import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader import matplotlib.pyplot as plt transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset torchvision.datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform ) test_dataset torchvision.datasets.MNIST( root./data, trainFalse, downloadTrue, transformtransform ) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size1024, shuffleFalse)这里有两个容易被忽略的细节。第一个是transforms.ToTensor()它会把形状为(H, W, C)且在0到255范围的数据转成形状为(C, H, W)、数据类型为浮点、范围在0到1之间的张量。第二个是transforms.Normalize()它用均值0.1307和标准差0.3081做标准化。这两个数值是MNIST数据集的整体统计量标准化后像素分布近似均值为0、方差为1能让模型收敛更稳定。DataLoader里的batch_size和shuffle也很关键。shuffleTrue让模型在每个epoch看到的样本顺序都不同避免模型按照数据顺序学到不必要的规律。测试集不需要shuffle因为评估结果与顺序无关而且保持固定顺序更便于复现。batch_size为64是比较常见的默认选择它既不会小到让训练过于震荡也不会大到占用太多显存。3.2 模型定义用nn.Module封装一个三层MLP有了数据流接下来就是模型本体。用nn.Module定义一个三层全连接网络输入是784维也就是28×28像素展平后的长度两个隐藏层分别有256和128个神经元输出层是10维对应10个数字类别。class MLP(nn.Module): def __init__(self, input_dim784, hidden_dims(256, 128), output_dim10): super(MLP, self).__init__() self.fc1 nn.Linear(input_dim, hidden_dims[0]) self.fc2 nn.Linear(hidden_dims[0], hidden_dims[1]) self.fc3 nn.Linear(hidden_dims[1], output_dim) self.relu nn.ReLU() def forward(self, x): x x.view(x.size(0), -1) x self.relu(self.fc1(x)) x self.relu(self.fc2(x)) x self.fc3(x) return x第一个关键点是x.view(x.size(0), -1)。CNN能直接处理二维图像而Linear层要求输入是二维的即[batch_size, feature_dim]。所以需要把每个批次里的28×28图像展平成784维向量这个操作不会改变样本数量只改变每个样本的表示形式。如果你忘记做这一步会直接遇到矩阵维度不匹配的报错。第二个关键点是为什么选择256和128这两个隐藏层大小。理论上只要隐藏层神经元足够多单隐藏层就能逼近任何连续函数但实际中多一层往往能更好地平衡表达能力和参数规模。256到128的过渡也不是硬性标准256→128→10是一个“不算太浪费、又不至于欠拟合”的常规配置。如果数据集更复杂可以适当放大这些数字如果追求轻量也可以单隐藏层128个神经元。第三个关键点是继承nn.Module的好处。它让PyTorch自动识别并注册模型里的所有子模块和参数后续调用.to(device)模型会被整体搬到GPU调用.state_dict()能保存权重调用model.train()和model.eval()能切换训练和推理模式。这些能力都是现成封装好的自己手工管理权重列表会非常繁琐。3.3 训练循环损失函数与优化器选型模型定义好后要选择训练用的损失函数和优化器。device torch.device(cuda if torch.cuda.is_available() else cpu) model MLP().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3)损失函数用CrossEntropyLoss优化器先选Adam。这里解释一下选择逻辑。CrossEntropyLoss内部做了三件事对模型输出做Softmax、取对数、计算负对数似然。把这些步骤合并在一起不只是为了代码简洁更是为了数值稳定性。如果你手动先Softmax再算交叉熵在模型预测置信度极高时log运算可能得到负无穷或NaN而融合版本用特殊数学变换避免了这个问题。优化器为什么先选Adam而不是SGDAdam会根据历史梯度自适应调整每个参数的学习率对学习率的选择不那么敏感即使lr设置得稍大一些也有较高的概率正常收敛。这对入门阶段非常友好。SGD配合动量也有它的优势比如有时能收敛到泛化能力更好的解但它对学习率的依赖更强需要更细致的调节。我个人的建议是第一版代码用Adam跑通流程后续想追求更好泛化性能时再切换到SGD加动量做对比实验。接下来是每个epoch循环里的核心代码def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0.0, 0, 0 for images, labels in loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) correct (outputs.argmax(dim1) labels).sum().item() total images.size(0) return total_loss / total, correct / total这五行的顺序是有讲究的。optimizer.zero_grad()必须放在前向传播之前因为PyTorch的梯度是累加模式如果不把上一batch的梯度清零这一batch的梯度会和之前的叠加导致更新方向完全错误。然后是前向传播得到outputs计算loss调用backward()让框架自动求出所有参数梯度最后step()真正更新参数。评估函数和训练函数结构类似但有两个关键区别def evaluate(model, loader, device): model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in loader: images, labels images.to(device), labels.to(device) outputs model(images) correct (outputs.argmax(dim1) labels).sum().item() total labels.size(0) return correct / total第一个区别是model.eval()。很多模型在训练和推理模式下行为不同比如Dropout在训练时随机丢弃神经元评估时需要关闭BatchNorm在训练时统计新批次的均值方差评估时要用历史统计值。虽然我们这个纯全连接模型当前没有这些层但养成切换模式的习惯很重要。第二个区别是torch.no_grad()。它告诉PyTorch这段计算不需要构建计算图也就不会保存中间梯度信息能大幅减少显存占用并加快评估速度。在with语句块里即使你忘记写model.eval()至少也不会有梯度累积的问题。3.4 多轮训练记录指标并可视化整体训练流程就是把上面的函数循环执行多个epoch同时记录每个epoch的训练损失、训练准确率和测试准确率。epochs 10 train_losses, train_accs, test_accs [], [], [] for epoch in range(epochs): train_loss, train_acc train_one_epoch( model, train_loader, criterion, optimizer, device ) test_acc evaluate(model, test_loader, device) train_losses.append(train_loss) train_accs.append(train_acc) test_accs.append(test_acc) print(fEpoch {epoch1}/{epochs} | fLoss {train_loss:.4f} | fTrain Acc {train_acc:.4f} | fTest Acc {test_acc:.4f})为什么要同时记录训练集指标和测试集指标因为只看训练集loss下降无法判断模型是否真的学到了通用规律。如果训练集准确率很高、测试集准确率明显偏低说明模型是在记忆训练样本而不是在泛化这种情况就是过拟合。只有两个指标同步变化、差距不大才可以认为模型状态比较健康。训练结束后把损失曲线和准确率曲线用matplotlib画出来plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(range(1, epochs 1), train_losses, markero) plt.title(Training Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.subplot(1, 2, 2) plt.plot(range(1, epochs 1), train_accs, markero, labelTrain) plt.plot(range(1, epochs 1), test_accs, markers, labelTest) plt.title(Accuracy) plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.legend() plt.tight_layout() plt.show()观察曲线时典型的健康状态是loss在第一轮快速下降之后逐步减缓训练和测试准确率同步上升并在几个epoch后趋于平稳。针对MNIST这个数据集这个三层MLP在未做任何精细调参的情况下测试准确率通常在97%到98%左右。如果你得到的数值略低不用惊慌随机种子会带来1%左右的波动更有可能的原因是学习率或批次参数不同稍加调整就能改善。4. 训练中的关键经验归一化、学习率与过拟合控制4.1 数据归一化不是可选项而是默认项很多新手第一次跑模型时为了省事直接去掉归一化然后发现loss下降非常慢甚至出现训练不稳定的情况。原因很简单原始图像像素值在0到255之间不同维度数值大小差异很大而神经网络对输入数值范围是敏感的。归一化把所有特征统一到差不多的尺度让梯度更新更加平滑也能避免某个维度因为数值太大而主导整个更新方向。在MNIST里用到的标准化公式是(x减均值)除以标准差。标准化后的数据均值接近0、标准差接近1这在数学上等价于让损失函数在不同方向上的“坡度”更加均衡梯度下降走得更直、更稳。实际对比一下就知道未归一化时可能第5个epoch才勉强开始出效果而归一化后往往第1个epoch就已经看到明显精度提升。要特别注意推理阶段的数据处理必须和训练阶段保持一致。如果训练时用了这个标准化预测时却忘了减均值除以标准差模型看到的分布就跟训练时完全不同精度会大幅下降。所以transform这套预处理逻辑要定义成一个可复用对象训练和预测都走同一套。4.2 学习率与batch size的配合关系学习率可以说是训练时最需要反复调的参数。学习率太大会导致loss震荡甚至直接变成NaN因为参数一步跨出的距离太大越过了损失函数的最低点甚至在陡峭区域出现数值爆炸。学习率太小则会让训练极其缓慢一个epoch下来loss几乎没动浪费大量时间。batch size的影响同样关键。小批量比如32或64每个batch的梯度方向带有更多随机性这种噪声反而能帮助模型跳出局部极值对小数据集来说等效于一种正则化效果。大批量比如256或512梯度更平滑训练更稳定但可能更容易收敛到比较尖锐的极值点泛化性能并不一定更好。大批量还意味着每个epoch更新的次数变少模型看到的“多样性”被压缩了。学习和批次配合的经验我整理一下起步阶段推荐batch size取64Adam学习率取0.001。如果训练速度太慢先把学习率调大一倍到0.002或0.005试试如果发现loss震荡厉害就调小到0.0005或0.0001。调整幅度建议按10倍或2倍阶梯来不要一次改动太大。也可以用学习率调度器比如torch.optim.lr_scheduler.ReduceLROnPlateau当测试准确率连续几个epoch不再改善时自动降低学习率省去手动干预的麻烦。我在项目里试过这个方法它最大的好处是后期学习率能自适应当前的收敛状态。4.3 过拟合与欠拟合怎么看、怎么办训练过程中最常遇到的两个状态就是欠拟合和过拟合两者要区分对待。欠拟合的表现是训练集上的loss一直降不下来准确率也上不去模型压根没有学会训练数据。排查方向是先确认模型容量是不是太小比如隐藏层只有几十个神经元表达力肯定不足可以加宽或加深。然后再看训练是不是没跑够epoch太少模型还处于欠学习阶段。如果数据和loss计算都没问题加大模型规模并延长训练时间通常会带来明显改善。过拟合的表现正好相反训练集准确率一路飙到接近100%但测试集准确率停在某个点不再上升甚至开始下降。这说明模型把训练集中的噪声和细节都背下来了失去了泛化能力。处理过拟合有几种常用手段。第一种是加Dropout层在训练时随机丢弃一部分神经元输出迫使模型不能过度依赖某几个特征相当于让网络学得更鲁棒。第二种是加权重衰减也就是weight decay它在损失函数中增加对参数的平方惩罚让参数值保持相对较小抑制模型的复杂度。第三种是减少模型容量或增加数据量如果能拿到更多训练样本过拟合问题通常会自然缓解。我建议做一个小实验来加深理解在模型里加入Dropout层设置dropout概率为0.2或0.5对比前后测试集准确率的变化。你会发现训练准确率可能略有下降但测试准确率通常会更好尤其是当原始模型已经处于过拟合区间时这种效果会更加明显。注意使用Dropout时训练代码里的model.train()和评估代码里的model.eval()就变得至关重要了因为Dropout只在训练模式下生效。4.4 随机种子与可复现性还有一个容易被忽视的细节是实验可复现性。PyTorch里模型权重初始化是随机的DataLoader打乱数据顺序也是随机的如果跑到GPU上某些运算还可能引入不确定性的额外波动。这意味着同一条代码跑两次结果可能略有不同。如果你需要做对比实验比如比较加Dropout前后的精度差异这种随机波动会干扰你的判断。解决的方法很直接在训练脚本开头统一设置随机种子def set_seed(seed42): torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)固定随机种子后第二次运行会得到和第一次一样的结果这样可以更准确地判断精度变化到底是来自代码改动还是纯随机。当然固定种子只是保证自己实验的内部一致性不同机器、不同环境之间仍可能存在细微差异这是正常的不必钻牛角尖。5. 踩坑记录常见报错与排查思路汇总5.1 维度不匹配与device不一致MLP项目里最频繁的报错就是矩阵乘法维度不匹配典型提示是“mat1 and mat2 shapes cannot be multiplied”。这个错误几乎都是因为Linear层收到的输入维度不对。比如输入还是三维的[batch, 1, 28, 28]而Linear期望的是[batch, features]。解决的办法就是在forward第一行加入x.view(x.size(0), -1)把每个样本展平成784维。排查这类问题最直接的方式是在forward里临时打印x.shape从第一层开始逐步看每个阶段的维度变化很快就能定位到出错的那一层。你还可以前向构造一个假数据比如torch.randn(4, 1, 28, 28)在模型外面手动调用一次并打印结果这样不必启动整个训练就能验证维度链路。另一个高频问题是device不匹配。当你用GPU训练时数据和模型都需要搬到同一块GPU上。如果数据还在CPU上模型在GPU上运行就会报错。我的习惯是定义device变量然后统一用images.to(device)和model.to(device)保证一切都在同一设备上这样就不会出现一边在显卡一边在内存的尴尬局面。5.2 loss变成NaN或inf怎么办训练时loss变成NaN是最让人头疼又最常见的问题。根据我自己的踩坑经验它通常来自这么几个原因学习率过大导致梯度更新过度震荡数据没有归一化导致数值范围太大损失函数里的log操作遇到0或负数以及某些层的中间激活值出现爆炸。排查时可以按顺序来。先把学习率缩小十倍比如从0.001改成0.0001如果loss恢复正常说明是学习率的问题。然后检查数据预处理确认ToTensor和Normalize是否都生效。再做一次单batch过拟合测试用训练集里取出的第一个batch反复迭代几十次看看loss能否正常下降如果这一步都发生NaN那基本可以确定是模型结构或数据本身有问题而不是训练参数的问题。对于某些梯度容易爆炸的模型还可以在反向传播之后手动加上梯度裁剪限制梯度的最大范数让训练更稳定。5.3 模型不收敛的通用排查顺序如果模型训练了很久loss就是不下降准确率一直在随机猜测水平徘徊我建议按固定的顺序排查不要东看一个西看一个。第一步取一个batch的数据让模型反复在这个batch上训练如果模型连一个小批数据都拟合不了那问题一定在模型结构、数据预处理或损失函数上而不是优化策略的问题。第二步检查标签是不是从0开始的连续整数因为CrossEntropyLoss要求标签是类别索引比如0到9如果混入其它数值会直接报错或导致错误的监督信号。第三步确认每个batch进入模型前是否调用了zero_grad忘记清零梯度可能会导致更新方向混乱。第四步检查输出层是否放了不合适的激活函数比如在分类任务里输出层加了ReLU会直接把负值截断网络整体表达能力立刻受损。第五步再看学习率和初始化参数如果学习率太小或者模型初始权重全零梯度更新也会很慢。把这些排查项整理成一个速查表遇到问题按表逐项走效率会高很多现象典型原因排查顺序loss不下降学习率过小、模型欠拟合先测单batch拟合再放大学习率loss剧烈震荡学习率过大、batch太小调小学习率适当增大batch训练精度高、测试精度低过拟合加Dropout、权重衰减减少模型容量训练测试精度都低模型容量不足、数据未归一化加宽加深网络、检查预处理loss为NaN学习率过大、数值不稳定调小lr、归一化数据、梯度裁剪5.4 用一个小技巧显著提升调试效率最后分享一个我在调试所有神经网络项目时都在用的习惯不要一开始就在全量数据上训练。先用一个很小的子集比如从训练集里取出100张图片把batch_size设为16跑2个epoch确认整个流程能正常跑通再切回全量数据正式训练。这种小规模试跑只需要几秒钟能帮你快速发现代码里的低级错误比如维度不对、标签类型错误、设备不统一等避免你在全量训练跑到一半时才看到报错浪费大量时间。另一个建议是每次训练时把关键指标记录下来保存到日志或者历史列表里不要只print到控制台。尤其当你调整了好几个参数想在两个实验之间做对比时记录下来的曲线和数值会帮你定位是哪一步改动让效果变好或变坏远比凭印象判断可靠。我个人跑完这个项目后最大的体会是终于建立了对“训练”这件事本身的直觉loss不降时知道先去查单batch拟合准确率上不去时知道判断是数据问题还是模型容量问题测试集突然变差时知道这大概率和过拟合有关。这种直觉不是看文档能得来的必须靠亲手把模型跑起来、改坏、修好、再改坏、再修好才能慢慢沉淀下来。所以我建议你拿到代码之后不要只让它跑通看一遍结果就结束而是主动去折腾把学习率调大一倍看loss曲线怎么崩把Dropout去掉看过拟合有多明显把隐藏层改成一层看精度掉多少。只有亲手经历过这些变化才算真正把多层感知机装进了脑子。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。