Photo by Daniel Stone on Unsplash

今早在GitHub上收到一则issue,发信人为Suaro

Suaro希望使用OpenCV来实现模型加载与推演,但是没有成功,因此开了issue寻求我的帮助。

现场围观该Issue:

Help to load HRNet model with OpenCV · Issue #3 · yinguobing/facial-landmark-detection-hrnet
Hi, First congratulate you on your amazing project. I have trained the network without problems and I was able to run the predict.py script on my webcam to check the operation of the model. However...

说实话这个功能并没有在我最初的考虑范围内。该项目所遵守的开源协议意味着我没有义务去解决这个问题。但是,Suaro提issue的方式在我看来是非常值得赞许与推广的。所以,我不仅帮TA解决了issue,还要以此为样本,与大家分享下一些提issue时的注意事项。

首先,我们先解决OpenCV加载模型的问题。

使用OpenCV加载模型

OpenCV在3.0的版本时引入了一个dnn模块,实现了一些基本的神经网络模型layer。在最新的4.5版本中,dnn模块使用函数 readNet 实现模型加载。不过根据官方解释,OpenCV不支持TensorFlow所推荐的模型保存格式 saved_model 。所以在加载模型之前,模型需要首先被冻结。

冻结网络

在之前的文章“TensorFlow如何冻结网络模型”中介绍过了冻结网络的具体含义以及原理。但是在TensorFlow2中网络冻结似乎被弃用了,文中提到的冻结脚本也无法使用。幸运的是,网络冻结的原理仍然有效,而且OpenCV作者提供了一小段示例样本展示了冻结网络的方法如下:

import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2

# Load the model from saved_model.
loaded = tf.saved_model.load('my_model')
infer = loaded.signatures['serving_default']

f = tf.function(infer).get_concrete_function(input_1=tf.TensorSpec(shape=[None, 256, 256, 3], dtype=tf.float32))
f2 = convert_variables_to_constants_v2(f)
graph_def = f2.graph.as_graph_def()

# Export frozen graph
with tf.io.gfile.GFile('frozen_graph.pb', 'wb') as f:
   f.write(graph_def.SerializeToString())
冻结TensorFlow2模型的示例代码,作者:Dmitry Kurtaev

在这段代码中,模型的推演功能被包裹在 tf.function 中,构建了静态图。然后通过 convert_variables_to_constant_v2 将变量转换为常量,并将最终获得的 graph_def 写入单独的protobuf文件。

加载并推演

网络冻结完成后,并可以使用OpenCV加载推演了。示例代码如下:

import numpy as np
import cv2 as cv

net = cv.dnn.readNet('frozen_graph.pb')
inp = np.random.standard_normal([1, 3, 256, 256]).astype(np.float32)
net.setInput(inp)
out = net.forward()
print(out.shape)
OpenCV加载模型的示例代码,作者:Dmitry Kurtaev

Issue顺利解决。  注意TensorFlow版本为2.3.1。OpenCV版本4.5.0。

接下来我们来谈谈如何提issue。

Issue是什么

Issue在中文环境下多译为“问题”,而且是那种可能造成反复纠缠、难以解决的问题例如社会问题(social issue)。电视剧“神盾局特工”中的“反派”局长也曾用这个词来调侃Daisy。

图源:Pintrest

实际上,这个词在现代制造业中也经常会遇到。在我工作的第一家公司中,每个项目在启动前都会组织不同维度与形式的技术研讨,核心就是可能会遇到的“技术issue”。而且项目一旦进入实质性质的试生产流片阶段,大家最害怕出现的也是issue。它通常意味着加班、业绩压力以及可能的推倒重来。

软件行业中,issue这个词常被用来指代计算机程序使用过程中出现的异常表现。因此源代码托管平台GitHub专门为每一个项目提供了一个专门的讨论空间,供代码的使用者提出自己遇到的issue使用。这个区域在UI界面上紧挨着源代码,其重要性可见一斑。

图源:作者GitHub页面截图

造成Issue的原因

程序的异常行为背后可能存在多种原因,有可能是代码中存在的错误,也有可能是使用者的不当操作造成。例如一台微波炉无法加热食物,可能是磁控管故障,也有可能是没插电源。面对“无法加热”这个现象,找到其背后的原因是解决issue的关键。这时候,提issue的方式在某种程度上直接决定了问题解决的速度。根据我的观察,一个优秀的issue提出者能够做到以下几点。

尝试解决问题

如果你家的微波炉不工作了,大部分人的第一反应可能会去检查电源线是不是没有插上。这就是在尝试解决问题。理论上越是成熟的项目代码,发生故障的概率越低。一旦异常事件发生了,首先要尝试排除最可能发生的状况。毕竟因为松动的电源线叫厂家的维修人员上门服务,除了费用不说,主要是耽误自己的午餐。

尝试解决问题的手段因每个人的能力不同而不同。在保证安全的前提下尽力即可。例如本次提issue的小伙伴就详细的描述了TA已经尝试过的方案,而且以列表的形式一一列出,这无疑显示了TA的诚意,是加分项。

逐条列出已经尝试过的方案

反过来,如果issue提出者上来就说“XXX不能用,该怎么办呀”,这样多多少少显得有些伸手党,不过也没什么大不了,只要TA能做到——

准确描述现象

这是最常见的现象,不仅仅是issue区域,在生活中也随处可见。当问题出现时,无法使用语言传递准确的有价值信息。同样是微波炉的例子,以下是两种不同的表述:

我家的微波炉坏了,咋办呀?
我家的微波炉不工作了,大火力加热两分钟,指示灯不亮,微波炉不响,食物不热。电源插好了,可能是什么原因呀?

准确描述现象的最大好处在于这样做可以尽可能的排除各种导致故障的可能性。最为厂家售后人员,如果你听到的是第一种回答,你多半会要求用户检查电源线是否插好。而第二种回答显然已经排除了这种可能性,甚至可以初步估计出故障的严重程度已经超过了普通用户的解决能力。这无疑有利于问题尽快解决。

代码issue与之类似,当问题出现时,在issue中要尽可能的描述出具体的现象。一个糟糕的描述可能是:

我的代码奔溃了,是什么原因呀?

而一个优秀的描述则是:

我在执行 python3 train.py --batch_size=32 的时候训练中断了,并且显示错误信息 ValueError ...

代码的错误信息就像是微波炉上的指示灯,它可以提供非常有用的信息帮助排除错误发生的原因。解决代码的错误需要“抓现行”,也就是要让程序在你眼前奔溃给你看。这也是为什么几乎所有的代码作者要求提issue的时候附上导致错误出现的具体指令或者示例代码。

这一点Suaro就做得很好。TA在issue中附上了完整的错误信息:

在issue中附上具体的错误信息

一般来说,做到这种程度解决该issue就有希望了。如果是代码问题,多半会修复。如果该issue无法复现,那有可能不是代码问题,此时代码作者多半会要求你——

提供必要信息

实际上这是最基础的要求,但往往是最先被忽略的部分。继续拿广受欢迎的微波炉举例。作为厂家的售后人员,当你要求客户提供微波炉的型号代码后,你有可能会发现用户买的其实根本就不是微波炉,而是洗碗机!

这一问题在软件行业更加突出。一是因为同一个软件可能存在多版本并存的现象。例如OpenCV就有2.x, 3.x和4.x这三个主版本分支。TensorFlow也有1和2两个主版本。除此以外,现今软件更迭速度前所未有的迅速,nightly(每日更新)也不罕见。如果把git commit算进去那更是多如牛毛。所以issue可能是由于版本不兼容造成的。

所以,我强烈建议在提issue时一定要附上自己所使用的环境信息。实际上,GitHub已经内置了issue模板,将必要的信息嵌入进去,用户提issue就像是在做选择题一样。

TensorFlow提供的issue模板

Suaro在issue中并没有附上这部分信息。这种情况下我会默认TA使用的环境信息与我在README文件中的一致。当然,反过来考虑,当代码作者的环境与README不同时,也需要在issue回复中明示。

我在issue回复时标明了版本信息

交流沟通的能力

写了这么多关于issue的内容,聪明的你应该已经看出来了。Issue只是表面形式,内里的核心是沟通与交流。有诚意的、有效率的沟通与交流是每个人都应该掌握的能力。工作中它有助于项目推进,生意中可以避免误解与争端,生活中可以让你交到真心朋友。虽然目前的大环境对于真诚交流的人不那么友好,但是,对于每一个真诚的人,总会有另一份真诚不负期望。