天天看点

PyTorch踩过的12坑

作者 | hyk_1996

 1. nn.module.cuda() 和 tensor.cuda() 的作用效果差异 无论是对于模型还是数据,cuda()函数都能实现从cpu到gpu的内存迁移,但是他们的作用效果有所不同。

对于nn.module:

model = model.cuda() 

model.cuda() 

上面两句能够达到一样的效果,即对model自身进行的内存迁移。

对于tensor:

和nn.module不同,调用tensor.cuda()只是返回这个tensor对象在gpu内存上的拷贝,而不会对自身进行改变。因此必须对tensor进行重新赋值,即tensor=tensor.cuda().

例子: 

model = create_a_model()

tensor = torch.zeros([2,3,10,10])

model.cuda()

tensor.cuda()

model(tensor)    # 会报错

tensor = tensor.cuda()

model(tensor)    # 正常运行

2. pytorch 0.4 计算累积损失的不同 以广泛使用的模式total_loss += loss.data[0]为例。python0.4.0之前,loss是一个封装了(1,)张量的variable,但python0.4.0的loss现在是一个零维的标量。对标量进行索引是没有意义的(似乎会报 invalid index to scalar variable 的错误)。使用loss.item()可以从标量中获取python数字。所以改为:

total_loss += loss.item()

 如果在累加损失时未将其转换为python数字,则可能出现程序内存使用量增加的情况。这是因为上面表达式的右侧原本是一个python浮点数,而它现在是一个零维张量。因此,总损失累加了张量和它们的梯度历史,这可能会产生很大的autograd 图,耗费内存和计算资源。3. pytorch 0.4 编写不限制设备的代码 

# torch.device object used throughout this script

device = torch.device("cuda" if use_cuda else "cpu")

model = myrnn().to(device)

# train

total_loss= 0

for input, target in train_loader:

    input, target = input.to(device), target.to(device)

    hidden = input.new_zeros(*h_shape)       # has the same device & dtype as `input`

    ...                                                               # get loss and optimize

    total_loss += loss.item()

# test

with torch.no_grad():                                    # operations inside don't track history

    for input, targetin test_loader:

        ...

 4. torch.tensor.detach()的使用 detach()的官方说明如下:

returns a new tensor, detached from the current graph.

    the result will never require gradient.

 假设有模型a和模型b,我们需要将a的输出作为b的输入,但训练时我们只训练模型b. 那么可以这样做:

input_b = output_a.detach()

它可以使两个计算图的梯度传递断开,从而实现我们所需的功能。5. error: unexpected bus error encountered in worker. this might be caused by insufficient shared memory (shm) 出现这个错误的情况是,在服务器上的docker中运行训练代码时,batch size设置得过大,shared memory不够(因为docker限制了shm).解决方法是,将dataloader的num_workers设置为0.6. pytorch中loss函数的参数设置 

以crossentropyloss为例:

crossentropyloss(self, weight=none, size_average=none, ignore_index=-100, reduce=none, reduction='elementwise_mean')

若 reduce = false,那么 size_average 参数失效,直接返回向量形式的 loss,即batch中每个元素对应的loss.

若 reduce = true,那么 loss 返回的是标量:

如果 size_average = true,返回 loss.mean().

如果 size_average = false,返回 loss.sum().

weight : 输入一个1d的权值向量,为各个类别的loss加权,如下公式所示: 

PyTorch踩过的12坑

ignore_index : 选择要忽视的目标值,使其对输入梯度不作贡献。如果 size_average = true,那么只计算不被忽视的目标的loss的均值。

reduction : 可选的参数有:‘none’ | ‘elementwise_mean’ | ‘sum’, 正如参数的字面意思,不解释。

 7. pytorch的可重复性问题 参考这篇博文:https://blog.csdn.net/hyk_1996/article/details/84307108

8. 多gpu的处理机制 使用多gpu时,应该记住pytorch的处理逻辑是:1)在各个gpu上初始化模型。2)前向传播时,把batch分配到各个gpu上进行计算。3)得到的输出在主gpu上进行汇总,计算loss并反向传播,更新主gpu上的权值。4)把主gpu上的模型复制到其它gpu上。

9. num_batches_tracked参数 今天读取模型参数时出现了错误

keyerror: 'unexpected key "module.bn1.num_batches_tracked" in state_dict'

经过研究发现,在pytorch 0.4.1及后面的版本里,batchnorm层新增了num_batches_tracked参数,用来统计训练时的forward过的batch数目,源码如下(pytorch0.4.1):

    if self.training and self.track_running_stats:

        self.num_batches_tracked += 1

        if self.momentum is none:  # use cumulative moving average

            exponential_average_factor = 1.0 / self.num_batches_tracked.item()

        else:  # use exponential moving average

            exponential_average_factor = self.momentum

 大概可以看出,这个参数和训练时的归一化的计算方式有关。

因此,我们可以知道该错误是由于训练和测试所用的pytorch版本(0.4.1版本前后的差异)不一致引起的。具体的解决方案是:如果是模型参数(orderdict格式,很容易修改)里少了num_batches_tracked变量,就加上去,如果是多了就删掉。偷懒的做法是将load_state_dict的strict参数置为false,如下所示:

load_state_dict(torch.load(weight_path), strict=false)

 还看到有人直接修改pytorch 0.4.1的源代码把num_batches_tracked参数删掉的,这就非常不建议了。

10. 训练时损失出现nan的问题 最近在训练模型时出现了损失为nan的情况,发现是个大坑。暂时先记录着。

可能导致梯度出现nan的三个原因: 1.梯度爆炸。也就是说梯度数值超出范围变成nan. 通常可以调小学习率、加bn层或者做梯度裁剪来试试看有没有解决。2.损失函数或者网络设计。比方说,出现了除0,或者出现一些边界情况导致函数不可导,比方说log(0)、sqrt(0).3.脏数据。可以事先对输入数据进行判断看看是否存在nan.

补充一下nan数据的判断方法:

注意!像nan或者inf这样的数值不能使用 == 或者 is 来判断!为了安全起见统一使用 math.isnan() 或者 numpy.isnan() 吧。

例如:

import numpy as np

# 判断输入数据是否存在nan

if np.any(np.isnan(input.cpu().numpy())):

  print('input data has nan!')

# 判断损失是否为nan

if np.isnan(loss.item()):

  print('loss value is nan!')

 11. valueerror: expected more than 1 value per channel when training 当batch里只有一个样本时,再调用batch_norm就会报下面这个错误:

  raise valueerror('expected more than 1 value per channel when training, got input size {}'.format(size))

没有什么特别好的解决办法,在训练前用 num_of_samples % batch_size 算一下会不会正好剩下一个样本。 12. 优化器的weight_decay项导致的隐蔽bug 我们都知道weight_decay指的是权值衰减,即在原损失的基础上加上一个l2惩罚项,使得模型趋向于选择更小的权重参数,起到正则化的效果。但是我经常会忽略掉这一项的存在,从而引发了意想不到的问题。

这次的坑是这样的,在训练一个resnet50的时候,网络的高层部分layer4暂时没有用到,因此也并不会有梯度回传,于是我就放心地将resnet50的所有参数都传递给optimizer进行更新了,想着layer4应该能保持原来的权重不变才对。但是实际上,尽管layer4没有梯度回传,但是weight_decay的作用仍然存在,它使得layer4权值越来越小,趋向于0。后面需要用到layer4的时候,发现输出异常(接近于0),才注意到这个问题的存在。

虽然这样的情况可能不容易遇到,但是还是要谨慎:暂时不需要更新的权值,一定不要传递给optimizer,避免不必要的麻烦。

PyTorch踩过的12坑