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

问题描述:

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

具体来说,
线下测试直接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相关的问题困扰的人也不少

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

  1. 从Myeclipe转向Idea,各种遇坑与填坑经验,持续更新(图文)

    我相信超过90%的java开发人员在刚开始学习java的第一个ide都是eclipse,没错,我也是,并且在工作过后,公司用的是Myeclipse,操作习惯其实和eclipse无差.有些人由于某些原因 ...

  2. 关于使用NodeJS+Express搭建服务器访问静态资源的一些填坑经验

    前言 NodeJs是一个能让前端开发工程师变成全栈工程师的神器.最近在搞一个私活,需要上传图片到服务器存储.按照以前的想法,是用Java写代码搭服务器.奈何,大学毕业后就一直在搞前端和安卓开发.Jav ...

  3. 【STM32H743+腾讯云IOT联合开发入坑及出坑经验分享】

    近期学习STM32H743+腾讯物联网遇到的问题及解决办法分享 遇到的问题 近期在做一个三相电压测试的case,希望达到的功能是通过嵌入式单片机就地采样三相交流电压并LCD显示,然后通过4G模块连接到 ...

  4. “坑“在亚马逊,我懂了,亚马逊避坑经验分享

    新手卖家在亚马逊上容易掉进哪些坑呢?东哥最近和一些卖家朋友聊了聊,发现大家都或多或少踩过亚马逊的坑,导致店铺运营不顺利,效果不佳,赚不到钱.今天东哥就总结了一些亚马逊容易踩的坑,帮助新手卖家们避坑! ...

  5. spring jpa.踩坑经验分享

    原生sql@Query()有返回的时候 别的注解不要加 这一个就够用 @Query("select m from MerchantBaseUserInfo m where m.mcu_id ...

  6. (填坑:SQL打印两次)mybatisplus+p6spy 日志打印

    执行 SQL 分析打印 | MyBatis-Plus为简化开发而生https://mp.baomidou.com/guide/p6spy.html使用mybatisplus参考mybatisplus官 ...

  7. 0基础自学Python,有哪些避坑经验?

    回顾自己近 2 年的Python 自学经历,有一些学习心得和避坑经验分享给大家,让大家在学习 Python 的过程中少走一些弯路!减少遇到不必要的学习困难! 首先,最开始最大的困难应该就是对编程的抵触 ...

  8. 云计算架构师分享:容器云在金融企业的落地方案 | 周末送资料(原题:某保险公司容器云PaaS平台建设实践经验分享)

    [摘要]随着技术和社区的成熟,容器.Kubernetes.微服务等新事物不再只是概念,已在很多企业落地并发挥了生产力,对容器和PaaS的需求也从试探性转向规模化推广和纵深探索,建设企业级容器PaaS平 ...

  9. 传统行业转型微服务的挖坑与填坑

    原文:传统行业转型微服务的挖坑与填坑 一.微服务落地是一个复杂问题,牵扯到IT架构,应用架构,组织架构多个方面 在多家传统行业的企业走访和落地了微服务之后,发现落地微服务是一个非常复杂的问题,甚至都不 ...

最新文章

  1. java日期存入数据库_怎样在Java中将日期转化插入到数据库
  2. Modelsim初级使用教程
  3. 【SSM框架系列】Spring 的 AOP(面向切面编程)
  4. Jquery日历编写小练习
  5. 怎样学c++程序语言,如何学好 C++——学习门槛最高的编程语言
  6. IDEA新特性:提前知道代码怎么走!
  7. (1)win10 64位系统ISE14.7闪退问题(FPGA不积跬步101)
  8. jax-ws开发的webservice集成到web项目中
  9. 腾控Multiprog 使用问题 (持续更新)
  10. SVN源码服务器搭建-详细教程(我的收藏)
  11. 哈哈哈,看着问题一个个解决,很有满足感哦
  12. 专注创新勇突破 宏杉科技七策定纲存储之道
  13. Ubuntu20.04如何解决QQ闪退问题(亲测有效)
  14. jmail qq邮箱的服务器,asp jmail qq邮箱发送邮件方法
  15. UG塑胶模具设计培训,、胶模具成型工艺培训
  16. 小心了,京东订单详情会变?下单记得录屏
  17. 06-作业练习盒子模型
  18. 数据中心机房光纤综合布线
  19. 两年工作经验,离职了...
  20. 游戏攻略 Re:LieF ~親愛なるあなたへ~ (relief给挚爱的你)

热门文章

  1. 基于深度学习的磁环表面缺陷检测算法
  2. 基于图嵌入的兵棋联合作战态势实体知识表示学习方法
  3. JavaWeb:XML总结
  4. C++开发WPF,开发环境配置
  5. 问题杂记,不定时更新
  6. c#之using关键字
  7. Python之旅Day8 socket网络编程
  8. Redis学习笔记之Redis的对象
  9. 安卓学习第9课——计时器chronometer
  10. Eclipse下如何导入jar包