反向传播、优化器、模型的训练
1. 什么是反向传播
- 在我们从输入层对数据进行一系列的操作,包括特征提取、函数激活、维度变换等,从输入层到输出层的各种变换可以称为前向传播。前向传播的用处是为了对输入数据转换为我们需要的回归值或者标签类别值,但是这种输出结果往往是有偏差的,这种偏差是通过误差函数进行计算的。
- 当我们构建了一个完整的前向传播结构后,就需要考虑如何使用误差来优化我们的网络结构。常见的优化算法包括梯度下降SGD,自适应学习率优化算法Adam等,这些都是基于梯度的优化算法。
- 为了方便各种优化器对模型进行优化,反向传播使用Tensor数据结构,基于误差计算了各个参数的梯度并保存在了Tensor张量中。总而言之:在一个神经网络结构中,反向传播的主要作用就是计算每个权重的梯度,从而结合优化器指导调整这些权重,从而最小化损失函数,提高模型的预测精度!!
2. 基本的网络构建以及数据集准备
-
我们延续使用之前针对CIFAR10的图像数据构建的网络结构,给出具体的网络结构构建的代码:
class Model(nn.Module): def __init__(self): super().__init__() self.model = nn.Sequential( nn.Conv2d(3, 32, 5, padding=2), nn.MaxPool2d(2), nn.Conv2d(32, 32, 5, padding=2), nn.MaxPool2d(2), nn.Conv2d(32, 64, 5, padding=2), nn.MaxPool2d(2), nn.Flatten(), nn.Linear(1024, 64), nn.Linear(64, 10) ) def forward(self, x): x = self.model(x) return x
-
使用的数据集如下所示
test_data = torchvision.datasets.CIFAR10(root='./data_torchvision', train=False, transform=torchvision.transforms.ToTensor(), download=True) dataloader = DataLoader(dataset = test_data, batch_size=64, shuffle=True, drop_last=True)
3. 模型的反向传播
-
进行反向传播,我们首先需要确定使用的损失函数,构建损失函数的实例后,通过返回的损失数据进行
backward()
方法,就会自动计算损失函数对于每个权重参数的梯度loss = nn.CrossEntropyLoss() model = Model() for data in dataloader: imgs, targets = data outputs = model(imgs) result = loss(outputs, targets) result.backward()
- 数据集是分类数据,构建交叉熵损失函数
nn.CrossEntropyLoss()
- 获取模型的数据结果
outputs
后,计算损失函数loss()
- 最后根据损失函数得到的结果
result
使用backward()
方法进行反向传播- 这里我们需要理解的一个计算逻辑是,pytorch会自动维护一张数据关系计算图,计算图记录了从输入数据到输出数据之间的所有操作,所以调用
backward()
方法的时候,torch
框架是会自动回溯计算出所有参与计算的张量的梯度,其中就包括model
模型中的所有参数
- 这里我们需要理解的一个计算逻辑是,pytorch会自动维护一张数据关系计算图,计算图记录了从输入数据到输出数据之间的所有操作,所以调用
- 数据集是分类数据,构建交叉熵损失函数
-
通过
named_parameters
是可以查看到模型的具体参数的,如果我们这里想要具体参数的,这其中就包括了模型中每一层的权重、偏置、权重的梯度、偏置的梯度等等for name, param in model.named_parameters(): print(f"{name} : {param.grad}")
- 上面的代码就输出了模型中每一层的权重的梯度和偏置的梯度
4. 优化器
-
当我们计算了相关参数包括权重和偏置的梯度之后,就可以通过这些梯度进行模型的优化,所谓模型的优化就是在不断调整各个权重,来最小化损失函数。我们以梯度下降优化算法为例构建一个优化器
from torch import optim sgd = optim.SGD(params, lr)
- 所有的优化器都至少会有两个参数
params、lr
params = model.parameters():
这个参数用来传入需要进行优化的具体的参数,一般情况下写法比较固定,直接传入构建的模型的parameters()
就可以lr:
这个是学习率,决定了模型的训练速度、学习步长
- 所有的优化器都至少会有两个参数
-
优化器的使用就是模型的训练过程,在我们每次获取
DataLoader
的数据并获取到误差时就可以使用具体的优化器对模型进行优化。from torch import optim model = Model() sgd = optim.SGD(model.parameters(), lr=0.001) loss = nn.CrossEntropyLoss() for data in dataloader: sgd.zero_grad() # 梯度置零 imgs, targets = data outputs = model(imgs) l = loss(outputs, targets) l.backward() # 反向传播 sgd.step() # 参数优化
sgd.zero_grad():
在进行模型的训练之前一定要对参数的梯度进行置零操作,这是因为在backward()
反向传播计算梯度时,进行的操作是梯度叠加,就是会将计算的梯度与原梯度进行累加,所以我们要在每一次反向传播、参数优化之前,先对模型的参数的梯度置零sgd.step():
这里的step()
方法就是对模型进行梯度下降优化- 上面这个过程就完成了一次模型的优化过程
5. 损失函数+优化器进行模型的简单训练
-
模型的训练过程往往需要对原始数据进行多次’‘扫描’'我们称之为
epoch
,我们关注的是在每一次训练过程中,模型的损失值是否在不断下降,以及最后降低为多少,是否能够满足我们对模型性能的要求。from torch import optim model = Model() sgd = optim.SGD(model.parameters(), lr=0.001) loss = nn.CrossEntropyLoss() for epoch in range(10): result_loss = 0 for data in dataloader: sgd.zero_grad() imgs, targets = data outputs = model(imgs) l = loss(outputs, targets) l.backward() sgd.step() result_loss += l print(result_loss)
- 上面的代码展示了
epoch=20
次训练过程中整体损失函数的变化情况 - 外层循环控制了训练次数,设置了用于记录损失值的变量
result_loss
,整体损失函数的计算方式为每一个batch_size
的损失函数值的总和 - 内层循环控制的每个批次的模型的训练,通过
DataLoader
构建的数据,按每个批次对模型进行训练,每个批次的输出数据都将计算一个误差值,通过误差进行反向传播,最后进行SGD优化模型参数 - 上面的两层循环展示了一个简单的模型训练的基本过程。
- 上面的代码展示了