🔥 本文专栏:机器学习
🌸作者主页:努力努力再努力wz



💪 今日博客励志语录:
不要用别人已经开花的季节,去否定自己还在扎根的阶段。
思路总览
text
传统程序:程序员直接编写输入到输出的规则
↓
一些复杂问题,无法人工列举出可靠的映射规则
↓
机器学习:利用数据学习合适的映射关系
↓
选择模型结构 + 初始化参数
↓
学习 / 训练:根据数据调整参数
↓
学习范式:监督学习 / 无监督学习 / 强化学习
↓
先研究监督学习:训练数据包含输入与真实输出
↓
预测目标不同:回归(数值)/ 分类(类别)
↓
一元线性回归:ŷ = wx + b
↓
模型参数还不知道怎么确定
↓
构造损失函数:衡量预测与真实输出的差异
↓
固定样本数据:J(w, b) 变成参数的二元函数
↓
两个观察视角:二维拟合直线 / 三维损失曲面
↓
优化目标:寻找让损失尽可能小的参数
↓
偏导数 → 梯度 → 负梯度方向
↓
方向确定了,还需要决定每步走多远
↓
学习率:控制参数更新幅度
↓
每到新位置,重新计算梯度,形成一条下降轨迹
↓
不同学习率 → 不同轨迹、收敛速度甚至不同结果
↓
学习率选择:实验调参 / 线搜索 / 数学分析 / 调度
↓
收敛与停止:损失变化 / 梯度大小 / 最大迭代次数
↓
得到训练后的模型,用于预测新样本
一、从普通程序开始:我们究竟想解决什么问题?
1. 一个程序,本质上可以抽象成输入到输出的映射
对于之前编写过的普通程序,我们可以把它抽象成:
y = f ( x ) y=f(x) y=f(x)
其中,x 代表输入,y 代表输出,而中间的 f 就是输入到输出之间的映射规则。
例如,一个简单的 C++ 加法函数:
cpp
int add(int a, int b) {
return a + b;
}
这个场景很容易理解:我们已经知道输入和输出之间应该遵循什么规则,因此可以由程序员直接实现。
但是,假设现在要求我们编写一个程序:给定一张图片,判断其中的动物是猫还是狗。
我们应该如何编写这个 f 呢?
直接判断动物是不是黑色?显然不行,因为猫和狗都可能是黑色。判断是不是尖耳朵?同样不行,因为这个条件也无法稳定地区分两类动物。
我们会发现,对于这些复杂场景,虽然能够提供输入,也知道自己希望获得什么输出,但是很难靠程序员人工列举一套覆盖各种情况的可靠规则。
那么,既然我们无法直接写出映射规则,能不能换一种方式:
给计算机提供大量已有数据,让它从这些数据中学习出一种可以用于新输入的映射关系?
这就是理解机器学习的一个自然切入点

2. 所谓"学习",究竟是在学习什么?
我们先不急着讨论复杂的神经网络,仍然从此前熟悉的数学函数出发。
假设我们选择了一个模型:
y ^ = w x + b \hat y=wx+b y^=wx+b
这个模型在几何上是一条直线。其中,w 表示斜率,b 表示截距;上面的 ŷ 表示模型预测出来的值,用来和真实值 y 区分。
这里需要认识到:选择了模型结构,并不意味着已经得到了合适的映射规则。
因为我们虽然知道模型采用一次函数的形式,但并不知道哪一组 w、b 能够更好地描述数据中的关系。
因此,实际训练时通常会先初始化参数,随后利用数据不断调整参数。
例如,从:
y ^ = 0.5 x + 3 \hat y=0.5x+3 y^=0.5x+3
逐步调整到某个更符合数据规律的函数。这个过程中,函数结构可以保持不变,变化的是内部参数。
所以,对于我们目前研究的参数化模型,可以先将"学习"理解为:
根据数据确定或者优化模型参数,使模型逐步获得对目标问题的预测能力。
需要注意,机器学习并不是所有算法都必须表现为"通过梯度下降调整参数";我们这里先研究的是最容易理解的一类模型。
还有一个概念要提前区分:训练(Training)和预测 / 推理(Inference)。
训练阶段利用已有数据学习参数;而预测阶段通常使用训练好的模型参数,直接对新的输入计算结果。我们之后讨论的"调整参数",主要发生在训练阶段。
二、学习方式为什么还要分类?
既然机器学习需要从数据中获得规律,那么一个很自然的问题就是:不同场景下,我们能够提供给计算机的信息是否都一样?
答案显然是否定的。
以动物图片为例,我们可以提供图片,同时明确告诉模型正确答案;也可以只提供图片,不告诉它对应的类别,希望它自己发现哪些图片更相似。
还可以让一个智能体与环境交互,例如不断尝试走迷宫,根据最终获得的奖励来学习如何行动。
这些场景的共同点是都涉及从经验中获得规律,但它们的学习信号不同。
| 学习范式 | 提供的信息 | 希望学到什么 |
|---|---|---|
| 监督学习(Supervised Learning) | 输入及其对应的目标输出 | 输入与输出之间的映射关系 |
| 无监督学习(Unsupervised Learning) | 通常没有人工提供的目标标签 | 数据中的相似性、潜在结构或分布规律 |
| 强化学习(Reinforcement Learning) | 与环境交互,以及奖励等反馈 | 能够获得较高长期收益的行动策略 |
我们当前只需要先建立这个分类认知,并不要求立刻弄清无监督学习和强化学习的内部算法。
这里还需要区分两个层次:
- 监督学习、无监督学习、强化学习,主要描述学习信号与任务组织方式。
- 梯度下降,则是一种利用目标函数的梯度优化参数的方法。
它们不是同一个维度的概念。并不是说监督学习对应梯度下降,而无监督学习就必须换成另一套完全不相关的数学机制。除此之外还有自监督学习、半监督学习等范式,这里暂不展开。
下面我们先研究监督学习,因为它可以直接与我们此前学习的函数、导数和梯度知识连接起来。
三、监督学习:为什么必须知道"正确答案"?
1. 从一批已知输入与输出的数据开始
假设我们希望通过房屋面积预测房价,并且已经收集了一批真实数据:
| 房屋面积(平方米) | 房屋价格(万元) |
|---|---|
| 50 | 100 |
| 60 | 120 |
| 80 | 160 |
| 100 | 200 |
注意,这里不仅存在输入,还存在每个输入对应的真实输出。
对于每一行数据,我们可以将其称为一个样本(Sample) 。在这个例子中,房屋面积是输入特征(Feature) ,房价是目标标签(Label)。所有样本组成数据集(Dataset)。
那么,我们就可以先选择一个模型,再将房屋面积输入模型,得到预测房价;随后将预测值与真实房价比较,根据差异来优化参数。
整个过程与此前建立的训练机制并没有本质区别:
text
带有正确输出的训练样本
↓
选择模型结构、初始化参数
↓
把输入送入模型,得到预测输出
↓
将预测输出与真实输出比较
↓
使用损失函数衡量差异
↓
调整模型参数
↓
重复训练,得到更合适的模型
这就是监督学习中最常见的训练思路。这里的"监督"并不是有人站在计算机旁边实时指挥它怎么改参数,而是训练数据提供了目标输出,使模型拥有可以据此学习的反馈信号。
2. 回归与分类:我们到底希望模型预测什么?
在监督学习的基础上,我们还可以按照预测目标来区分两类常见任务。
回归(Regression):希望预测一个有实际数值意义的量,例如房价、温度、油耗。
分类(Classification):希望判断输入属于哪一个类别,例如猫还是狗、正常邮件还是垃圾邮件。
这里最容易产生的误区是:把"模型输出的是不是数字"直接当作分类依据。
例如,在猫狗分类中,我们完全可以约定:0 表示猫,1 表示狗。虽然模型最终可能用整数表示类别,但其任务仍然属于分类。
因为这些数字只是类别编码;而回归希望预测的数值,本身具有大小、距离等业务含义。
| 场景 | 输出示例 | 任务类型 |
|---|---|---|
| 预测房价 | 150 万元 | 回归 |
| 预测温度 | 26.5℃ | 回归 |
| 猫狗识别 | 猫 / 狗(也可以编码为 0 / 1) | 分类 |
| 邮件识别 | 正常 / 垃圾邮件 | 分类 |
3. 从回归过渡到一元线性回归
我们目前选择研究回归。回归任务并不限定只能使用一次函数,例如复杂回归任务也可以使用其他非线性模型。
如果我们选择:
y ^ = w x + b \hat y=wx+b y^=wx+b
利用这一类线性参数模型来预测具体数值,就得到了最简单的一种线性回归模型。
而这里的"一元"表示只有一个输入特征,不是只有一个样本,更不是只有一个模型参数。
对于一元线性回归:
y ^ = w x + b \hat y=wx+b y^=wx+b
只有一个输入特征 x,却有两个待训练参数 w、b。
如果我们同时考虑房屋面积、房龄、距离市中心的距离等多个输入特征,则可以扩展为多元线性回归:
y ^ = w 1 x 1 + w 2 x 2 + w 3 x 3 + b \hat y=w_1x_1+w_2x_2+w_3x_3+b y^=w1x1+w2x2+w3x3+b
这里仍然预测房价这样一个具体数值,只是影响预测结果的输入特征变多了。
四、一元线性回归:如何找到一条合适的直线?
1. 在二维平面中,训练样本就是一个个点
现在我们回到一元线性回归:
y ^ = w x + b \hat y=wx+b y^=wx+b
由于只有一个输入特征,因此可以很自然地在二维坐标系中观察:横轴是输入 x,纵轴是输出 y。
每一个训练样本 (xᵢ, yᵢ) 对应平面上的一个点;而我们的模型,对应的是一条直线。
这就可以建立一个非常直观的理解:
所谓训练一元线性回归模型,就是调整直线的斜率和截距,让这条直线尽可能好地描述所有样本点的变化趋势。
但是请注意,我们追求的并不是"让直线穿过尽可能多的样本点"。
真正需要解决的问题是:哪一条直线能使所有样本的总体预测误差尽可能小?

2. 为什么需要损失函数?
我们既然希望找到"更好"的直线,就必须先规定什么叫"更好"。
假设某一个样本的真实输出为 yᵢ,预测结果为 ŷᵢ,二者之间的差称为误差或残差。一个很容易想到的损失形式,就是平方误差:
L i = ( y i − y ^ i ) 2 L_i=(y_i-\hat y_i)^2 Li=(yi−y^i)2
为什么要平方?一个直接的好处是,正负误差不会互相抵消;而且较大的误差会受到更大惩罚。
由于我们的数据集包含多个样本,通常需要衡量整体误差,而不能只让其中一个样本预测准确。
因此,把所有样本的平方误差加起来,再除以样本数量,就得到均方误差(Mean Squared Error,MSE):
J = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 J=\frac1n\sum_{i=1}^{n}(y_i-\hat y_i)^2 J=n1i=1∑n(yi−y^i)2
这里:
n是训练样本数量;yᵢ是第i个样本的真实输出;ŷᵢ是模型预测输出;J是全部样本对应的平均平方损失。
这里特别强调:均方误差是"平方之后求平均",不是直接把有正有负的原始误差累加。
五、最关键的一步:将模型训练转换为函数最小值问题
1. 为什么损失函数的自变量变成了模型参数?
我们刚才得到:
J = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 J=\frac1n\sum_{i=1}^{n}(y_i-\hat y_i)^2 J=n1i=1∑n(yi−y^i)2
但这里的预测结果并不是凭空出现的,而是由模型计算得到:
y ^ i = w x i + b \hat y_i=wx_i+b y^i=wxi+b
那么,将预测函数代入均方误差:
J ( w , b ) = 1 n ∑ i = 1 n ( y i − w x i − b ) 2 \boxed{J(w,b)=\frac1n\sum_{i=1}^{n}(y_i-wx_i-b)^2} J(w,b)=n1i=1∑n(yi−wxi−b)2
这里需要认真观察一下。
对于一批已经确定的训练样本,每个 xᵢ 和 yᵢ 都是已知常量。真正需要调整的是模型参数 w 和 b。
因此,我们原本用于预测的函数是:
text
预测函数
输入 x
↓
ŷ = wx + b
↓
输出预测值 ŷ
而训练时需要研究的函数变成了:
text
损失函数
输入模型参数 (w, b)
↓
用该参数预测全部训练样本
↓
计算均方误差 J(w, b)
↓
输出损失值 J
换句话说,我们现在实际上有了两个不同的函数:
| 函数 | 自变量 | 函数值 | 几何图像 |
|---|---|---|---|
预测函数 ŷ = wx + b |
输入特征 x(参数暂时固定) |
预测结果 ŷ |
二维平面中的直线 |
损失函数 J(w,b) |
模型参数 w,b(数据暂时固定) |
总体损失 J |
三维空间中的曲面 |
这两个观察视角,通过同一组模型参数 w,b 联系在一起。
改变参数,相当于一方面改变二维平面上的拟合直线,另一方面移动到三维损失曲面上的另一个位置。
2. 一个具体的数据集
为了后面能够复现整个过程,我们统一使用下面四个样本:
输入 x |
真实输出 y |
|---|---|
| 0 | 1 |
| 1 | 3 |
| 2 | 5 |
| 3 | 6 |
那么损失函数就是:
J ( w , b ) = 1 4 ( 1 − b ) 2 + ( 3 − w − b ) 2 + ( 5 − 2 w − b ) 2 + ( 6 − 3 w − b ) 2 J(w,b)=\frac14\left(1-b)\^2+(3-w-b)\^2+(5-2w-b)\^2+(6-3w-b)\^2\\right J(w,b)=41(1−b)2+(3−w−b)2+(5−2w−b)2+(6−3w−b)2
例如,参数取 w = 1, b = 0 时,预测结果为 0、1、2、3,对应的平方误差为 1、4、9、9:
J ( 1 , 0 ) = 1 + 4 + 9 + 9 4 = 5.75 J(1,0)=\frac{1+4+9+9}{4}=5.75 J(1,0)=41+4+9+9=5.75
如果我们换一组参数,损失值也会变化。对于这一组示例数据,均方误差最小的一组参数为:
w = 1.7 , b = 1.2 w=1.7,\qquad b=1.2 w=1.7,b=1.2
此时:
J ( 1.7 , 1.2 ) = 0.075 J(1.7,1.2)=0.075 J(1.7,1.2)=0.075
3. 从这里开始,训练问题就变成了最优化问题
现在我们真正需要解决的问题是:
min w , b J ( w , b ) \boxed{\min_{w,b}J(w,b)} w,bminJ(w,b)
也就是寻找一组模型参数,让整个训练数据集的均方误差尽可能小。
这一步非常重要,因为我们成功将"怎么让模型预测得更好"转换成了一个熟悉的数学问题:
给定一个关于参数的函数,如何寻找它的最小值?
接下来此前学习的导数、偏导数与梯度,就有了明确的用途。
六、从二维直线到三维曲面:损失函数究竟长什么样?
1. 我们可以通过代入不同参数来绘制曲面
损失函数具有两个自变量 w,b,因此绘图时可以使用三条坐标轴:
- 横轴表示参数
w; - 另一条水平轴表示参数
b; - 竖直方向表示损失值
J。
每选择一组 (w,b),就能够计算出一个 J,从而得到三维空间中的点 (w,b,J)。
不断代入不同参数,就能绘制或近似绘制出整个损失曲面。
2. 先从我们熟悉的一元抛物线出发
对于一元函数:
z = x 2 z=x^2 z=x2
它的图像是开口向上的抛物线。
现在多增加一个自变量:
z = x 2 + y 2 z=x^2+y^2 z=x2+y2
如何理解这个二元函数的三维图像?
一个非常直观的思路,就是固定一个自变量,将二元函数重新转换为一元函数。
假设固定 y=1:
z = x 2 + 1 z=x^2+1 z=x2+1
那么我们得到的仍然是一条开口向上的抛物线。几何上,相当于使用一个平行于 x-z 平面的竖直平面截取原曲面。
如果反过来固定 x=1:
z = y 2 + 1 z=y^2+1 z=y2+1
同样是一条开口向上的抛物线。
现在我们再进一步:不仅沿着 x 或 y 轴观察,而是让一个经过竖直 z 轴的平面不断旋转。
对于这个特殊的 z=x²+y²,无论向哪个水平朝向旋转,截面都会是一条开口向上的抛物线。不同方向的抛物线共同构成一个类似碗状的曲面。

这样,我们就从熟悉的一元抛物线,自然过渡到了三维的抛物面。
七、已经有了损失曲面,怎样决定参数往哪里移动?
1. 初始化参数,相当于在曲面上确定起点
当我们给定初始的 w,b 后,就能够计算出当前损失,于是在三维损失曲面上确定一个位置。
接下来,我们要做的事情就非常形象了:
从这个位置出发,沿着曲面向更低的地方移动,希望逐步到达损失较小的位置。
但是,如果完全不知道方向,就只能不停地盲目尝试;有时损失变小,有时损失变大,显然缺少可靠的调整依据。
于是我们需要回到之前学习过的微积分。
2. 偏导数:分别观察两个参数的影响
对于二元损失函数 J(w,b),我们可以分别求偏导:
∂ J ∂ w , ∂ J ∂ b \frac{\partial J}{\partial w},\qquad \frac{\partial J}{\partial b} ∂w∂J,∂b∂J
求偏导时,把另一个参数暂时看作常量,就能够研究当前参数发生微小变化时,损失的瞬时变化率。
但是实际更新参数时,我们并不局限于只调整 w 或只调整 b。两个参数完全可以同时变化。
既然如此,能不能研究沿着参数平面中任意一个方向移动时,损失到底会如何变化?
这里就需要方向导数。
3. 方向导数与梯度:确定局部下降最快的方向
我们把两个偏导数组合在一起,就得到梯度向量:
∇ J ( w , b ) = ∂ J ∂ w ∂ J ∂ b \nabla J(w,b)= \begin{bmatrix} \frac{\partial J}{\partial w}\\4pt \frac{\partial J}{\partial b} \end{bmatrix} ∇J(w,b)=∂w∂J∂b∂J
注意,这里的两个分量是对**模型参数 w,b**求偏导,而不是对原始训练样本的输入 x 求偏导。
如果选择一个单位方向向量 u,那么沿这个方向的方向导数就是梯度与该方向向量的点乘:
D u J = ∇ J ⋅ u = ∥ ∇ J ∥ cos θ D_{\mathbf u}J=\nabla J\cdot\mathbf u =\|\nabla J\|\cos\theta DuJ=∇J⋅u=∥∇J∥cosθ
这里 θ 是梯度方向与单位方向向量之间的夹角。
通过这个公式,我们就能够理解:
- 当夹角为
0°时,方向导数最大,表示局部上升最快。 - 当夹角为
180°时,方向导数最小,表示局部下降最快。
因此,假设当前位置梯度不为零,那么负梯度方向就是当前点的局部最速下降方向。
这就是梯度下降(Gradient Descent)为什么使用负梯度的根本原因。
不过,这里只是回答了"现在往哪里走 ",还没有回答"走多远"。
八、为什么不能找到一个负梯度方向后,一直沿着它移动?
这个问题其实可以继续从几何意义上理解,而不需要一开始就计算复杂公式。
1. 从三维曲面截取一条二维曲线
假设我们在损失曲面上的 A 点计算出了负梯度方向。
现在暂时不重新计算方向,只沿着这个固定方向前进。
从几何意义上,我们相当于使用一个经过 A 点、并沿当前方向展开的竖直平面截取损失曲面。平面和曲面的交线就是一条二维曲线。
这条曲线的高度 表示损失值,而它在某个位置的切线斜率表示沿当前固定方向的瞬时变化率。
因此,刚从 A 点出发时,切线斜率为负,意味着走一小段距离会降低损失。
但继续沿这个方向移动,切线斜率可能逐渐变小、趋近零,甚至转为正数。
那么,如果步子迈得过大,越过了这条截面曲线的最低点,就可能导致损失反而增大。
这恰好说明:
负梯度描述当前位置局部下降最快的方向,并不保证沿着该方向移动任意远,损失都持续下降。

2. 每次移动之后,为什么又要重新计算梯度?
接下来还有一个非常重要的细节。
假设我们从 A 点沿负梯度方向移动一小步,到达 B 点。
但是,B 点已经不是 A 点了。损失曲面在 B 点附近的形状可能发生了变化,因此 B 点的两个偏导数、梯度以及局部最速下降方向,也可能与 A 点不同。
所以,梯度下降真正采用的是:
text
A 点
↓ 计算 A 点的负梯度方向
沿这个方向移动一步
↓
B 点
↓ 重新计算 B 点的负梯度方向
再移动一步
↓
C 点
↓ 重新计算 C 点的负梯度方向
继续更新......
也就是说,梯度下降并不是"求一次梯度,然后一直走到底",而是每一次移动之后,都重新使用当前位置的梯度决定下一步。
我们不是连续移动无穷多个微小距离,而是通过程序进行一次次离散的参数更新。
九、学习率:方向确定了,每次到底走多远?
1. 从直观步长过渡到真正的更新公式
此前我们把学习率(Learning Rate)形象地理解为步长。这对于建立认知非常有帮助,但严格来说,学习率本身是对梯度向量的缩放系数,并不总是等于真实移动距离。
通常使用希腊字母 η 表示学习率。
梯度下降更新规则就是:
w new = w − η ∂ J ∂ w b new = b − η ∂ J ∂ b \boxed{ \begin{aligned} w_{\text{new}} &= w-\eta\frac{\partial J}{\partial w}\\4pt b_{\text{new}} &= b-\eta\frac{\partial J}{\partial b} \end{aligned}} wnewbnew=w−η∂w∂J=b−η∂b∂J
从几何意义看:
- 负梯度决定更新方向;
- 学习率控制梯度向量被放大或缩小多少;
- 实际参数空间中的移动距离,还受到梯度模长影响。
因此,同样的学习率,在不同位置也可能产生不同大小的参数更新。
2. 一个最容易忽略的问题:学习率还会影响完整的下降路径
现在我们回到一个此前反复推敲的思想。
假设从同一个初始点 A 出发,我们算出来的初始负梯度方向完全一样。
但只要第一次更新使用不同的学习率,就可能到达两个不同的新位置 B₁、B₂。
之后:
text
起点 A
/ \
较小更新幅度 较大更新幅度
↓ ↓
B₁ B₂
↓ ↓
重新计算梯度 重新计算梯度
↓ ↓
得到新方向 得到新方向
↓ ↓
后续路径 1 后续路径 2
由于到达的位置不同,下一次计算出的负梯度方向也可能不同。因此,学习率不仅影响走得快不快,还会影响整个参数优化轨迹。
而且理论上每一步的学习率都可以从无数个正实数中选择,因此从同一个起点出发,可以产生大量乃至无限多条不同的离散更新轨迹。
这里有一个非常重要的结论:梯度下降并不枚举所有可能的路径,再挑选出一条"最好的路径"。
它采取的是一种依据当前位置局部信息逐步做决策的方式,从思想上有点类似贪心算法,但"当前局部下降最快"不代表"整条路径最短"。
3. 路径不同,最终最低点就一定不同吗?
这里必须把优化路径 和优化终点区分开。
对于一般的非凸损失曲面,可能存在多个高低不同的局部低谷。从同一个起点出发,不同步长可能让更新轨迹进入不同区域,最终收敛到不同的局部极小值;步长过大时甚至可能无法收敛。
但是,对于当前研究的线性回归均方误差,其损失是凸二次函数。
只要满足适当的收敛条件,不同学习率产生的路径虽然不同,但仍可以收敛到全局最小损失。如果全局最优参数唯一,那么正确收敛到最优解的路径最终会趋向同一组参数。
因此,这三个问题需要分开:
- 路径是否可能不同? 可以。
- 下降速度与经过的位置是否可能不同? 可以。
- 最终最优损失是否一定不同? 不一定,取决于函数性质与是否收敛。
十、什么时候才算收敛?什么时候应该停止训练?
假设我们已经选择合适的优化方法,按照"计算梯度 → 更新参数 → 重新计算梯度"的过程不断迭代。
那么,这个过程什么时候结束?
我们先从数学意义理解收敛(Convergence)。
1. 从几何意义上理解收敛
假设损失曲面是一个碗状曲面,我们从某个初始点出发,沿着一条不断修正方向的轨迹往下移动。
随着更新继续,参数可能越来越接近某个最低位置,损失值也可能越来越接近某个确定数值。
从数学意义上说,如果随着迭代次数增加,参数序列或者损失序列趋向某个极限,那么就可以讨论它们的收敛。
但是,实际程序不可能真的执行无穷多次更新,因此我们还需要设置停止条件。
注意:数学意义上的收敛,与程序设置的停止条件不是同一个概念。
2. 第一种停止依据:损失几乎不再变化
假设连续几轮更新,损失从 2.001 变成 2.0005,再变成 2.0001。
我们可能认为模型已经逐渐稳定,于是可以设置阈值:当连续多次迭代的损失变化很小,就考虑停止。
不过,损失变化很小并不能单独证明已经达到最优。学习率过小,也可能让每次更新都极其微弱。
3. 第二种停止依据:梯度已经很小
这一种方式与几何意义更加直接。
对于光滑函数,在内部局部最低点处,各个方向的一阶变化率为零,因此梯度为零。
所以,可以在训练中观察梯度的模长:如果已经小到某个阈值以内,就考虑停止。
但需要注意,对于一般非凸函数,梯度为零的位置还可能是局部最大点或者鞍点。因此,梯度很小是一个有用的停止信号,却不自动等于找到了全局最优。
对于当前凸的线性回归均方误差,梯度为零则对应全局最优解。
4. 第三种停止条件:达到最大迭代次数
实际程序不能无限执行,因此通常还会设置最大迭代次数,例如 1000 次或 10000 次。
这个条件的作用是保证计算在有限预算内结束。
但是:
达到最大迭代次数,只能说明程序按照限制结束,不能证明算法已经收敛。
真实训练中,还可能监控验证集损失。如果训练损失继续降低,但验证集表现长期不改善,甚至开始变差,可能需要提前停止以避免过拟合。这与纯粹判断优化算法是否收敛不是完全相同的目标。
还有一点:损失收敛并不意味着损失一定变成零,也不必然意味着找到了全局最优解。
例如,本文示例数据的最低均方误差就是 0.075,并不是零。
十二、最后,将整条知识链重新梳理一遍
我们最初从传统程序出发,知道程序可以抽象为 y=f(x)。对于一些复杂场景,程序员很难直接描述输入到输出之间的规则,所以希望计算机从数据中学习到合适的映射关系。
对于参数化模型,所谓学习,可以先理解为:选定模型结构,初始化参数,再利用数据优化参数。
其中,监督学习的特点是训练样本包含输入与目标输出,模型可以比较预测结果与真实结果,形成学习信号。
如果目标是预测具有实际数值意义的量,我们称其为回归;如果目标是判断所属类别,则称为分类。
在回归中,我们选择最简单的一元线性模型 ŷ=wx+b。从二维几何意义来看,模型是一条直线,训练就是调整直线的斜率和截距,让总体误差尽可能小。
为了衡量误差,我们构造均方误差损失函数。由于训练数据已固定,将预测模型代入损失表达式后,损失就变成了关于模型参数 w,b 的二元函数。
这样,原本的机器学习训练问题,就变成了一个更加明确的数学问题:如何寻找使损失函数尽可能小的参数?
接下来,从几何上我们可以把损失函数想象成一个三维曲面。初始化模型参数,相当于在曲面上确定一个起点;偏导数告诉我们分别改变参数的局部变化率,梯度将这些信息组合起来,而负梯度给出了当前位置局部下降最快的方向。
但负梯度只是方向,不能保证沿这个方向移动任意距离都降低损失,因此我们需要通过学习率控制更新幅度。
每次更新后到达新的位置,都要重新计算梯度。学习率不同,就可能形成不同的更新轨迹、产生不同的下降速度;对于非凸函数,甚至可能收敛到不同的局部低谷。但对于我们目前研究的凸二次损失,在合适的条件下,梯度下降可以收敛到全局最低损失。
最后,所谓收敛,是指数学上的参数或损失序列逐渐趋近某个稳定极限;而程序实际运行时,还需要通过梯度大小、损失变化、迭代次数等停止条件,决定什么时候结束训练。
到这里,我们就能够从一个非常简单的映射函数出发,沿着自然的逻辑,一直解释到梯度下降的方向、步长、轨迹、收敛和停止条件。
机器学习训练不是凭空"让模型变聪明",而是通过定义可优化的目标,并使用相应的算法,在参数空间中不断寻找更合适的模型参数。
后续可以继续学习: 数据集的划分与泛化能力、过拟合与欠拟合、特征处理、分类模型中的逻辑回归,以及多元线性回归与矩阵表示。这些内容都可以建立在本文的认知基础之上。
