训练模型
构建模型后,接下来就是训练模型。PyTorch训练模型的主要步骤包括加载和预处理数据集、损失计算、定义优化算法、反向传播、参数更新等主要步骤。
1)加载和预处理数据集:可以使用PyTorch的数据处理工具,如torch.utils和torchvision等。
2)定义损失函数:通过自定义的方法或使用PyTorch内置的损失函数定义,如回归使用nn.MSELoss(),分类使用nn.BCELoss等损失函数。
3)定义优化方法:PyTorch的优化方法都封装在torch.optim中,可以扩展为自定义的方法,都是继承了基类optim.Optimizer,并实现了自己的优化步骤。最常用的优化算法就是梯度下降法及其各种变种,大都使用梯度更新参数。
4)循环训练模型(假设模型实例化为model )
model.train() 将模型设置为训练模式
optimizer.zero_grad():梯度清零,默认情况下梯度值是累加的。
y_pre = model(x):预测值
loss = loss_fun(y_prev, y_true):把预测值和真实值放入损失函数中。
loss.backward():自动求导,实现梯度的反向传播。
optimizer.step():更新参数
5)循环测试或验证模式
model.eval():设置为测试或验证模式,把所有的training属性设置为False
with.torch.no_grad():在不跟踪梯度的模式下计算损失值、预测值等。
6)可视化结果
注:model.train()与model.eval()的使用
1)如果模型中有批量归一化层(Batch Normalization, BN)和dropout层,需要在训练时添加model.train(),在测试时添加model.eval()。
2)model.train()是保证BN层用每一批数据的均值和方差,而model.eval()是保证BN层用全部训练数据的均值和方差。
3)对于dropout层,model.train()是随机取一部分网络连接来更新参数,model.eval()是利用所有网络的连接。
实现神经网络实例
实现手写数字数据集进行识别的实例。
PyTorch:1.5+版本,GPU或CPU,数据集MNIST
主要步骤如下:
1、下载训练和测试的数据集。
2、利用torchvision对数据进行预处理,调用torch.utils建立一个数据迭代器。
3、可视化源数据。
4、利用nn工具箱构建神经网络模型。
5、实例化模型,定义损失函数及优化器。
6、训练模型。
7、可视化结果。
# 准备数据
# 1) 导入必要的模块
import torch
import torchvision
import numpy as np
# 导入预处理模块
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# 导入nn及优化器
import torch.nn.functional as F
import torch.optim as optim
from torch import nn
# 可视化源数据
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriter
# 2) 定义一些超参数
# 训练数据的批量大小
train_batch_size = 64
# 测试数据的批量大小
test_batch_size = 128
# 学习率
learning_rate = 0.01
# 遍历所有数据集的次数
num_epoches = 20
# 学习率
lr = 0.01
# 动量
momentum = 0.5
# 3) 下载数据并对数据进行预处理
# 定义预处理函数
# transforms.Compose可以把一些转换函数组合在一起,以列表的形式。
# Normalize([全局平均值], [方差])对张量进行归一化,图像是灰色,只有一个通道Normalize([0.5], [0.5])
# 假如有3个通道,应该是Normalize([m1, m2, m3], [n1, n2, n3])
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])])
# 下载数据,并对数据进行预处理: './':同一级 '../':上一级,下载训练数据集
train_dataset = torchvision.datasets.MNIST('./data/', train=True, transform=transform, download=True)
# 下载测试数据集
test_dataset = torchvision.datasets.MNIST('./data/', train=False, transform=transform, download=True)
# 得到一个生成器
# 将 训练数据集 打包 给后面的神经网络,批量处理,重新排序(shuffle):是
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
# 将 测试数据集 打包 给后面的神经网络,批量处理,重新排序(shuffle):否
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
# 可视化源数据
# 对数据集中的部分数据进行可视化
# enumerate()函数:用于将一个可遍历的数据对象(列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标。
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
for i in range(6):
# 表示将整个图像窗口分为2行3列,当前位置为i+1
plt.subplot(2, 3, i+1)
# 自动调整子图参数,使之填充整个图像区域
plt.tight_layout()
# 用于绘制二维的灰度图像(或彩色图像,矩阵,热力图、地图等)。 cmap(color map,色图):gray,灰度图 interpolation:插值
plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
# 标题
plt.title("Ground Truth: {}".format(example_targets[i]))
# 用于设置x轴
plt.xticks()
# 用于设置y轴
plt.yticks()
# 显示图像
plt.show()
# 构建模型
# 1) 构建网络:通过继承基类nn.Module来构建模型
class Net(nn.Module):
# 使用nn.Sequential构建网络,Sequential()函数的功能是将网络的层组合到一起。
# 把模型中需要用到的层放到构造函数__init__()中
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
# 初始化父类
super(Net, self).__init__()
# 展平(flatten)为向量
self.flatten = nn.Flatten()
# 第一层
# 全连接层 + bn:batch normalization,批量归一化层
self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1))
# 第二层
# 全连接层 + bn:batch normalization,批量归一化层
self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.BatchNorm1d(n_hidden_2))
# 输出层
# 全连接层
self.out = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
# 展平(flatten)为向量
x = self.flatten(x)
# 第一层激活函数ReLU(没有学习参数,使用nn.functional实现)
x = F.relu(self.layer1(x))
# 第二层激活函数ReLU(没有学习参数,使用nn.functional实现)
x = F.relu(self.layer2(x))
# 输出层激活函数softmax(没有学习参数,使用nn.functional实现)
x = F.softmax(self.out(x), dim=1)
return x
# 2) 实例化网络
# 检测是否有可用的GPU,有则使用,否则使用GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("*" * 10 + "device" + "*" * 10)
print(device)
# 实例化网络
model = Net(28 * 28, 300, 100, 10)
# 把网络放入device中
model.to(device)
# 定义损失函数和优化器
# 交叉熵损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
# 训练模型
# 损失值
losses = []
# 准确率
acces = []
# 测试集损失值
eval_losses = []
# 测试集准确率
eval_acces = []
# 用于创建一个 tensorboard 文件
writer = SummaryWriter(log_dir='logs', comment='train-loss')
# for循环训练
for epoch in range(num_epoches):
# 训练集损失值
train_loss = 0
# 训练集准确率
train_acc = 0
# 如果模型中有批量归一化层(Batch Normalization, BN)和dropout层,需要在训练时添加model.train()
# model.train()是保证BN层用 每一批 数据的均值和方差。
model.train()
# 动态修改学习率:第0、5、10、15轮更新学习率
if epoch % 5 == 0:
# optimizer.param_groups:是一个list,其中的元素为字典。
# optimizer.param_groups[0]:长度为7的字典,包括['params', 'lr', 'betas', 'eps', 'weight_decay', 'amsgrad', 'maximize']
optimizer.param_groups[0]['lr'] *= 0.9
# 输出动态修改后的学习率
print("学习率:{:.6f}".format(optimizer.param_groups[0]['lr']))
# 图片 和 标签 在训练生成器中
for img, label in train_loader:
# 把图片放到device中
img = img.to(device)
# 把标签放到device中
label = label.to(device)
# 训练集的图片放到神经网络中
output = model(img)
# 正向传播:计算loss
loss = criterion(output, label)
# 梯度清零:通过 autograd计算的梯度会自动累加到grad中
optimizer.zero_grad()
# 反向传播:自动计算梯度
loss.backward()
# 更新参数
optimizer.step()
# 求取训练集的损失,记录误差
# .item()取出张量具体位置的元素的元素值,并且返回的是该位置元素值的高精度值(int类型)
# 一般用在求loss或者accuracy时,使用.item()
train_loss += loss.item()
# add_scalar(名称,y轴值,x轴值):将一个标量(平均loss)添加到summary中
# 保存loss的数据与epoch数值
writer.add_scalar('Train', train_loss/len(train_loader), epoch)
# 计算分类的准确率
# torch.max()返回的是两个值,第一个值是具体的value,第二个值是value所在的index(索引值)。
# _:不需要用到的变量,不关心。 只关心第二个值:变量所对应的索引值。
# dim=1:取最大值,最后形成1列。 dim=0:取最大值,最后形成1行。
_, pred = output.max(1)
# pred == label:预测值与真实值相等返回1,不等返回0。 返回张量
# .sum():张量求和,求出预测值与真实值相等的个数。
# .item():返回int类型的元素值
# num_correct:该批次的训练集中预测值与真实值相等的个数。
num_correct = (pred == label).sum().item()
# img.shape[0]:图片的高度(垂直尺寸,矩阵中的行数)
# img.shape[1]:图片的宽度(水平尺寸,矩阵中的列数)
# img.shape[2]:图片的通道数(图片的个数)
acc = num_correct / img.shape[0]
# 求取训练集的准确率
train_acc += acc
# append 向训练集列表末尾添加元素(损失率,准确率)
losses.append(train_loss / len(train_loader))
acces.append(train_acc / len(train_loader))
# 在测试集上验证效果
# 测试集的损失率
eval_loss = 0
# 测试集的准确率
eval_acc = 0
# 将模型改为预测模式,model.eval()是保证BN层用全部训练数据的均值和方差。
# 把所有的training属性设置为False
# net.eval()
model.eval()
# 把 图片 和 标签 在测试生成器中
for img, label in test_loader:
# 把图片放到 device 中
img = img.to(device)
# 把标签放到 device 中
label = label.to(device)
# 修改img的形状,行数:img.size(0),列数:根据输入数据和行数的变化而变化。
# img.size():返回张量的shape属性值:行值×列值
# img.size(0):返回张量的行数
# img.size(1):返回张量的列数
img = img.view(img.size(0), -1)
# 把测试集的图像放入神经网络中
output = model(img)
# 正向传播:计算loss
loss = criterion(output, label)
# 求取测试集的损失,记录误差
# .item()取出张量具体位置的元素的元素值,并且返回的是该位置元素值的高精度值(int类型)
# 一般用在求loss或者accuracy时,使用.item()
eval_loss += loss.item()
# 计算分类的准确率
# torch.max()返回的是两个值,第一个值是具体的value,第二个值是value所在的index(索引值)。
# _:不需要用到的变量,不关心。 只关心第二个值:变量所对应的索引值。
# dim=1:取最大值,最后形成1列。 dim=0:取最大值,最后形成1行。
_, pred = output.max(1)
# pred == label:预测值与真实值相等返回1,不等返回0。 返回张量
# .sum():张量求和,求出预测值与真实值相等的个数。
# .item():返回int类型的元素值
# num_correct:该批次的训练集中预测值与真实值相等的个数。
num_correct = (pred == label).sum().item()
# img.shape[0]:图片的高度(垂直尺寸,矩阵中的行数)
# img.shape[1]:图片的宽度(水平尺寸,矩阵中的列数)
# img.shape[2]:图片的通道数(图片的个数)
acc = num_correct / img.shape[0]
# 求取测试集的准确率
eval_acc += acc
# append 向测试集列表末尾添加元素(损失率,准确率)
eval_losses.append(eval_loss / len(test_loader))
eval_acces.append(eval_acc / len(test_loader))
# 输出 训练的批次、训练集的损失率、训练集的准确率、测试集的损失率、测试集的准确率
print('epoch:{}, Train Loss:{:.4f}, Train Acc:{:.4f}, Test Loss:{:.4f}, Test Acc:{:.4f}'.format(epoch, train_loss / len(train_loader), train_acc / len(train_loader),
eval_loss / len(test_loader), eval_acc / len(test_loader)))
# 设置标题
plt.title('train-loss')
# 用于画图,可以绘制点和线。plt.plot(x轴数据, y轴数据)
plt.plot(np.arange(len(losses)), losses)
# 设置标签名称与位置
plt.legend(['Train Loss'], loc='upper right')
plt.show()
运行结果:
小结
本章介绍了神经网络的核心组件,即层、模型、损失函数及优化器。介绍了PyTorch如何使用包、模块等来搭建、训练、评估、优化神经网络。介绍了PyTorch的工具箱nn以及nn的一些常用类或模块等。最后通过一个手写数字识别的实例演示了这些模块的功能。