神经网络反向传播(BP)通俗讲义
1. 核心思想:链式接力赛
想象一个接力赛跑:
- 前向传播 = 运动员从起点跑到终点(数据从输入到输出)
- 反向传播 = 把比赛结果(误差)从终点传回起点,告诉每个运动员该怎样调整
2. 为什么要反向传播?
目标:让神经网络变得”更聪明”,减少预测错误
方法:通过计算”每个参数对错误的贡献程度”来调整参数
- 贡献大的 → 多调整
- 贡献小的 → 少调整
3. 链式法则:误差的传递规则
基本公式
text
∂L/∂B = (∂L/∂A) × (∂A/∂B)
通俗理解
∂L/∂A= A对总错误的”责任大小”∂A/∂B= B对A错误的”影响程度”∂L/∂B= B对总错误的”最终责任”
好比公司问责:
- 公司亏损(L) → 部门绩效不好(A) → 员工表现差(B)
- 员工的责任 = 部门责任 × 员工对部门的影响
4. 具体例子:房价预测网络
网络结构
text
输入x(房屋面积) → w×x+b → a → σ(a) → h(预测价格) → 计算损失L
前向传播(计算预测值)
python
# 已知:房屋面积x=100, 真实价格y=300
# 参数:w=2, b=50
a = w * x + b # = 2×100 + 50 = 250
h = sigmoid(a) # ≈ 1.0 (预测价格)
L = (h - y)^2 # = (1-300)^2 ≈ 90000 (误差很大!)
反向传播(计算该怎样调整)
第1步:计算h的责任
text
∂L/∂h = 2×(h-y) = 2×(1-300) = -598
→ 预测值h对误差负有598单位的责任
第2步:计算a的责任
text
∂L/∂a = (∂L/∂h) × (∂h/∂a)
= -598 × [sigmoid(a)×(1-sigmoid(a))]
= -598 × [1×(1-1)] ≈ 0
→ a的责任被sigmoid函数”减弱”了
第3步:计算w和b的责任
text
∂L/∂w = (∂L/∂a) × (∂a/∂w) = 0 × x = 0
∂L/∂b = (∂L/∂a) × (∂a/∂b) = 0 × 1 = 0
5. 关键技巧:反向传播为什么高效?
计算复用原则
一旦计算出∂L/∂a,就可以同时计算:
∂L/∂w = (∂L/∂a) × x∂L/∂b = (∂L/∂a) × 1
好比:知道了部门的总责任,就能快速算出每个员工的责任
模块化设计
每个网络层只需要:
- 前向时:计算结果并保存中间值
- 反向时:接收”后层责任”,计算”本层责任”,传递”前层责任”
text
[输入层] ← 梯度 ← [隐藏层1] ← 梯度 ← [隐藏层2] ← 梯度 ← [输出层] ← 误差
6. 生动比喻:热水器调节问题
场景描述
- 目标:调节出水温度到合适值
- 参数:燃气阀门(w1)、进水阀(w2)
- 过程:燃气+水 → 加热 → 混合 → 出水
反向调节过程
- 发现误差:出水太烫了(误差L很大)
- 追溯责任: - 混合器说:”我责任不大,主要是加热器给的水太热” - 加热器说:”燃气阀开太大了,进水又太少”
- 调整参数: - 关小燃气阀(w1减小) - 开大进水阀(w2增大)
这正是反向传播的思维过程!
7. 重要概念总结
| 概念 | 通俗解释 | 数学表示 |
|---|---|---|
| 前向传播 | 数据从输入到输出的计算过程 | a = w×x + b |
| 损失函数 | 预测值的好坏程度 | L = (预测-真实)^2 |
| 梯度 | 参数调整的方向和幅度 | ∂L/∂w |
| 链式法则 | 误差的责任追溯规则 | ∂L/∂B = (∂L/∂A)×(∂A/∂B) |
| 学习率 | 每次调整的步长大小 | w = w - η×(∂L/∂w) |
8. 实际应用提醒
- 梯度消失:责任在传递过程中越来越小,早期层学不到东西
- 梯度爆炸:责任在传递过程中越来越大,导致调整过度
- 学习率选择:步长太小学得慢,步长太大会”震荡”
9. 核心要点记住三句话
- 反向传播就是”责任追溯”:从最终错误反向追查每个参数的责任
- 链式法则是”追溯规则”:用乘法计算各层之间的责任传递
- 目标是”均匀收敛”:让所有参数以合适的速度共同优化
实例:
import torch
import torch.nn as nn
from torch.optim import Adam
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
import numpy as np
BATCH_SIZE = 64 #设置每一批的大小
EPOCHS = 5 #设置训练轮数
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") #确定是否有可用的CUDA设备
#定义一个BPNet类,它继承于nn.Module类,nn.Module是pytorch给出的默认神经网络类
class MLP(nn.Module):
def __init__(self): #定义类对象实例化方法
super().__init__() #初始化神经网络
#定义类的接口函数,使用nn.Sequential将操作合并到一起
self.classifier = nn.Sequential(
nn.Linear(28 * 28, 128), #放入了一个全连接层,从28*28—>128,28*28是图像大小
nn.ReLU(inplace=True), #放入了一个ReLU激活函数,inplace=True节省显存
nn.Linear(128, 64), #放入了一个全连接层,从128—>64,128是上层大小
nn.ReLU(inplace=True), #放入了一个ReLU激活函数
nn.Linear(64, 10) #放入了一个全连接层,从64—>10,64是上层大小
)
#forward函数定义该神经如何处理数据,也就是数据如何在网络中前进
def forward(self, x):
x = torch.flatten(x,1) #首先将x展平为一维数组
x = self.classifier(x) #将x放入上面定义的函数中
x = torch.softmax(x, dim=1) #将x进行归一化处理,转换为概率分布
return x
#定义训练神经网络模型的代码
def train(model):
#使用pytorch框架给出的接口datasets,创建一个MNIST数据集
train_dataset = datasets.MNIST(root=r'data', #root指明文件存储位置
train=True, #train代表是否是训练集
transform=transforms.ToTensor(), #transform表明数据集的数据应该如何处理
download=True) #download表明是否运行下载
#使用pytorch框架给出的接口DataLoader,将数据集载入
train_loader = DataLoader(dataset=train_dataset, #dataset指明数据集
batch_size=BATCH_SIZE, #batch_size指明批大小
shuffle=True) #shuffle指明是否需要打乱
#实例化一个Adam优化器,用来对数据进行优化
optimizer = Adam(model.parameters(), lr=0.05) #第一个参数指明需要被优化的数据是模型的参数,lr指明了学习率
criterion = nn.CrossEntropyLoss() #指明误差计算方式,这里使用交叉熵损失
model.train() #可以先理解为:训练模式,启动!(启用 Batch Normalization 和 Dropout)
#训练EPOCHS这么多轮(话说是不是es
for epoch in range(EPOCHS):
#使用enumerate对train_loader进行迭代
for index, (data, target) in enumerate(train_loader):
data, target = data.to(DEVICE), target.to(DEVICE) #将数据和真实标签部署在选定的设备上,加快运算速度
optimizer.zero_grad() #每次训练前要先将梯度归零
pred = model(data) #使用现在的模型得到现实输出
loss = criterion(pred, target) #计算现实输出和期望输出之间的误差
loss.backward() #反向传播计算得到每个参数的梯度值
optimizer.step() #梯度下降执行一步参数更新
#每到一定阶段就打印目前训练进度以及相关信息,下面代码是print(f"")格式化输出,看不懂就把代码跑起来一看就懂
if index % 100 == 0:
print(f'Train Epoch: {epoch} [{index * len(data)}/{len(train_loader)} ({(100. * index / len(train_loader)):.0f}%)]\tLoss: {loss.item():.6f}')
model.eval() #训练结束(不启用 Batch Normalization 和 Dropout)
torch.save(model.state_dict(),"MLP.pth") #保存模型(仅保存参数)
#定义校验模型效果的代码
def test(model):
#如果你问我这里为什么没注释我就梆梆给你两拳你自己看看上面写的什么气死我了我再梆梆给你两拳自己往上翻读代码去
test_dataset = datasets.MNIST(root='data',
train=False,
transform=transforms.ToTensor(),
download=True)
test_loader = DataLoader(dataset=test_dataset,
batch_size=BATCH_SIZE,
shuffle=False)
total = 0 #总计
correct = 0 #正确
#和训练一样,使用enumerate对test_loader进行迭代
for index, (data, target) in enumerate(test_loader):
data, target = data.to(DEVICE), target.to(DEVICE)
pred = model(data)
correct += (torch.argmax(pred, dim=1) == target).sum() #检测预测是否正确,因为是批处理,所以求和
total += pred.size(0)
print("Correct : ",correct,'/',total,sep='')
print('Accuracy : ',float(correct)/float(total) * 100. ,"%",sep='')
#通过main函数调用个函数及方法
if __name__ == "__main__":
model = MLP().to(DEVICE) #在指定的设备(cpu或者gpu)上将模型实例化
train(model)
test(model)
pip install torch torchvision numpy