图1
为了更好地理解,下面以一个单变量输入的神经网络来举例:众所周知,函数z=w*x+b在二维坐标轴中是一条直线,那么对于一个随机点,我们如何判断它是否在直线上呢?
图2
一般思路是构造一个判断函数即可:f(x)=z-(w*x+b),如果f(x)=0,则判断点就在函数上。示意图如下:
图3
图4
图5
2、计算损失:
对于回归问题,我们可以使用均方误差(MSE)损失函数:
然后,通过链式法则计算损失相对于加权和z的偏导数:
接下来再计算损失相对于每个权重Wi的偏导数:
最后,对偏置b求导:
偏置b在神经网络中相当于一个平移量(独立的常数项),它的作用是调整神经元的输出不受输入X的影响。因此在计算反向传播时,加权和相对于b的导数就是1。
4、更新权重和偏置:
有了梯度之后,就可以使用梯度下降法更新权重和偏置,使神经网络向最小化误差的方向调整。更新公式为:
偏置更新:bias = bias - η * dL/db
其中,η 是学习率,控制更新的步长。
重复以上步骤,通过多次迭代更新权重和偏置,逐渐减少预测误差,就能使模型更准确地拟合数据,使神经网络的输出逐步逼近理想输出。综上,反向传播的核心步骤就是:前向传播计算网络输出,并计算损失函数。再从输出层开始,通过链式法则逐层计算每个参数的梯度。
import torch
import torch.nn as nn
import torch.optim as optim
# 1. 创建数据
torch.manual_seed(0) # 设置随机种子,确保每次生成的数据都是一样的
X = torch.rand(100, 1) * 10 # 输入数据,随机生成100个x值,范围在0到10之间
Y = 2 * X + 1 + torch.randn(100, 1) * 2 # 生成对应的y值,根据线性关系y = 2x + 1,并添加一些正态分布的噪声
# 2. 定义模型
# 使用 PyTorch 的 nn.Linear 构建一个简单的线性模型
model = nn.Linear(1, 1) # 输入和输出都是一维
# 3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失
optimizer = optim.SGD(model.parameters(), lr=0.003) # 使用随机梯度下降,学习率为0.003
# 4. 训练模型
epochs = 1000 # 训练的轮数
for epoch in range(epochs):
# 前向传播:计算预测值
y_pred = model(X)
# 计算损失
loss = criterion(y_pred, Y)
# 反向传播:计算梯度
optimizer.zero_grad() # 清零上一步的梯度
loss.backward() # 反向传播,计算梯度
optimizer.step() # 更新参数
# 每隔100轮输出一次损失
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')
# 5. 查看训练后的参数
print("w = ", model.weight.item())
print("b = ", model.bias.item())
1000轮训练过程如下,每100轮输出一次当前的损失值,可以看出损失值是逐渐收敛的,最后打印出模型的权重 w 和偏置 b与设定的数据生成公式中的参数(2和1)也比较接近:
看似挺简单的对不对?但在实际中,你会遇到很多问题。比如:学习率过大,导致更新步骤时参数发散;模型训练前没有先将特征进行标准化或归一化处理,导致梯度更新不稳定,等等。
另外,神经网络的结构对小样本量的数据集和复杂曲线的拟合效果其实并不太好。如果在训练过程中观察到损失值在达到某一轮次后开始波动或逐渐增大,说明模型可能已经达到了最佳拟合状态,继续训练反而可能导致过拟合。总而言之,需要不断地改进(增加神经网络的层数和神经元数量)、调超参数(调整学习率或迭代次数)才能提高神经网络的拟合效果。我也只学了皮毛,连入门都谈不上,一起努力吧!共勉