前两章,我们构建了模型的基本框架:
前向传播:根据模型参数(权重和偏置)计算预测值;
损失函数:量化预测值与真实的标签之间的误差。
目前的模型参数是我们手动初始化的,从推理评估的结果看,并不准确。现在我们面临的核心问题是:如何调整模型参数,让损失值变小?
答案是:让网络模型从数据中自动学习,这个过程称为模型训练(Model Training)。
模型训练¶
模型训练的目标很明确:找到一组模型参数(权重和偏置),使损失值尽可能小。
这是一个经典的数学问题:函数最优化(Function Optimization)。一个同样经典的比喻是:如果把损失值看作一座山,模型训练的目标就是找到山谷(损失值最小的地方)。
神经网络使用的优化方法叫梯度下降法(Gradient Descent)。它的思路非常直观:站在山上,每次都朝着最陡的下坡方向迈一步,最终就能走到山谷。
具体来说,如果我们把推理函数代入损失函数,就会发现损失值 是权重 和偏置 的函数,记作 。
梯度(Gradient)就是这个函数“上坡最陡”的方向,用数学语言表达就是偏导数:
下降(Descent)就是让参数沿梯度相反的方向移动一步。因为梯度指向上坡,反方向自然就是下坡:
梯度计算¶
梯度是损失函数对模型参数的偏导数。计算梯度有三种方式:
数值微分(Numerical Differentiation)¶
给每个参数加一个微小扰动 ,观察损失的变化来(近似)计算导数:
在模型训练中这样做代价极高:有多少个参数,就需要做多少次前向传播。一个百万参数的模型需要运行一百万次推理,完全不可接受。
符号微分(Symbolic Differentiation)¶
手动推导每个参数的导数公式。对简单的线性回归还好,但实际应用的网络模型可能有几十层、包含各种非线性变换,手推公式可能长达数页,导致表达式爆炸。更致命的是,每次修改网络结构,所有公式就要推倒重来。
自动微分(Automatic Differentiation)¶
利用微积分的链式法则(Chain Rule)。它的核心思想是:任何复杂的运算都可以拆解为简单的基本运算的组合,而每种基本运算的导数规则是固定已知的。将前向传播的计算分解为基本计算的组合,就能反向应用链式法则,逐步计算出所有参数的梯度。
损失值 是预测值 的函数(损失函数),预测值 又是模型参数 、 的函数(推理函数)。根据微积分的链式法则,可以把它们的导数“链”起来,得到损失值关于参数的导数:
现在,让我们利用链式法则推导一下梯度的计算公式。
第一步:计算损失函数关于预测值的导数。损失函数(单样本):,对 求导:
第二步:计算推理函数关于参数的导数。推理函数:,分别对 和 求导:
第三步:用链式法则合并,得到损失值 关于 和 的完整梯度:
这两个梯度公式的公共部分,也就是损失函数关于预测值的导数:,在深度学习中有个专门的名字:误差项(Delta),记作 :
有了误差项,梯度可以简洁地表示为:
误差项 的意义非常直观:,说明模型预测偏低;,说明模型预测偏高。梯度下降正是利用这个信号来纠正参数的。
import numpy as np数据¶
feature = np.array([28.1, 58.0])
label = np.array([165])模型¶
梯度下降法分为两个步骤:梯度计算(Gradient Computation)和参数调整(Parameter Update)。我们首先把根据误差项调整模型参数的部分添加到网络模型里,而梯度计算的部分将在损失函数里完成。
反向函数¶
反向函数利用(损失函数计算的)误差项 更新模型参数:
这种数据从输出向输入方向流动的过程,称为反向传播(Backpropagation)。
class Linear:
def __init__(self, in_size, out_size):
self.weight = np.ones((out_size, in_size)) / in_size
self.bias = np.zeros(out_size)
def __call__(self, x):
return self.forward(x)
def forward(self, x):
return x @ self.weight.T + self.bias
# 反向函数(反向传播、更新参数)
def backward(self, d, x):
self.weight -= d * x
self.bias -= np.sum(d)class MSELoss:
def __call__(self, p, y):
return self.loss(p, y)
def loss(self, p, y):
return np.mean(np.square(y - p))
# 梯度函数(反向传播的起点)
def gradient(self, p, y):
# 误差项
return -2 * (y - p)建模¶
model = Linear(2, 1)
loss_fn = MSELoss()prediction = model(feature)
delta = loss_fn.gradient(prediction, label)
model.backward(delta, feature)推理¶
完成训练后,我们用调整后的模型参数再进行一次推理。
prediction = model(feature)
print(f'prediction:\t{prediction}')prediction: [1013352.429]
评估¶
loss = loss_fn(prediction, label)
print(f'loss:\t{loss}')loss: 1026548766283.6302
损失值从 14,872 爆炸到 1,026,548,766,283,增大了近七千万倍。
发生了什么?
预测值从 43 一步跳到了 1,013,352,完全越过了真实值 165,一路冲到了山谷的另一边。这就好比下山时,一脚步子太大,直接踩到了对面的山坡上,甚至比出发点更高。
这种现象在深度学习中称为发散(Divergence):损失值不仅没有降低,反而急剧增大,模型训练失败。
💡 提示:后续章节中,我们将引入学习率(Learning Rate)的概念,来控制模型参数调整的幅度,解决训练过程中梯度发散的问题。
课后练习¶
如果预测值比真实值大(预测偏高),误差项 会是正数还是负数?参数会朝哪个方向更新?结合公式思考一下。