FA :Formulas and Algorithm,CNN:Convolutional Neural Network
1. 优化器
在神经网络训练过程中,优化器(Optimizer)扮演着至关重要的角色。它的核心作用是根据损失函数计算出的梯度,对网络参数进行调整,从而逐步降低整体误差。
1.1 损失函数与反向传播
当我们调用损失函数的 backward() 方法时,就会触发反向传播机制,自动计算出我们需要调节的梯度。随后,优化器便可以利用这些梯度对参数进行调整,达到整体误差降低的目的。
1.2 梯度清零
在每次参数更新之前,必须将梯度清零。如果梯度不清零,会导致梯度累加,从而影响参数更新的准确性。这是训练过程中最容易忽略却又极其重要的细节。
2. 神经网络优化一轮
下面我们通过一个完整的示例,演示如何对神经网络进行一轮优化。这里我们使用 CIFAR10 数据集,构建一个简单的卷积神经网络,并采用随机梯度下降(SGD)优化器。
python
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss() # 交叉熵损失函数
tudui = Tudui()
optim = torch.optim.SGD(tudui.parameters(), lr=0.01) # 随机梯度下降优化器
for data in dataloader:
imgs, targets = data
outputs = tudui(imgs)
result_loss = loss(outputs, targets) # 计算实际输出与目标输出的差距
optim.zero_grad() # 梯度清零
result_loss.backward() # 反向传播,计算损失函数的梯度
optim.step() # 根据梯度,对网络的参数进行调优
print(result_loss) # 对数据只看了一遍,只看了一轮,所以loss下降不大
2.1 运行结果分析
运行上述代码,可以看到每一批数据的损失值都在 2.3 左右波动,下降幅度并不明显。这是因为我们只对数据看了一遍(即只训练了一轮),模型尚未充分学习到数据的特征。
tensor(2.2978, grad_fn=<NllLossBackward0>)
tensor(2.2988, grad_fn=<NllLossBackward0>)
tensor(2.3163, grad_fn=<NllLossBackward0>)
tensor(2.3253, grad_fn=<NllLossBackward0>)
...
3. 神经网络优化多轮
单轮训练显然不够,我们需要让模型多次遍历整个数据集。下面我们引入 epoch 的概念,对数据集进行多轮训练,并统计每一轮的累计损失。
python
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss() # 交叉熵损失函数
tudui = Tudui()
optim = torch.optim.SGD(tudui.parameters(), lr=0.01) # 随机梯度下降优化器
for epoch in range(20):
running_loss = 0.0
for data in dataloader:
imgs, targets = data
outputs = tudui(imgs)
result_loss = loss(outputs, targets) # 计算实际输出与目标输出的差距
optim.zero_grad() # 梯度清零
result_loss.backward() # 反向传播,计算损失函数的梯度
optim.step() # 根据梯度,对网络的参数进行调优
running_loss = running_loss + result_loss
print(running_loss) # 对这一轮所有误差的总和
3.1 运行结果分析
从输出结果可以清晰地看到,随着训练轮次的增加,每一轮的累计损失在稳步下降:
tensor(358.1069, grad_fn=<AddBackward0>)
tensor(353.8411, grad_fn=<AddBackward0>)
tensor(337.3790, grad_fn=<AddBackward0>)
tensor(317.3237, grad_fn=<AddBackward0>)
tensor(307.6762, grad_fn=<AddBackward0>)
tensor(298.2425, grad_fn=<AddBackward0>)
tensor(289.7010, grad_fn=<AddBackward0>)
tensor(282.7116, grad_fn=<AddBackward0>)
tensor(275.8972, grad_fn=<AddBackward0>)
tensor(269.5961, grad_fn=<AddBackward0>)
tensor(263.8480, grad_fn=<AddBackward0>)
tensor(258.5006, grad_fn=<AddBackward0>)
tensor(253.4671, grad_fn=<AddBackward0>)
tensor(248.7994, grad_fn=<AddBackward0>)
tensor(244.4917, grad_fn=<AddBackward0>)
tensor(240.5728, grad_fn=<AddBackward0>)
tensor(236.9719, grad_fn=<AddBackward0>)
tensor(233.6264, grad_fn=<AddBackward0>)
tensor(230.4298, grad_fn=<AddBackward0>)
tensor(227.3427, grad_fn=<AddBackward0>)
这说明多轮训练能够有效帮助模型学习数据特征,逐步降低损失。
4. 神经网络学习率优化
在训练过程中,学习率的大小直接影响模型的收敛速度和最终效果。PyTorch 提供了 torch.optim.lr_scheduler 模块,可以在训练过程中动态调整学习率。
下面我们使用 StepLR 调度器,每经过 step_size 个 epoch,将学习率乘以 gamma 进行衰减。
python
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss() # 交叉熵损失函数
tudui = Tudui()
optim = torch.optim.SGD(tudui.parameters(), lr=0.01) # 随机梯度下降优化器
scheduler = torch.optim.lr_scheduler.StepLR(optim, step_size=5, gamma=0.1) # 每过 step_size 更新一次优化器,学习率变为原来的 0.1 倍
for epoch in range(20):
running_loss = 0.0
for data in dataloader:
imgs, targets = data
outputs = tudui(imgs)
result_loss = loss(outputs, targets) # 计算实际输出与目标输出的差距
optim.zero_grad() # 梯度清零
result_loss.backward() # 反向传播,计算损失函数的梯度
optim.step() # 根据梯度,对网络的参数进行调优
scheduler.step() # 学习率太小了,所以20个轮次后,相当于没走多少
running_loss = running_loss + result_loss
print(running_loss) # 对这一轮所有误差的总和
4.1 运行结果分析
观察输出可以发现,损失值几乎没有下降,始终维持在 359 左右:
tensor(359.4722, grad_fn=<AddBackward0>)
tensor(359.4630, grad_fn=<AddBackward0>)
tensor(359.4630, grad_fn=<AddBackward0>)
...
这是因为我们在每个 batch 之后 都调用了 scheduler.step(),导致学习率被过早、过快地衰减到极小值。学习率太小,模型几乎无法更新参数,20 个轮次后相当于没走多少步。
4.2 正确用法
StepLR 的 step() 方法应该在每个 epoch 结束后 调用一次,而不是在每个 batch 之后。正确的写法是将 scheduler.step() 放在内层循环之外:
python
for epoch in range(20):
running_loss = 0.0
for data in dataloader:
imgs, targets = data
outputs = tudui(imgs)
result_loss = loss(outputs, targets)
optim.zero_grad()
result_loss.backward()
optim.step()
running_loss = running_loss + result_loss
scheduler.step() # 每个 epoch 结束后更新一次学习率
print(running_loss)
5. 总结
通过本文的实战演练,我们掌握了以下核心要点:
- 优化器:利用反向传播计算出的梯度对参数进行调整,逐步降低损失。
- 梯度清零 :每次参数更新前必须调用
optim.zero_grad(),防止梯度累加。 - 多轮训练 :通过
epoch循环让模型多次遍历数据集,损失会稳步下降。 - 学习率调度 :
lr_scheduler可以动态调整学习率,但要注意step()的调用时机------应在每个 epoch 结束后调用,而非每个 batch 之后。
希望本文能帮助你更好地理解 PyTorch 中神经网络优化的完整流程。欢迎在评论区交流你的训练心得!