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 相关文章推荐
Linux下编译安装MySQL-Python教程
Feb 02 Python
python中异常报错处理方法汇总
Nov 20 Python
Python3 加密(hashlib和hmac)模块的实现
Nov 23 Python
Python OpenCV 直方图的计算与显示的方法示例
Feb 08 Python
在pandas中一次性删除dataframe的多个列方法
Apr 10 Python
基于python实现KNN分类算法
Apr 23 Python
浅谈Django2.0 加xadmin踩的坑
Nov 15 Python
Pytorch 实现focal_loss 多类别和二分类示例
Jan 14 Python
python定义类self用法实例解析
Jan 22 Python
flask框架自定义url转换器操作详解
Jan 25 Python
在python中logger setlevel没有生效的解决
Feb 21 Python
python中sympy库求常微分方程的用法
Apr 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
php echo()和print()、require()和include()函数区别说明
2010/03/27 PHP
php带密码功能并下载远程文件保存本地指定目录 修改加强版
2010/05/16 PHP
Fatal error: session_start(): Failed to initialize storage module: files问题解决方法
2014/05/04 PHP
PHP字典树(Trie树)定义与实现方法示例
2017/10/09 PHP
PHP实现求连续子数组最大和问题2种解决方法
2017/12/26 PHP
PHP实现动态压缩js与css文件的方法
2018/05/02 PHP
jQuery 点击图片跳转上一张或下一张功能的实现代码
2010/03/12 Javascript
js 限制数字 js限制输入实现代码
2012/12/04 Javascript
js 实现 input type=&quot;file&quot; 文件上传示例代码
2013/08/07 Javascript
ComboBox 和 DateField 在IE下消失的解决方法
2013/08/30 Javascript
Javascript模块化编程详解
2014/12/01 Javascript
js结合正则实现国内手机号段校验
2015/06/19 Javascript
javascript实现鼠标移到Image上方时显示文字效果的方法
2015/08/07 Javascript
详解jQuery的表单验证插件--Validation
2016/12/21 Javascript
jQuery实现为table表格动态添加或删除tr功能示例
2019/02/19 jQuery
VUE的history模式下除了index外其他路由404报错解决办法
2019/08/21 Javascript
微信小程序 导入图标实现过程详解
2019/10/11 Javascript
JS表格的动态操作完整示例
2020/01/13 Javascript
Vue 实现拨打电话操作
2020/11/16 Javascript
[00:44]TI7不朽珍藏III——军团指挥官不朽展示
2017/07/15 DOTA
Python的lambda匿名函数的简单介绍
2013/04/25 Python
VSCode下好用的Python插件及配置
2018/04/06 Python
Python爬虫包BeautifulSoup学习实例(五)
2018/06/17 Python
python集合比较(交集,并集,差集)方法详解
2018/09/13 Python
python使用sklearn实现决策树的方法示例
2019/09/12 Python
main 主函数执行完毕后,是否可能会再执行一段代码,给出说明
2012/12/05 面试题
2013年保送生自荐信格式
2013/11/20 职场文书
大型活动策划方案
2014/01/12 职场文书
八项规定整改方案
2014/02/21 职场文书
《放飞蜻蜓》教学反思
2014/04/27 职场文书
酒店管理毕业生自荐信
2014/05/25 职场文书
爱护公共设施倡议书
2014/08/29 职场文书
计划生育证明书写要求
2014/09/17 职场文书
pytorch--之halfTensor的使用详解
2021/05/24 Python
Vue h函数的使用详解
2022/02/18 Vue.js
Java 数组的使用
2022/05/11 Java/Android