十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問題一站解決
目前創(chuàng)新互聯(lián)公司已為近1000家的企業(yè)提供了網(wǎng)站建設(shè)、域名、雅安服務(wù)器托管、網(wǎng)站改版維護(hù)、企業(yè)網(wǎng)站設(shè)計(jì)、定陶網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。
作者:京東AI研究院 張建浩
煉丹師在轉(zhuǎn)換模型的時(shí)候,經(jīng)常會(huì)發(fā)現(xiàn)給轉(zhuǎn)換前后的模型輸入同樣的圖片,模型結(jié)果有微小的差別。其中的原因有數(shù)值算法的誤差、不同 jpeg 解碼庫(kù)產(chǎn)生的結(jié)果不同等等,也有不同框架內(nèi)部對(duì)某些算子的實(shí)現(xiàn)差異。
在給 ONNX 貢獻(xiàn) Resize 算子的 spec 的時(shí)候,我發(fā)現(xiàn) Resize 是一個(gè)突出體現(xiàn)了框架實(shí)現(xiàn)差異的算子——多種 Resize 類型、不統(tǒng)一的超參數(shù)、將錯(cuò)就錯(cuò)的歷史遺留 bug 和其它極易被忽略的問題集中在一起,導(dǎo)致幾乎每個(gè)框架的 Resize 操作的結(jié)果都有差異,而 ONNX 是一個(gè)神經(jīng)網(wǎng)絡(luò)模型的中間格式,它應(yīng)該盡量保留原始框架的算子的語(yǔ)義。經(jīng)過查看相關(guān)論文和各種框架的源代碼,我分析和總結(jié)了 Resize 操作眾多的實(shí)現(xiàn)方式。最終為 ONNX 貢獻(xiàn)了一個(gè)較為完善的、標(biāo)準(zhǔn)化的 Resize 算子的 spec,它包含多個(gè)(基本)正交的參數(shù),TensorFlow 1.x、TensorFlow 2.x、PyTorch、OpenCV 的 resize/interpolation 方法都可以用這個(gè)算子 100% 無(wú)損的表達(dá)。 本文將簡(jiǎn)單介紹各種 resize 操作的共同流程,并分析是哪些因素引起了不同框架 resize 操作的不同。
多維 tensor (例如二維圖像)的 resize 操作是用多個(gè)在一維 tensor 上進(jìn)行的 resize 操作組合出來的,所以我們只討論一維 tensor 上的 resize 操作,經(jīng)過分析各個(gè)框架的源代碼,我發(fā)現(xiàn)它的流程可以總結(jié)如下:
先討論w和f,w(i)是第 i 個(gè)像素點(diǎn)的坐標(biāo),乍一看, w(i)完全可以等于i本身,其實(shí)沒有這么簡(jiǎn)單。例如一個(gè)長(zhǎng)度為 3 的 tensor,如果第i個(gè)像素點(diǎn)的坐標(biāo)等于i本身,那么三個(gè)像素點(diǎn)在tensor 中的位置就如下圖中最左邊的樣子,橫線的長(zhǎng)度代表一維 tensor 的長(zhǎng)度,圓圈代表像素點(diǎn):
三個(gè)像素點(diǎn)沒有對(duì)稱地分布在 tensor 上,而是往左偏了。出于直覺,我們覺得這不是一件特別好的事情。在各種框架中,有兩種常見的方法來解決這個(gè)問題:
一個(gè)是選取w(i)=i+0.5,以一個(gè)長(zhǎng)度為 3 的一維 tensor 為例,它第 0 個(gè)像素點(diǎn)在 0.5 位置,第 1 個(gè)像素點(diǎn)在 1.5 位置,第 2 個(gè)像素點(diǎn)在 2.5 位置,這稱為 half_pixel,也就是上圖中中間的方法。這種方法中,
(這很符合直覺)。另一個(gè)是仍讓w(i)=i,但改變函數(shù)f,使
仍以長(zhǎng)度為 3 的一維 tensor 為例,這種方法相當(dāng)于在 resize 時(shí)砍掉了最右邊長(zhǎng)度為 1 的部分,使像素點(diǎn)的分布“被”對(duì)稱了。這稱為 align_corner,也就是上圖中最右邊的方法,在各種框架的 resize 方法的參數(shù)里常見的 align_corner=True/False 就是它了,它的名字來源于它可以讓 tensor 中第一個(gè)和最后一個(gè)像素(即 corner)在縮放后保持不變。
那如果我們不采用這兩種方法,一定要使用“直覺不好”的 asymmetric 方法,究竟會(huì)發(fā)生什么呢?TensorFlow 1.x 就給我們提供了這樣一個(gè)反面典型,它在 align_corner=False 時(shí)的實(shí)現(xiàn)是錯(cuò)的,原因就是使用了上圖中錯(cuò)誤的 asymmetric 方法,這會(huì)導(dǎo)致奇怪的縮放結(jié)果,這篇博客中???? https://hackernoon.com/how-tensorflows-tf-image-resize-stole-60-days-of-my-life-aba5eb093f35,
作者用 TensorFlow 1.x 訓(xùn)練的超分辨率神經(jīng)網(wǎng)絡(luò)總是出現(xiàn)奇怪的問題,最終他發(fā)現(xiàn)問題根源是 TensorFlow 錯(cuò)誤的 resize 實(shí)現(xiàn),他還給了一個(gè)形象的例子:把 16x16 的下圖左側(cè)圖像縮小到 4x4,本應(yīng)得到如下圖右側(cè)所示的圖像,而 TensorFlow 1.x 卻給出了下圖中間的奇怪結(jié)果,圖像的對(duì)稱性被完全破壞了,其中的原因就如上文所述。TensorFlow 1.x 的 resize 結(jié)果和其它框架不同的一大原因就是它錯(cuò)誤的 resize 實(shí)現(xiàn),好在 TensorFlow 2.x 已經(jīng)修復(fù)了這個(gè)問題。
接下來討論另外兩個(gè)函數(shù)g和h,nearest, linear, cubic 這三種常見的 resize 的不同方式,是在g和h上有所不同。如上文所述,函數(shù)
得到離
最近的像素點(diǎn),nearest 只需要找最近的一個(gè)像素點(diǎn),linear 要找最近的兩個(gè)(左右各一個(gè)),cubic 要找最近的四個(gè)(左右各兩個(gè));函數(shù)h(a,r)是計(jì)算這一個(gè)/兩個(gè)/四個(gè)像素點(diǎn)的加權(quán)平均值,其中權(quán)值是由r確定的(如上文所述,r是
距左側(cè)像素點(diǎn)的距離)。對(duì) nearest/linear/cubic 的每一種來說,如何從r得到各個(gè)像素點(diǎn)的權(quán)值都有各自標(biāo)準(zhǔn)的實(shí)現(xiàn),nearest resize 不必說,對(duì)于 linear resize,兩個(gè)像素點(diǎn)的權(quán)值是
。對(duì) cubic 來說,四個(gè)像素點(diǎn)的權(quán)值是
[1]其中A是一個(gè)固定的參數(shù),它的取值卻是每個(gè)框架不同,兩個(gè)常見的選擇是 -0.5 (TensorFlow 部分版本的實(shí)現(xiàn))和 -0.75(PyTorch)。因?yàn)锳沒有統(tǒng)一的標(biāo)準(zhǔn)取值,所以各個(gè)框架的 cubic resize 結(jié)果不同是常見的事情。
補(bǔ)充一句題外話:cubic resize 的權(quán)值計(jì)算起來比 linear resize 復(fù)雜的多,所以它的耗時(shí)肯定會(huì)長(zhǎng)一些,但產(chǎn)生的圖像性質(zhì)更好(這篇 paper ???? https://arxiv.org/abs/1812.0118 7 發(fā)現(xiàn)圖片預(yù)處理使用 cubic resize 可以提升分類網(wǎng)絡(luò)準(zhǔn)確率)。
還有一個(gè)會(huì)引起 cubic resize 結(jié)果差異的細(xì)節(jié)是,cubic resize 需要找到
的左右各兩個(gè)最相鄰的像素點(diǎn),但
左右兩側(cè)不一定能保證各有兩個(gè)像素點(diǎn)(假設(shè)某種情況下計(jì)算得到
,那么它左邊只有一個(gè)像素點(diǎn)),此時(shí)也有兩種現(xiàn)存的不同方法,一種是對(duì)圖像做 edge padding,即認(rèn)為仍從左邊找到了兩個(gè)像素點(diǎn),并且這兩個(gè)像素點(diǎn)的值都是第一個(gè)像素點(diǎn)的值;另一種是認(rèn)為找到了三個(gè)而不是四個(gè)像素點(diǎn),并對(duì)三個(gè)像素點(diǎn)的權(quán)值做歸一化。
總結(jié)一下,各個(gè)框架 Resize 操作的結(jié)果不同的原因是多種多樣的,例如 TensorFlow 用了自己發(fā)明的錯(cuò)誤實(shí)現(xiàn)、cubic resize 中參數(shù) A 沒有固定的取值、非整數(shù)的
是否自動(dòng)取整等等。
ONNX Resize 算子的 spec 就是基于上面的分析寫出來的,具體的描述在???? https://github.com/onnx/onnx/bl ob/master/docs/Operators.md#Resize ,
Python 版的參考實(shí)現(xiàn)在 ???? https://github.com/onnx/onnx/bl ob/master/onnx/backend/test/case/node/resize.py
其中比較核心的屬性 coordinate_transformation_mode 是把w、f和
復(fù)合得到的單個(gè)函數(shù)
,即
在這里沒有用獨(dú)立的函數(shù)w和f的原因除了看起來更簡(jiǎn)單之外,也有解決現(xiàn)實(shí)問題的考慮——有一些框架的某些 resize 實(shí)現(xiàn)沒有使用
的形式,而是直接讓
雖然這顯然是不合理的(coordinate_transformation_mode=tf_half_pixel_for_nn 就描述了這樣一個(gè)不合理的實(shí)現(xiàn)),但也只能承認(rèn)它們的存在。相比起來,上一個(gè)版本的 ONNX Resize 算子 spec 的制定者沒有意識(shí)到 Resize 算子的復(fù)雜性,完全模仿了 TensorFlow 的實(shí)現(xiàn),不僅和其它框架的結(jié)果不一致,而且連 TensorFlow 的 bug 也一并模仿了。
現(xiàn)在 TensorFlow、PyTorch 都支持了導(dǎo)出這一版本的 Resize 算子,TensorRT 等部署框架也支持導(dǎo)入和運(yùn)行這個(gè) Resize 算子。自己創(chuàng)造的東西能被眾多知名的框架跟進(jìn),我感到非常大的成就感。
參考: https://ieeexplore.ieee.org/doc ument/1163711
歡迎點(diǎn)擊“ 京東智聯(lián)云 ”了解更多精彩內(nèi)容!