为啥叫再遇caffe,是因为近一年前因为实验室项目需要,第一次使用到了caffe。当时caffe的环境配置和不够直观易用简直让我对caffe印象很坏…现在因为涉及部署相关的工作,再次接触到caffe。现在再看,倒觉得caffe像是深度学习框架特别朴素纯真的样子,也学到了一些新的caffe的使用技巧,以后可能还会用到,做个记录。
Python环境下直接使用opencv的dnn模块运行caffe模型
偶然发现opencv的dnn模块直接加载和使用caffe模型,以后遇到仅有caffe预训练模型的情况,可以不必配置caffe环境,直接使用opencv运行使用啦!(opencv永远的神!)
一个样例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
protoFile = 'path/to/xx.prototxt'
weightsFile = 'path/to/xx.caffemodel'
net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)
# === CPU === #
# net.setPreferableBackend(cv2.dnn.DNN_TARGET_CPU)
# print("Using CPU device")
# === GPU(not tested) === #
# net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
# net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
# print("Using GPU device")
img = cv2.imread('path/to/img')
inpBlob = cv2.dnn.blobFromImage(img, 1.0 / 255, (inWidth, inHeight),
(0, 0, 0), swapRB=False, crop=False)
net.setInput(inpBlob, 'image') # bind the input and input layer
output = net.forward('net_output') # set to the layer of which you want the feature
相关函数
caffe模型转pytorch
一次需要把caffe模型转到Pytorch形式,记录一下具体转化的过程。
已知:caffe模型定义文件model_deploy.prototxt
,caffe权重文件model_weight.caffemodel
;
目标:Pytorch模型定义文件(model.py
)与pytorch权重文件model.pt
流程:
权重转化:使用caffemodel2pytorch工具,将caffe权重文件转化位pytorch权重文件。
1
python caffemodel2pytorch.py model_weight.caffemodel -o model_weight.pt
得到pytorch权重文件
model_weight.pt
。得到Pytorch模型结构定义。
- 使用netron可视化caffe模型结构;
- 结合可视化的网络结构,手写pytorch模型定义文件(我真的是手写,不知道是否有更方便的办法?)。
输入同一样本,检查caffe和Pytorch两种模型的输出是否相同。相同便转化成功,如不相同则比对网络内部对应输出,逐点排查。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
img0 = cv2.imread('path/to/img')
# get caffe output
net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)
inpBlob = cv2.dnn.blobFromImage(img0, 1.0 / 255, (inWidth, inHeight),
(0, 0, 0), swapRB=False, crop=False)
net.setInput(inpBlob, 'image') # bind the input and input layer
caffe_out = net.forward('net_output') # set to the layer of which you want the feature
# get pytorch output
model = Model()
model.load_state_dict(torch.load('path/to/model.pt'))
img = cv2.resize(img0, ((inWidth, inHeight)))
img = np.transpose(img, (2, 0, 1)).astype(np.float32) # HWC -> CWH
img = torch.from_numpy(img)
img = img / 255
img = torch.unsqueeze(img, 0)
pytorch_out = model(img)
一个供参考的pytorch模型定义文件框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# model.py
import torch
import torch.nn as nn
from collections import OrderedDict
class Pose25Model(nn.Module):
def __init__(self):
super(Pose25Model, self).__init__()
self.get_module_dict()
self.get_torch_module()
# 配置网络层名称及其参数
def get_module_dict(self):
...
for stage_id in range(0, 4):
self.blocks['Mconv1_stage%d' % stage_id] = OrderedDict([
# ('conv_name': [in_channels, inner_channel, kernel_size, stride(default=1), padding(default=0)])
('Mconv1_stage%d' % stage_id, [input_channel, inner_channel, 3, 1, 1]),
...
])
# 为网络层分配实际的pytorch网络module
def get_torch_module(self):
for key in self.blocks.keys():
block_info = self.blocks[key]
for layer_name, v in block_info.items():
# print(layer_name, v)
if 'pool' in layer_name:
layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1],
padding=v[2], ceil_mode=True)
self.add_module(layer_name, layer)
else:
layer = nn.Conv2d(in_channels=v[0], out_channels=v[1], kernel_size=v[2], stride=v[3], padding=v[4])
self.add_module(layer_name, layer)
ReLU_name = 'relu' + layer_name.split('conv')[1]
self.add_module(ReLU_name, torch.nn.ReLU(inplace=True))
def forward(self, x):
x = ...
...
return x
大小坑们
Q:caffe和pytorch模型得到的输出大小不一致?
A:看看Pytorch中,maxpooling层的定义
1
class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
Pytorch中ceil_mode参数默认为False,在计算特征图时,会向下取整;而caffe是默认向上取整的。因此若使用Pytorch的默认参数,会使caffe得到的特征图尺寸比pytorch的要大。在Pytorch中修改ceil_mode=True
就可以啦。参考
Q:转化得到的pt文件是正确的(可以通过将对应层的参数打印出来比对下),Pytorch的模型定义似乎也没有问题,但是为什么输出结果就不一样呢?
A:这个原因可能有很多…我的原因是cat的拼接顺序。比如对特征沿channel维进行拼接时,不同的拼接顺序,得到的结果肯定也是不一样的。所以遇到cat操作要小心,要看准确的拼接顺序,需要到.prototxt
中去看,并据此写pytorch的cat语句,netron作图是不会反映这个顺序的,不可想当然就直接按照它给的顺序从左向右依次拼接了。