深度学习入门:神经网络是如何训练的
上篇我们从一条直线搭出了多层感知器,但此时权重 w 还是随机值,模型什么都认不出来。这一篇回答深度学习最核心的问题:参数是怎么被"训练"出来的?
一、损失函数:衡量"错得有多离谱"
模型训练的目的,是让参数尽可能逼近真实的模型。具体做法是:
-
给所有参数赋上随机值,用这些随机参数去预测训练样本;
-
得到预测值 ŷ,与真实值 y 比较;
-
定义一个损失值 loss 来衡量预测值与真实值之间的误差------误差越小越好。
常用的损失函数有:0-1 损失函数、均方差损失(MSE,公式为 loss = 1/n·Σ(ŷ - y)²)、平均绝对差损失、交叉熵损失、合页损失。
二分类的损失好算,多分类怎么办?答案是 softmax + 交叉熵。假设网络对一张猫的照片输出了三个分值 5, 4.9, -2,计算分两步:
第一步 softmax:先取指数,e^5 ≈ 148.4、e^4.9 ≈ 134.3、e^(-2) ≈ 0.135------这一步放大了数据之间的差距;再归一化,得到各个结果的概率 0.5247, 0.4748, 0.0005,三者之和为 1。
第二步交叉熵:这张图是猫,对应概率 0.5247 的那一类。对该概率取 -log,得到约 0.28,这就是本次预测的损失。取 -log 的原因:softmax 已经把输出归一化到 0~1 之间,概率越接近 1,-log 越接近 0,损失越小;概率越小,损失越大。
直观理解:猫对应的神经元输出相比其他类越大,损失值就越小,说明预测越接近真实结果;如果训练时把类别分错了,就会得到一个大大的损失值。
二、正则化:防止"死记硬背"

如果模型把训练集"背"了下来,换个新数据就抓瞎,这就是过拟合。正则化惩罚专门用来惩罚权重参数 w,常用 L1 和 L2 两种:L1 正则化是 Σ|wi|,L2 正则化是 Σwi²。
看一个例子。输入 x = 1, 1, 1, 1,有两种权重:w1 = 1, 0, 0, 0 和 w2 = 0.25, 0.25, 0.25, 0.25,它们与 x 的乘积都为 1。
乘积相同,但 w2 与每一个输入数据都发生了作用,能学到每一个特征的信息------"雨露均沾";而 w1 只和第 1 个输入有关,容易出现过拟合。因此 w2 的效果比 w1 好。正则化的作用,就是把这种"雨露均沾"的偏好写进损失函数里,与前面的均方差损失相加共同参与训练。
三、梯度下降:让损失一步步变小
至此,数据从前往后的计算已经全部完成,但还没有 w 的更新方法。
-
偏导数:多变量函数关于其中一个变量的导数,求导时其他变量保持恒定。
-
梯度:函数全部偏导数构成的向量。梯度向量的方向,就是函数值增长最快的方向。
-
梯度下降(Gradient Descent):一个一阶最优化算法,也叫最陡下降法。既然梯度方向是增长最快的方向,那沿着反方向走就是下降最快的方向,一步步就能找到函数的局部极小值。
关键超参数是学习率(步长):梯度决定移动方向,学习率决定每一步走多大。学习率太小训练太慢,太大会在最小值附近来回震荡、甚至越走越远。
梯度下降找到的是局部极小值,那全局最小怎么办?实用的做法:多生成几个随机初始点,从不同位置分别求最小值,取其中最好的结果。
四、BP 反向传播:把误差传回去
有了损失和梯度下降,还差最后一块拼图:多层网络里,怎么求每个 w 的偏导数?答案是 BP(Back-propagation,反向传播)。
完整训练步骤如下:
-
前向传播:g(W·x) = ŷ,得到预测输出;
-
计算损失:loss = 均方差损失 + 正则化惩罚项;
-
求 w 的梯度:对每个维度求偏导数------多层隐函数求偏导,本质就是链式法则;
-
误差反向传播:新 w = 旧 w - 学习率 × 梯度。初次 w 随机初始化,误差从输出层逐层往回传,更新每一层的权重;
-
循环调整 w 的值,直到损失值小于允许的范围。
一句话总结 BP:前向传播得到误差,反向传播调整误差,再前向传播、再反向传播,一轮一轮逼近最优解。
五、实战:PyTorch 训练手写数字识别(MNIST)
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
1. 加载数据(首次运行自动下载,MNIST 训练集 60000 张、测试集 10000 张)
training_data = datasets.MNIST(root="data", train=True, download=True, transform=ToTensor())
test_data = datasets.MNIST(root="data", train=False, download=True, transform=ToTensor())
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
每张图 28×28 像素,一批数据的形状 N, C, H, W = 64, 1, 28, 28
2. 定义网络:784 -> 128 -> 256 -> 10(三层全连接)
class NeuralNetwork(nn.Module):
def init(self):
super().init()
self.flatten = nn.Flatten() # 把 28×28 展平成一维 784
self.hidden1 = nn.Linear(28*28, 128)
self.hidden2 = nn.Linear(128, 256)
self.out = nn.Linear(256, 10) # 输出 10 类(数字 0~9)
def forward(self, x):
x = self.flatten(x)
x = torch.sigmoid(self.hidden1(x)) # 激活函数:sigmoid
x = torch.sigmoid(self.hidden2(x))
return self.out(x)
model = NeuralNetwork()
有 NVIDIA 显卡就 model.to("cuda"),训练会快很多
3. 损失函数 + 优化器
loss_fn = nn.CrossEntropyLoss() # 交叉熵(内部已含 softmax)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 随机梯度下降,学习率 0.01
4. 训练循环(10 个 epoch)
for epoch in range(10):
for X, y in train_dataloader:
pred = model(X) # 前向传播
loss = loss_fn(pred, y) # 计算损失
optimizer.zero_grad() # 清空上一次的梯度
loss.backward() # 反向传播:计算梯度
optimizer.step() # 梯度下降:更新参数 w = w - lr * 梯度
5. 测试:argmax 取概率最大的类别,与标签比较得到准确率
with torch.no_grad():
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
对照前面的理论来看:
nn.Linear(784, 128) 就是一层神经元,内部做的正是 W·x + b 的矩阵运算;
torch.sigmoid 是上篇讲过的激活函数;
loss.backward() 就是 BP 反向传播求梯度;
optimizer.step() 就是梯度下降更新参数;
epoch 指完整过一遍训练集,这里训练 10 轮,训练完成后在测试集上通常能达到 90% 左右的准确率。另外 batch_size=64 表示每次喂 64 张图更新一次参数,比一张一张更新更快更稳定。


到这里,深度学习入门的闭环就完成了:搭网络、算损失、求梯度、更新参数,反复循环。这四步构成了几乎所有深度学习模型训练的骨架------未来的 CNN、Transformer,区别只在网络结构和数据形式上,训练逻辑万变不离其宗。