# 何かと話題のonnx, pytorch, caffe2を使ってみる

onnx, pytorch, caffe2を使った機械学習(jupyter notebookにcodeをcopy & paste)

スポンサーリンク

## squeezenetの携帯端末への転送法¶

# Some standard imports
import io
import numpy as np
import torch.onnx


import math
import torch
import torch.nn as nn
import torch.nn.init as init
import torch.utils.model_zoo as model_zoo

__all__ = ['SqueezeNet', 'squeezenet1_0', 'squeezenet1_1']

model_urls = {
}

class Fire(nn.Module):

def __init__(self, inplanes, squeeze_planes,
expand1x1_planes, expand3x3_planes):
super(Fire, self).__init__()
self.inplanes = inplanes
self.squeeze = nn.Conv2d(inplanes, squeeze_planes, kernel_size=1)
self.squeeze_activation = nn.ReLU(inplace=True)
self.expand1x1 = nn.Conv2d(squeeze_planes, expand1x1_planes,
kernel_size=1)
self.expand1x1_activation = nn.ReLU(inplace=True)
self.expand3x3 = nn.Conv2d(squeeze_planes, expand3x3_planes,
self.expand3x3_activation = nn.ReLU(inplace=True)

def forward(self, x):
x = self.squeeze_activation(self.squeeze(x))
self.expand1x1_activation(self.expand1x1(x)),
self.expand3x3_activation(self.expand3x3(x))
], 1)

class SqueezeNet(nn.Module):

def __init__(self, version=1.0, num_classes=1000):
super(SqueezeNet, self).__init__()
if version not in [1.0, 1.1]:
raise ValueError("Unsupported SqueezeNet version {version}:"
"1.0 or 1.1 expected".format(version=version))
self.num_classes = num_classes
if version == 1.0:
self.features = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=7, stride=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=False),
Fire(96, 16, 64, 64),
Fire(128, 16, 64, 64),
Fire(128, 32, 128, 128),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=False),
Fire(256, 32, 128, 128),
Fire(256, 48, 192, 192),
Fire(384, 48, 192, 192),
Fire(384, 64, 256, 256),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=False),
Fire(512, 64, 256, 256),
)
else:
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=False),
Fire(64, 16, 64, 64),
Fire(128, 16, 64, 64),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=False),
Fire(128, 32, 128, 128),
Fire(256, 32, 128, 128),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=False),
Fire(256, 48, 192, 192),
Fire(384, 48, 192, 192),
Fire(384, 64, 256, 256),
Fire(512, 64, 256, 256),
)
# Final convolution is initialized differently form the rest
final_conv = nn.Conv2d(512, self.num_classes, kernel_size=1)
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
final_conv,
nn.ReLU(inplace=True),
nn.AvgPool2d(13)
)

for m in self.modules():
if isinstance(m, nn.Conv2d):
if m is final_conv:
init.normal(m.weight.data, mean=0.0, std=0.01)
else:
init.kaiming_uniform(m.weight.data)
if m.bias is not None:
m.bias.data.zero_()

def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x.view(x.size(0), self.num_classes)

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:
return model


# Get pretrained squeezenet model
torch_model = squeezenet1_1(True)

/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/ipykernel_launcher.py:94: UserWarning: nn.init.kaiming_uniform is now deprecated in favor of nn.init.kaiming_uniform_.
/root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/ipykernel_launcher.py:92: UserWarning: nn.init.normal is now deprecated in favor of nn.init.normal_.
100.0%


そしてpytorch modelをonnx modelとしてexportする。

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

import onnx
import caffe2.python.onnx.backend
from onnx import helper

# Load the ONNX GraphProto object. Graph is a standard Python protobuf object

# prepare the caffe2 backend for executing the model this converts the ONNX graph into a
# Caffe2 NetDef that can execute it. Other ONNX backends, like one for CNTK will be
# availiable soon.
prepared_backend = caffe2.python.onnx.backend.prepare(model)

# run the model in Caffe2

# Construct a map from input names to Tensor data.
# The graph itself contains inputs for all weight parameters, followed by the input image.
# Since the weights are already embedded, we just need to pass the input image.
# last input the grap
W = {model.graph.input[0].name: x.data.numpy()}

# Run the Caffe2 net:
c2_out = prepared_backend.run(W)[0]

# Verify the numerical correctness upto 3 decimal places
np.testing.assert_almost_equal(torch_out.data.cpu().numpy(), c2_out, decimal=3)


この後、exportしたモデルを走らせて、pytorch上で走らせたモデルの結果がonnx-caffe2 backend上で走らせた結果とマッチするかを確認する準備が可能になる。

import onnx
import caffe2.python.onnx.backend
from onnx import helper

# Load the ONNX GraphProto object. Graph is a standard Python protobuf object

# prepare the caffe2 backend for executing the model this converts the ONNX graph into a
# Caffe2 NetDef that can execute it. Other ONNX backends, like one for CNTK will be
# availiable soon.
prepared_backend = caffe2.python.onnx.backend.prepare(model)

# run the model in Caffe2

# Construct a map from input names to Tensor data.
# The graph itself contains inputs for all weight parameters, followed by the input image.
# Since the weights are already embedded, we just need to pass the input image.
# last input the grap
W = {model.graph.input[0].name: x.data.numpy()}

# Run the Caffe2 net:
c2_out = prepared_backend.run(W)[0]

# Verify the numerical correctness upto 3 decimal places
np.testing.assert_almost_equal(torch_out.data.cpu().numpy(), c2_out, decimal=3)


この後、caffe2のクロスプラットフォーム機能を活用することで、モバイル端末で実行するためにモデルを転送できる。

# Export to mobile
from caffe2.python.onnx.backend import Caffe2Backend as c2

init_net, predict_net = c2.onnx_graph_to_caffe2_net(model.graph)
with open("squeeze_init_net.pb", "wb") as f:
f.write(init_net.SerializeToString())
with open("squeeze_predict_net.pb", "wb") as f:
f.write(predict_net.SerializeToString())

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-16-f1060b33f068> in <module>()
2 from caffe2.python.onnx.backend import Caffe2Backend as c2
3
----> 4 init_net, predict_net = c2.onnx_graph_to_caffe2_net(model.graph)
5 with open("squeeze_init_net.pb", "wb") as f:
6     f.write(init_net.SerializeToString())

~/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/caffe2/python/onnx/backend.py in onnx_graph_to_caffe2_net(cls, model, device, opset_version)
960     @classmethod
961     def onnx_graph_to_caffe2_net(cls, model, device="CPU", opset_version=_known_opset_version):
--> 962         return cls._onnx_model_to_caffe2_net(model, device=device, opset_version=opset_version, include_initializers=True)
963
964     @classmethod

~/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/caffe2/python/onnx/backend.py in _onnx_model_to_caffe2_net(cls, onnx_model, device, opset_version, include_initializers)
919         device_option = get_device_option(Device(device))
920
--> 921         init_model = cls.optimize_onnx(onnx_model, init=True)
922         pred_model = cls.optimize_onnx(onnx_model, predict=True)
923

~/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/caffe2/python/onnx/backend.py in optimize_onnx(input, init, predict)
662         if predict:
663             passes.append('split_predict')
--> 664         out = onnx.optimizer.optimize(input, passes)
665         return out
666

~/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/onnx-1.2.1-py3.6-linux-x86_64.egg/onnx/optimizer.py in optimize(model, passes)
42                   'fuse_transpose_into_gemm']
43     if not isinstance(model, ModelProto):
---> 44         raise ValueError('Optimizer only accepts ModelProto, incorrect type: {}'.format(type(model)))
45
46     model_str = model.SerializeToString()

ValueError: Optimizer only accepts ModelProto, incorrect type: <class 'onnx_pb2.GraphProto'>

どうやらAPIの仕様が変わったらしく、pytorchサイト経由で新たにcaffe2をソースからビルドする必要があるらしい。

スポンサーリンク

## 仕様がコロコロ変わり過ぎる¶

caffe2は仕様がコロコロ変わるので、新しい仕様に追い付くのが大変だ。そのことをネット上で嘆いている人たちも結構いたりする。結局はsourceからcaffe2とonnxをビルドするしかなさそうだし、tutorialも常に最新のものを使うしかなさそうだ。

スポンサーリンク
スポンサーリンク

フォローする