PyTorch:模型训练和预测
Posted -柚子皮-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyTorch:模型训练和预测相关的知识,希望对你有一定的参考价值。
模型训练和预测
模型训练
单机训练
传统的batch训练函数
简单的说就是进来一个batch的数据,计算一次梯度,更新一次网络。
for i,(images,target) in enumerate(train_loader):
# 1. input output
images = images.cuda(non_blocking=True)
target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
outputs = model(images)
loss = criterion(outputs,target)
# 2. backward
optimizer.zero_grad() # reset gradient
loss.backward()
optimizer.step()
1 获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数;
2 optimizer.zero_grad() 清空过往梯度; lz:这里需要手动进行optimizer.zero_grad(),不然pytorch中默认是累加的。
3 loss.backward() 反向传播,计算当前梯度;
4 optimizer.step() 根据梯度更新网络参数
使用梯度累加的batch训练函数
梯度累加就是,每次获取1个batch的数据,计算1次梯度,梯度不清空,不断累加,累加一定次数后,根据累加的梯度更新网络参数,然后清空梯度,进行下一次循环。
for i,(images,target) in enumerate(train_loader):
# 1. input output
images = images.cuda(non_blocking=True)
target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
outputs = model(images)
loss = criterion(outputs,target)
# 2.1 loss regularization
loss = loss/accumulation_steps
# 2.2 back propagation
loss.backward()
# 3. update parameters of net
if((i+1)%accumulation_steps)==0:
# optimizer the net
optimizer.step() # update parameters of net
optimizer.zero_grad() # reset gradient
1 获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数;
2 loss.backward() 反向传播,计算当前梯度; lz:2和3之间应该有一个pytorch自动累加梯度的过程(应该就是backward里面的过程吧)。
3 多次循环步骤1-2,不清空梯度,使梯度累加在已有梯度上;
4 梯度累加了一定次数后,先optimizer.step() 根据累计的梯度更新网络参数,然后optimizer.zero_grad() 清空过往梯度,为下一波梯度累加做准备;
与非梯度累加区别
一定条件下,batchsize越大训练效果越好,梯度累加则实现了batchsize的变相扩大,如果accumulation_steps为8,则batchsize '变相' 扩大了8倍,是解决显存受限的一个不错的trick。
使用时需要注意,学习率也要适当放大。经验法则是,如果batch size加倍,那么学习率就加倍。
PyTorch中在反向传播前为什么要手动将梯度清零?
有时候会不会觉得optimizer.zero_grad()这句很多余,怎么不自动清零,需要手动清零呢?原因在于在PyTorch中,计算得到的梯度值会自动进行累加(而不是覆盖)。pytorch选择自动累加而不是覆盖的原因:
1 从内存消耗的角度来看。这种模式可以让梯度玩出更多花样,比如说上面讲到的梯度累加(gradient accumulation)实现的“显存受限”解决:在内存大小不够的情况下叠加多个batch的grad作为一个大batch进行迭代,因为二者得到的梯度是等价的综上可知,这种梯度累加的思路是对内存的极大友好,是由FAIR的设计理念出发的。即当你GPU显存较少时,你又想要调大batch-size,此时你就可以利用PyTorch的这个性质进行梯度的累加来进行backward。
示例:
在你已经达到计算资源上限的情况下,你的batch size仍然太小(比如8),然后我们需要模拟一个更大的batch size来进行梯度下降,以提供一个良好的估计。
假设我们想要达到128的batch size大小。我们需要以batch size为8执行16个前向传播和向后传播,然后再执行一次优化步骤。
# clear last step
optimizer.zero_grad()
# 16 accumulated gradient steps
scaled_loss = 0
for accumulated_step_i in range(16):
out = model.forward()
loss = some_loss(out,y)
loss.backward()
scaled_loss += loss.item()
# update weights after 8 steps. effective batch = 8*16
optimizer.step()
# loss is now scaled up by the number of accumulated batches
actual_loss = scaled_loss / 16
2 利用梯度累加,可以在最多保存一张计算图的情况下进行multi-task任务的训练:
从PyTorch的设计原理上来说,在每次进行前向计算得到pred时,会产生一个用于梯度回传的计算图,这张图储存了进行back propagation需要的中间结果,当调用了.backward()后,会从内存中将这张图进行释放。
for idx, data in enumerate(train_loader):
xs, ys = data
optmizer.zero_grad()
# 计算d(l1)/d(x)
pred1 = model1(xs) #生成graph1
loss1 = loss_fn1(pred1, ys)
loss1.backward() #释放graph1
# 计算d(l2)/d(x)
pred2 = model2(xs)#生成graph2
loss2 = loss_fn2(pred2, ys)
loss2.backward() #释放graph2
# 使用d(l1)/d(x)+d(l2)/d(x)进行优化
optmizer.step()
模型训练加速
16-bit 精度
16bit精度是将内存占用减半的惊人技术。大多数模型使用32bit精度数字进行训练。然而,最近的研究发现,16bit模型也可以工作得很好。混合精度意味着对某些内容使用16bit,但将权重等内容保持在32bit。
要在Pytorch中使用16bit精度,请安装NVIDIA的apex库,并对你的模型进行这些更改。
# enable 16-bit on the model and the optimizer
model, optimizers = amp.initialize(model, optimizers, opt_level='O2')
# when doing .backward, let amp do it so it can scale the loss
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
amp包会处理好大部分事情。如果梯度爆炸或趋向于0,它甚至会缩放loss。
在lightning中,启用16bit并不需要修改模型中的任何内容,也不需要执行我上面所写的操作。设置Trainer(precision=16)
就可以了。
trainer = Trainer(amp_level='O2', use_amp=False)
trainer.fit(model)
移动到多个GPUs中
多节点GPU训练
在单个节点上多GPU更快的训练distributedDataParallel
Gpu训练
要进行2个张量的运算,就必须都在CPU或者都在GPU上。对于不同存储位置的变量,我们是不可以对他们直接进行计算的。存储在不同位置中的数据是不可以直接进行交互计算的。否则出错:RuntimeError: Expected object of backend CUDA but got backend CPU for ***。[PyTorch 20.GPU训练]
把数据和模型从cpu移到gpu中的两种方法
use_cuda = torch.cuda.is_available()
# 方法一:
if use_cuda:
data = data.cuda()
model.cuda()
# 方法二:
device = torch.device("cuda" if use_cuda else "cpu")
data = data.to(device)
model.to(device)
个人比较习惯第二种方法,可以少一个 if 语句。而且该方法还可以通过设备号指定使用哪个GPU设备,比如使用0号设备:
device = torch.device("cuda:0" if use_cuda else "cpu")
[PyTorch:tensor-基本操作---CUDA 的用法]
1 如果模型已经在GPU上了,model.to(device)/model.cuda()不会做任何事情。
2 对于自己创建的模型类,由于继承了 torch.nn.Module ,则可同样使用 model.to(device)/model.cuda() 来将模型中用到的所有参数都存储到显存中去。
model_cuda = model.cuda()后,将 cpu类型数据 投入 model_cuda 中去可以发现,系统会报错,而将 .cuda 之后的gpu数据投入 model_cuda 才能正常工作,并且输出的也是具有cuda的数据类型。
self.dnn_layer = MLP(input_dim, dnn_hidden_units, activation=dnn_activation,
use_bn=dnn_use_bn, l2_reg=l2_reg_dnn, dropout_rate=dnn_dropout,
init_std=init_std, device=device)
self.to(device)
...
print("self.dnn_layer.device: ", self.dnn_layer.device) #cpu,模型to('gpu')时函数本身没有变化,只是参数put到gpu了。
print("self.dnn_layer.parameters()).device: ", next(self.dnn_layer.parameters()).device) #gpu
3 当我们对模型存储到显存中去之后,那么这个模型中的方法后面所创建出来的Tensor是不是都会默认变成cuda的数据类型?答案是否定的。
Note:
def compile(self, optimizer_name, loss_func_name=None, metrics_name=None):
"""
:param optimizer_name: String (name of optimizer) or optimizer instance. See [optimizers](https://pytorch.org/docs/stable/optim.html).
:param loss_func_name: String (name of objective function) or objective function. See [losses](https://pytorch.org/docs/stable/nn.functional.html#loss-functions).
:param metrics_name: List of metrics to be evaluated by the model during training and testing. Typically you will use `metrics=['accuracy']`.
"""
self.optimizer = self._get_optim(optimizer_name)
self.loss_func = self._get_loss_func(loss_func_name)
self.metrics = self._get_metrics(metrics_name)
def _get_optim(self, optimizer):
if isinstance(optimizer, str):
if optimizer == "sgd":
optim = torch.optim.SGD(self.parameters(), lr=0.01)
elif optimizer == "adam":
optim = torch.optim.Adam(self.parameters())
elif optimizer == "adagrad":
optim = torch.optim.Adagrad(self.parameters())
elif optimizer == "rmsprop":
optim = torch.optim.RMSprop(self.parameters())
else:
raise NotImplementedError
else:
optim = optimizer
return optim
def optimize(self, max_norm=None, norm_type=2):
'''
进行最优化操作
'''
with time_block("backward_in_optimize"):
self.loss.backward()
if max_norm:
torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm, norm_type)
print("next(self.parameters()).device: ", next(self.parameters()).device)
self.optimizer.step()
init中初始化时:
self.to(device)
self.compile(optimizer, loss, metrics) #需要在self.to(device)后面,因为optim = torch.optim.Adagrad(self.parameters())中next(self.parameters()).device的类型还未确定,self.parameters()为gpu时,optim才是gpu中,self.optimizer.step()执行时才不会出错:RuntimeError: Expected object of backend CPU but got backend CUDA for argument #4 'tensor1'。
指定GPU编号
设置当前使用的GPU设备仅为0号设备,设备名称为 /gpu:0:
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
设置当前使用的GPU设备为0,1号两个设备,名称依次为 /gpu:0、/gpu:1:
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1"
根据顺序表示优先使用0号设备,然后使用1号设备。
Note: 指定GPU的命令需要放在和神经网络相关的一系列操作的前面。
单机多GPU的实现代码
分布式训练
模型预测
model.eval()作用:不启用 BatchNormalization 和 Dropout,保证BN和dropout不发生变化,pytorch框架会自动把BN和Dropout固定住,不会取平均,而是用训练好的值,不然的话,一旦test的batch_size过小,很容易就会被BN层影响结果。
# Set to not-training mode to disable dropout
model.eval() #实际调用model.train(False)
...
with torch.no_grad(): #禁止梯度的计算
y_pred = model(x)
loss = ...
训练时:
# Set back to training mode
model.train(True) # todo
训练设置
梯度裁剪
nn.utils.clip_grad_norm_ 的参数
parameters – 一个基于变量的迭代器,会进行梯度归一化
max_norm – 梯度的最大范数
norm_type – 规定范数的类型,默认为L2
Note: 梯度裁剪在某些任务上会额外消耗大量的计算时间。
[https://pytorch.org/docs/master/generated/torch.nn.utils.clip_grad_norm_.html]
示例
def optimize(self, max_norm=None, norm_type=2):
'''
进行最优化操作
'''
self.loss.backward()
if max_norm:
torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm, norm_type)
self.optimizer.step()
import torch.nn as nn
outputs = model(data)
loss= loss_fn(outputs, target)
optimizer.zero_grad()
self.optimize()
16-bit 精度
16bit精度是将内存占用减半的惊人技术。大多数模型使用32bit精度数字进行训练。然而,最近的研究发现,16bit模型也可以工作得很好。混合精度意味着对某些内容使用16bit,但将权重等内容保持在32bit。
要在Pytorch中使用16bit精度,请安装NVIDIA的apex库,并对你的模型进行这些更改。
# enable 16-bit on the model and the optimizer
model, optimizers = amp.initialize(model, optimizers, opt_level='O2')
# when doing .backward, let amp do it so it can scale the loss
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
amp包会处理好大部分事情。如果梯度爆炸或趋向于0,它甚至会缩放loss。
防止验证模型时爆显存
torch.no_grad()
torch.no_grad()是一个上下文管理器,用来禁止梯度的计算,通常用来网络推断中(因为验证模型时不需要求导,即不需要梯度计算),关闭autograd可以减少计算内存的使用量,提高速度,如果不关闭可能会爆显存。
with torch.no_grad():
# 使用model进行预测的代码
pass
示例
>>> a = torch.tensor([1.0, 2.0], requires_grad=True)
>>> with torch.no_grad():
... b = n.pow(2).sum()
...
>>> b
tensor(5.)
>>> b.requires_grad
False
>>> c = a.pow(2).sum()
>>> c.requires_grad
True
上面的例子中,
当a的requires_grad=True时,不使用torch.no_grad(),c.requires_grad为True;
使用torch.no_grad()时,b.requires_grad为False,当不需要进行反向传播时(推断)或不需要计算梯度(网络输入)时,requires_grad=True会占用更多的计算资源及存储资源。
Note: 需要注意的是,torch.no_grad()只是禁止梯度计算,将计算过程中的tensor的requires_grad设置为False,但不会将模型参数如weight这样的tensor的requires_grad设置为False,因为如果训练和测试同时进行,跳出predict的torch.no_grad()后的训练过程还是需要weight中requires_grad=True的。
释放保留的计算图
一个最简单撑爆你的内存的方法是为了记录日志存储你的loss。
losses = []
...
losses.append(loss)
print(f'current loss: torch.mean(losses)')
上面的问题是,loss仍然包含有整个图的副本。在这种情况下,调用.item()来释放它。
# good
losses.append(loss.item())
torch.cuda.empty_cache()
Pytorch 训练时无用的临时变量可能会越来越多,导致 out of memory ,可以使用下面语句来清理这些不需要的变量:torch.cuda.empty_cache() 。
官网 上的解释为:
Releases all unoccupied cached memory currently held by the caching allocator so that those can be used in other GPU application and visible innvidia-smi. 意思就是PyTorch的缓存分配器会事先分配一些固定的显存,即使实际上tensors并没有使用完这些显存,这些显存也不能被其他应用使用。这个分配过程由第一次CUDA内存访问触发的。而 torch.cuda.empty_cache() 的作用就是释放缓存分配器当前持有的且未占用的缓存显存,以便这些显存可以被其他GPU应用程序中使用,并且通过 nvidia-smi命令可见。注意使用此命令不会释放tensors占用的显存。对于不用的数据变量,Pytorch 可以自动进行回收从而释放相应的显存。
更详细的优化可以查查 优化显存使用 和 显存利用问题。
BUGs
UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.'precision', 'predicted', average, warn_for)`
metrics.f1_score(y_test, y_pred, average='weighted')
原因是预测的label都预测成了一个,可能是预测批数据量少了,也可能是模型有问题,从而全部预测成了同一个label。
some labels in y_true don't appear in y_pred.
from: -柚子皮-
ref: https://zhuanlan.zhihu.com/p/76459295
[使用pytorch时,训练集数据太多达到上千万张,Dataloader加载很慢怎么办?]
以上是关于PyTorch:模型训练和预测的主要内容,如果未能解决你的问题,请参考以下文章