[Pytorch进阶技巧(零)] Pytorch中计算图的建立与运行
前言
如今Pytorch的使用率正在超过tensorflow。虽然pytorch的开发效率比tensorflow高很多,但是使用tensorflow可以对计算图的概率更加清晰。关于tensorflow可以参考我的博文。
不清楚pytorch背后的计算图概念,在进行pytorch编程时可能会遇到:
- 随着迭代显存占用不断增大,直至报错;
- 报错参数不在同个设备,比如报错cpu里的浮点数无法和cuda:0里的浮点数进行运算;
- 自动求导时,程序没有按照原本的设计进行导数更新。
- 进行NAS实验时,在多卡训练很多epoch后,想根据训练结果改变网络结构再继续训练咋办?
本文涉及的pytorch API包括: torch.nn torch.register_buffer torch.register_parameter torch.no_grad torch.nn.Module.cuda torch.nn.Module.cpu torch.nn.DataParallel
本文将依次回答如下问题:
- pytorch的计算图是怎么建立的;
- 如何控制自动求导;
- cpu,gpu,多卡分别是如何运行模型。
动态计算图
任何一个深度学习框架都无法离开计算图这个概念,主要的原因就是框架需要将运算过程并行化。如果无法定义运算过程相对固定的计算图,框架也就无法决定如何并行,从而也就无法实现GPU加速的目的。
都在说pytorch是动图,tensorflow是静图。因为Tensorflow的计算图基本在开启Session前已经写死了,也就是说在真正进行计算时,有哪些变量、这些变量怎么运算,已经确定了,所以称为静图。
那么pytorch"动"在哪呢。pytorch在进行计算前,基本确定下来的是有哪些变量参与运算,但是这些变量是如何运算的并不确定。从代码层面看,tensorflow中,变量的运算(加减乘除卷积啥的)在Session run之前就写好了,而且需要写一遍。而pytorch中,每次计算时,都需要调用Module的forward,其实就是又写了一遍变量的运算。
网络模型搭建
不得不佩服pytorch框架设计者,通过torch.nn.Module可以很好地组织网络。与从最小元素阐述的方式不一样,本文直接描述pytorch是如何搭建一个网络模型。
- 一般而言,一个pytorch模型是个torch.nn.Module的子类,比如ResNet(torch.nn.Module)。使用的时候需要实例化。查看源码可以发现,torch.nn.Conv2d(torch.nn.Module)其实也是torch.nn.Module的子类。
# 经常看代码的你一定知道,一般这么用是吧 model = ResNet() pre = model(img)
- 继承torch.nn.Module,要求在__init__()里定义变量,在forward()里定义前向传播过程。
- (太精髓了)在__init__()里可以实例化其它torch.nn.Module的子类。此时被实列化的Module会调用__init__()初始化其中的参数或者Module。
- (啧啧啧)在forward()里定义前向传播,可以调用被实例化的
自动求导
最"大"的torch.nn.Module树遍历到每一个子module的forward函数,就可以确定前向传播的路径。那么沿着这个计算路径反向传播,就可以完成自动求导。当某些前向传播处不希望被纳入求导范围内是,只需用with torch.no_grad():包装即可。
cpu与gpu
在cpu里的model可以进行参数增减与前向传播修改,而放入了cuda与多卡的model不可进行参数的增减。因此,在修改模型前,需要model.cpu()后修改,在通过model.cuda()放入gpu内训练。