背景:pytorch版本:0.3.0,一個pytorch需要C++接口調用,同時也想試試ncnn模型是不快點,所以有 pytorch->onnx->ncnn.
1、pytorch2onnx
代碼:
import io
import torch
import torch.onnx
from torch.autograd import Variable
from models.ssd_new_mobilenet_FFA import build_ssd
import torchvision
# device = torch.device("cpu")
def test():
# model = SSD(300,2)
num_classes =2
# args.trained_model = 'weights/mobilenet_v1_1.0_224.pth'
net = build_ssd('test', 300, num_classes=num_classes)
pthfile = r'./weights/ssd_new_mobilenet_FFA.pth'
net.load_state_dict(torch.load(pthfile, map_location=lambda storage, loc: storage))
net.eval()
dummy_input = Variable(torch.randn(1, 3, 300, 300)).cpu()
model = net.cpu()
torch.onnx.export(model, dummy_input, "ssd_new_mobilenet_FFA.onnx")
if __name__ == "__main__":
test()
運行,出現問題:RuntimeError: Attempted to trace Detect, but tracing of legacy functions is not supported
查看網上答案,修改,網絡輸出,如下(注釋的是原始代碼,新的是修改的):
# output = self.detect(
# loc.view(loc.size(0), -1, 10), # loc preds
# self.softmax(conf.view(-1, self.num_classes)), # conf preds
# # self.priors.type(type(x.data)).cuda() # default boxes
# self.priors.type(type(x.data)).cpu()
# )
output = (
loc.view(loc.size(0), -1, 10),
conf.view(conf.size(0), -1, self.num_classes),
x
)
再次運行,出現問題:RuntimeError: ONNX export failed: Couldn't export operator hardtanh
跟進代碼查看,其實就是不支持Relu6,查看網上資料修改,可以通過Relu+clamp來實現Relu6修改
relu = ReLU(inplace=True)(input)
replace_relu6 = relu.clamp(max=6)
但還是不行,不支持clamp,於是我想了一下,就是一個激活函數(沒有權重參數),而且我最后要用的也不是onnx模型,而是ncnn,ncnn支持relu6就可以了。
於是在pytorch網絡結構模型中先用Relu代替了Relu6(因為我的網絡結構中激活函數只用了Relu6,所以用Relu代替不會混淆,如果本來就有Relu,那就要考慮其他激活函數代 替),
改了Relu6之后就可以成功轉成功了生成了轉換后的onnx模型。
然后,在onnx轉ncnn時,針對Relu轉的時候就轉Relu6.
2、onnx2ncnn
首先,直接用onnx2ncnn.exe試了下,不行,出現錯誤:ResizeNearest not supported yet! # height_scale=2 # width_scale=2剛好,我需要改Relu轉Relu6,所以直接備份ncnn- master,在ncnn-master目錄下新建build文件夾,然后通過cmake-gui創建新的工程(提前准備好protobuf相關文件和路徑)一起解決這兩個問題。打開工程:(1)修改Relu轉 ReLu6,查看caffe2ncnn.cpp,發現Relu6在ncnn中表示為Clip,所以修改Relu在ncnn中也表示為Clip,圖示如下:
同時,Relu6在ncnn中用Clip表示還需要參數,對照caffe2ncnn增添參數圖示如下:
至此,從pytorch2onnx中Relu6不支持問題解決。
(2)ResizeNearest不支持問題
首先,ncnn是支持最近鄰插值的,只不過在onnx轉ncnn時,只匹配Resize,Nearest作為一個屬性參數附帶在里面,所以匹配不到ResizeNearst。
如下圖所示,增添層名ResizeNearest,都轉為ncnn的插值層Interp
再添加Interp的屬性參數w_scale,h_scale,可以查看onnx網絡結構看到,圖示如下:
最后在屬性中添加命令參數運行onnx2ncnn工程,成功生成param和bin文件。