动手深度学习——训练详解
文章目录
- 动手深度学习——训练详解
- 前言
- 一、线性模型
- 1.线性回归
- 二、分类精准度
- 1.定义accuracy函数:
- 2.精度函数
- 3.训练
- 4.画图
- 5.train_ch3函数
- 三、卷积网络训练
- 1.精度计算
- 2.训练模型
- 总结
前言
- 在刚刚入门深度学习的大坑时,怎么完成一次训练往往是第一道门槛。而完成一次训练大概有两个难点,其一是数据集的搭建,这个主要涉及dataset和dataloader的使用,后续会更如何构造一个可以训练的数据集;其二就是训练的步骤。
- 而训练步骤其实非常简单,现在看来,本人在此徘徊许久还是因为把深度学习代码想的太难了,没有认真的好好看看,好好打打代码,所以在此整理下从线性模型到CNN的模型的训练过程。
本文参照李沐老师的动手深度学习课程的相关内容
一、线性模型
1.线性回归
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
################数据读入#################
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
################模型确定#################
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
通过这个简单的案例,就可以得到一个大概的训练框架。
- 数据集准备,将数据准备成一个dataloader,dataloader中需要指明数据集,batch_size等参数
- 确定好网络结构、损失函数、训练器等部分
- epoch利用循环控制:(1)计算当前网络权重下的正向输出结果和实际结果的损失;(2)梯度置0,防止梯度累计;(3)反向传播;(4)梯度下降(最经典的训练四步走)
- 每个epoch结束输出损失。
二、分类精准度
在李沐大神的动手深度学习的配套代码中,chapter_linear-networks章节的softmax-regression-scratch第一次引入了train_ch3训练函数,该函数引入了分类精确度及画图的具体过程
1.定义accuracy函数:
代码如下(示例):
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
函数的思路很简单:(1)判预测结果是否是二维矩阵(二维矩阵的原因:一个维度是需要预测的数据记录数量,一个维度是预测的各个类别的概率);(2)如果是,我们默认概率最大的那个类别是预测类别;(3)由于等式运算符“==”对数据类型很敏感, 因此我们将y_hat的数据类型转换为与y的数据类型一致。 结果是一个包含0(错)和1(对)的张量;(4)求和
函数返回的结果是预测正确的数量,如果要得到正确率,需要/数据总量,即accuracy(y_hat, y) / len(y)
2.精度函数
class Accumulator: #@save
"""累加器"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式,只能进行前向传播
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
从evaluate_accuracy函数进入聊下代码思路:
- 如果是nn的net,置为只能forward的状态
- 定义一个累加器(累加器的每一个维度自定义,Accumulator只做单纯的累加)
- 每一轮dataiter进行累加正确数和总数的累加
- 相除就是准确率
注意:Accumulator是一个完全自由,完全自定义的类,千万不要认为Accumulator(2)的含义就是正确预测数、预测总数,它仅仅是一个累加器罢了。
Accumulator中做重要的两个函数分别是__init__,add。下面简单讲述下两个函数的具体意义与实现方式:
- init:定义了一个数组,n表示的是希望的维度
- add:这个函数的实现非常简洁,*args作为传参需要和data同长,zip(x,y)将x,y中的元素依次拿出,配对打包,形成(x[n],y[n])付给a,b,完成累加。
3.训练
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.mean().backward()
updater.step()
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
将训练四步走和评估结合即可
4.画图
class Animator: #@save
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
不重要,知道参数意义即可
5.train_ch3函数
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
这样各个代码的意义就很清楚了:
- 定义画图工具
- 得到每个epoch的训练矩阵
- 计算准确度
- 画图
三、卷积网络训练
由于卷积网络所需算力较大,因此需要使用gpu,因此和train_ch3不同的点在于,需要将tensor移至gpu上运算。
1.精度计算
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad():
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
和之前的evaluate_accuracy相比只差在to(device)
2.训练模型
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
print(y_hat)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
- 仔细看下代码内容,和添加了分类精度后的训练步骤是十分相似的,定义好各个要素,四步走训练,累加器累加,计算准确度等参数,放入画图工具画图。
- 唯一的区别就在于,所有的tensor都放在了gpu上。
需要注意的是,在调用训练函数时,使用到了try_gpu()函数
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
其中的try_gpu()代码如下:
def try_gpu(i=0): #@save
"""如果存在,则返回gpu(i),否则返回cpu()"""
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
实际上也只是完成了gpu的选择。
总结
本人才疏学浅,整理过程中必然会有一定的纰漏以及错误,请各位移步B站李沐大神动手深度学习pytorch版进行学习。