寫於 2019-01-08 的舊文, 當時是針對一個比賽的探索. 以爲可能對其餘人有用, 就放出來分享一下html
生產與學術, 真實的對立...python
這是我這兩天對pytorch深度學習->android實際使用
的這個流程的一個切身感覺.android
說句實在的, 對於模型轉換的探索, 算是我這兩天最大的收穫了...git
所有濃縮在了這裏: https://github.com/lartpang/DHSNet-PyTorch/blob/master/converter.ipynbgithub
鑑於github加載ipynb太慢, 這裏可使用這個連接 https://nbviewer.jupyter.org/github/lartpang/DHSNet-PyTorch/blob/master/converter.ipynb數組
最近在研究將pytorch的模型轉換爲獨立的app, 網上尋找, 找到了一個流程: pytorch->onnx->caffe2->android apk. 主要是基於這篇文章的啓發: caffe2&pytorch之在移動端部署深度學習模型(全過程!).網絡
這兩天就在折騰這個工具鏈,爲了導出onnx的模型, 不肯定要基於怎樣的網絡, 是已經訓練好的, 仍是原始搭建網絡後再訓練來做爲基礎. 因此不斷地翻閱pytorch和onnx的官方示例, 想要研究出來點什麼, 但是, 都是本身手動搭建的模型. 並且使用的是預訓練權重, 不是這樣:架構
def squeezenet1_1(pretrained=False, **kwargs): r"""SqueezeNet 1.1 model from the `official SqueezeNet repo <https://github.com/DeepScale/SqueezeNet/tree/master/SqueezeNet_v1.1>`_. SqueezeNet 1.1 has 2.4x less computation and slightly fewer parameters than SqueezeNet 1.0, without sacrificing accuracy. Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ model = SqueezeNet(version=1.1, **kwargs) if pretrained: model.load_state_dict(model_zoo.load_url(model_urls['squeezenet1_1'])) return model # Get pretrained squeezenet model torch_model = squeezenet1_1(True) from torch.autograd import Variable batch_size = 1 # just a random number # Input to the model x = Variable(torch.randn(batch_size, 3, 224, 224), requires_grad=True) # Export the model torch_out = torch.onnx._export( torch_model, # model being run x, # model input (or a tuple for multiple inputs) "squeezenet.onnx", # where to save the model (can be a file or file-like object) export_params=True) # store the trained parameter weights inside the model file
就是這樣:app
# Create the super-resolution model by using the above model definition. torch_model = SuperResolutionNet(upscale_factor=3) # Load pretrained model weights model_url = 'https://s3.amazonaws.com/pytorch/test_data/export/superres_epoch100-44c6958e.pth' batch_size = 1 # just a random number # Initialize model with the pretrained weights torch_model.load_state_dict(model_zoo.load_url(model_url)) # set the train mode to false since we will only run the forward pass. torch_model.train(False)
兩種都在載入預訓練權重, 直接加載到搭建好的網絡上. 對於我手頭有的已經訓練好的模型, 彷佛並不符合這樣的條件.less
最後採用儘量模仿上面的例子代碼的策略, 將整個網絡完整的導出(torch.save(model)
), 而後再仿照上面那樣, 將完整的網絡加載(torch.load()
)到轉換的代碼中, 照貓畫虎, 以進一步處理.
這裏也很大程度上受到這裏的啓發: https://github.com/akirasosa/mobile-semantic-segmentation
原本想嘗試使用以前找到的不論效果仍是性能都很強的R3Net進行轉換, 但是, 出於做者搭建網絡使用的特殊手段, 加上pickle和onnx的限制, 這個嘗試沒有奏效, 只好轉回頭使用以前學習的DHS-Net的代碼, 由於它的實現是基於VGG的, 裏面的搭建的網絡也是須要修改來符合onnx的要求, 主要是更改上採樣操做爲轉置卷積(也就是分數步長卷積, 這裏順帶溫習了下pytorch裏的nn.ConvTranspose2d()
的計算方式), 由於pytorch的上採樣在onnx轉換過程當中有不少的問題, 特別麻煩, 外加上修改最大池化的一個參數(nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=False)
的參數ceil_mode
改成ceil_mode=False
, 這裏參考自前面的知乎專欄的那篇文章), 這樣終於能夠轉換了, 爲了方便和快速的測試, 我只是訓練了一個epoch, 就直接導出模型, 此次終於能夠順利的torch.save()
了.
filename_opti = ('%s/model-best.pth' % check_root_model) torch.save(model, filename_opti)
以後便利用相似的代碼進行了書寫.
IMG_SIZE = 224 TMP_ONNX = 'cache/onnx/DHSNet.onnx' MODEL_PATH = 'cache/opti/total-opti-current.pth' # Convert to ONNX once model = torch.load(MODEL_PATH).cuda() model.train(False) x = Variable(torch.randn(1, 3, 224, 224), requires_grad=True).cuda() torch_out = torch.onnx._export(model, x, TMP_ONNX, export_params=True)
載入模型後, 即可以開始轉換了, 這裏須要安裝caffe2, 官方推薦直接conda安裝pytorch1每夜版便可, 會自動安裝好依賴.
提及來這個conda, 就讓我又愛又恨, 用它裝pytorch從這裏能夠看出來, 確實不錯, 對系統自身的環境沒有太多的破壞, 但是用它裝tensorflow-gpu的時候, 倒是要自動把conda源裏的cuda, cudnn工具包都給帶上, 有時候彷佛會破壞掉系統自身裝載的cuda環境(? 不太確定, 反正如今我不這樣裝, 直接上pip裝, 乾淨又快速).
以後的代碼中, 主要的問題也就是tensor的cpu/cuda, 或者numpy的轉換的問題了. 多嘗試一下, 輸出下類型就能夠看到了.
# Let's also save the init_net and predict_net to a file that we will later use for running them on mobile with open('./cache/model_mobile/init_net.pb', "wb") as fopen: fopen.write(init_net.SerializeToString()) with open('./cache/model_mobile/predict_net.pb', "wb") as fopen: fopen.write(predict_net.SerializeToString())
這裏記錄下, 查看pytorch的tensor的形狀使用tensor.size()
方法, 查看numpy數組的形狀則使用numpy數組的adarray.shape
方法, 而對於PIL(from PIL import Image
)讀取的Image對象而言, 使用Image.size
查看, 並且, 這裏只會顯示寬和高的長度, 並且Image的對象, 是三維, 在於pytorch的tensor轉換的時候, 或者輸入網絡的時候, 要注意添加維度, 並且要調整通道位置(img = img.transpose(2, 0, 1)
).
因爲網絡保存的部分中, 只涉及到了網絡的結構內的部分, 對於數據的預處理的部分並不涉及, 因此說要想真正的利用網絡, 還得調整真實的輸入, 來做爲更適合網絡的數據輸入.
要注意, 這裏針對導出的模型的相關測試, 程其實是按照測試網絡的流程來的.
# load the resized image and convert it to Ybr format mean = np.array([0.485, 0.456, 0.406]) std = np.array([0.229, 0.224, 0.225]) img = Image.open("./data/ILSVRC2012_test_00000004_224x224.jpg") img = np.array(img) img = img.astype(np.float64) / 255 img -= mean img /= std img = img.transpose(2, 0, 1)
首先安卓環境的配置就折騰了很久, 一堆破事, 真實的生產開發, 真心不易啊...
這裏最終仍是失敗了, 由於對於安卓的代碼是在是不熟悉, 最起碼的基礎認知都不足, 只有這先前學習Java的一點皮毛知識, 根本不足以二次開發. 也就跑了跑幾個完整的demo而已.
這個跑通了, 可是這是個分類網絡的例子, 對於咱們要作的分割的任務而言, 有不少細節不同.
這個例子咱們參考了一下, 只是由於它的任務是對攝像頭視頻流數據風格遷移, 並且會直接回顯到手機屏幕上, 這裏咱們主要是想初步實現對於咱們網絡模型安卓遷移的測試, 在第一個例子的基礎上可否實現初步的攝像頭視頻流的分割, 而後下一步再進一步知足比賽要求.
但是, 嘗試失敗了. 雖然AS打包成了APK, 手機也安裝上了, 但是莫名的, 在"loading..."中便閃退了...
這個例子很給力, 可是使用的是tensorflowlite, 雖然能夠用, 可以實現下面的效果, 但是, 不會改.
並且是量化網絡, 準確率仍是有待提高.
最後仍是要思考一下的, 作個總結.
吃就吃在沒經驗的虧上了, 都是初次接觸, 以前沒怎麼接觸過安卓, 主要是安卓的開發對於電腦的配置要求過高了, 本身的筆記本根本不夠玩的. 也就沒有接觸過了.
外加上以前的研究學習, 主要是在學術的環境下搞得, 和實際的生產還有很大的距離, 科研與生產的分離, 這對於深度學習這一實際上更偏重實踐的領域來講, 有些時候是尤其致命的. 關鍵時刻下不去手, 這多麼無奈, 科學技術沒法轉化爲實實在在的生產力, 突然有些如夢通常的縹緲.
固然, 最關鍵的仍是, 沒有仔細分析賽方的需求, 沒有徹底思考清楚, 直接就開幹了, 這個魯莽的毛病, 仍是沒有改掉, 浪費時間不說, 也無助於實際的進度. 賽方的說明含糊, 應該問清楚.
如果擔憂時間, 那更應該看清楚要求, 切莫隨意下手. 比賽說明裏只是說要提交一個打包好的應用, 把環境, 依賴什麼都處理好, 可是不必定是安卓apk呀, 能夠有不少的形式, 可是這也只是最後的一點額外的輔助而已, 重點是模型的性能和效率呢.
莫忘初心, 方得始終. 爲何我想到的是這句.
基本上就定了仍是使用R3Net, 只能是進一步的細節修改了, 換換後面的循環結構了, 改改鏈接什麼的.
我準備再開始看論文, 學姐的論文能夠看看, 彷佛提出了一種很不錯的後處理的方法, 效果提高很明顯, 須要研究下.
pytorch的torch.save(model)
保存模型的時候, 模型架構的代碼裏不能使用一些特殊的構建形式, R3Net的ResNeXt結構就用了, 主要是一些lambda結構, 雖然不是太清楚, 可是通常的搭建手段都是能夠的.
onnx對於pytorch的支持的操做, 在個人轉化中, 主要是最大池化和上採樣的問題, 前者能夠修改ceil_mode
爲False
, 後者則建議修改成轉置卷積, 避免沒必要要的麻煩. 可見"導出總體模型"小節的描述.
這裏主要是用release版本構建的apk.
未簽名的apk在個人mi 8se (android 8.1)上不能安裝, 會解析失敗, 須要簽名, AS的簽名的生成也很簡單, 和生成apk在同一級上, 有生成的選項.