书接上回,让我们继续看神经网络参数优化的关键算法。上次我们说到,神经网络的核心就是参数优化的工作,参数优化的过程对应到MINST数据集分类的代码里是以下这一段:
参数优化:
for epoch in range(2): # 训练循环:遍历数据集2个epoch(轮次),用于快速验证模型性能变化
for (x, y) in train_data: # 遍历训练数据加载器,每次获取一个批次的输入x(图像)和标签y
net.zero_grad() # 清空模型参数的梯度缓存,防止梯度累积导致参数更新错误
output = net.forward(x.view(-1, 28 * 28)) # 前向传播:将输入图像展平为(batch_size, 784)的向量并输入网络
loss = torch.nn.functional.nll_loss(output, y) # 计算负对数似然损失(NLL Loss),适用于分类任务
loss.backward() # 反向传播:通过计算图自动求导,更新各层参数的梯度
optimizer.step() # 优化器根据梯度更新模型参数(如SGD/Adam)
print("epoch", epoch, "accuracy:", evaluate(test_data, net)) # 每轮训练后打印测试集准确率
再重新梳理下整个参数优化的步骤,首先是梯度清零,然后是定义神经网络,神经网络分为四层,全连接层1→全连接层2→全连接层3→输出层,完成从784维-64维-64维-10维的特征映射。下一步是前向传播,引入Relu函数,提取非线性特征,增强模型表达能力。最后用LOG_softmax计算对数概率,然后反向传播,通过计算图自动求导,更新各层参数的梯度,最后用优化器更新模型参数。
二、梯度清零
代码如下:
net.zero_grad() #清空模型参数的梯度缓存,防止梯度累积导致参数更新错误
首先说一下什么是梯度,梯度本质是多元函数对各个自变量的偏导数构成的向量。在神经网络中,损失函数(如均方误差、交叉熵)是模型预测值与真实值之间误差的量化函数,而梯度则描述了这一误差对每个权重(如 w)和偏置(如 b)的局部变化率。若损失函数为 J(w,b),其梯度可表示为:
这一向量方向指向函数值增长最快的方向,而负梯度方向则是减小误差的最优调整方向。梯度清零非常重要,它确保每次反向传播时梯度是独立计算的,避免梯度累积导致训练错误。一句话总结,是一系列偏导数,用来表征损失函数相对于每个参数的局部变化。
三、网络定义及前向传播
核心代码:
output = net.forward(x.view(-1, 28 * 28)) #前向传播:将输入图像展平为(batch_size, 784)的向量并输入网络
class Net(torch.nn.Module): # 定义神经网络类,继承自PyTorch的Module基类
def __init__(self):
super().__init__() # 调用父类构造函数初始化,确保继承Module的所有功能
# 定义网络层结构
self.fc1 = torch.nn.Linear(28 * 28, 64) # 全连接层1:输入784维(MNIST图像展平后的28x28像素),输出64维
self.fc2 = torch.nn.Linear(64, 64) # 全连接层2:输入64维,输出64维,用于特征抽象
self.fc3 = torch.nn.Linear(64, 64) # 全连接层3:继续深化特征提取,保持维度不变
self.fc4 = torch.nn.Linear(64, 10) # 输出层:将64维特征映射到10维,对应0-9数字分类
def forward(self, x): # 定义前向传播流程,描述数据在网络中的流动路径
x = torch.nn.functional.relu(self.fc1(x)) # 第一层:全连接+ReLU激活函数,引入非线性
x = torch.nn.functional.relu(self.fc2(x)) # 第二层:同上,进一步提取非线性特征
x = torch.nn.functional.relu(self.fc3(x)) # 第三层:继续使用ReLU增强模型表达能力
x = torch.nn.functional.log_softmax(self.fc4(x), dim=1) # 输出层:log_softmax计算对数概率,适用于NLLLoss损失函数
return x
(如第一层里,特征值为784,代入即可)
loss = torch.nn.functional.nll_loss(output, y) # 计算负对数似然损失(NLL Loss),适用于分类任务
在神经网络的结构里,引入了负对数概率,这里解释下第一步是Softmax,Softmax函数将任意实数向量转换为概率分布
第二步是对数化,此公式通过合并指数和对数运算,直接输出对数概率,避免了单独计算Softmax 再取对数时的数值不稳定问题。
取对数这个操作应该挺好理解的,能解决两个问题:
上溢/下溢问题:直接计算 exi可能导致浮点数溢出(如 e1000 超出计算范围)。将向量平移至负值或零附近,确保指数运算的数值安全。
对数合并:使用 PyTorch 内部优化的 logsumexp 函数计算,避免了显式求和时的中间值溢出。
四、计算负对数似然损失
核心代码:
loss = torch.nn.functional.nll_loss(output, y) # 计算负对数似然损失(NLL Loss),适用于分类任务
上边那一步得到的对数计算后各参数的值(即output),这一步用来计算负对数似然损失,公式如下:
这里的N指的是样本数,即batch_size,由自己定义,即定义每个批次加载多少个样本。而批次也是深度学习里的一个核心概念,将训练数据分成多个小集合,每个小集合包含固定数量的样本,一个小集合就是一个批次。
计算出的负对数似然损失值越小,模型预测概率与真实标签越接近,需要注意的是,这里损失函数已经输出相对于各个参数的值了。
五、反向传播
核心代码:
loss.backward()# 反向传播:通过计算图自动求导,更新各层参数的梯度通过
用动态计算图自动计算损失函数对模型参数的梯度。动态计算图是pytorch架构的核心,这个着实有点复杂,我把大模型的回答放这里了。
https://yuanbao.tencent.com/bot/app/share/chat/2b236ca861eac51b52c5af2bd454be9b
大模型回答
最终它的输出是损失函数对模型参数的梯度。
六、更新参数
核心代码:
optimizer.step()# 优化器根据梯度更新模型参数(如SGD/Adam)
在上一步里,我们已经得到了损失函数对模型参数的梯度,这一步直接代入即可,数学公式如下
基于梯度下降法,通过反向传播计算得到的梯度信息,调整模型参数以最小化损失函数。
七、总结
这篇文章的目的是讲清楚参数优化的逻辑和基本过程,这样能有个大概框架上的理解了。