PyTorch轉ONNX之F.interpolate
文章目錄
一、環境說明
- Conda 4.7.11
- Python 3.6.9
- PyTorch 1.4.0
- ONNX 1.6.0
- protobuf 3.9.2
二、ONNX安裝問題
如果使用conda install onnx
或者conda install -c conda-forge onnx
,大有可能會在import onnx
時,出現ImportError: libprotobuf.so.20: cannot open shared object file: No such file or directory
,這是因爲在現在的python
版本下,conda
默認安裝的protobuf
版本較低。可參考這條issue#2434,按下列命令進行安裝:
conda install protobuf=3.9
conda install -c conda-forge onnx
三、F.interpolate
PyTorch轉ONNX目前遇到的最難受的地方,是對F.interpolate
的差強人意的支持,在模型裏面一旦有使用F.interpolate
的上採樣方法,就會出問題。問題如下:
1. ONNX的op版本opset_version
在轉換過程中,我們一般會使用命令torch.onnx.export(model, input, "onnx_name.onnx")
。那麼默認採用的opset_version=9
,當切換爲opset_version=10
、opset_version=11
後,用Netro可視化下進行對比,對比如下。
可以看出,對於同一個節點(node),當F.interpolate(mode='bilinear', align_corners=False)
時,op9
會將F.interpolate
替換爲onnx.Upsample
,op10
會將其替換爲onnx.Resize
,而op11
會提供一個onnx.Constant
,裏面是一個tensor,而且onnx.Resize
內部會出現其它屬性。
那麼這三者有什麼具體區別呢。我可能提供不了準確的區別,下面是我的看法。
對於op9
與op10
,應該是比較近似的,除了方法從onnx.Upsample
變成了onnx.Resize
,因此需要看看ONNX
的源碼,兩者有什麼區別,另外,注意INPUT的scales,它們都使用了同樣的scales,這個scales,是一個onnx.Constant
的node,在可視化中是看不到的,它的格式是float32
,這就是op9
、op10
與op11
的重要區別,也是後續坑的來源。接着,對於op11
而言,它使用了onnx.Constant
作爲一個node,而且在點開看onnx.Resize
後,可以看見出現了coordinate_transformation_mode
與cubic_coeff_a
、nearest_mode
屬性,這是op11
完全支持F.interpolate
所產生的屬性,coordinate_transformation_mode
是對應align_corners
,cubic_coeff_a
對應mode=bicubic
,nearest_mode
是對應mode=nearest
,而查看INPUTS欄,它的sizes內容的格式是int64
,op9
/op10
的float32
與op11
的int64
的不同,造成了坑點,接下來是說明這裏的問題。
2. 插值方法與op版本
首先,在op9
/op10
下,F.interpolate(mode=nearest)
是沒問題的,也不會出現什麼警告,當對於F.interpolate(mode=bilinear, align_corners=False)
時,能轉換成功,但會出現如下警告,爲什麼會出現這個警告,我感覺是與下面的計算輸出大小
的問題有關,不知道大家對此有什麼看法,麻煩大家賜教。
You are trying to export the model with onnx:Upsample for ONNX opset version 9.
UserWarning: You are trying to export the model with onnx:Resize for ONNX opset version 10.
This operator might cause results to not match the expected results by PyTorch.
ONNX's Upsample/Resize operator did not match Pytorch's Interpolation until opset 11. Attributes to determine how to transform the input were added in onnx:Resize in opset 11 to support Pytorch's behavior (like coordinate_transformation_mode and nearest_mode).
We recommend using opset 11 and above for models using this operator.
接着,當F.interpolate(mode=bilinear, align_corners=True)
時,轉換就會失敗,報錯如下:
UserWarning: ONNX export failed on upsample_bilinear2d because align_corners == True not supported
而在op10
下,上訴警告、報錯就不會出現,那麼總結一下,對於ONNX1.6而言,目前支持如下操作:
F.interpolate | nearest | bilinear, align_corners=False | bilinear, align_corners=True | bicubic |
---|---|---|---|---|
op-9 | Y | Y | N | N |
op-10 | Y | Y | N | N |
op-11 | Y | Y | Y | Y |
那麼我們需要知道,ONNX是怎麼確認上採樣後輸出的大小的呢,之前提到,op9
/op10
的scales的格式爲float32
,op11
的sizes的格式爲int64
,爲什麼一直在提scales與sizes呢,因爲它們與計算輸出大小
有緊密聯繫。
例如,大小爲input_size=[1, 3, 5, 5]
的tensor作爲輸入,我們希望將tensor插值到output_size=[1, 3, 9, 9]
。對於op9
/op10
而言,INPUTS中的X爲輸入tensor,scales爲input_size * scales = output_size
,這就是scales的作用,因爲scales的格式爲float32
,因此這個output_size
竟然就是float32
的,而scales=[1., 1., 1.799, 1.799]
,所以得到的這個output_size=[1, 3, 8.999, 8.999]
,所以預計的output_size
與ONNX計算出來的output_size
在精度上就會出問題,導致前後不相等,這樣的結果很神奇吧。對於op11
而言,它提供了額外的onnx.Constant
的node,INPUTS的sizes直接就是output_size
,而sizes的格式爲int64
,所以op11
的output_size
與ONNX計算出來的output_size
一致。我猜這就是ONNX's Upsample/Resize operator did not match Pytorch's Interpolation until opset 11.
警告的來源之一。
好了,今天就寫到這麼多,接下來會開openvion的新坑,也就是pytorch->onnx->openvino這個過程。