开发、调试计算机视觉代码有哪些技巧?

本文转载自知乎答主小磊在「开发, 调试计算机视觉代码有哪些技巧?」问题下的回答。原文链接: https://www.zhihu.com/question/280472255/answer/422068650

boxx 是一个旨在提高计算机视觉代码开发和调试效率的 Python 工具库。

在调试视觉代码时, 基本就是和多维数组打交道, 多维数组有很多的属性,打印起来比较麻烦。 boxx.loga 可以一次性展现出一个数组的大多数属性。

Note:

  • loga 是 “log array” 的缩写, 若 array 里面还含有 nan 或 inf, loga 也会一并提示出来。
  • loga 支持许多可以转为 numpy 的数据类型,包括 torch.tensor, mxnet.ndarray, PIL.Image 等。

做计算机视觉,可视化图像和 feature 非常重要。boxx.show 能方便地做可视化。

Note:

show 会在复杂的数据结构中 找出所有可能是图像的矩阵,并一一显示(plt.imshow)出来。 当然,show也支持 numpy,torch,mxnet,PIL.Image .etc

在开发 CV 代码时,会遇到一些复杂的 dict,list(比如 batch、模型参数)。boxx.tree 可以直观地展示复杂结构。

Note:

在理解和适配别人的代码时,经常用到 tree。tree 还支持自动从 torch.Dataloader/Dataset 中 sample 一个 batch 来可视化 (P.S. boxx.show 也支持)。

以上三个工具是我在视觉领域经常用到的工具, 接下来介绍一些通用的 Python 开发调试工具,只要写 Python 代码,都可以用上。

打印变量是最简单、直接的debug方式, 那能不能更简单?

Note:

boxx.p 使用了 magic method, p/x 便会打印 x 并返回 x。这样便可以在任何地方打印,比如 例子中的 p/randint(0, 3) 就不需要新建变量便可直接打印

在函数内运行 p(),便会将函数或 module 内的所有变量名和值一同打印(相当于快捷打印 locals())。

Note:

在函数内 import boxx.p 和 p() 有相同的效果

许多数情况下, 直接 print 无法获得调试的关键信息。 比如训练 loss 跑飞了, 导致 Bug 的可能是 tensor 的尺度/类型不对, 矩阵里有 nan , inf 等情况。我曾遇到过 梯度含有nan 的情况

这时 就必须对矩阵进行分析, 方式有:

  1. 在调试处加上 print(x.mean(), np.hasinf(x),.np.hasnan(x))
  2. 设置断点进行分析

方法1 每改一次调试代码 都要运行整个代码, 不灵活,操作也繁琐。 方法2 中进入和退出 Debug console 比较麻烦,Debug console 本身也不太好用(没有自动补全功能) boxx.g 提供了一种新的方式,通过 g.name 可以将变量传到当前的 Python 交互终端

变量传到 Python 终端后,就能对变量进行全面分析了,比如 使用 loga,tree 来分析

Note:

  • gg 的意思是 to Global and log, 和 g 的用法一样, 但 gg 会在传输的同时打印变量.
  • 需要注意, 如果之前在终端中存在一样的变量名称,则变量的值会被新值覆盖.

在函数内运行 g(),便会将函数 (或 module) 内的所有变量一同传到当前的 Python 交互终端。

这样 任何错误都可以在终端中复现和分析了。当然, 注意不要覆盖重要的全局变量。

Note:

在函数内 import boxx.g 和 g() 有相同的效果。

在实际开发调试中, 函数或 module 内可能含有非常多的变量 但我们只对几个变量感兴趣, with p, with g, with gg可以使操作只作用于几个感兴趣的变量,只需把变量放入 with 结构中即可 :

Note:

  • with p, with g, with gg 只作用于在 with 结构中进行赋值操作的变量.
  • 如果变量名在 with 前存在于 locals() 中, 同时 id(变量) 没有变化 , with 结构可能无法检测到该变量.

总结一下,boxx 的调试工具可以汇总为一个表

boxx 调试工具矩阵

Note:

  • transport 操作是指 把函数内的变量传送到 Python interactive console 中
  • locals() 指 作用于函数或 module 内的所有变量
  • locals()_2: 当 boxx 未导入时, import boxx.{操作} 能等价于 from boxx import {操作};{操作}()

在学习新框架或适配大佬代码时,经常会使用 print(x), dir(x), help(x), type(x) 来了解某个变量的各方面的信息 (变量可能是 值/function/class/module 等),于是我写了一个 boox.what(x) 来全面了解"what is x?":

Note:

what(x) 通过打印 x 自己及x 的 文档, 父类继承关系, 内部结构 及 所有属性 来全面了解 x. 是 help(x)的补充.

说了这么多调试 再说一下性能调优

测试代码性能时,计时很常用, 我写了一个方便的计时工具boxx.timeit 将想要计时的代码块放入 with timeit():中就可以计时了:

此外 SnakeViz 是一个很棒的性能分析工具,SnakeViz 能够通过 cProfile 文件,来统计代码的函数调用情况,并可视化出代码的 火焰图。但是, 先生成 cProfile 文件,再运行 SnakeViz 的流程非常繁琐,我把这一套操作封装成了 boxx.performance 来简化流程:

Note:

performance 也支持字符串形式的 Python 代码。

如今的数据集都数百上千 GB,在数据清洗和预处理时 要写多进程的 Python 代码 来榨干 CPU 的每一个线程获得加速。但我觉得 Python 多进程的几个范式都不够方便,我参照 map 的思想和用法把多进程操作封装成 boxx.mapmp 函数(意思是"Map for Mulit Processing"). mapmp 和 map 有一样的用法, 只需把 map 替换为 mapmp 即可获得多进程加速:

Note:

  • mapmp 的 pool 参数来控制进程数目,默认为 CPU 线程数目.
  • 在多进程程序中, 打印进度往往非常麻烦. mapmp 的 printfreq 参数能解决这个问题.
  • 如同 map 一样,mapmp 支持将多个参数输入函数,如mapmp(add, list_1, list_2)
  • 在 Python 中,多进程代码最好在 __name__ == '__main__' 环境中运行.
  • 如果加速 numpy 程序,请注意 在 MKL 版本的 numpy 中,多进程会更慢, 可以运行 boxx.testNumpyMultiprocessing() 来测试当前环境对多进程 numpy 的友好程度

当要下载 url 形式的数据集或网络爬取图片时,多线程编程对这类高IO操作会很有用。boxx 还有个多线程版本的 map – mapmt (意思是 “Map for Mulit Threading”)。mapmt 用法和 mapmp 一样, 但没有多进程的诸多限制。

再分享一下我自己在写视觉代码的感受吧

由于我自己

  1. 写 CV 代码离不开强大的 Qt console for IPython
  2. 受不了远程编辑对网络的依赖和延迟

所以 一直用 Anaconda 自带的 Spyder 作为 Python 开发的 IDE. Spyder 虽然不够强大,但自带的 Qt-IPython, 配合自己写的工具,调试起来还是比较方便, 顺手。所以, 我开发的工具都尽可能地直接, 简洁,上面介绍的大部分工具都支持 func-x 来代替 func(x) 以方便调试时调用。甚至,我还写了一些字符串处理工具,直接在IPython 内使用, 以弥补 Spyder 作为 IDE 的不足。

此外,我的工作流一般是 先在本地开发调试, 用 boxx.sysi 检测运行环境来自动切换运行参数, 本地开发调试 OK 了, 用 rsync 命令 只传改过的 .py 文件到服务器 再来 train。虽然这样传代码比较麻烦, 但开发, 调试起来会方便很多。

之前在实验室一直是本地 GPU 环境调试 比较方便。实习后, 旷厂不提供本地 GPU。我主力是 PyTorch, 为了方便调试 我写了个 boxx.ylth 包,如果检测到没有 CUDA 环境,boxx.ylth 会强行使 .cuda() 和大部分 GPU 操作无效。只要在代码开头 import boxx.ylth 大多数只基于 GPU 的 torch 代码, 可以不经更改 直接在 CPU 上运行和调试。(这操作太暴力 请慎用)

boxx 兼容 Python 2/3,支持任何 Python 运行环境,包括纯 Python环境、IPython、Spyder、Jupyer Notebook 等

GitHub 主页:

https://github.com/DIYer22/boxx(这儿有更多说明和用例)

安装:

pip install boxx (其他安装方法及说明)

教程:

boxx 的教程是一个可执行的在线 Notebook。 也就是说,无需下载和运行任何代码,只需浏览器点击下面的链接,就可以在线执行 Notebook 教程中的代码块:

https://mybinder.org/v2/gh/DIYer22/boxx-ipynb/master?filepath=tutorial_for_boxx.ipynb

最后,特别感谢徐晓栋、吴国栋、范浩强和熊鹏飞对 boxx 提出的建议。

原文发布于微信公众号 - AI研习社(okweiwu)

原文发表时间:2018-07-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

数据说话:Go语言的Switch和Map性能实测

在开发pgx(一个针对Go语言的PostgreSQL driver)的时候,有好几次我都需要在20多个代码分支间跳转。通常我会选用switch语句。还有个更加可...

44550
来自专栏人人都是极客

环形缓冲区的实现

队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),在另一端进行删除(出队)。

42640
来自专栏程序员叨叨叨

4.3 CG 编译

计算机只能理解和执行由 0、1 序列(电压序列)构成的机器语言,所以汇编语言和高级语言程序都需要进行翻译才能被计算机所理解,担负这一任务的程序称为语言处理程序,...

13020
来自专栏AI科技大本营的专栏

TensorFlow tfjs 0.10.3 发布

TensorFlow tfjs 0.10.3 近日正式发布,新版本主要有以下改进内容,AI科技大本营对其编译如下。 ▌资源

14520
来自专栏Golang语言社区

【golang】调优工具 pprof

Golang 提供了 pprof 包(runtime/pprof)用于输出运行时的 profiling 数据,这些数据可以被 pprof 工具(或者 go to...

18130
来自专栏Jerry的SAP技术分享

用代码判断当前系统是否支持某个版本的feature

JDK9已经出来有一段时间了,因此很多流行的Java应用纷纷增添了对JDK9乃至JDK10的支持,比如Tomcat。

15020
来自专栏linux驱动个人学习

Linux CFS调度器之虚拟时钟vruntime与调度延迟--Linux进程的管理与调度(二十六)

CFS为了实现公平,必须惩罚当前正在运行的进程,以使那些正在等待的进程下次被调度。

27640
来自专栏数据派THU

独家 | 手把手教你如何用Python从PDF文件中导出数据(附链接)

有很多时候你会想用Python从PDF中提取数据,然后将其导出成其他格式。不幸的是,并没有多少Python包可以很好的执行这部分工作。在这篇贴子中,我们将探讨多...

29930
来自专栏同步博客

memcached分布式缓存

  memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。Memcache集群主机不能够相互通信传输数据,它的“分布式”是基于客户端的...

17510
来自专栏前端杂货铺

[译] Cookbook of QUnit

本篇文章是QUnit的简介,可以作为很好的入门教程。文章原址 介绍 自动化测试时软件开发过程中必不可少的一部分,而单元测试则是自动化测试的最为基本的一块,软件的...

312110

扫码关注云+社区

领取腾讯云代金券