PyTorch踩坑记
前言
自己刚开始使用深度学习框架做事情的时候,选择了最容易入门的Keras。Keras是在其它深度学习框架(谷歌的TensorFlow,微软的CNTK以及Theano)的基础上,抽象了底层实现的差异,提供的更高层的API接口。说说Keras的好处吧!个人觉得Keras最吸引人的地方就是API接口的设计特别人性化,对于样本的训练,结果的测试都有一种使用传统机器学习库的感觉;函数式接口设计使得深度网络的时候特别容易,简直就像在玩乐高。如果有人想入门深度学习,我一定也会推荐Keras。
后来,我为什么转到PyTorch呢?因为PyTorch大部分框架是基于Python实现的(虽然底层也有C代码),PyTorch提供了很简单的接口使得tensor
和NumPy中的ndarray
互相转换,这样基于NumPy的各种库我们也可以直接拿来使用。当然,这不是最重要的。我选择PyTorch的原因是因为:第一,基于Python实现,而不是像其它库一样只是提供了一个Python的调用接口而已。这样对于深度框架的调试就特别容易,如果你使用TensorFlow或者Keras,底层的实现都是C/C++,无法很好地进行底层的调试;第二,PyTorch使用动态图,而TensorFlow这样的框架使用静态图。这就是说当你使用TensorFlow框架编译一个深度模型,模型就是固定的,不容易改变,而PyTorch的动态图提供了更多的灵活性,特别是对RNN网络。所以,我在PyTorch脱离了Beta版本(0.4)以后,我果断转到了PyTorch,开始了新的学习之旅。
下面记录的是我在使用PyTorch遇到的一些问题及其解决方案:
In-place operation
这个问题是在我设计一个残差网络(ResNet)的时候遇到的,报错如下:RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation.
我是参考了PyTorch官方的ResNet实现来设计我自己的网络的。其实,问题主要出在forward()
函数中的out += residual
这句代码。
我们首先来看一下+=
这个操作符,这是一个原位操作符因为+=
是对out
张量直接进行的+
操作,就是说执行完+=
操作以后原来out
指向的那个张量已经改变了。如果使用out = out + residual
会有什么不同呢?这个操作是将out
和residual
相加,然后将结果赋值给out
变量。在这个过程中原来out
变量指向的那个张量并没有被修改。
那么问题来了,为什么PyTorch官方的实现中,使用+=
的写法没有问题,而我自己代码中这样写就有问题了呢?这是因为官方的ResNet中forward()
函数中进行相加赋值操作以后就是一个relu
激活函数,而激活函数层不需要反向传播,所以是没问题的;而我自己设计的网络中后面还有别的层,所以就不能这样写了。
Input type and weight type should be the same
这个问题是我将代码移植到GPU上运行时遇到的问题,报错如下:RuntimeError: Input type (CUDAFloatTensor) and weight type (CPUFloatTensor) should be the same
有人可能说,这个简单!这是你的输入数据在GPU上,而模型参数不在GPU上,使用to()
方法将模型复制到GPU上即可。非也,我这里说的不是个问题。当然,如果有人遇到这个错误了,第一要检查的是你是不是使用to()
或者cuda()
方法将模型搬运到GPU上去了。
我的代码已经使用to()
将模型复制到GPU上去了,为什么还会有这个问题呢?通过两天的调试,我发现我的模型大部分参数是位于GPU上的,而模型中的一些层却在CPU上,所以导致了这个问题。
注:在调试程序的时候怎么查看模型是否在GPU上呢?使用如下函数可以进行测试:next(model.parameters()).is_cuda
我后来发现,是我在设计ResNet的时候使用了list
存储我的残差层导致的。如果在定义模型的时候,使用普通的list
存储的模型层,PyTorch提供的to()
方法是不会将对应的层复制到GPU上去的。解决办法也很简单,使用torch.nn.ModuleList
容器来存储就好了。