1. 定义可学习参数的网络结构(堆叠各层和层的设计);继承 nn.Module
模块,改写 forward
方法。
2. 数据集输入;
3. 对输入进行处理(由定义的网络层进行处理),主要体现在网络的前向传播;
4. 计算loss ,由Loss层计算;
5. 反向传播求梯度;
6. 根据梯度改变参数值,最简单的实现方式(SGD)为: weight = weight - learning_rate * gradient
其中,torch.nn是用来构建神经网络每个层的,例如卷积层,全连接层等。torch.nn.functional用以引用各种数学函数,例如激活函数等。torch.optim是各种优化方法,例如SGD,ADAM等。
1 定义网络
构建一个简单的CNN网络,两个 (卷积+激活+池化)的模块,接两层全连接层,然后是输出层。然后实例化对象net。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
#实例化对象
net = Net()
2 定义损失函数和优化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
3 网络训练
为方便理解,只设置2个epoch,在每个 epoch 的循环中会遍历所有 trainloader 的数据。
每一次遍历时:
optimizer.zero_grad()
把梯度清理干净,防止受之前遗留梯度的影响。outputs = net(inputs)
, 前向传播,等价于net.forward(inputs) ,把输入数据输入到网络,得到预测结果。loss = criterion(outputs, labels)
, 计算当前 batch 的损失值。loss.backward()
,执行链式求导,计算梯度。optimizer.step()
,通过4中计算出来的梯度,更新每个可训练权重。# 定义训练函数
def train(trainloader, model, criterion, optimizer):
loss, current, n = 0.0, 0.0, 0
for batch, data in enumerate(trainloader, 0):
# get the inputs; data is a list of [inputs, labels]
inputs, labels = data
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
cur_loss = criterion(outputs, labels)
_, pred = torch.max(output, axis=1)
cur_acc = torch.sum(labels==pred)/output.shape[0]
loss.backward()
optimizer.step()
loss += cur_loss.item()
current += cur_acc.item()
n = n+1
train_loss = loss / n n个batch的平均?
tran_acc = current /n
# 定义验证函数
def val(valloader, model, criterion):
# 将模型转为验证模型
model.eval()
loss, current, n = 0.0, 0.0, 0
with torch.no_grad():
for batch, data in enumerate(valloader):
inputs, labels = data
output = net(inputs)
cur_loss = crietrion(output, y)
_, pred = torch.max(output, axis=1)
cur_acc = torch.sum(y == pred) / output.shape[0]
loss += cur_loss.item()
current += cur_acc.item()
n = n+1
val_loss = loss / n
val_acc = current / n
#开始训练
# 开始训练
loss_train = []
acc_train = []
loss_val = []
acc_val = []
min_acc = 0
for t in range(2):
lr_scheduler.step()
print(f"epoch{t+1}\n--------------")
train_loss, train_acc = train(train_dataloader, model, crietrion, optimizer)
val_loss, val_acc = val(val_dataloader, model, crietrion)
loss_train.append(train_loss)
acc_train.append(train_acc)
loss_val.append(val_loss)
acc_val.append(val_acc)
# 保存最好的模型权重文件
if val_acc > min_acc:
folder = 'save_model'
if not os.path.exists(folder):
os.mkdir('save_model')
min_acc = val_acc
print(f'save best model,第{t+1}轮')
torch.save(model.state_dict(), 'save_model/best_model.pth')
# 保存最后的权重模型文件
if t == epoch - 1:
#使用 torch.save 将模型序列化保存在指定地址,方便后续调用,调用时使用torch.load
torch.save(model.state_dict(), 'save_model/last_model.pth')
print('Done!')
其中,几个主要函数
1 optimizer.zero_grad():
该函数会遍历模型的所有参数,通过p.grad.detach_()方法截断反向传播的梯度流,再通过p.grad.zero_()函数将每个参数的梯度值设为0,即上一次的梯度记录被清空。因为训练的过程通常使用mini-batch方法,所以如果不将梯度清零的话,梯度会与上一个batch的数据相关,因此该函数要写在反向传播和梯度下降之前。
2 loss.backward():
PyTorch的反向传播(即tensor.backward())是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。具体来说,torch.tensor是autograd包的基础类,如果你设置tensor的requires_grads为True,就会开始跟踪这个tensor上面的所有运算,如果你做完运算后使用tensor.backward(),所有的梯度就会自动运算,tensor的梯度将会累加到它的.grad属性里面去。更具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个w的requires_grads为True,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()后,会一层层的反向传播计算每个w的梯度值,并保存到该w的.grad属性中。如果没有进行tensor.backward()的话,梯度值将会是None,因此loss.backward()要写在optimizer.step()之前。
3 optimizer.step():
step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。其使用的是参数空间(param_groups)中的grad,也就是当前参数空间对应的梯度,这也就解释了为什么optimzier使用之前需要zero清零一下,因为如果不清零,那么使用的这个grad就得同上一个mini-batch有关。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。注意:optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的。
optimizer.step()放在每一个batch训练中,而不是epoch训练中,这是因为现在的mini-batch训练模式是假定每一个训练集就只有mini-batch这样大,因此实际上可以将每一次mini-batch看做是一次训练,一次训练更新一次参数空间,因而optimizer.step()放在这里。
scheduler.step()按照Pytorch的定义是用来更新优化器的学习率的,一般是按照epoch为单位进行更换,即多少个epoch后更换一次学习率,因而scheduler.step()放在epoch这个大循环下。
注意:1 验证集使用时,使用model.eval转换为验证模型,验证集不用来更新权重和梯度;2 验证集的作用:可以用在训练的过程中,经过几个epoch后,跑一次验证集看一下效果,可以及时发现模型或者参数的问题,主要用来调整超参数(如人为设定的初始学习率、层数、权值衰减系数、训练次数等);2 每个batch训练后更新梯度和权重。
4 预测