小心使用tf.image.resize_images,填坑经验分享给你

上上周,我在一个项目上线前对模型进行测试时出现了问题,这个问题困扰了我近两周,终于找到了问题根源,做个简短总结分享给你,希望对大家有帮助。

问题描述:

线上线下测试结果不一致,且差异很大

具体来说,
线下测试直接load由ckpt存储的模型,然后使用cv进行数据预处理,然后评估测试集上的准召,一切正常。
线上测试时,首先使用tf.image相关函数将预处理写死在模型中,将ckpt模型转为savemodel格式,然后使用tf-serving部署后,发送请求进行线上实测,此时和线下测试结果差异较大。

问题定位:

主要问题出在ckpt转savemodel时,预处理部分 tf.image.resize_images 和 tf.cast 两个函数的使用上
虽然问题发生在模型转换时,但真正的问题出在对于tf.image.resize_images函数的使用上,因此任何可能的使用场景,包括预处理,数据增强,模型转换等,都有可能被它坑到,这也是我写这篇文章的原因,提醒大家不要向我一样被它坑到。
小小吐槽: 在发现真正的问题所在之前,由于我大幅的修改了我的训练框架,所以我从模型结构到loss函数,再到数据增强方法,排查了一遍,最终才发现,问题的出现,仅仅是我将
tf.image.resize_images 的 method 参数:
tf.image.ResizeMethod.BILINEAR 修改为了 tf.image.ResizeMethod.BICUBIC

what?这样小的一个修改就崩了?
下面我将我的排查过程详细描述出来,希望对大家有所启发。

如果打印tf.image.resize_images函数前后的数据类型

print(img_decoded.dtype)
resized_image = tf.image.resize_images(img_decoded, [new_height, new_width], method=tf.image.ResizeMethod.BICUBIC)
print(resized_image.dtype)

可以观察到如下结果
tf.uint8
tf.float32

而如果打印resize后的数据范围
tf img max 294.077484131
tf img min -25.2455863953

可以看到本来是0-255的uint8数据处理后不但数据类型发生了变化,而且像素值越界了!

此外,在预处理结束后我还使用了tf.cast函数转换数据类型

padd_image = tf.cast(resized_image, tf.uint8)

如果输入数据已经越界,此时tf.cast函数的使用也存在问题:

为方便理解问题,观察以下可视化结果:
使用cv2进行预处理的结果
在这里插入图片描述
tf版本的预处理结果(resize_images + cast)
在这里插入图片描述
可以看到resize_images + cast 函数的使用对原图有很明显的破坏

我们找到越界的部分,对resize后越界的部分进行可视化(用255或0截断后显示,正常区域用黑色填充)
小于0的部分
在这里插入图片描述
超过255的部分
在这里插入图片描述
上面两张图是正常越界截断后的结果,为了观察与tf.cast函数处理的区别

将 resize+cast 后 >255 部分的像素值可视化出来(为了凸显这部分像素,正常区域改用白色填充)
在这里插入图片描述
通过上图可以观察到,tf.cast对越界的处理机制并不是截断,而是类似取余操作,或者类似变量赋值时超过数据类型取值范围时的处理机制。

具体来说,如果越界的像素值是256,得到的返回结果对应的像素值是0;如果是257,得到的像素值是1,以此类推。

从图中越界的黄色区域(255,255,0)被tf.cast函数处理后变为蓝色区域(0,0,255)可以印证这一说法。

解决方法:
首先这并不能算google工程师的一个bug,因为tf.image.resize_images函数并没有对返回值的取值范围做保证,本质它就是进行插值,插值结果它不管。只是cv2或者PIL的类似函数中帮我们做了很多的“保护“。

通过尝试,最简便的解决方法是修改插值方法,经验证:
在这里插入图片描述
上面两种插值方法都不会造成像素值越界
如果你需要确保你的返回结果是在正常范围内的,那就在上面两个方法中选一个。此外,最邻近插值会带来比较明显的“不连续感”,因此推荐选择双线性插值,同时它也是默认参数。三次样条,虽然平滑性好,但是tf的实现版本真的是坑到我了。。。

当然,单单使用tf.image.resize_images也仅仅是对图片造成了微弱的扰动,但是配合上tf.cast函数的特有机制,对模型的干扰就比较大了。

综上:

1.使用 tf.image.resize_images函数时,如果使用三次样条插值,不要想当然的认为返回值是0-255的。
2.tf.cast函数的处理机制要注意,类似取余,而不是截断

搜了一下,被其它和resize相关的问题困扰的人也不少😅
感兴趣可以探究下
How Tensorflow’s tf.image.resize stole 60 days of my life
tensorflow-issues-19627
tf-image-resize-bilinear-vs-cv2-resize
说明tf的resize实现多少有些问题,这些应该不是bug,但确实给tensorflow的使用者们造成了不少困扰
在这里插入图片描述
这些函数的使用并不像cv2的api那样安全可信任
因此使用tf.image系的函数要慎重,一定要check数据类型,check函数处理后是否在0-255的范围,尤其是resize相关。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PID控制器开发笔记之十:步进式PID控制器的实现

对于一般的PID控制系统来说,当设定值发生较大的突变时,很容易产生超调而使系统不稳定。为了解决这种阶跃变化造成的不利影响,人们发明了步进式PID控制算法。 1、步进式PID的基本思想 所谓步进式PID算法,实际就是在设定值发生阶跃…

AutoML 与 Bayesian Optimization 概述

1. AutoML 概述 AutoML是指对于一个超参数优化任务(比如规定计算资源内,调整网络结构找到准确率最高的网络),尽量减少人为干预,使用某种学习机制,来调节这些超参数,使得目标问题达到最优。 这…

使用Eclipse进行Makefile项目

最近在MCU on Eclipse网站上看到Erich Styger所写的一篇有关在Eclipse中使用Makefile创建项目的文章,文章讲解清晰明了非常不错,所以呢没人将其翻译过来供各位同仁参考。当然限于个人水平,有不当之处恳请指正。原文网址:https://m…

C语言学习及应用笔记之一:C运算符优先级及使用问题

C语言中的运算符绝对是C语言学习和使用的一个难点,因为在2011版的标准中,C语言的运算符的数量超过40个,甚至比关键字的数量还要多。这些运算符有单目运算符、双目运算符以及三目运算符,又涉及到左结合和右结合的问题,真…

使用FreeRTOS进行性能和运行时分析

在MCU on Eclipse网站上看到Erich Styger在2月25日发的博文,一篇关于使用FreeRTOS进行性能和运行分析的文章,本人觉得很有启发,特将其翻译过来以备参考。当然限于个人水平,有描述不当之处恳请指正。原文网址:https://m…

生成微信公众号对应二维码的两种简单方法

方法1 在浏览器中打开下面的链接 https://open.weixin.qq.com/qr/code?usernameName 其中Name替换为对应公众号的微信号 例如,我们打算生成公众号 AI算法联盟 的二维码 只需首先关注这个公众号 在其详细信息中,查找到微信号信息:AIReport…

在Amazon FreeRTOS V10中使用运行时统计信息

在MCU on Eclipse网站上看到Erich Styger在8月2日发的博文,一篇关于在Amazon FreeRTOS V10中使用运行时统计信息的文章,本人觉得很有启发,特将其翻译过来以备参考。原文网址:https://mcuoneclipse.com/2018/08/02/tutorial-using-…

github无法加载图片的解决办法

最近发现我的github上面项目README里面的图片全裂了,一直以为是github最近服务器不稳定。今天通过简单的查询,发现原来这个问题可以解决,但是不能永久有效,之后还会用到,因此记录在这里, 也分享给大家。 解…

C语言学习及应用笔记之二:C语言static关键字及其使用

C语言有很多关键字,大多关键字使用起来是很明确的,但有一些关键字却要相对复杂一些。我们这里要说明的static关键字就是如此,它的功能很强大,相应的使用也就更复杂。 一般来说static关键字的常见用法有三种:一是用作局…

μCUnit,微控制器的单元测试框架

在MCU on Eclipse网站上看到Erich Styger在8月26日发布的博文,一篇关于微控制器单元测试的文章,有很高的参考价值,特将其翻译过来以备学习。原文网址:https://mcuoneclipse.com/2018/08/26/tutorial-%CE%BCcunit-a-unit-test-fram…

PID控制器开发笔记之十一:专家PID控制器的实现

前面我们讨论了经典的数字PID控制算法及其常见的改进与补偿算法,基本已经覆盖了无模型和简单模型PID控制经典算法的大部。再接下来的我们将讨论智能PID控制,智能PID控制不同于常规意义下的智能控制,是智能算法与PID控制算法的结合&#xff0c…

Modbus协议栈开发笔记之七:Modbus ASCII Slave开发

与Modbus RTU在串行链路上分为Slave和Master一样,Modbus ASCII也分为Slave和Master,这一节我们就来开发Slave。对于Modbus ASCII从站来说,需要实现的功能其实与Modbus RTU的Slave是一样的。其操作过程也是一样的。首先接收到主站的访问命令&a…

Modbus协议栈开发笔记之八:Modbus ASCII Master开发

这一节我们来封装Modbus ASCII Master应用,Modbus ASCII主站的开发与RTU主站的开发是一致的。同样的我们也不是做具体的应用,而是实现ASCII主站的基本功能。我们将ASCII主站的功能封装为函数,以便在开发具体应用时调用。 对于ASCII主站我们主…

在Eclipse中使用Git

本文原文出自MCU on Eclipse网站,作者为Erich Styger,原文网址:https://mcuoneclipse.com/2018/09/30/tutorial-git-with-eclipse/。本人翻译了此篇文章,有些短语难以找到准确表达的中文词语,所以保持了原文。限于个人…

C语言学习及应用笔记之三:C语言const关键字及其使用

在C语言程序中,const关键字也是经常会用到的一个关键字,那么使用const关键字的目的是什么呢?事实上,在程序中使用const关键字的主要目的就是为了向使用者传递设计者的一些意图。 事实上,无论我们是使用const关键字声明…

nlp cs224n 学习笔记1 Introduction and Word Vectors

注:个人笔记,价值有限,不建议逗留。 word embedding 的意义和目的? 通过一种映射,将自然语言中的单词,嵌入到n维欧式空间中,得到可以用数学语言表达并用计算机计算的“词向量”。 同时我们希望…

Modbus协议栈开发笔记之一:实现功能的基本设计

Modbus作为开放式的工业通讯协议,在各种工业设备中应用极其广泛。本人也使用Modbus通讯很多年了,或者用现成的,或者针对具体应用开发,一直以来都想要开发一个比较通用的协议栈能在后续的项目中复用,而不必每次都写一遍…

天池CV学习赛:街景字符识别-思路与上分技巧汇总

Datawhale 和 天池 合作的零基础入门CV - 街景字符编码识别比赛的正式赛已经结束。本文对一些比赛思路和上分技巧进行了汇总和整理,希望对大家深入学习CV能够有帮助。 本文分为以下几部分: 如何优化官方baseline的效果? 其它解题思路的整理…

Modbus协议栈开发笔记之二:Modbus消息帧的生成

前面我们已经对Modbus的基本事务作了说明,也据此设计了我们将要实现的主从站的操作流程。这其中与Modbus直接相关的就是Modbus消息帧的生成。Modbus消息帧也是实现Modbus通讯协议的根本。 1、Modbus消息帧分析 MODBUS协议在不同的物理链路上的消息帧有一些差异&am…

动手学CV-目标检测入门教程:基本概念

3.1 目标检测基本概念 本文来自开源组织 DataWhale 🐳 CV小组创作的目标检测入门教程。 对应开源项目 《动手学CV-Pytorch》 的第3章的内容,教程中涉及的代码也可以在项目中找到,后续会持续更新更多的优质内容,欢迎⭐️。 如果使…