写于 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在同一级上, 有生成的选项.