深度学习火了,一同热的还有深度学习框架。当然经过了几年的淘汰(比如theano),目前最火的大概有Tensorflow、PyTorch以及Caffe了。他们各有所长,开发时可能会选定一个单独的平台,但是在应用时就需要考虑其他的因素。重新在其他框架上改代码就变得比较麻烦,如果能把模型直接转换好,更重要的是不同平台效果相同,那就会节省很多时间。MMdnn就是这样的一个模型转换工具,而且是微软出品,质量有保障。
下图是它转换的过程,左边是输入的模型框架,会被统一转换成IR文件,然后再输出成目标框架模型。
pip install mmdnn
安装实际上很简单,但有一点注意的是它所支持的各平台版本,在提供的环境文件中(MMdnn/requirements/linux-py3.txt
):
six==1.10.0
numpy==1.15.4
protobuf==3.6.1
pillow == 5.2.0
setuptools==39.1.0
tensorflow==1.13.1
keras==2.2.4
coremltools==2.1.0
mxnet==1.2.0
cntk==2.6
http://download.pytorch.org/whl/cpu/torch-0.4.0-cp35-cp35m-linux_x86_64.whl
https://raw.githubusercontent.com/PaulaQin/pycaffe_packages/master/caffe-1.0-py3-none-any.whl
torchvision==0.2.1
onnx==1.4.1
onnx-tf==1.2.1
可以看到,很多都不是最新框架的版本,所以对于很新的模型可能还会有一些问题。
我的目标是将PyTorch模型转换为Caffe和Tensorflow模型。首先注意的一点是原始模型是用torch.save(model, filename)
直接保存的,而不是只保存了'state_dict'
。否则可能会出现:"AttributeError: 'collections.OrderedDict' object has no attribute 'state_dict'"
这样的错误。
我们使用官方提供的resnet18模型,通过mmdownload
可以直接下载:
mmdownload -f pytorch -n resnet101 -o ./
然后进行转换:
mmconvert -sf pytorch -iw imagenet_resnet18.pth -df caffe -om caffe_resnet18_official --inputShape 3,224,224
这里注意的是要输入--inputShape
。
然后会输出caffe_resnet18_official.caffemodel
参数文件和caffe_resnet18_official.prototxt
模型文件。如果直接使用Caffe推荐的LMDB格式作为输入运行,就会得到很惨的结果(大概是0%)。这是转换出问题了吗?如果读取模型文件,会发现对应层的参数并没有什么问题。困扰了两天左右,终于发现了原因:想要得到相同的结果,要有和原模型一样的输入。
这是什么意思呢?模型转换只能保证运算参数相同,但测试时的数据输入与原模型训练时所经过的预处理函数不同,就会出现输入不一致的情况,这时输出肯定也不一致了。
PyTorch官方训练代码中给出了预处理过程:
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
data_loader = torch.utils.data.DataLoader(
datasets.ImageFolder(args.data, transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize,
])),
batch_size=args.batch_size, shuffle=False,
num_workers=2, pin_memory=True)
因此输入时也要按照这样的形式。由于命令行接口难以加入预处理函数,因此需要python实现:
model_converted = caffe.Net(args.ModelPath, args.WeightPath, caffe.TEST)
for i, (data, target) in enumerate(data_loader):
input_data = data.numpy()
target = target.numpy()
model_converted.blobs[model_converted.inputs[0]].data[...] = input_data
predict = model_converted.forward()[model_converted.outputs[-1]]
converted_predict = np.squeeze(predict)
...
这是加入到caffe的预处理部分。再次运行,准确率和原来是一致的。
mmconvert -sf pytorch -iw imagenet_resnet18.pth -df tensorflow -om tf_resnet18_official --inputShape 3,224,224
转换命令基本上没有区别,运行后输出一个目录。然后就是怎么用的问题了,官网上给出的是:
export_dir = "./tf_resnet152"
with tf.Session(graph=tf.Graph()) as sess:
tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.TRAINING], export_dir)
x = sess.graph.get_tensor_by_name('input:0')
y = sess.graph.get_tensor_by_name('xxxxxx:0')
......
_y = sess.run(y, feed_dict={x: _x})
有两点需要注意:
tf.saved_model.tag_constants
这一项,是与--dump_tag {SERVING,TRAINING}
所对应的;dense/BiasAdd:0
。还有一个方法可以手动改名,但就不能一步转换了,只能改IR模型文件最后一层的name
(默认是空)。load = tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], args.model)
input_tf = sess.graph.get_tensor_by_name('input:0')
model_tf = sess.graph.get_tensor_by_name(args.tf_output_node)
init = tf.global_variables_initializer()
sess.run(init)
for i, (data, target) in enumerate(val_loader):
input_data = data.numpy()
input_data = np.transpose(input_data, (0, 2, 3, 1))
target = target.numpy()
predict = sess.run(model_tf, feed_dict = {input_tf : input_data})
converted_predict = np.squeeze(predict)
...
这和上面caffe还有一点不同,就是多了一次np.transpose
维度变换,是一个小坑。
这样一来,tensorflow也成功转换啦~
上面的坑都是自己踩的,网上似乎没有找到类似的教程,当时真的有点无语。。记录一下,希望能帮到别人吧。