PyTorch的Debug指南


Posted in Python onMay 07, 2021

一、ipdb 介绍

很多初学 python 的同学会使用 print 或 log 调试程序,但是这只在小规模的程序下调试很方便,更好的调试应该是在一边运行的时候一边检查里面的变量和方法。

感兴趣的可以去了解 pycharm 的 debug 模式,功能也很强大,能够满足一般的需求,这里不多做赘述,我们这里介绍一个更适用于 pytorch 的一个灵活的 pdb 交互式调试工具。

Pdb 是一个交互式的调试工具,集成与 Python 标准库中,它能让你根据需求跳转到任意的 Python 代码断点、查看任意变量、单步执行代码,甚至还能修改变量的值,而没有必要去重启程序。

ipdb 则是一个增强版的 pdb,它提供了调试模式下的代码自动补全,还有更好的语法高亮和代码溯源,以及更好的内省功能,最重要的是它和 pdb 接口完全兼容,可以通过 pip install ipdb 安装。

二、ipdb 的使用

首先看一个例子,要使用 ipdb 的话,只需要在想要进行调试的地方插入 ipdb.set_trace(),当代码运行到这个地方时,就会自动进入交互式调试模式。

import ipdb


def sum(x):
    r = 0
    for ii in x:
        r += ii
    return r


def mul(x):
    r = 1
    for ii in x:
        r *= 11
    return r


ipdb.set_trace()
x = [1, 2, 3, 4, 5]
r = sum(x)
r = mul(x)
> /Users/mac/Desktop/jupyter/test.py(19)<module>()
     18 ipdb.set_trace()
---> 19 x = [1, 2, 3, 4, 5]
     20 r = sum(x)

ipdb> l 1,5  # l(ist) 1,5 的缩写,查看第 1 行到第 5 行的代码
      1 import ipdb
      2 
      3 
      4 def sum(x):
      5     r = 0

ipdb> n  # n(ext) 的缩写执行下一步
> /Users/mac/Desktop/jupyter/test.py(20)<module>()
     19 x = [1, 2, 3, 4, 5]
---> 20 r = sum(x)
     21 r = mul(x)

ipdb> s  # s(tep) 的缩写,进入 sum 函数内部
--Call--
> /Users/mac/Desktop/jupyter/test.py(4)sum()
      3 
----> 4 def sum(x):
      5     r = 0

ipdb> n  # n(ext) 单步执行
> /Users/mac/Desktop/jupyter/test.py(5)sum()
      4 def sum(x):
----> 5     r = 0
      6     for ii in x:

ipdb> n
> /Users/mac/Desktop/jupyter/test.py(6)sum()
      5     r = 0
----> 6     for ii in x:
      7         r += ii

ipdb> u  # u(p) 的缩写,调回上一层的调用
> /Users/mac/Desktop/jupyter/test.py(20)<module>()
     19 x = [1, 2, 3, 4, 5]
---> 20 r = sum(x)
     21 r = mul(x)

ipdb> d  # d(own) 的缩写,跳到调用的下一层
> /Users/mac/Desktop/jupyter/test.py(6)sum()
      5     r = 0
----> 6     for ii in x:
      7         r += ii

ipdb> n
> /Users/mac/Desktop/jupyter/test.py(7)sum()
      6     for ii in x:
----> 7         r += ii
      8     return r

ipdb> !r  # 查看变量 r 的值,该变量名与调试命令 `r(eturn)` 冲突
0
    
ipdb> return  # 继续运行知道函数返回
--Return--
15
> /Users/mac/Desktop/jupyter/test.py(8)sum()
      7         r += ii
----> 8     return r
      9 

ipdb> n
> /Users/mac/Desktop/jupyter/test.py(21)<module>()
     19 x = [1, 2, 3, 4, 5]
     20 r = sum(x)
---> 21 r = mul(x)

ipdb> x  # 查看变量 x
[1, 2, 3, 4, 5]
    
ipdb> x[0] = 10000  # 修改变量 x
    
ipdb> x
[10000, 2, 3, 4, 5]
    
ipdb> b 12  # b(reak) 的缩写,在第 10 行设置断点
Breakpoint 1 at /Users/mac/Desktop/jupyter/test.py:12
    
ipdb> c  # c(ontinue) 的缩写,继续运行,直到遇到断点
> /Users/mac/Desktop/jupyter/test.py(12)mul()
     11 def mul(x):
1--> 12     r = 1
     13     for ii in x:

ipdb> return  # 可以看到计算的是修改之后的 x 的乘积
--Return--
1200000
> /Users/mac/Desktop/jupyter/test.py(15)mul()
     14         r *= ii
---> 15     return r
     16 

ipdb> q  # q(uit) 的缩写,退出 debug

上述只是给出了 ipdb 的一部分使用方法,关于 ipdb 还有一些小的使用技巧:

  • 键能够自动补齐,补齐用法和 IPython 中的类似
  • j(ump) 能够跳过中间某些行的代码的执行
  • 可以直接在 ipdb 中修改变量的值
  • help 能够查看调试命令的用法,比如 h h 可以查看 help 命令的用法,h j(ump) 能够查看 j(ump) 命令的用法

三、在 PyTorch 中 Debug

PyTorch 作为一个动态图框架,和 ipdb 结合使用能够让调试过程更加便捷,下面我们将距离说明以下三点:

  • 如何在 PyTorch 中查看神经网络各个层的输出
  • 如何在 PyTorch 中分析各个参数的梯度
  • 如何动态修改 PyTorch 的训练流程

首先,运行上一篇文章给出的“猫狗大战”程序:python main.py train --debug-file='debug/debug.txt'

程序运行一段时间后,在debug目录下创建debug.txt标识文件,当程序检测到这个文件存在时,会自动进入debug模式。

99it [00:17,  6.07it/s]loss: 0.22854854568839075
119it [00:21,  5.79it/s]loss: 0.21267264398435753
139it [00:24,  5.99it/s]loss: 0.19839374726372108
> e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py(80)train()
     79         loss_meter.reset()
---> 80         confusion_matrix.reset()
     81         for ii, (data, label) in tqdm(enumerate(train_dataloader)):

ipdb> break 88    # 在第88行设置断点,当程序运行到此处进入debug模式
Breakpoint 1 at e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py:88

ipdb> # 打印所有参数及其梯度的标准差
for (name,p) in model.named_parameters(): \
    print(name,p.data.std(),p.grad.data.std())
model.features.0.weight tensor(0.2615, device='cuda:0') tensor(0.3769, device='cuda:0')
model.features.0.bias tensor(0.4862, device='cuda:0') tensor(0.3368, device='cuda:0')
model.features.3.squeeze.weight tensor(0.2738, device='cuda:0') tensor(0.3023, device='cuda:0')
model.features.3.squeeze.bias tensor(0.5867, device='cuda:0') tensor(0.3753, device='cuda:0')
model.features.3.expand1x1.weight tensor(0.2168, device='cuda:0') tensor(0.2883, device='cuda:0')
model.features.3.expand1x1.bias tensor(0.2256, device='cuda:0') tensor(0.1147, device='cuda:0')
model.features.3.expand3x3.weight tensor(0.0935, device='cuda:0') tensor(0.1605, device='cuda:0')
model.features.3.expand3x3.bias tensor(0.1421, device='cuda:0') tensor(0.0583, device='cuda:0')
model.features.4.squeeze.weight tensor(0.1976, device='cuda:0') tensor(0.2137, device='cuda:0')
model.features.4.squeeze.bias tensor(0.4058, device='cuda:0') tensor(0.1798, device='cuda:0')
model.features.4.expand1x1.weight tensor(0.2144, device='cuda:0') tensor(0.4214, device='cuda:0')
model.features.4.expand1x1.bias tensor(0.4994, device='cuda:0') tensor(0.0958, device='cuda:0')
model.features.4.expand3x3.weight tensor(0.1063, device='cuda:0') tensor(0.2963, device='cuda:0')
model.features.4.expand3x3.bias tensor(0.0489, device='cuda:0') tensor(0.0719, device='cuda:0')
model.features.6.squeeze.weight tensor(0.1736, device='cuda:0') tensor(0.3544, device='cuda:0')
model.features.6.squeeze.bias tensor(0.2420, device='cuda:0') tensor(0.0896, device='cuda:0')
model.features.6.expand1x1.weight tensor(0.1211, device='cuda:0') tensor(0.2428, device='cuda:0')
model.features.6.expand1x1.bias tensor(0.0670, device='cuda:0') tensor(0.0162, device='cuda:0')
model.features.6.expand3x3.weight tensor(0.0593, device='cuda:0') tensor(0.1917, device='cuda:0')
model.features.6.expand3x3.bias tensor(0.0227, device='cuda:0') tensor(0.0160, device='cuda:0')
model.features.7.squeeze.weight tensor(0.1207, device='cuda:0') tensor(0.2179, device='cuda:0')
model.features.7.squeeze.bias tensor(0.1484, device='cuda:0') tensor(0.0381, device='cuda:0')
model.features.7.expand1x1.weight tensor(0.1235, device='cuda:0') tensor(0.2279, device='cuda:0')
model.features.7.expand1x1.bias tensor(0.0450, device='cuda:0') tensor(0.0100, device='cuda:0')
model.features.7.expand3x3.weight tensor(0.0609, device='cuda:0') tensor(0.1628, device='cuda:0')
model.features.7.expand3x3.bias tensor(0.0132, device='cuda:0') tensor(0.0079, device='cuda:0')
model.features.9.squeeze.weight tensor(0.1093, device='cuda:0') tensor(0.2459, device='cuda:0')
model.features.9.squeeze.bias tensor(0.0646, device='cuda:0') tensor(0.0135, device='cuda:0')
model.features.9.expand1x1.weight tensor(0.0840, device='cuda:0') tensor(0.1860, device='cuda:0')
model.features.9.expand1x1.bias tensor(0.0177, device='cuda:0') tensor(0.0033, device='cuda:0')
model.features.9.expand3x3.weight tensor(0.0476, device='cuda:0') tensor(0.1393, device='cuda:0')
model.features.9.expand3x3.bias tensor(0.0058, device='cuda:0') tensor(0.0030, device='cuda:0')
model.features.10.squeeze.weight tensor(0.0872, device='cuda:0') tensor(0.1676, device='cuda:0')
model.features.10.squeeze.bias tensor(0.0484, device='cuda:0') tensor(0.0088, device='cuda:0')
model.features.10.expand1x1.weight tensor(0.0859, device='cuda:0') tensor(0.2145, device='cuda:0')
model.features.10.expand1x1.bias tensor(0.0160, device='cuda:0') tensor(0.0025, device='cuda:0')
model.features.10.expand3x3.weight tensor(0.0456, device='cuda:0') tensor(0.1429, device='cuda:0')
model.features.10.expand3x3.bias tensor(0.0070, device='cuda:0') tensor(0.0021, device='cuda:0')
model.features.11.squeeze.weight tensor(0.0786, device='cuda:0') tensor(0.2003, device='cuda:0')
model.features.11.squeeze.bias tensor(0.0422, device='cuda:0') tensor(0.0069, device='cuda:0')
model.features.11.expand1x1.weight tensor(0.0690, device='cuda:0') tensor(0.1400, device='cuda:0')
model.features.11.expand1x1.bias tensor(0.0138, device='cuda:0') tensor(0.0022, device='cuda:0')
model.features.11.expand3x3.weight tensor(0.0366, device='cuda:0') tensor(0.1517, device='cuda:0')
model.features.11.expand3x3.bias tensor(0.0109, device='cuda:0') tensor(0.0023, device='cuda:0')
model.features.12.squeeze.weight tensor(0.0729, device='cuda:0') tensor(0.1736, device='cuda:0')
model.features.12.squeeze.bias tensor(0.0814, device='cuda:0') tensor(0.0084, device='cuda:0')
model.features.12.expand1x1.weight tensor(0.0977, device='cuda:0') tensor(0.1385, device='cuda:0')
model.features.12.expand1x1.bias tensor(0.0102, device='cuda:0') tensor(0.0032, device='cuda:0')
model.features.12.expand3x3.weight tensor(0.0365, device='cuda:0') tensor(0.1312, device='cuda:0')
model.features.12.expand3x3.bias tensor(0.0038, device='cuda:0') tensor(0.0026, device='cuda:0')
model.classifier.1.weight tensor(0.0285, device='cuda:0') tensor(0.0865, device='cuda:0')
model.classifier.1.bias tensor(0.0362, device='cuda:0') tensor(0.0192, device='cuda:0')

ipdb> opt.lr    # 查看学习率
0.001

ipdb> opt.lr = 0.002    # 更改学习率

ipdb> for p in optimizer.param_groups: \
    p['lr'] = opt.lr

ipdb> model.save()    # 保存模型
'checkpoints/squeezenet_20191004212249.pth'

ipdb> c    # 继续运行,直到第88行暂停
222it [16:38, 35.62s/it]> e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py(88)train()
     87             optimizer.zero_grad()
1--> 88             score = model(input)
     89             loss = criterion(score, target)

ipdb> s    # 进入model(input)内部,即model.__call__(input)
--Call--
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(537)__call__()
    536 
--> 537     def __call__(self, *input, **kwargs):
    538         for hook in self._forward_pre_hooks.values():

ipdb> n    # 下一步
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(538)__call__()
    537     def __call__(self, *input, **kwargs):
--> 538         for hook in self._forward_pre_hooks.values():
    539             result = hook(self, input)

ipdb> n    # 下一步
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(544)__call__()
    543                 input = result
--> 544         if torch._C._get_tracing_state():
    545             result = self._slow_forward(*input, **kwargs)

ipdb> n    # 下一步
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(547)__call__()
    546         else:
--> 547             result = self.forward(*input, **kwargs)
    548         for hook in self._forward_hooks.values():

ipdb> s    # 进入forward函数内容
--Call--
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\loss.py(914)forward()
    913 
--> 914     def forward(self, input, target):
    915         return F.cross_entropy(input, target, weight=self.weight,

ipdb> input    # 查看input变量值
tensor([[4.5005, 2.0725],
        [3.5933, 7.8643],
        [2.9086, 3.4209],
        [2.7740, 4.4332],
        [6.0164, 2.3033],
        [5.2261, 3.2189],
        [2.6529, 2.0749],
        [6.3259, 2.2383],
        [3.0629, 3.4832],
        [2.7008, 8.2818],
        [5.5684, 2.1567],
        [3.0689, 6.1022],
        [3.4848, 5.3831],
        [1.7920, 5.7709],
        [6.5032, 2.8080],
        [2.3071, 5.2417],
        [3.7474, 5.0263],
        [4.3682, 3.6707],
        [2.2196, 6.9298],
        [5.2201, 2.3034],
        [6.4315, 1.4970],
        [3.4684, 4.0371],
        [3.9620, 1.7629],
        [1.7069, 7.8898],
        [3.0462, 1.6505],
        [2.4081, 6.4456],
        [2.1932, 7.4614],
        [2.3405, 2.7603],
        [1.9478, 8.4156],
        [2.7935, 7.8331],
        [1.8898, 3.8836],
        [3.3008, 1.6832]], device='cuda:0', grad_fn=<AsStridedBackward>)

ipdb> input.data.mean()    # 查看input的均值和标准差
tensor(3.9630, device='cuda:0')
ipdb> input.data.std()
tensor(1.9513, device='cuda:0')

ipdb> u    # 跳回上一层
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(547)__call__()
    546         else:
--> 547             result = self.forward(*input, **kwargs)
    548         for hook in self._forward_hooks.values():

ipdb> u    # 跳回上一层
> e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py(88)train()
     87             optimizer.zero_grad()
1--> 88             score = model(input)
     89             loss = criterion(score, target)

ipdb> clear    # 清除所有断点
Clear all breaks? y
Deleted breakpoint 1 at e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py:88

ipdb> c    # 继续运行,记得先删除"debug/debug.txt",否则很快又会进入调试模式
59it [06:21,  5.75it/s]loss: 0.24856307208538073
76it [06:24,  5.91it/s]

当我们想要进入 debug 模式,修改程序中某些参数值或者想分析程序时,就可以通过创建 debug 标识文件,此时程序会进入调试模式,调试完成之后删除这个文件并在 ipdb 调试接口输入 c 继续运行程序。如果想退出程序,也可以使用这种方式,先创建 debug 标识文件,然后输入 quit 在退出 debug 的同时退出程序。这种退出程序的方式,与使用 Ctrl + C 的方式相比更安全,因为这能保证数据加载的多进程程序也能正确地退出,并释放内存、显存等资源。

PyTorch 和 ipdb 集合能完成很多其他框架所不能完成或很难完成的功能。根据笔者日常使用的总结,主要有以下几个部分:

  1. 通过 debug 暂停程序。当程序进入 debug 模式后,将不再执行 CPU 和 GPU 运算,但是内存和显存及相应的堆栈空间不会释放。
  2. 通过 debug 分析程序,查看每个层的输出,查看网络的参数情况。通过 u(p) 、 d(own) 、 s(tep) 等命令,能够进入指定的代码,通过 n(ext) 可以单步执行,从而看到每一层的运算结果,便于分析网络的数值分布等信息。
  3. 作为动态图框架, PyTorch 拥有 Python 动态语言解释执行的优点,我们能够在运行程序时,用过 ipdb 修改某些变量的值或属性,这些修改能够立即生效。例如可以在训练开始不久根据损失函数调整学习率,不必重启程序。
  4. 如果在 IPython 中通过 %run 魔法方法运行程序,那么在程序异常退出时,可以使用 %debug 命令,直接进入 debug 模式,通过 u(p) 和 d(own) 跳到报错的地方,查看对应的变量,找出原因后修改相应的代码即可。有时我们的模式训练了好几个小时,却在将要保存模式之前,因为一个小小的拼写错误异常退出。此时,如果修改错误再重新运行程序又要花费好几个小时,太浪费时间。因此最好的方法就是看利用 %debug 进入调试模式,在调试模式中直接运行 model . save() 保存模型。在 IPython 中, %pdb 魔术方法能够使得程序出现问题后,不用手动输入 %debug 而自动进入 debug 模式,建议使用。

四、 通过PyTorch实现项目中容易遇到的问题

PyTorch 调用 CuDNN 报错时,报错信息诸如 CUDNN_STATUS_BAD_PARAM,从这些报错内容很难得到有用的帮助信息,最后先利用 PCU 运行代码,此时一般会得到相对友好的报错信息,例如在 ipdb 中执行 model.cpu() (input.cpu()), PyTorch 底层的 TH 库会给出相对比较详细的信息。

常见的错误主要有以下几种:

  • 类型不匹配问题。例如 CrossEntropyLoss 的输入 target 应该是一个 LongTensor ,而很多人输入 FloatTensor 。
  • 部分数据忘记从 CPU 转移到 GPU 。例如,当 model 存放于 GPU 时,输入 input 也需要转移到 GPU 才能输入到 model 中。还有可能就是把多个 model 存放于一个 list 对象,而在执行 model.cuda() 时,这个 list 中的对象是不会被转移到 CUDA 上的,正确的用法是用 ModuleList 代替。
  • Tensor 形状不匹配。此类问题一般是输入数据形状不对,或是网络结构设计有问题,一般通过 u(p) 跳到指定代码,查看输入和模型参数的形状即可得知。

此外,可能还会经常遇到程序正常运行、没有报错,但是模型无法收敛的问题。例如对于二分类问题,交叉熵损失一直徘徊在 0.69 附近(ln2),或者是数值出现溢出等问题,此时可以进入 debug 模式,用单步执行查看,每一层输出的均值和方差,观察从哪一层的输出开始出现数值异常。还要查看每个参数梯度的均值和方差,查看是否出现梯度消失或者梯度爆炸等问题。一般来说,通过再激活函数之前增加 BatchNorm 层、合理的参数初始化、使用 Adam 优化器、学习率设为0.001,基本就能确保模型在一定程度收敛。

五、总结

本章带同学们从头实现了一个 Kaggle 上的经典竞赛,重点讲解了如何合理地组合安排程序,同时介绍了一些在PyTorch中调试的技巧,下章将正式的进入编程实战之旅,其中一些细节不会再讲的如此详细,做好心理准备。

以上就是PyTorch的Debug指南的详细内容,更多关于PyTorch Debug的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python将xml和xsl转换为html的方法
Mar 10 Python
python中enumerate函数遍历元素用法分析
Mar 11 Python
利用Python将时间或时间间隔转为ISO 8601格式方法示例
Sep 05 Python
使用Python操作excel文件的实例代码
Oct 15 Python
Python模块搜索路径代码详解
Jan 29 Python
Python3基础教程之递归函数简单示例
Jun 07 Python
Python集中化管理平台Ansible介绍与YAML简介
Jun 12 Python
python打印直角三角形与等腰三角形实例代码
Oct 20 Python
Python3.x+迅雷x 自动下载高分电影的实现方法
Jan 12 Python
浅谈keras2 predict和fit_generator的坑
Jun 17 Python
Python实现异步IO的示例
Nov 05 Python
Python使用Turtle模块绘制国旗的方法示例
Feb 28 Python
基于Python的EasyGUI学习实践
Python列表删除重复元素与图像相似度判断及删除实例代码
使用python如何删除同一文件夹下相似的图片
May 07 #Python
python学习之panda数据分析核心支持库
Python基于Tkinter开发一个爬取B站直播弹幕的工具
May 06 #Python
Python爬虫之爬取最新更新的小说网站
May 06 #Python
Python基础之操作MySQL数据库
You might like
建立文件交换功能的脚本(三)
2006/10/09 PHP
discuz Passport 通行证 整合笔记
2008/06/30 PHP
php基于base64解码图片与加密图片还原实例
2014/11/03 PHP
PHP经典面试题之设计模式(经常遇到)
2015/10/15 PHP
CI框架实现优化文件上传及多文件上传的方法
2017/01/04 PHP
jQuery EasyUI 开源插件套装 完全替代ExtJS
2010/03/24 Javascript
jQuery EasyUI API 中文文档 可调整尺寸
2011/09/29 Javascript
前后台交互过程中json格式如何解析以及如何生成
2012/12/26 Javascript
解析页面加载与js函数的执行 onload or ready
2013/12/12 Javascript
禁止页面刷新让F5快捷键及右键都无效
2014/01/22 Javascript
Node.js与PHP、Python的字符处理性能对比
2014/07/06 Javascript
javascript验证手机号和实现星号(*)代替实例
2016/08/16 Javascript
jquery实现超简单的瀑布流布局【推荐】
2017/03/08 Javascript
纯js的右下角弹窗实例
2017/03/12 Javascript
在 Node.js 中使用原生 ES 模块方法解析
2017/09/19 Javascript
JavaScript实现二叉树的先序、中序及后序遍历方法详解
2017/10/26 Javascript
webpack+vue+express(hot)热启动调试简单配置方法
2018/09/19 Javascript
跟老齐学Python之类的细节
2014/10/13 Python
基于Python对象引用、可变性和垃圾回收详解
2017/08/21 Python
Python文件操作之合并文本文件内容示例代码
2017/09/19 Python
用python实现刷点击率的示例代码
2019/02/21 Python
python内存动态分配过程详解
2019/07/15 Python
Python函数的默认参数设计示例详解
2019/12/01 Python
python3 webp转gif格式的实现示例
2019/12/10 Python
python已协程方式处理任务实现过程
2019/12/27 Python
python3下pygame如何实现显示中文
2020/01/11 Python
Python图像处理库PIL的ImageFont模块使用介绍
2020/02/26 Python
python 爬虫爬取京东ps4售卖情况
2020/12/18 Python
Hotels.com印度:酒店预订
2019/05/11 全球购物
在C语言中实现抽象数据类型什么方法最好
2014/06/26 面试题
银行学习十八大感想
2014/01/11 职场文书
开朗女孩的自我评价
2014/02/10 职场文书
保护环境建议书300字
2014/05/13 职场文书
经济贸易专业自荐信
2014/06/11 职场文书
2014年干部作风建设总结
2014/10/23 职场文书
浅谈MySql update会锁定哪些范围的数据
2022/06/25 MySQL