Home 再遇caffe
Post
Cancel

再遇caffe

为啥叫再遇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

流程:

  1. 权重转化:使用caffemodel2pytorch工具,将caffe权重文件转化位pytorch权重文件。

    1
    
    python caffemodel2pytorch.py model_weight.caffemodel -o model_weight.pt
    

    得到pytorch权重文件model_weight.pt

  2. 得到Pytorch模型结构定义。

    1. 使用netron可视化caffe模型结构;
    2. 结合可视化的网络结构,手写pytorch模型定义文件(我真的是手写,不知道是否有更方便的办法?)。
  3. 输入同一样本,检查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作图是不会反映这个顺序的,不可想当然就直接按照它给的顺序从左向右依次拼接了。

This post is licensed under CC BY 4.0 by the author.

TensorRT使用

Follow!