使用ONNX将模型转移至Caffe2和移动端
使用ONNX将模型转移至Caffe2和移动端
本文介绍如何使用 ONNX 将 PyTorch 中定义的模型转换为 ONNX 格式,然后将其加载到 Caffe2 中。一旦进入 Caffe2, 就可以运行模型来仔细检查它是否正确导出,然后展示了如何使用 Caffe2 功能(如移动导出器)在移动设备上执行模型。
需要安装onnx和Caffe2。 可以使用pip install onnx来获取 onnx。
注意:需要 PyTorch master 分支,可以按照这里说明进行安装。
1.引入模型
# 一些包的导入
import io
import numpy as np
from torch import nn
import torch.utils.model_zoo as model_zoo
import torch.onnx
1.1 SuperResolution模型
超分辨率是一种提高图像、视频分辨率的方法,广泛用于图像处理或视频剪辑。首先使用带有虚拟输入的小型超分辨率模型。
首先,让在 PyTorch 中创建一个SuperResolution模型。这个模型 直接来自 PyTorch 的例子,没有修改:
# PyTorch中定义的Super Resolution模型
import torch.nn as nn
import torch.nn.init as init
class SuperResolutionNet(nn.Module):
def __init__(self, upscale_factor, inplace=False):
super(SuperResolutionNet, self).__init__()
self.relu = nn.ReLU(inplace=inplace)
self.conv1 = nn.Conv2d(1, 64, (5, 5), (1, 1), (2, 2))
self.conv2 = nn.Conv2d(64, 64, (3, 3), (1, 1), (1, 1))
self.conv3 = nn.Conv2d(64, 32, (3, 3), (1, 1), (1, 1))
self.conv4 = nn.Conv2d(32, upscale_factor ** 2, (3, 3), (1, 1), (1, 1))
self.pixel_shuffle = nn.PixelShuffle(upscale_factor)
self._initialize_weights()
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.relu(self.conv2(x))
x = self.relu(self.conv3(x))
x = self.pixel_shuffle(self.conv4(x))
return x
def _initialize_weights(self):
init.orthogonal_(self.conv1.weight, init.calculate_gain('relu'))
init.orthogonal_(self.conv2.weight, init.calculate_gain('relu'))
init.orthogonal_(self.conv3.weight, init.calculate_gain('relu'))
init.orthogonal_(self.conv4.weight)
# 使用上面模型定义,创建super-resolution模型
torch_model = SuperResolutionNet(upscale_factor=3)
1.2 训练模型
通常,现在会训练这个模型; 但是,将下载一些预先训练的权重。请注意,此模型未经过充分训练来获得良好的准确性,此处 仅用于演示目的。
# 加载预先训练好的模型权重
del_url = 'https://s3.amazonaws.com/pytorch/test_data/export/superres_epoch100-44c6958e.pth'
batch_size = 1 # just a random number
# 使用预训练的权重初始化模型
map_location = lambda storage, loc: storage
if torch.cuda.is_available():
map_location = None
torch_model.load_state_dict(model_zoo.load_url(model_url, map_location=map_location))
# 将训练模式设置为falsesince we will only run the forward pass.
torch_model.train(False)
1.3 导出模型
在 PyTorch 中通过跟踪工作导出模型。要导出模型,调用torch.onnx._export()函数。这将执行模型,记录运算符用于计算输出的轨迹。 因为_export运行模型,需要提供输入张量x。这个张量的值并不重要; 它可以是图像或随机张量,只要它大小是正确的。
要了解有关PyTorch导出界面的更多详细信息,请查看torch.onnx documentation文档。
# 输入模型
x = torch.randn(batch_size, 1, 224, 224, requires_grad=True)
# 导出模型
torch_out = torch.onnx._export(torch_model, # model being run
x, # model input (or a tuple for multiple inputs)
"super_resolution.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
torch_out是执行模型后的输出。通常可以忽略此输出,但在这里将使用它来验证导出的模型在Caffe2中运行时是否计算出相同的值。
1.4 采用ONNX表示模型并在Caffe2中使用
现在让采用 ONNX 表示并在 Caffe2 中使用它。这部分通常可以在一个单独的进程中或在另一台机器上完成,但将在同一个进程中继续, 以便可以验证 Caffe2 和 PyTorch 是否为网络计算出相同的值:
import onnx
import caffe2.python.onnx.backend as onnx_caffe2_backend
#加载ONNX ModelProto对象。模型是一个标准的Python protobuf对象
model = onnx.load("super_resolution.onnx")
# 为执行模型准备caffe2后端,将ONNX模型转换为可以执行它的Caffe2 NetDef。
# 其他ONNX后端,如CNTK的后端即将推出。
prepared_backend = onnx_caffe2_backend.prepare(model)
# 在Caffe2中运行模型
# 构造从输入名称到Tensor数据的映射。
# 模型图形本身包含输入图像之后所有权重参数的输入。由于权重已经嵌入,只需要传递输入图像。
# 设置第一个输入。
W = {model.graph.input[0].name: x.data.numpy()}
# 运行Caffe2 net:
c2_out = prepared_backend.run(W)[0]
# 验证数字正确性,最多3位小数
np.testing.assert_almost_equal(torch_out.data.cpu().numpy(), c2_out, decimal=3)
print("Exported model has been executed on Caffe2 backend, and the result looks good!")
应该看到 PyTorch 和 Caffe2 的输出在数字上匹配最多3位小数。作为旁注,如果不匹配则存在 Caffe2 和 PyTorch 中的运算符以 不同方式实现的问题,请在这种情况下与联系。
2.使用ONNX转换SRResNET
使用与上述相同的过程,参考文章中提出的超分辨率转移了一个有趣的新模型“SRResNet” (感谢Twitter上的作者为本文的目的提供了代码和预训练参数)。可在此处 找到模型定义和预训练模型。下面是 SRResNet 模型的输入、输出。

3.在移动设备上运行模型
到目前为止,已经从 PyTorch 导出了一个模型,并展示了如何加载它并在 Caffe2 中运行它。现在模型已加载到 Caffe2 中,可以 将其转换为适合在移动设备上运行的格式。
将使用 Caffe2 的mobile_exporter 生成可在移动设备上运行的两个模型protobufs。第一个用于使用正确的权重初始化网络,第二个实际运行执行模型。在本文的其余部分, 将继续使用小型超分辨率模型。
# 从内部表示中提取工作空间和模型原型
c2_workspace = prepared_backend.workspace
c2_model = prepared_backend.predict_net
# 现在导入caffe2的`mobile_exporter`
from caffe2.python.predictor import mobile_exporter
# 调用Export来获取predict_net,init_net。 在移动设备上运行时需要这些网络
init_net, predict_net = mobile_exporter.Export(c2_workspace, c2_model, c2_model.external_input)
# 还将init_net和predict_net保存到稍后将用于在移动设备上运行的文件中
with open('init_net.pb', "wb") as fopen:
fopen.write(init_net.SerializeToString())
with open('predict_net.pb', "wb") as fopen:
fopen.write(predict_net.SerializeToString())
init_net具有模型参数和嵌入在其中的模型输入,predict_net将用于指导运行时的init_net执行。在本文中,将使用上面生成 的init_net和predict_net,并在正常的 Caffe2 后端和移动设备中运行,并验证两次运行中生成的输出高分辨率猫咪图像是否相同。
在本文中,将使用广泛使用的著名猫咪图像,如下所示:

# 一些必备的导入包
from caffe2.proto import caffe2_pb2
from caffe2.python import core, net_drawer, net_printer, visualize, workspace, utils
import numpy as np
import os
import subprocess
from PIL import Image
from matplotlib import pyplot
from skimage import io, transform
3.1 加载图像并预处理
首先,让加载图像,使用标准的skimage python库对其进行预处理。请注意,此预处理是处理用于训练/测试神经网络的数据的标准做法。
# 加载图像
img_in = io.imread("./_static/img/cat.jpg")
# 设置图片分辨率为 224x224
img = transform.resize(img_in, [224, 224])
# 保存好设置的图片作为模型的输入
io.imsave("./_static/img/cat_224x224.jpg", img)
3.2 在Caffe2运行并输出
拍摄调整大小的猫图像并在 Caffe2 后端运行超分辨率模型并保存输出图像。这里的图像处理步骤已经从 PyTorch 实 现的超分辨率模型中采用。
# 加载设置好的图片并更改为YCbCr的格式
img = Image.open("./_static/img/cat_224x224.jpg")
img_ycbcr = img.convert('YCbCr')
img_y, img_cb, img_cr = img_ycbcr.split()
# 让运行上面生成的移动网络,以便正确初始化caffe2工作区
workspace.RunNetOnce(init_net)
workspace.RunNetOnce(predict_net)
# Caffe2有一个很好的net_printer能够检查网络的外观
# 并确定的输入和输出blob名称是什么。
print(net_printer.to_string(predict_net))
从上面的输出中,可以看到输入名为“9”,输出名为“27”(将数字作为blob名称有点奇怪,但这是因为跟踪JIT为模型生成了编 号条目)。
# 现在,让传递调整大小的猫图像以供模型处理。
workspace.FeedBlob("9", np.array(img_y)[np.newaxis, np.newaxis, :, :].astype(np.float32))
# 运行predict_net以获取模型输出
workspace.RunNetOnce(predict_net)
# 现在让得到模型输出blob
img_out = workspace.FetchBlob("27")
现在,将在这里回顾PyTorch实现超分辨率模型的后处理步骤,以构建最终输出图像并保存图像。
img_out_y = Image.fromarray(np.uint8((img_out[0, 0]).clip(0, 255)), mode='L')
# 获取输出图像遵循PyTorch实现的后处理步骤
final_img = Image.merge(
"YCbCr", [
img_out_y,
img_cb.resize(img_out_y.size, Image.BICUBIC),
img_cr.resize(img_out_y.size, Image.BICUBIC),
]).convert("RGB")
# 保存图像,将其与移动设备的输出图像进行比较
final_img.save("./_static/img/cat_superres.jpg")
3.3 在移动端上执行模型
已经完成了在纯Caffe2后端运行的移动网络,在Android设备上执行该模型并获取模型输出。
注意:对于 Android 开发,需要adb shell,以下部分将无法运行。
在在移动设备上运行模型的第一步中,把基于移动设备的本机速度测试基准二进制文件推送到 adb 。这个二进制文件可以在移动设备 上执行模型,也可以导出稍后可以检索的模型输出。二进制文件可在此处 获得。要构建二进制文件,请按照此处的说明执行build_android.sh脚本。
注意:需要已经安装了ANDROID_NDK,并且设置环境变量ANDROID_NDK=path to ndk root。
# 让先把一堆东西推到adb,指定二进制的路径
CAFFE2_MOBILE_BINARY = ('caffe2/binaries/speed_benchmark')
# 已经在上面的步骤中保存了`init_net`和`proto_net`,现在使用。
# 推送二进制文件和模型protos
os.system('adb push ' + CAFFE2_MOBILE_BINARY + ' /data/local/tmp/')
os.system('adb push init_net.pb /data/local/tmp')
os.system('adb push predict_net.pb /data/local/tmp')
# 让将输入图像blob序列化为blob proto,然后将其发送到移动设备以供执行。
with open("input.blobproto", "wb") as fid:
fid.write(workspace.SerializeBlob("9"))
# 将输入图像blob推送到adb
os.system('adb push input.blobproto /data/local/tmp/')
# 现在在移动设备上运行网络,查看`speed_benchmark --help`,了解各种选项的含义
os.system(
'adb shell /data/local/tmp/speed_benchmark ' # binary to execute
'--init_net=/data/local/tmp/super_resolution_mobile_init.pb ' # mobile init_net
'--net=/data/local/tmp/super_resolution_mobile_predict.pb ' # mobile predict_net
'--input=9 ' # name of our input image blob
'--input_file=/data/local/tmp/input.blobproto ' # serialized input image
'--output_folder=/data/local/tmp ' # destination folder for saving mobile output
'--output=27,9 ' # output blobs we are interested in
'--iter=1 ' # number of net iterations to execute
'--caffe2_log_level=0 '
)
# 从adb获取模型输出并保存到文件
os.system('adb pull /data/local/tmp/27 ./output.blobproto')
# 可以使用与之前相同的步骤恢复输出内容并对模型进行后处理
blob_proto = caffe2_pb2.BlobProto()
blob_proto.ParseFromString(open('./output.blobproto').read())
img_out = utils.Caffe2TensorToNumpyArray(blob_proto.tensor)
img_out_y = Image.fromarray(np.uint8((img_out[0,0]).clip(0, 255)), mode='L')
final_img = Image.merge(
"YCbCr", [
img_out_y,
img_cb.resize(img_out_y.size, Image.BICUBIC),
img_cr.resize(img_out_y.size, Image.BICUBIC),
]).convert("RGB")
final_img.save("./_static/img/cat_superres_mobile.jpg")
现在,可以比较图像 cat_superres.jpg(来自纯caffe2后端执行的模型输出)和 cat_superres_mobile.jpg(来自移动执行的模型输出), 并看到两个图像看起来相同。如果看起来不一样,那么在移动设备上执行会出现问题,在这种情况下,请联系Caffe2社区。应该期望看

使用上述步骤,可以轻松地在移动设备上部署模型。 另外,有关caffe2移动后端的更多信息,请查看caffe2-android-demo。
使用ONNX将模型转移至Caffe2和移动端的更多相关文章
- 将 ExpressRoute 线路从经典部署模型转移到 Resource Manager 部署模型
本文概述将 Azure ExpressRoute 线路从经典部署模型转移到 Azure Resource Manager 部署模型的效果. Azure 当前使用两种部署模型:Resource Mana ...
- 手绘模型图带你认识Kafka服务端网络模型
摘要:Kafka中的网络模型就是基于主从Reactor多线程进行设计的. 本文分享自华为云社区<图解Kafka服务端网络模型>,作者:石臻臻的杂货铺 . Kafka中的网络模型就是基于主从 ...
- PyTorch专栏(八):微调基于torchvision 0.3的目标检测模型
专栏目录: 第一章:PyTorch之简介与下载 PyTorch简介 PyTorch环境搭建 第二章:PyTorch之60分钟入门 PyTorch入门 PyTorch自动微分 PyTorch神经网络 P ...
- PyTorch专栏(六): 混合前端的seq2seq模型部署
欢迎关注磐创博客资源汇总站: http://docs.panchuang.net/ 欢迎关注PyTorch官方中文教程站: http://pytorch.panchuang.net/ 专栏目录: 第一 ...
- PyTorch专栏(五):迁移学习
专栏目录: 第一章:PyTorch之简介与下载 PyTorch简介 PyTorch环境搭建 第二章:PyTorch之60分钟入门 PyTorch入门 PyTorch自动微分 PyTorch神经网络 P ...
- PyTorch专栏(二)
专栏目录: 第一章:PyTorch之简介与下载 PyTorch简介 PyTorch环境搭建 第二章:PyTorch之60min入门 PyTorch 入门 PyTorch 自动微分 PyTorch 神经 ...
- PyTorch专栏(一)
专栏目录: 第一章:PyTorch之简介与下载 PyTorch简介 PyTorch环境搭建 第二章:PyTorch之60min入门 PyTorch 入门 PyTorch 自动微分 PyTorch 神经 ...
- PyTorch专栏开篇
目前研究人员正在使用的深度学习框架不尽相同,有 TensorFlow .PyTorch.Keras等.这些深度学习框架被应用于计算机视觉.语音识别.自然语言处理与生物信息学等领域,并获取了极好的效果. ...
- 生产与学术之Pytorch模型导出为安卓Apk尝试记录
生产与学术 写于 2019-01-08 的旧文, 当时是针对一个比赛的探索. 觉得可能对其他人有用, 就放出来分享一下 生产与学术, 真实的对立... 这是我这两天对pytorch深度学习->a ...
随机推荐
- 软件篇-05-融合ORB_SLAM2和IMU闭环控制SLAM底盘运动轨迹
前面我们已经得到了当前底盘在世界坐标系中的位姿,这个位姿是通过融合ORB_SLAM2位姿和IMU积分得到的,在当前位姿已知的case下,给SLAM小车设置一个goal,我这里是通过上位机设置,然后 ...
- hdu4720 三角形的外接圆
题意: 给你四个点,问你第四个点是否在前三个点围成的三角形的外接圆上. 思路: 水题,就是练练用魔板罢了,当该三角形是锐角三角形的时候,圆心是任意两条边中垂线的交点,半径是圆心 ...
- hdu 5059 判断数字表示方式以及范围合法(int型之内)
题意: 给你一个串和两个整数a,b,问你这个串表示的数字是否合法,并且在a,b之间, 和法的要求是无论是正数还是负数都没有前导0,并且注意 -0 是不合法的. 思路: 写了将近 ...
- POJ 3228 二分最大流
题意: 给你N个位置,每个位置都有金矿数量和仓库数量,然后位置和位置之间的距离给了出来,最后问你吧所有的金矿都放到库里面走的路径 最长的最短 是多少? 思路: 比较简单的一个题, ...
- hdu3182 状态压缩dp
题意: 一个人做汉堡包,每个汉堡包有自己的花费和价值,某些汉堡包必须是在其他的某些汉堡包已经做好了的前提下才能制作,给你这个人的初始钱数,问最大的价值是多少. 思路: 比较简单 ...
- 6.PHP与JavaScript交互
PHP与JS交互 JS年闰年判断(body里直接引用JS) <form name="form1" method="post" action="& ...
- Windows核心编程 第十七章 -内存映射文件(下)
17.3 使用内存映射文件 若要使用内存映射文件,必须执行下列操作步骤: 1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件. 2) 创建一个文件映射内核对象,告诉系统该 ...
- WindowsPE 第五章 导出表
导出表 PE中的导出表存在于动态链接库文件里.导出表的主要作用是将PE中存在的函数导出到外部,以便其他人可以使用这些函数,实现代码重用. 5.1导出表的作用 代码重用机制提供了重用代码的动态链接库,它 ...
- 一、postman基础
- Git 系列教程(5)- 记录每次更新到仓库
文件状态 你工作目录下的每一个文件只有两种状态:tracked 或 untracked tracked 已跟踪 tracked 的文件是指那些被纳入了版本控制的文件 在上一次快照中有它们的记录,在工作 ...