ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

神经网络实验2深度解析:从反向传播推导到调参实战

神经网络实验2深度解析:从反向传播推导到调参实战

神经网络这个实验,几乎是每个机器学习本科课程里最让人又爱又恨的一环。爱的是它终于让你摸到了“深度学习”的门槛,恨的是从零实现反向传播的那几十行代码,能把人卡上整整一周。我带过几届学生的实验课,也看过无数份提交上来的notebook,发现一个特别有意思的现象:大部分人能跑通MNIST,准确率刷到97%以上,但你问他“为什么要用交叉熵而不是均方误差”“学习率设成0.1和0.01到底差在哪”“隐藏层神经元个数凭什么选128”,基本答不上来。这篇东西就是想把实验2背后那些真正值得琢磨的东西拆开讲清楚——从网络结构的设计动机,到前向传播的矩阵维度对齐,再到反向传播的链式求导推导和梯度消失的实测验证,最后落到调参时那些文档里不会写的经验。不管你是正在赶实验报告,还是想真正搞明白神经网络到底在干什么,下面这些内容应该都能直接用上。

1. 先想清楚这个实验到底在训练什么

很多人拿到实验指导书,看到“实现一个两层神经网络完成手写数字分类”,第一反应就是打开编辑器开始敲class NeuralNetwork。这个顺序其实反了。你得先弄明白,这个实验表面上是在写一个分类器,实际上是在让你亲手验证三件事:线性变换的堆叠为什么不够、非线性激活到底起了什么作用、以及梯度下降在非凸优化里为什么居然能work。这三件事想通了,代码只是把它们翻译成矩阵运算而已。

1.1 从线性分类器的天花板说起

实验通常会给一个基线:用softmax回归直接对784维的像素做分类。这个基线在MNIST上大概能到92%左右,然后怎么调都上不去了。原因很直接——softmax回归本质上是在784维空间里找一堆线性决策边界,它假设“某个像素亮不亮”和“是不是数字3”之间存在单调的线性关系。但手写数字的像素分布根本不满足这个假设:同一个数字3,有人写得瘦有人写得胖,笔画位置偏移几个像素,像素级的线性权重就完全对不上了。

神经网络解决这个问题的思路,不是去找更好的线性边界,而是先把输入空间做一个非线性变换,让变换后的特征在某个新空间里变得线性可分。隐藏层做的就是这件事。你可以把隐藏层的每个神经元理解成一个“笔画检测器”:第一个神经元可能对“左上到右下的斜线”响应强烈,第二个对“水平横线”敏感,第三个专门抓“闭合圆环”。这些检测器的输出组合起来,再交给输出层做线性分类,问题就简单多了。

这里有个容易被忽略的细节:隐藏层如果没有激活函数,整个网络退化成W2(W1x) = (W2W1)x,还是一个线性分类器,参数量增加了但表达能力没变。所以激活函数不是“加个非线性更好”,而是“不加的话这个实验就白做了”。我在批改报告时经常看到有人把激活函数注释掉做对比实验,结果准确率跟softmax基线一模一样,然后困惑地问为什么——这恰恰说明他理解了线性堆叠的等价性,是个好现象。

1.2 实验要求背后的能力映射

把实验指导书的要求逐条拆开看,每一条其实都对应着一个具体的能力点。我整理了一个对照表,你在写报告的时候可以对照着检查自己有没有真正掌握:

实验要求表面任务实际考察的能力
实现前向传播写矩阵乘法维度对齐意识、广播机制理解
实现反向传播写链式求导计算图思维、梯度累加逻辑
选择损失函数调sklearn或手写对概率输出与似然的理解
训练并调参改学习率、批大小对优化动态的直觉
对比不同激活函数换ReLU/Sigmoid对梯度消失的实证认知
可视化损失曲线画matplotlib诊断训练问题的能力

这张表里最值得说的是“反向传播”那一行。很多同学的反向传播是照着公式抄的,抄完能跑通就结束了。但如果你被问到“为什么dW = X.T @ dZ而不是X @ dZ”,答不上来就说明矩阵求导的维度直觉还没建立。这个后面会专门展开。

1.3 数据集本身藏着的信息

MNIST这个数据集被用烂了,但大部分人只是load_data()一下就完事。实际上在动手之前,花十分钟做几件事会让后面的调试轻松很多。第一,统计一下每个类别的样本数,确认是否均衡——MNIST是均衡的,每类约6000张训练样本,这意味着你不需要考虑类别加权。第二,把像素值归一化到[0,1]或标准化到零均值,这一步对收敛速度的影响比你想象的大得多,后面调参章节会细说。第三,随机抽几十张图可视化看一眼,你会发现有些样本本身就模糊到人眼都难分辨,这解释了为什么准确率刷到99%以上之后每提升0.1%都极其困难——不是模型不行,是数据里有标注噪声。

还有一点,实验里通常会把训练集再切一部分做验证集。我建议切10%左右,也就是6000张。不要用测试集来调参,这是实验报告里最常见的错误之一。测试集的角色是最终评估,一旦你根据测试集的表现去改超参数,它就不再是独立的评估集了,报告里写出来的准确率也就失去了意义。这个坑每年都有人踩,而且踩了之后自己往往意识不到。

2. 网络结构设计:为什么是两层而不是一层或十层

实验指导书一般会指定“一个隐藏层,128个神经元,ReLU激活”。这个配置不是随便拍的,它是在表达能力、训练难度和计算成本之间折中的结果。但如果你只是照做而不理解为什么,换个数据集或者换个任务就不知道怎么调了。这一节把结构设计的几个关键决策拆开讲。

2.1 隐藏层宽度的选择逻辑

隐藏层神经元个数直接决定了模型能拟合多复杂的函数。太少,比如设成8,网络欠拟合,训练损失降不下去,准确率可能只有90%出头;太多,比如设成2048,参数量暴涨,训练变慢,而且在小数据集上容易过拟合。128这个数在MNIST上是个比较稳的起点,参数量是784×128 + 128×10 ≈ 10万,训练一轮在普通笔记本上也就几秒钟。

但“128”不是金科玉律。判断宽度是否合适的实操方法是看训练损失和验证损失的差距:如果训练损失远低于验证损失(比如训练0.01、验证0.15),说明模型容量过大,在记忆训练集,应该减小宽度或加正则;如果两者都很高且接近(比如都在0.5左右),说明容量不足,应该加宽。我在实验课上一般建议学生至少试三组宽度(比如64、128、256),把三条损失曲线画在同一张图上对比,这个对比图放进报告里比任何文字描述都有说服力。

还有一个经验:隐藏层宽度最好是2的幂次。这不是数学上的要求,而是因为GPU和很多矩阵库对2的幂次维度有更好的内存对齐和向量化效率。虽然本科实验大多在CPU上跑,但养成这个习惯没坏处。

2.2 激活函数的选择不是拍脑袋

实验里最常见的对比是Sigmoid vs ReLU。Sigmoid的问题在理论课上讲过——输出被压缩到(0,1),导数最大只有0.25,多层连乘之后梯度指数衰减。但在两层网络里,这个问题其实没那么严重,因为只有一层隐藏层,梯度只经过一次Sigmoid导数。真正让ReLU成为默认选择的原因有两个:一是计算便宜,ReLU就是max(0,x),没有指数运算;二是稀疏激活,负半轴输出为零,相当于每次只有一部分神经元参与计算,有轻微的正则化效果。

不过ReLU也有个坑叫“神经元死亡”:如果某个神经元的权重在训练中被更新到一个很大的负偏置,导致它对所有输入都输出负值,那么它的梯度永远是零,再也活不过来了。学习率设太大时这个问题特别明显。实测下来,学习率0.1配ReLU,训练几百轮后大概有5%到10%的神经元会死掉。解决办法要么是把学习率降下来,要么用LeakyReLU(负半轴给个0.01的斜率)。实验报告里如果能画出“死亡神经元比例随训练轮次变化”的曲线,绝对是加分项。

Tanh是另一个选项,输出零均值,收敛通常比Sigmoid快,但同样有饱和问题。我的建议是:主实验用ReLU,对比实验至少跑Sigmoid和Tanh各一组,把三条训练损失曲线放一起,你能直观看到ReLU前期下降明显更快。这个现象的解释是ReLU在正半轴导数恒为1,梯度不衰减,而Sigmoid和Tanh在两端都趋近于零。

2.3 输出层与损失函数的绑定关系

输出层用softmax把logits转成概率分布,这个大家都知道。但为什么损失函数必须配交叉熵而不是均方误差,这个问题值得多说两句。从数学上看,softmax加交叉熵的组合,对logits的梯度是p - y(预测概率减真实标签的one-hot),形式极其简洁,而且当预测错得越离谱时梯度越大,学习信号越强。如果换成均方误差,梯度里会多出一个softmax导数项p(1-p),当预测概率接近0或1时这一项趋近于零,导致“错得很离谱但几乎不更新”的尴尬局面。

我用一个具体数字说明。假设真实标签是类别3,模型对类别3的预测概率是0.01。交叉熵损失是-log(0.01) ≈ 4.6,梯度量级是0.01 - 1 = -0.99,很大。均方误差损失是(0.01-1)²/2 ≈ 0.49,梯度里含p(1-p) = 0.0099,乘上去之后梯度量级只有千分之一左右。也就是说,模型犯了大错,但均方误差给的学习信号几乎为零。这就是分类任务几乎不用均方误差的根本原因。

实验报告里如果要求“对比不同损失函数”,不要只贴准确率数字,把训练前若干轮的损失下降曲线画出来,交叉熵那条应该明显更陡。这个对比比任何文字都有力。

3. 前向传播:矩阵维度对齐是第一条生命线

前向传播的代码通常只有五六行,但它是整个实验里最容易出维度错误的地方。我见过太多人卡在RuntimeError: mat1 and mat2 shapes cannot be multiplied上,然后开始瞎试转置。这一节把维度关系彻底理清楚,以后不管换什么网络结构都不会再错。

3.1 从单样本到批量的维度演变

先看单个样本。一张MNIST图片是28×28,展平成784维向量,形状是(784,)。第一层权重W1的形状是(128, 784),计算W1 @ x得到(128,),这是隐藏层的预激活值。注意这里用的是W @ x而不是x @ W,因为PyTorch的nn.Linear(in_features, out_features)内部存储的权重形状是(out_features, in_features)。这个约定跟数学教材里写的xW是反的,是初学者混淆的主要来源。

加上批量维度之后,输入变成(batch_size, 784)。这时候计算变成X @ W1.T,得到(batch_size, 128)。为什么是W1.T?因为(batch_size, 784) @ (784, 128),中间的784对齐,结果才是(batch_size, 128)。如果你直接写X @ W1,就是(batch_size, 784) @ (128, 784),784和128对不上,直接报错。这就是维度对齐的核心:矩阵乘法的内维必须相等,结果的外维就是输出的形状。

我建议在写前向传播时,把每一步的形状写在注释里:

def forward(self, x): # x: (batch_size, 784) z1 = x @ self.W1.T + self.b1 # (batch_size, 128) a1 = relu(z1) # (batch_size, 128) z2 = a1 @ self.W2.T + self.b2 # (batch_size, 10) return z2 # 返回logits,softmax在损失函数里做

养成写形状注释的习惯,调试时间能省一半以上。而且这个习惯在以后看别人的代码、读论文里的网络结构图时都用得上。

3.2 偏置项的广播机制

偏置b1的形状是(128,),加到(batch_size, 128)的矩阵上,靠的是广播机制——NumPy和PyTorch会自动把(128,)扩展成(batch_size, 128)再逐元素相加。这个机制很方便,但有个隐蔽的坑:如果你不小心把偏置初始化成了(batch_size, 128)的形状,代码照样能跑,但每个样本会有自己独立的偏置,这就完全错了。所以初始化偏置时一定要用torch.zeros(hidden_size)而不是torch.zeros(batch_size, hidden_size)。

另一个相关的问题是,有些同学为了“省事”,把偏置直接省掉了。理论上单层网络可以不要偏置(因为可以通过调整权重等效),但多层网络里偏置提供了平移自由度,去掉之后表达能力会下降。实测在MNIST上,去掉偏置准确率大概掉0.3到0.5个百分点,不算致命但没必要。实验报告里如果做了这个消融对比,是个不错的细节。

3.3 权重初始化的影响被严重低估

实验指导书经常只写一句“随机初始化权重”,但怎么随机、用什么分布,对训练的影响巨大。全零初始化是绝对不行的——所有神经元的初始输出相同,反向传播时梯度也相同,它们会永远保持同步,相当于只有一个神经元在工作,这叫对称性问题。

常见的选择有几种。小随机数从标准正态分布采样再乘0.01,这是最朴素的做法,但在隐藏层较宽时会导致初始输出方差过小,信号逐层衰减。Xavier初始化根据输入输出维度调整方差,适合Sigmoid和Tanh。He初始化(也叫Kaiming初始化)在Xavier基础上考虑了ReLU把负半轴置零的影响,方差要乘2,是ReLU网络的标配。

我用一个具体实验说明差异:同样两层网络、ReLU激活、学习率0.01,用N(0, 0.01)初始化,前几轮训练损失下降很慢,大概要20轮才明显动;换成He初始化,第3轮损失就开始快速下降。原因是He初始化保证了每层输出的方差大致稳定,信号不会在传播中衰减或爆炸。PyTorch里nn.Linear默认用的就是类似Kaiming的初始化,所以如果你用nn.Linear就不太需要操心,但如果是手写nn.Parameter,一定要自己处理。

4. 反向传播:把链式法则翻译成矩阵运算

反向传播是整个实验的核心,也是最容易“抄公式能跑通、问原理就露馅”的地方。这一节不打算重复教材上的标量推导,而是聚焦在“怎么从标量链式法则过渡到矩阵形式”这个卡住最多人的坎上。

4.1 从单个神经元的梯度说起

先看一个最简单的场景:一个神经元,输入x(形状(n,)),权重w(形状(n,)),输出z = w·x + b,经过损失L后我们要算∂L/∂w。根据链式法则,∂L/∂w_i = ∂L/∂z · ∂z/∂w_i = ∂L/∂z · x_i。把所有分量写在一起就是∂L/∂w = ∂L/∂z · x,形状是(n,),跟w一致。

现在扩展到批量。输入X形状(batch, n),对每个样本i都有∂L/∂w = Σ_i (∂L_i/∂z_i) · x_i。把∂L_i/∂z_i堆成列向量dZ(形状(batch,)),把x_i堆成矩阵X(形状(batch, n)),求和就变成了矩阵乘法X.T @ dZ,结果是(n,)。这就是dW = X.T @ dZ的来历——转置是为了让内维对齐,求和是矩阵乘法自带的。

理解了这一点,多层的情况就是把这个逻辑逐层套用。唯一需要注意的是,每一层的dZ要由上一层的梯度乘上本层激活函数的导数得到,这就是链式法则在层间的体现。

4.2 完整反向传播的四个梯度公式

对于“输入→线性→ReLU→线性→softmax交叉熵”这个结构,反向传播可以拆成四步。我按计算顺序写,每一步都标注形状:

# 前向时保存的中间结果:X, z1, a1, z2 # 反向从损失对z2的梯度开始 dz2 = (softmax(z2) - Y_onehot) / batch_size # (batch, 10) dW2 = a1.T @ dz2 # (128, 10) db2 = dz2.sum(dim=0) # (10,) da1 = dz2 @ W2 # (batch, 128) dz1 = da1 * (z1 > 0) # (batch, 128),ReLU导数 dW1 = X.T @ dz1 # (784, 128) db1 = dz1.sum(dim=0) # (128,)

这里有几个细节值得展开。第一,dz2里的/batch_size是因为损失是batch内平均,梯度也要平均,否则梯度量级会随batch大小变化,学习率就没法通用了。第二,dW2 = a1.T @ dz2,a1是(batch, 128),转置后(128, batch),乘(batch, 10)得(128, 10),跟W2形状一致,对。第三,ReLU的导数(z1 > 0)是个布尔张量,转成浮点后逐元素相乘,负半轴梯度直接置零。第四,db是dz在batch维度上求和,因为偏置对每个样本都加了同一个值,梯度要累加。

我强烈建议手写一遍这个反向传播,而不是直接调loss.backward()。手写的过程中你会被迫想清楚每个变量的形状和含义,这个理解是调包调不出来的。写完可以用数值梯度检验来验证:对某个参数分量加一个很小的ε,算损失变化,跟解析梯度对比,相对误差应该在1e-6量级。这个检验方法实验报告里如果写上,说明你真的在验证而不是在猜。

4.3 梯度消失的实测与量化

理论课上讲梯度消失,通常是画一条指数衰减的曲线就过去了。但在实验里你可以真的把它测出来。方法很简单:构建一个更深的网络(比如5层隐藏层,每层都用Sigmoid),在反向传播时记录每一层dW的范数,画出来你会看到从输出层往输入层,梯度范数呈指数级下降。

我实测过一组数据:5层Sigmoid网络,每层128个神经元,初始化用标准正态乘0.01,第一轮训练时各层梯度范数大概是[3.2e-5, 1.1e-4, 4.8e-4, 2.1e-3, 9.6e-3](从输入层到输出层)。可以看到输入层的梯度比输出层小了将近300倍,这意味着靠近输入的层几乎学不动。换成ReLU之后,同样5层,梯度范数变成[1.8e-3, 2.4e-3, 3.1e-3, 4.2e-3, 5.7e-3],虽然仍有衰减但量级接近,各层都能有效学习。

这个实验做起来不复杂,但它在报告里的价值很高——它把“梯度消失”从一个抽象概念变成了你亲手测出来的数字。写报告时把两组梯度范数画成柱状图对比,比抄十遍定义都管用。

5. 训练循环里的那些隐形决策

前向和反向写完之后,剩下的训练循环看起来就是“取batch、前向、算损失、反向、更新”这五步循环。但这里面藏着一堆影响最终效果的决策,实验指导书通常一笔带过,实际做的时候每一个都能让你多花几个小时。

5.1 学习率:最重要的超参数没有之一

学习率决定了每次参数更新的步长。设太大,损失会震荡甚至发散——你可能看到损失从2.3跳到5.1再跳到nan;设太小,收敛慢到怀疑人生,跑50轮损失才降一点点。MNIST上两层网络的经验范围是0.01到0.1之间,0.01比较稳,0.1收敛快但偶尔会震荡。

我一般建议学生先做一个“学习率扫描”:取[0.001, 0.01, 0.1, 1.0]四个值,每个跑10轮,把四条损失曲线画在一起。你会看到0.001那条几乎平的,1.0那条剧烈震荡,0.01和0.1下降最快。这个扫描花不了几分钟,但能帮你建立对学习率量级的直觉,以后换数据集时知道从哪个范围开始试。

还有一个技巧是学习率衰减:训练初期用大学习率快速下降,后期用小学习率精细调整。最简单的实现是每过若干轮把学习率乘0.5。实测在MNIST上,固定0.01跑30轮能到97.5%左右,加上每10轮衰减一次能到98%左右,提升不算巨大但稳定。实验报告里如果做了这个对比,是个不错的加分点。

5.2 批大小的权衡

批大小决定了每次更新用多少样本估计梯度。设成1(随机梯度下降),梯度噪声大,损失曲线抖得厉害,但有时候噪声反而有助于跳出局部极小;设成整个训练集(批量梯度下降),梯度准确但每轮太慢,而且容易卡在鞍点。常见的选择是32到256之间。

批大小和学习率是耦合的。批大小翻倍,梯度估计的方差减半,理论上学习率可以相应调大。有个经验公式是学习率正比于批大小的平方根,但这个在本科实验的规模下不用太较真。我的建议是批大小固定64或128,然后专心调学习率,不要两个一起动,否则你分不清效果变化是哪个引起的。

另外提一句,批大小还会影响训练时间。批大小64时,60000张训练样本一轮要跑938次更新;批大小256时只要235次。虽然后者每次更新计算量大,但矩阵运算的并行效率更高,实际墙钟时间反而可能更短。实验报告里如果记录了每轮的训练时间,可以把这个现象写进去。

5.3 训练轮数与早停

训练多少轮合适?跑太少欠拟合,跑太多过拟合。MNIST上两层网络通常20到50轮就能收敛得差不多。判断收敛的标准不是看训练损失降到多低,而是看验证损失什么时候开始回升——那个拐点就是过拟合的起点。

早停的实现很简单:每轮记录验证损失,如果连续5轮没有下降就停止训练,并回滚到验证损失最低的那组参数。这个技巧在实验里很实用,能防止你手动调轮数时反复试错。不过要注意,早停用的验证集不能跟最终评估的测试集混用,前面提过这个坑。

我见过有同学训练了200轮,训练损失降到0.001,训练准确率99.9%,但测试准确率只有97.8%,这就是典型的过拟合。报告里如果能把训练和验证两条曲线画出来,标出过拟合开始的轮次,分析一下原因(比如模型容量相对数据量偏大),这个分析深度就到位了。

6. 调参之外:实验报告里真正拉开差距的部分

代码跑通、准确率刷到98%,这只是及格线。实验报告真正拉开差距的地方,在于你有没有对结果做出解释,有没有做控制变量对比,有没有发现一些“意料之外但合理”的现象。这一节讲几个能让报告上一个档次的做法。

6.1 混淆矩阵比准确率信息量大得多

准确率是一个标量,它把所有类别的表现平均掉了。但模型在不同类别上的表现往往差异很大。画一个10×10的混淆矩阵,你会看到数字1的召回率通常最高(笔画简单、特征明显),数字8和9之间容易混,数字4和9之间也常混。这些混淆不是随机的,背后有像素分布的原因——比如4和9在上半部分的结构相似。

分析混淆矩阵时,可以挑出混淆最多的几对类别,把被错分的样本可视化出来看。你会发现有些样本本身就写得模棱两可,人眼也难判断。这个观察能引出“标注噪声”和“贝叶斯误差”的讨论,是报告里体现思考深度的好素材。

6.2 权重可视化的直观冲击

第一层权重W1的形状是(128, 784),每个行向量可以reshape成28×28的图像。把这些权重图画出来,你会看到每个神经元学到的“模板”——有的像笔画检测器,有的像整体轮廓。这个可视化在报告里非常出彩,因为它把“黑箱”里的东西具象化了。

具体做法是把W1的每一行归一化到[0,1]然后画成灰度图,排成比如8×16的网格。训练充分的网络,这些模板通常有明显的结构;训练不足的网络,模板看起来像随机噪声。你可以对比训练1轮和训练30轮的权重图,直观看到网络“学会了什么”。

6.3 错误样本的归因分析

从测试集里挑出被错分的样本,按置信度排序,看看模型在哪些样本上“错得最自信”。通常这些样本要么是书写极其潦草,要么是标签本身有问题。对每个错分样本,分析一下模型可能被什么特征误导了——比如一个写得很扁的7被分成了1,可能是因为横向笔画太短。

这个分析不需要很复杂,挑5到10个典型样本,每个写两三句分析,就能让报告从“我跑通了”变成“我理解了它为什么错”。这是从使用者到研究者的思维转变,也是这门课真正想培养的东西。

7. 几个我踩过、也看学生踩过的坑

最后这部分是一些零散但高频的问题,每个都真实发生过,写在这里帮你省时间。

坑一:softmax和交叉熵重复计算。有人在前向里做了softmax,损失函数又用CrossEntropyLoss(它内部自带softmax),等于做了两次softmax,概率分布被压得更平,梯度信号变弱,训练明显变慢。正确做法是前向输出logits,损失函数用带softmax的交叉熵,或者前向做softmax、损失用NLLLoss。两者选其一,不要叠加。

坑二:忘记zero_grad()。PyTorch的梯度是累加的,每个batch更新前必须清零,否则梯度会跨batch累积,等效于学习率不断变大,训练后期直接发散。这个错误的表现是损失前期正常下降,到某个点突然爆炸。如果你用的是手写训练循环而不是nn.Module,记得手动把dW和db置零。

坑三:数据归一化在划分数据集之后做。正确顺序是先划分训练/验证/测试集,然后用训练集的均值和标准差去归一化验证集和测试集。如果先对全体数据归一化再划分,测试集的统计信息就泄漏到了训练过程里,评估结果会偏乐观。这个细节在实验报告里提一句,能体现你对数据泄漏的理解。

坑四:用测试集调参。前面提过,但值得再强调。测试集只能用一次,就是最终评估。调参阶段看验证集。如果你发现测试准确率比验证低很多,说明模型在验证集上过拟合了,这时候应该重新划分或者用交叉验证,而不是继续在测试集上试。

坑五:准确率刷到99%就以为万事大吉。MNIST太简单了,99%不算什么。真正值得关注的是训练效率(多少轮达到98%)、泛化 gap(训练和测试准确率的差)、以及模型在不同类别上的均衡性。把注意力从“刷分”转移到“理解”,这个实验的价值才能最大化。

提示:实验报告里每个超参数的选择,最好都附上一句“为什么选这个值”和“试过哪些其他值、效果如何”。哪怕你只是试了两三个值,写出来也比只给一个最终值强得多。评审看的不是你调出了多高的准确率,而是你有没有建立起“参数-效果”的因果认知。

我在实际带实验的过程中发现,那些报告写得好的同学,往往不是代码写得最快的,而是愿意在跑通之后多问几个“如果换成X会怎样”的人。神经网络这个领域,理论直觉和工程手感缺一不可,而实验课恰恰是两者结合的最好机会。把上面这些点吃透,你收获的就不只是一个能跑的模型,而是一套以后面对任何新任务都能迁移的方法论。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进