-柚子皮-
模型训练和预测
简单的说就是进来一个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() 根据梯度更新网络参数
梯度累加就是,每次获取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 gradient1 获取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加倍,那么学习率就加倍。
有时候会不会觉得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()
[PyTorch中在反向传播前为什么要手动将梯度清零?]
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
[9个技巧让你的PyTorch模型训练变得飞快!]
要进行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的数据类型?答案是否定的。
[浅谈将Pytorch模型从CPU转换成GPU]
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设备仅为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的命令需要放在和神经网络相关的一系列操作的前面。
[Pytorch 高效使用GPU的操作]
[PyTorch:模型训练-分布式训练]
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()
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()是一个上下文管理器,用来禁止梯度的计算,通常用来网络推断中(因为验证模型时不需要求导,即不需要梯度计算),关闭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())
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 可以自动进行回收从而释放相应的显存。 更详细的优化可以查查 优化显存使用 和 显存利用问题。
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.
[UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples]
from: -柚子皮-
ref: https://zhuanlan.zhihu.com/p/76459295
[9个技巧让你的PyTorch模型训练变得飞快!]
[使用pytorch时,训练集数据太多达到上千万张,Dataloader加载很慢怎么办?]