【PyTorch】生產與學術之Pytorch模型導出爲安卓Apk嘗試記錄

生產與學術

寫於 2019-01-08 的舊文, 當時是針對一個比賽的探索. 以爲可能對其餘人有用, 就放出來分享一下html

upsplash

生產與學術, 真實的對立...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的模型, 不肯定要基於怎樣的網絡, 是已經訓練好的, 仍是原始搭建網絡後再訓練來做爲基礎. 因此不斷地翻閱pytorchonnx的官方示例, 想要研究出來點什麼, 但是, 都是本身手動搭建的模型. 並且使用的是預訓練權重, 不是這樣:架構

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模型轉換

載入模型後, 即可以開始轉換了, 這裏須要安裝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而已.

AiCamera

這個跑通了, 可是這是個分類網絡的例子, 對於咱們要作的分割的任務而言, 有不少細節不同.

  • 輸入有差別: 比賽要求的是如果提交apk, 那麼要求能夠從相冊讀取圖片, 而例子是從攝像頭讀取的視頻數據流. 雖然也處理的是視頻幀, 可是要咱們再次補充的內容又多了起來, 仍是那句話, android一竅不通.
  • 輸出有差別: 自我猜想, 比賽爲了測評, 輸出必然也要輸出到相冊裏, 否則何來測評一說?

AICamera-Style-Transfer

這個例子咱們參考了一下, 只是由於它的任務是對攝像頭視頻流數據風格遷移, 並且會直接回顯到手機屏幕上, 這裏咱們主要是想初步實現對於咱們網絡模型安卓遷移的測試, 在第一個例子的基礎上可否實現初步的攝像頭視頻流的分割, 而後下一步再進一步知足比賽要求.

但是, 嘗試失敗了. 雖然AS打包成了APK, 手機也安裝上了, 但是莫名的, 在"loading..."中便閃退了...

JejuNet

這個例子很給力, 可是使用的是tensorflowlite, 雖然能夠用, 可以實現下面的效果, 但是, 不會改.

img

並且是量化網絡, 準確率仍是有待提高.

最後的思考

最後仍是要思考一下的, 作個總結.

沒經驗

吃就吃在沒經驗的虧上了, 都是初次接觸, 以前沒怎麼接觸過安卓, 主要是安卓的開發對於電腦的配置要求過高了, 本身的筆記本根本不夠玩的. 也就沒有接觸過了.

外加上以前的研究學習, 主要是在學術的環境下搞得, 和實際的生產還有很大的距離, 科研與生產的分離, 這對於深度學習這一實際上更偏重實踐的領域來講, 有些時候是尤其致命的. 關鍵時刻下不去手, 這多麼無奈, 科學技術沒法轉化爲實實在在的生產力, 突然有些如夢通常的縹緲.

固然, 最關鍵的仍是, 沒有仔細分析賽方的需求, 沒有徹底思考清楚, 直接就開幹了, 這個魯莽的毛病, 仍是沒有改掉, 浪費時間不說, 也無助於實際的進度. 賽方的說明含糊, 應該問清楚.

如果擔憂時間, 那更應該看清楚要求, 切莫隨意下手. 比賽說明裏只是說要提交一個打包好的應用, 把環境, 依賴什麼都處理好, 可是不必定是安卓apk呀, 能夠有不少的形式, 可是這也只是最後的一點額外的輔助而已, 重點是模型的性能和效率呢.

莫忘初心, 方得始終. 爲何我想到的是這句.

下一步

基本上就定了仍是使用R3Net, 只能是進一步的細節修改了, 換換後面的循環結構了, 改改鏈接什麼的.

我準備再開始看論文, 學姐的論文能夠看看, 彷佛提出了一種很不錯的後處理的方法, 效果提高很明顯, 須要研究下.

pickle和onnx的限制

pytorch的torch.save(model)保存模型的時候, 模型架構的代碼裏不能使用一些特殊的構建形式, R3Net的ResNeXt結構就用了, 主要是一些lambda結構, 雖然不是太清楚, 可是通常的搭建手段都是能夠的.

onnx對於pytorch的支持的操做, 在個人轉化中, 主要是最大池化和上採樣的問題, 前者能夠修改ceil_modeFalse, 後者則建議修改成轉置卷積, 避免沒必要要的麻煩. 可見"導出總體模型"小節的描述.

打包apk安裝

這裏主要是用release版本構建的apk.

未簽名的apk在個人mi 8se (android 8.1)上不能安裝, 會解析失敗, 須要簽名, AS的簽名的生成也很簡單, 和生成apk在同一級上, 有生成的選項.

相關文章
相關標籤/搜索