是什么引起了各个框架Resize操作的结果不同?——来自ONNX的标准化尝试

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

目前创新互联公司已为近1000家的企业提供了网站建设、域名、雅安服务器托管、网站改版维护、企业网站设计、定陶网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。

作者:京东AI研究院 张建浩

炼丹师在转换模型的时候,经常会发现给转换前后的模型输入同样的图片,模型结果有微小的差别。其中的原因有数值算法的误差、不同 jpeg 解码库产生的结果不同等等,也有不同框架内部对某些算子的实现差异。

在给 ONNX 贡献 Resize 算子的 spec 的时候,我发现 Resize 是一个突出体现了框架实现差异的算子——多种 Resize 类型、不统一的超参数、将错就错的历史遗留 bug 和其它极易被忽略的问题集中在一起,导致几乎每个框架的 Resize 操作的结果都有差异,而 ONNX 是一个神经网络模型的中间格式,它应该尽量保留原始框架的算子的语义。经过查看相关论文和各种框架的源代码,我分析和总结了 Resize 操作众多的实现方式。最终为 ONNX 贡献了一个较为完善的、标准化的 Resize 算子的 spec,它包含多个(基本)正交的参数,TensorFlow 1.x、TensorFlow 2.x、PyTorch、OpenCV 的 resize/interpolation 方法都可以用这个算子 100% 无损的表达。 本文将简单介绍各种 resize 操作的共同流程,并分析是哪些因素引起了不同框架 resize 操作的不同。

多维 tensor (例如二维图像)的 resize 操作是用多个在一维 tensor 上进行的 resize 操作组合出来的,所以我们只讨论一维 tensor 上的 resize 操作,经过分析各个框架的源代码,我发现它的流程可以总结如下:

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

1

先讨论w和f,w(i)是第 i 个像素点的坐标,乍一看, w(i)完全可以等于i本身,其实没有这么简单。例如一个长度为 3 的 tensor,如果第i个像素点的坐标等于i本身,那么三个像素点在tensor 中的位置就如下图中最左边的样子,横线的长度代表一维 tensor 的长度,圆圈代表像素点:

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

三个像素点没有对称地分布在 tensor 上,而是往左偏了。出于直觉,我们觉得这不是一件特别好的事情。在各种框架中,有两种常见的方法来解决这个问题:

一个是选取w(i)=i+0.5,以一个长度为 3 的一维 tensor 为例,它第 0 个像素点在 0.5 位置,第 1 个像素点在 1.5 位置,第 2 个像素点在 2.5 位置,这称为 half_pixel,也就是上图中中间的方法。这种方法中,

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

(这很符合直觉)。另一个是仍让w(i)=i,但改变函数f,使

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

仍以长度为 3 的一维 tensor 为例,这种方法相当于在 resize 时砍掉了最右边长度为 1 的部分,使像素点的分布“被”对称了。这称为 align_corner,也就是上图中最右边的方法,在各种框架的 resize 方法的参数里常见的 align_corner=True/False 就是它了,它的名字来源于它可以让 tensor 中第一个和最后一个像素(即 corner)在缩放后保持不变。

那如果我们不采用这两种方法,一定要使用“直觉不好”的 asymmetric 方法,究竟会发生什么呢?TensorFlow 1.x 就给我们提供了这样一个反面典型,它在 align_corner=False 时的实现是错的,原因就是使用了上图中错误的 asymmetric 方法,这会导致奇怪的缩放结果,这篇博客中???? https://hackernoon.com/how-tensorflows-tf-image-resize-stole-60-days-of-my-life-aba5eb093f35,

作者用 TensorFlow 1.x 训练的超分辨率神经网络总是出现奇怪的问题,最终他发现问题根源是 TensorFlow 错误的 resize 实现,他还给了一个形象的例子:把 16x16 的下图左侧图像缩小到 4x4,本应得到如下图右侧所示的图像,而 TensorFlow 1.x 却给出了下图中间的奇怪结果,图像的对称性被完全破坏了,其中的原因就如上文所述。TensorFlow 1.x 的 resize 结果和其它框架不同的一大原因就是它错误的 resize 实现,好在 TensorFlow 2.x 已经修复了这个问题。

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

2

接下来讨论另外两个函数g和h,nearest, linear, cubic 这三种常见的 resize 的不同方式,是在g和h上有所不同。如上文所述,函数  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 得到离  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 最近的像素点,nearest 只需要找最近的一个像素点,linear 要找最近的两个(左右各一个),cubic 要找最近的四个(左右各两个);函数h(a,r)是计算这一个/两个/四个像素点的加权平均值,其中权值是由r确定的(如上文所述,r是  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试距左侧像素点的距离)。对 nearest/linear/cubic 的每一种来说,如何从r得到各个像素点的权值都有各自标准的实现,nearest resize 不必说,对于 linear resize,两个像素点的权值是  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 。对 cubic 来说,四个像素点的权值是

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试


[1]其中A是一个固定的参数,它的取值却是每个框架不同,两个常见的选择是 -0.5 (TensorFlow 部分版本的实现)和 -0.75(PyTorch)。因为A没有统一的标准取值,所以各个框架的 cubic resize 结果不同是常见的事情。

补充一句题外话:cubic resize 的权值计算起来比 linear resize 复杂的多,所以它的耗时肯定会长一些,但产生的图像性质更好(这篇 paper ???? https://arxiv.org/abs/1812.0118 7 发现图片预处理使用 cubic resize 可以提升分类网络准确率)。

还有一个会引起 cubic resize 结果差异的细节是,cubic resize 需要找到  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 的左右各两个最相邻的像素点,但  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 左右两侧不一定能保证各有两个像素点(假设某种情况下计算得到  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 ,那么它左边只有一个像素点),此时也有两种现存的不同方法,一种是对图像做 edge padding,即认为仍从左边找到了两个像素点,并且这两个像素点的值都是第一个像素点的值;另一种是认为找到了三个而不是四个像素点,并对三个像素点的权值做归一化。

3

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

小结

总结一下,各个框架 Resize 操作的结果不同的原因是多种多样的,例如 TensorFlow 用了自己发明的错误实现、cubic resize 中参数 A 没有固定的取值、非整数的

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

是否自动取整等等。

ONNX Resize 算子的 spec 就是基于上面的分析写出来的,具体的描述在???? https://github.com/onnx/onnx/bl ob/master/docs/Operators.md#Resize ,

Python 版的参考实现在 ???? https://github.com/onnx/onnx/bl ob/master/onnx/backend/test/case/node/resize.py

其中比较核心的属性 coordinate_transformation_mode 是把w、f和  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试 复合得到的单个函数  是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试,即

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

在这里没有用独立的函数w和f的原因除了看起来更简单之外,也有解决现实问题的考虑——有一些框架的某些 resize 实现没有使用

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

的形式,而是直接让

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试

虽然这显然是不合理的(coordinate_transformation_mode=tf_half_pixel_for_nn 就描述了这样一个不合理的实现),但也只能承认它们的存在。相比起来,上一个版本的 ONNX Resize 算子 spec 的制定者没有意识到 Resize 算子的复杂性,完全模仿了 TensorFlow 的实现,不仅和其它框架的结果不一致,而且连 TensorFlow 的 bug 也一并模仿了。

现在 TensorFlow、PyTorch 都支持了导出这一版本的 Resize 算子,TensorRT 等部署框架也支持导入和运行这个 Resize 算子。自己创造的东西能被众多知名的框架跟进,我感到非常大的成就感。

参考: https://ieeexplore.ieee.org/doc ument/1163711

欢迎点击“ 京东智联云 ”了解更多精彩内容!

是什么引起了各个框架 Resize 操作的结果不同?——来自 ONNX 的标准化尝试


文章名称:是什么引起了各个框架Resize操作的结果不同?——来自ONNX的标准化尝试
文章转载:http://myzitong.com/article/gjgcih.html