小心使用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相關。
總結
以上是生活随笔為你收集整理的小心使用tf.image.resize_images,填坑经验分享给你的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 外设驱动库开发笔记13:MLX90614
- 下一篇: C10K 非阻塞 Web 服务器