机器学习入门最让人头疼的地方,从来不是概念本身有多难,而是你翻开任何一本教材,它都从“监督学习是给定输入输出对学习映射”这种定义开始讲,讲完你还是不知道这些东西到底怎么跑起来的。我见过太多人背得出梯度下降的公式,却说不清为什么学习率设大了会震荡、设小了又像蜗牛爬;能默写反向传播的链式法则,但遇到梯度消失还是一脸茫然。这篇东西就是想把监督学习这条链路从头到尾捋一遍,不堆公式,讲清楚每个环节到底在干什么、为什么这么干、实际写代码时哪里最容易翻车。
1. 监督学习到底在解决什么问题
1.1 从“有答案的练习题”说起
监督学习这个概念,用最朴素的话讲就是:你手里有一堆题目,每道题都附了标准答案,你让模型去反复做这些题,做错了就调整,直到它能在没见过的题目上也给出靠谱的答案。这里的“题目”就是输入特征,“标准答案”就是标签。
举个具体的例子。假设你想做一个手写数字识别系统,输入是一张28x28像素的灰度图,输出是0到9之间的一个数字。你有六万张已经标注好答案的图片(这就是著名的MNIST数据集),每张图片对应一个标签。监督学习要做的就是找到一个函数,让这个函数在看过这六万张图之后,面对第七万张从没见过的图,也能正确说出它是几。
这个过程中有几个关键角色需要分清楚。输入空间是所有可能的输入构成的集合,比如所有28x28的灰度图。输出空间是所有可能的输出,比如0到9这十个类别。假设空间是你能接受的所有候选函数的集合,比如所有可能的神经网络结构。损失函数是衡量一个候选函数好不好的标准。优化算法是在假设空间里搜索最佳函数的工具。
很多人初学的时候会把“模型”和“算法”搞混。模型是那个函数本身,比如一个三层神经网络;算法是找到这个函数参数的方法,比如梯度下降。模型决定了你能表达多复杂的映射关系,算法决定了你能不能高效地找到好的参数。两者缺一不可,但解决的问题完全不同。
1.2 分类和回归:两种最基本的输出形态
监督学习按输出类型大致分成两类。输出是离散的类别,叫分类问题;输出是连续的数值,叫回归问题。
分类问题的典型场景包括:垃圾邮件识别(是/否)、图像分类(猫/狗/鸟)、情感分析(正面/负面/中性)。回归问题的典型场景包括:房价预测(具体金额)、温度预测(具体度数)、股票走势预测(具体数值)。
这个区分看起来简单,但它直接决定了你选什么损失函数、用什么激活函数、怎么评估模型好坏。分类问题常用交叉熵损失,输出层用softmax或sigmoid;回归问题常用均方误差损失,输出层通常不加激活函数或者用线性激活。评估指标上,分类看准确率、精确率、召回率、F1值,回归看均方误差、平均绝对误差、R方。
我见过新手拿回归的损失函数去做分类任务,训练半天loss降不下去,还以为是网络结构有问题。其实只要把输出层的激活函数和损失函数换对,问题立刻解决。这种错误不涉及什么高深理论,纯粹是没搞清楚任务类型和对应工具的关系。
1.3 监督学习的完整链路长什么样
把监督学习拆开来看,它其实是一条很清晰的流水线:
- 数据准备:收集带标签的数据,划分训练集、验证集、测试集
- 模型定义:选择假设空间,确定网络结构或函数形式
- 前向传播:把输入喂给模型,得到预测输出
- 损失计算:比较预测输出和真实标签,算出差距
- 反向传播:根据损失,计算每个参数对损失的梯度
- 参数更新:沿着梯度的反方向调整参数,减小损失
- 迭代:重复3到6步,直到损失收敛或达到停止条件
- 评估:在测试集上衡量模型的泛化能力
这条链路里,前向传播和反向传播是核心。前向传播负责“算答案”,反向传播负责“找方向”。很多人只关注网络结构怎么设计,却忽略了反向传播才是让网络真正能学习的关键。没有反向传播,再深的网络也只是一堆随机数。
2. 神经网络:从线性模型到非线性表达
2.1 为什么线性模型不够用
最简单的监督学习模型是线性回归和逻辑回归。它们假设输出是输入的线性组合,形式是 y = wx + b。这个假设在很多场景下够用,但它有一个致命缺陷:只能表达线性关系。
什么叫线性关系?输入翻倍,输出也翻倍;输入相加,输出也相加。但现实世界里的关系大多是非线性的。比如图像识别,像素值和“这是不是一只猫”之间根本没有线性关系。再比如异或问题(XOR),两个输入相同输出0,不同输出1,你找不到任何一条直线能把两类点分开。
这就是神经网络要解决的核心问题:如何用简单的非线性单元堆叠出任意复杂的函数。神经网络的基本思路是,先做一次线性变换,再通过一个非线性激活函数,然后再做线性变换,再非线性,反复叠加。理论上,只要层数够多、神经元够多,神经网络可以逼近任意连续函数(这就是通用近似定理)。
2.2 单个神经元在做什么
一个神经元做的事情极其简单:接收一组输入,给每个输入乘一个权重,求和,加上偏置,然后过一个激活函数。用公式写就是:
z = w1*x1 + w2*x2 + ... + wn*xn + b a = f(z)其中f就是激活函数。如果没有激活函数,多层神经网络等价于一层线性变换,堆再多层也没用。激活函数是引入非线性的关键。
权重w决定了每个输入的重要性,偏置b决定了神经元激活的阈值。训练的过程就是不断调整这些w和b,让网络的输出越来越接近真实标签。
2.3 激活函数:给网络注入非线性
激活函数的选择直接影响网络的训练效果。常见的激活函数有几种,各有各的适用场景。
Sigmoid:把输出压缩到0到1之间,形状像S。优点是输出范围有限,适合做概率输出。缺点是当输入很大或很小时,梯度接近0,导致梯度消失。深层网络里基本不用了。
Tanh:把输出压缩到-1到1之间,形状和Sigmoid类似但中心在0。比Sigmoid好一点,但梯度消失问题依然存在。
ReLU:f(x) = max(0, x)。计算极其简单,正区间梯度恒为1,有效缓解了梯度消失。缺点是负区间梯度为0,神经元可能“死亡”。尽管如此,ReLU仍然是目前最常用的激活函数之一。
Leaky ReLU:给负区间一个很小的斜率,比如0.01,避免神经元完全死亡。
GELU:高斯误差线性单元,形式是x乘以标准正态分布的累积分布函数。在Transformer架构里非常流行,效果通常比ReLU好,但计算稍复杂。
SiLU/Swish:f(x) = x * sigmoid(x)。平滑版的ReLU,在深层网络中表现不错。
选激活函数的经验法则是:隐藏层优先用ReLU或其变体(Leaky ReLU、GELU、SiLU),输出层根据任务选(分类用softmax或sigmoid,回归用线性)。不要在小问题上纠结激活函数的选择,差别通常不大,但深层网络里ReLU系列确实比Sigmoid系列好训练得多。
2.4 从单层到多层:前馈神经网络的结构
前馈神经网络是最基础的神经网络结构。信息从输入层单向流向输出层,中间经过若干隐藏层,没有反馈连接。
一个典型的前馈网络长这样:输入层接收原始特征,第一个隐藏层做线性变换加激活,第二个隐藏层继续变换,最后输出层给出预测结果。每一层的输出是下一层的输入,层层递进。
层数越多,网络能表达的函数的复杂度越高。但层数多了也会带来问题:梯度消失、训练困难、过拟合。所以实际中需要根据任务复杂度来权衡。简单的分类任务两三层就够了,复杂的图像识别可能需要几十层甚至上百层。
3. 前向传播与反向传播:网络学习的核心机制
3.1 前向传播:从输入到预测
前向传播就是数据从输入层一路流到输出层的过程。每一层做的事情都一样:接收上一层的输出,做线性变换,过激活函数,传给下一层。
假设一个三层网络,输入是x,第一层权重W1偏置b1,第二层W2偏置b2,输出层W3偏置b3。前向传播就是:
a1 = relu(W1 * x + b1) a2 = relu(W2 * a1 + b2) y_pred = softmax(W3 * a2 + b3)每一步都是矩阵乘法加偏置再加激活。矩阵乘法负责线性变换,激活函数负责非线性。最终得到的y_pred就是模型对当前输入的预测。
前向传播本身不涉及学习,它只是“算答案”。但它是反向传播的基础,因为反向传播需要用到前向传播过程中每一层的中间结果。
3.2 损失函数:衡量预测和真实答案的差距
损失函数的作用是给模型的预测打分。预测越接近真实标签,损失越小;偏差越大,损失越大。训练的目标就是找到一组参数,让损失函数在训练数据上尽可能小。
分类问题最常用的损失函数是交叉熵损失。对于二分类,形式是:
L = -[y * log(y_pred) + (1-y) * log(1-y_pred)]对于多分类,形式是:
L = -sum(y_i * log(y_pred_i))其中y是真实标签的one-hot编码,y_pred是模型输出的概率分布。交叉熵衡量的是两个概率分布之间的差异,当预测分布和真实分布完全一致时,交叉熵为0。
回归问题最常用的是均方误差:
L = (1/n) * sum((y_i - y_pred_i)^2)均方误差对大的偏差惩罚更重,因为误差被平方了。如果数据里有异常值,均方误差可能会被拉偏,这时候可以考虑用平均绝对误差。
损失函数的选择要和任务匹配。我见过有人做多分类任务用均方误差,训练也能跑,但收敛慢、效果差。换成交叉熵之后,同样的网络结构,准确率直接涨了好几个点。这不是玄学,是因为交叉熵的梯度性质更适合分类任务。
3.3 反向传播:链式法则的工程实现
反向传播是整个监督学习里最核心也最容易让人迷糊的部分。它的本质就是链式法则,但工程实现上有一些细节值得说清楚。
先回顾链式法则。如果 y = f(g(x)),那么 dy/dx = f'(g(x)) * g'(x)。对于嵌套很多层的神经网络,输出对某个参数的梯度就是沿着从输出到该参数的路径,把每一段的局部梯度乘起来。
反向传播的过程就是从输出层开始,先算损失对输出层输出的梯度,然后一层一层往回传,算损失对每一层参数的梯度。为什么要从后往前?因为链式法则要求你先知道后一层的梯度,才能算前一层的梯度。
具体来说,假设第l层的输出是a_l,损失是L。反向传播要算的是dL/dW_l和dL/db_l。根据链式法则:
dL/dW_l = dL/da_l * da_l/dz_l * dz_l/dW_l其中z_l是第l层的线性输出(激活前),a_l = f(z_l)。da_l/dz_l就是激活函数的导数,dz_l/dW_l就是上一层的输出。
这里有一个关键点:反向传播复用前向传播的中间结果。前向传播时算出的每一层的z和a,在反向传播时都要用到。所以实际实现中,前向传播不仅要算出最终输出,还要把中间结果存下来。这也是为什么训练比推理更耗内存。
3.4 梯度消失和梯度爆炸:深层网络的经典难题
当网络层数很多时,反向传播的梯度是很多项相乘的结果。如果每一项都小于1,乘起来就会趋近于0,这就是梯度消失。如果每一项都大于1,乘起来就会变得极大,这就是梯度爆炸。
Sigmoid激活函数的导数最大只有0.25,多层叠加之后梯度衰减得非常快。这就是为什么早期深层网络很难训练。ReLU的导数在正区间恒为1,很大程度上缓解了这个问题,但负区间导数为0,仍然可能导致部分神经元“死亡”。
梯度爆炸相对少见,但一旦出现,损失会变成NaN,训练直接崩溃。解决办法是梯度裁剪:如果梯度的范数超过某个阈值,就把它缩放到阈值以内。
另一个缓解梯度问题的技巧是残差连接。残差连接让梯度可以绕过某些层直接传回去,相当于给梯度开了一条高速公路。ResNet就是靠这个把网络做到了上百层。
3.5 用MATLAB做数字识别:一个完整的反向传播实例
MATLAB的神经网络工具箱提供了现成的函数,但如果你想真正理解反向传播,建议自己手写一遍。下面是一个简化的数字识别流程。
数据准备阶段,加载MNIST数据集,把图片展平成784维向量,归一化到0到1之间。标签做one-hot编码,比如数字3变成[0,0,0,1,0,0,0,0,0,0]。
网络结构可以设成784-128-64-10。输入层784个节点对应28x28像素,两个隐藏层分别128和64个节点,输出层10个节点对应0到9。
前向传播用矩阵运算实现:
a1 = relu(W1 * x + b1); a2 = relu(W2 * a1 + b2); y_pred = softmax(W3 * a2 + b3);损失用交叉熵。反向传播手动实现链式法则,从输出层往回算每一层的梯度。参数更新用梯度下降:
W3 = W3 - lr * dW3; b3 = b3 - lr * db3;学习率设0.01到0.1之间,批量大小设32或64,迭代几十个epoch。训练过程中观察损失曲线,如果损失震荡说明学习率太大,如果损失下降太慢说明学习率太小。
这个手写版本跑下来,在MNIST上准确率能到95%以上。虽然比不上调好的CNN,但整个过程能让你彻底搞清楚反向传播在算什么。
4. 梯度下降:参数优化的引擎
4.1 梯度下降的直观理解
梯度下降的思路可以用下山来类比。你站在山坡上,想走到山谷最低点,但视野有限,只能看到脚下附近的地形。梯度告诉你哪个方向最陡,你就朝着那个方向走一小步,然后重新判断方向,再走一步,反复直到走到谷底。
这里的“山坡”就是损失函数,“位置”就是参数值,“方向”就是梯度,“步长”就是学习率。梯度是损失函数对参数的偏导数,指向损失上升最快的方向。所以往梯度的反方向走,损失就会下降。
用公式写就是:
w = w - lr * dL/dw其中lr是学习率,dL/dw是损失对w的梯度。这个更新规则会一直执行,直到损失不再明显下降或者达到预设的迭代次数。
4.2 批量梯度下降、随机梯度下降和小批量梯度下降
梯度下降有三种变体,区别在于每次更新参数时用多少数据来算梯度。
批量梯度下降用全部训练数据算梯度。优点是梯度估计准确,下降方向稳定。缺点是每次更新都要遍历整个数据集,计算量太大,而且容易陷入局部最优。
随机梯度下降每次只用一个样本算梯度。优点是更新快,能跳出局部最优。缺点是梯度噪声大,损失曲线震荡严重。
小批量梯度下降折中,每次用一小批数据(比如32或64个样本)算梯度。这是实际中最常用的方式。批量大小是一个超参数,太小噪声大,太大内存吃紧且更新慢。经验上,32到256之间是比较常见的范围。
| 类型 | 每次用样本数 | 优点 | 缺点 |
|---|---|---|---|
| 批量梯度下降 | 全部 | 梯度准确,下降稳定 | 计算量大,更新慢 |
| 随机梯度下降 | 1 | 更新快,能跳出局部最优 | 噪声大,震荡严重 |
| 小批量梯度下降 | 32-256 | 兼顾稳定和效率 | 需要调批量大小 |
4.3 学习率:最重要的超参数
学习率决定了每次参数更新的步长。它可能是所有超参数里最需要仔细调的一个。
学习率太大,参数更新步子太大,损失可能震荡甚至发散。学习率太小,训练速度慢得让人抓狂,而且容易卡在局部最优或鞍点附近。
实际调学习率的经验是:先从0.001或0.01开始试,观察损失曲线。如果损失震荡,减小学习率;如果损失下降太慢,增大学习率。更好的做法是用学习率调度:训练初期用大学习率快速下降,后期用小学习率精细调整。常见策略包括步进衰减、余弦退火、指数衰减等。
还有一种自适应学习率的方法,比如Adam、RMSProp,它们会为每个参数自动调整学习率。Adam在很多任务上表现不错,基本可以拿来就用,但有时候需要配合学习率预热(warmup)才能稳定训练。
4.4 动量与自适应优化器
普通梯度下降有一个问题:在峡谷形的地形里,梯度会在两侧来回震荡,下降很慢。动量的思路是给参数更新加一个“惯性”,让更新方向不仅取决于当前梯度,还取决于之前的更新方向。这样在梯度方向一致的维度上加速,在震荡的维度上抑制。
动量更新规则:
v = beta * v + (1-beta) * dL/dw w = w - lr * v其中beta通常设0.9。动量让训练更稳定,收敛更快。
Adam结合了动量和自适应学习率,为每个参数维护一阶矩和二阶矩的估计。它的更新规则稍微复杂一些,但效果通常很好,而且对学习率不那么敏感。不过Adam也有缺点,在某些任务上泛化能力不如带动量的SGD。
选择优化器的建议:如果不想调太多东西,Adam是安全的选择;如果追求极致性能,带动量的SGD配合学习率调度往往能取得更好的最终效果,但需要更多调参。
4.5 梯度下降曲线拟合的实操观察
训练过程中,损失曲线是最重要的诊断工具。健康的损失曲线应该是一条平滑下降的曲线,最终趋于平稳。
如果损失曲线震荡剧烈,通常是学习率太大或批量太小。如果损失曲线下降太慢,可能是学习率太小或网络容量不够。如果损失曲线先降后升,说明过拟合了,需要加正则化或早停。如果损失直接变成NaN,说明梯度爆炸了,需要减小学习率或加梯度裁剪。
我习惯在训练时同时记录训练损失和验证损失。如果训练损失持续下降但验证损失开始上升,那就是过拟合的明确信号。这时候可以减小模型复杂度、加Dropout、加L2正则化,或者直接早停。
5. 从BP神经网络到CNN、RNN和GNN
5.1 BP神经网络:最经典的监督学习模型
BP神经网络就是前面讲的前馈神经网络加反向传播。它是所有深度学习模型的祖先,结构简单,原理清晰。
一个典型的BP网络包括输入层、若干隐藏层和输出层。每层之间全连接,信息单向流动。训练时用反向传播算梯度,用梯度下降更新参数。
BP网络能解决很多实际问题,比如手写数字识别、简单的情感分类、房价预测。但它的局限也很明显:全连接结构导致参数数量巨大,处理图像时效率极低;没有利用数据的空间或时间结构;深层BP网络容易梯度消失。
尽管如此,理解BP网络是理解所有其他神经网络的基础。CNN、RNN、Transformer,本质上都是在BP网络的基础上加了结构先验。
5.2 卷积神经网络:利用空间结构
CNN的核心思想是局部连接和权重共享。在图像里,相邻像素之间的关系比远距离像素更紧密,所以卷积层只连接局部区域。同一个卷积核在整张图上滑动,检测相同的特征。这大大减少了参数数量,也让网络具有平移不变性。
一个典型的CNN包括卷积层、池化层和全连接层。卷积层提取局部特征,池化层降低空间维度,全连接层做最终分类。深层CNN能学到从边缘到纹理到部件到物体的层次化特征。
CNN在图像分类、目标检测、人脸识别等任务上取得了巨大成功。它的训练同样依赖反向传播和梯度下降,只是梯度计算要考虑卷积和池化的特殊结构。
5.3 循环神经网络:处理序列数据
RNN的特点是它有“记忆”。每个时间步的输出不仅取决于当前输入,还取决于上一个时间步的隐藏状态。这让RNN天然适合处理序列数据,比如文本、语音、时间序列。
但普通RNN有严重的梯度消失问题,很难捕捉长距离依赖。LSTM通过门控机制(输入门、遗忘门、输出门)控制信息的流动,有效缓解了这个问题。GRU是LSTM的简化版,参数更少,效果相当。
RNN的训练用的是时间反向传播,本质还是链式法则,只是要沿着时间维度展开。梯度消失和爆炸在RNN里更严重,所以梯度裁剪几乎是标配。
5.4 图神经网络:处理不规则结构
有些数据既不是网格(图像)也不是序列(文本),而是图结构。比如社交网络、分子结构、知识图谱。GNN就是为这类数据设计的。
GNN的核心操作是消息传递:每个节点从邻居节点收集信息,更新自己的表示。经过多轮消息传递,每个节点都能捕捉到局部图结构的信息。
GNN在节点分类、链接预测、图分类等任务上表现优异。它的训练同样依赖反向传播,只是梯度要沿着图的边传播。
5.5 Neural ODE:连续视角下的神经网络
Neural ODE是一个比较新的思路,把神经网络的层看成连续时间的微分方程。传统网络是离散的层堆叠,Neural ODE把层数变成连续变量,用ODE求解器来计算前向传播。
这种视角的好处是:参数效率高,能处理不规则时间序列,理论上可以做到任意深度。但训练时需要反向传播通过ODE求解器,计算成本较高。
Neural ODE的参数化方式是用一个神经网络来建模状态对时间的导数:dh/dt = f(h(t), t, theta)。前向传播就是求解这个ODE,反向传播可以用伴随方法(adjoint method)来算梯度。
6. 训练一个监督学习模型的实操避坑指南
6.1 数据预处理的几个关键点
数据预处理做得好不好,直接决定模型能不能训起来。几个必须做的步骤:
归一化:把输入特征缩放到相近的范围。图像数据通常除以255缩放到0到1,或者用均值和标准差做标准化。不做归一化,梯度下降会很难收敛,因为不同特征的尺度差异会导致损失曲面变成狭长的峡谷。
标签编码:分类任务的标签要做one-hot编码。比如三分类任务,标签1变成[0,1,0]。不做one-hot直接用整数标签,交叉熵损失算出来是错的。
数据划分:训练集、验证集、测试集要严格分开。验证集用来调超参数,测试集只在最后评估时用一次。如果拿测试集调参,评估结果就不可信了。
处理缺失值:缺失值可以用均值、中位数填充,或者用模型预测填充。直接删掉有缺失值的样本也是一种选择,但要小心不要删掉太多导致数据偏差。
6.2 过拟合和欠拟合的判断与应对
欠拟合的表现是训练损失和验证损失都很高,模型连训练数据都学不好。原因通常是模型太简单、特征太少、训练不够。解决办法是增加模型容量、加更多特征、训练更久。
过拟合的表现是训练损失很低但验证损失很高,模型记住了训练数据但泛化能力差。解决办法包括:增加数据量、加正则化(L2正则、Dropout)、减小模型复杂度、早停。
判断过拟合和欠拟合最直接的方法就是看损失曲线。训练损失和验证损失的差距是过拟合的指标,两者的绝对值是欠拟合的指标。
6.3 超参数调优的实用策略
超参数调优没有银弹,但有一些策略能提高效率。
网格搜索:在预设的参数组合里穷举。简单但计算量大,适合参数少的情况。
随机搜索:在参数空间里随机采样。比网格搜索更高效,因为不是所有参数都同等重要。
贝叶斯优化:用概率模型指导搜索。效率更高,但实现复杂。
实际中,我通常先用手动调参找到大致范围,再用随机搜索细化。学习率、批量大小、网络层数、每层神经元数量、正则化系数是最需要调的。激活函数和优化器的选择相对不那么敏感,先用默认的就行。
6.4 梯度问题的诊断和解决
训练中遇到梯度问题,可以从以下几个方面排查:
检查损失曲线:如果损失变成NaN,大概率是梯度爆炸。如果损失下降极慢,可能是梯度消失。
检查梯度范数:在训练循环里打印每一层的梯度范数。如果某层的梯度范数接近0,说明梯度消失了。如果梯度范数极大,说明梯度爆炸了。
梯度裁剪:设置一个阈值,梯度超过阈值就缩放。这是解决梯度爆炸最直接的方法。
换激活函数:把Sigmoid换成ReLU或GELU,能有效缓解梯度消失。
加残差连接:让梯度有捷径可以传回去。
用Batch Normalization:对每一层的输入做归一化,稳定训练过程,也有助于缓解梯度问题。
6.5 从训练到部署的注意事项
模型训练好了不等于万事大吉。部署时还有几个坑要避开。
推理模式:部署时要关掉Dropout和Batch Normalization的训练模式,否则推理结果会不稳定。
输入预处理一致性:部署时的预处理必须和训练时完全一致。训练时用了归一化,推理时也要归一化,否则结果会完全错误。
模型大小和延迟:实际部署要考虑模型大小和推理延迟。太大的模型跑不动,太慢的模型用户体验差。可以用模型剪枝、量化、知识蒸馏来压缩模型。
监控和更新:部署后要监控模型表现,数据分布变化时模型效果会下降,需要定期用新数据重新训练。
7. 一些容易被忽略但很重要的细节
7.1 权重初始化不是随便设的
权重初始化对训练影响很大。如果全部初始化为0,所有神经元的输出都一样,反向传播时梯度也一样,网络永远学不到东西。如果初始化太大,前向传播的输出会爆炸,反向传播的梯度也会爆炸。如果初始化太小,输出和梯度都会趋近于0。
常用的初始化方法有Xavier初始化和He初始化。Xavier适合Sigmoid和Tanh激活函数,He初始化适合ReLU系列。它们的核心思想是让每一层的输出方差保持一致,避免信号在传播过程中衰减或放大。
7.2 Batch Normalization到底在做什么
Batch Normalization(BN)对每一层的输入做归一化,让均值为0、方差为1,然后再做一次可学习的缩放和平移。它的直接效果是稳定了每层的输入分布,让训练更稳定、收敛更快。
BN还有轻微的正则化效果,因为每个批次的均值和方差有噪声。但BN的行为在训练和推理时不一样:训练时用当前批次的统计量,推理时用训练过程中累积的移动平均。这个细节如果搞错,推理结果会完全不对。
7.3 学习率预热和衰减的配合
学习率预热(warmup)是在训练初期从小学习率逐渐增大到预设值。这样做是为了避免训练初期参数随机时大学习率导致的不稳定。预热通常用在Transformer等大模型上,但小模型也可以受益。
预热之后通常配合衰减策略。常见的是余弦退火:学习率按余弦曲线从最大值降到接近0。这种策略在训练后期能精细调整参数,往往能取得更好的最终效果。
7.4 早停:简单但有效的正则化
早停的思路很朴素:在验证损失开始上升时就停止训练。它不需要修改模型结构或损失函数,实现简单,效果却很好。
早停的关键是选一个合适的耐心值(patience)。耐心值太小,可能过早停止;耐心值太大,浪费计算资源。通常设5到20个epoch比较合理。训练过程中保存验证损失最低的模型参数,最后用这个参数作为最终模型。
7.5 随机种子的重要性
深度学习实验有很强的随机性:权重初始化随机、数据打乱随机、Dropout随机。如果不设随机种子,每次跑出来的结果都不一样,根本无法复现。
设随机种子能让实验可复现,但要注意的是,即使设了种子,不同硬件、不同框架版本、不同并行策略也可能导致结果有细微差异。所以复现性是一个程度问题,不是绝对的。
我在实际项目中的习惯是:每次实验都记录随机种子、框架版本、硬件信息,这样至少能在相同环境下复现结果。
8. 写在最后
监督学习这条链路,从数据到模型到训练到部署,每一步都有细节,每一步都可能出问题。但核心思想其实很朴素:定义一个函数,定义一个衡量好坏的指标,然后想办法调整函数的参数让指标变好。神经网络提供了强大的函数表达能力,反向传播提供了高效的梯度计算,梯度下降提供了参数更新机制。这三者组合起来,就是现代深度学习的基石。
我在带新人的时候经常说,不要一上来就追求最新的模型架构,先把最简单的BP网络手写一遍,把前向传播、反向传播、梯度下降这三件事彻底搞明白。搞明白之后,再看CNN、RNN、Transformer,你会发现它们只是在BP网络的基础上换了连接方式或加了结构先验,核心的学习机制没有变。
另外一个建议是,不要怕犯错。训练不收敛、梯度爆炸、过拟合,这些都是必经之路。每次踩坑都是一次理解加深的机会。我到现在调模型的时候,也经常遇到损失不下降的情况,排查思路无非就是那几样:检查数据、检查梯度、检查学习率、检查模型结构。把这些基本功练扎实了,遇到什么问题都不慌。