把Caffe的模型转换为Pytorch模型
系列博客目录:Caffe转Pytorch模型系列教程 概述
目录
- 一、可视化Caffe的网络模型
- 1、在线网站
- 2、本地可视化
- 二、SfSNet的第一部分
- 1、网络名
- 2、输入层
- 3、第一个卷积层conv1
- 4、第一个BN层bn1
- 5、第一个Relu层relu1
- 6、第一部分的完整Pytorch代码
- 二、SfSNet的残差块
- 1、Caffe的Eltwise层
- 2、实现一个“ResidualBlock”
- 三、反卷积层、Concat层、池化层以及全连接层
- 1、反卷积层
- 2、Concat层
- 3、池化层
- 4、全连接层
- 四、总结
本文用的Caffe网络模型文件:SfSNet_deploy.prototxt(右键另存为)。这是一个很牛逼的模型,SfSNet_deploy.prototxt的定义也很长,本文就以它(SfSNet)为例,说明如何把Caffe网络手动转换为Pytorch网络。转换后的Pytorch代码地址:model.py(右键另存为)。
一、可视化Caffe的网络模型
可视化网络模型的好处是可以直观地看到网络模型,对网络模型能先有个大概的了解,再结合.prototxt文件,就能完整无误的把Caffe模型转换为Pytorch模型。在线网站要比本地可视化直观准确得多。在后面的实现过程中,请大家结合结合.prototxt和可视化结果来转换模型,很重要。
1、在线网站
打开网站http://ethereon.github.io/netscope/#/editor之后,把SfSNet_deploy.prototxt的内容粘贴到编辑框里,然后按Shift+Enter,右边的框就会出现网络模型。鼠标移动到右边的方块上时,会显示每层的参数。
2、本地可视化
请参考:https://www.cnblogs.com/denny402/p/5106764.html
二、SfSNet的第一部分
第一部分的网络结构图。Caffe模型中所有的操作都定义为层。
1、网络名
打开SfSNet_deploy.prototxt中,第一行是:
name : "PS-Net"
指定此网络的名称是"PS-Net"。
2、输入层
#data
layer {name: "data" # 名称,使用Caffe的Python接口时会用到这个。type: "Input" # 类型,为输入top: "data" # “顶”,暂时可以理解为数据流向。input_param { shape: { dim: 1 dim: 3 dim: 128 dim: 128 } }
}
此部分代码的输入层:shape=(1, 3, 128, 128)。 “#”后面是我添加的注释,之后也是如此。
3、第一个卷积层conv1
#C64
layer {name: "conv1" # 层名type: "Convolution" # 类型:卷积bottom: "data" # 数据输入:datatop: "conv1" # 数据输出:conv1param { # 权重 weightname : "c1_w" # 权重名lr_mult: 1 # 权重学习率decay_mult: 1 # 衰减系数}param { # 偏置 biasname : "c1_b" # 偏置名lr_mult: 2 # 偏置学习率decay_mult: 0 # 衰减系数}convolution_param { # 卷积层参数num_output: 64 # 卷积核个数,对应torch.nn.Conv2d的out_channelskernel_size: 7 # 卷积核大小stride: 1 # 步长大小pad: 3 # padding大小weight_filler { # 权重初始化器参数type: "xavier" # 权重初始化器类型}}
}
以上就是一个卷积层的定义。Pytorch中,卷积层是:
Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
in_channels是输入卷积层的通道数,也就是data层中的“3”;out_channels对应num_output,也就是卷积核的个数:“64”;其他的就是一一对应。在Pytorch中,conv1就定义为:
conv1 = nn.Conv2d(3, 64, 7, 1, 3)
4、第一个BN层bn1
下图是Pytorch中BatchNorm完成的工作,“*”号左边的部分是使用E[x](均值)和Var[x](方差) 归一化x,乘以γ加上β是缩放x和平移x。Caffe中的BatchNorm层只完成了归一化x的工作,缩放和平移由Scale层完成,所以:BatchNorm(Pytorch)= BatchNorm(Caffe)+ Scale(Caffe)。
SfSNet只是使用了BatchNorm层,没有使用Scale层。所以使用Pytorch的BatchNorm层来替换Caffe中的层时,需要把nn.BatchNorm参数affine设置为True,且要把BatchNorm层的weight(相当于γ)设置为全1,bias(相当于β)设置为全0。如果affine=False,weight会被设置为范围在0~1之间的随机数,bias会被设置为0。 至于如何设置,将会在下一篇博客给出,本文只关注如何转换模型。给出bn1的.prototxt定义:
layer {name: "bn1" # 层名type: "BatchNorm" # 类型:BatchNormbottom: "conv1" # 数据输入:conv1top: "conv1" # 数据输出:conv1batch_norm_param {use_global_stats: false # 训练时为false,不使用保存的E[x]和Var[x]}param {name : "b1_a"lr_mult: 0}param {name: "b1_b"lr_mult: 0}param {name: "b1_c"lr_mult: 0}include {phase: TRAIN # 训练时包含此层}
}layer {name: "bn1"type: "BatchNorm"bottom: "conv1"top: "conv1"batch_norm_param {use_global_stats: true # 测试模型是使用保存的E[x]和Var[x]}param {name : "b1_a"lr_mult: 0}param {name : "b1_b"lr_mult: 0}param {name: "b1_c"lr_mult: 0}include {phase: TEST # 测试时包含此层}
}
bn1的输入和输出均为conv1,所以bn1是“In-Place”操作,把修改完的数据又存回了conv1。 可以发现,bn1层被定义了两次,主要差别是参数use_global_stats和phase。第一个定义是在训练网络(phase:TRAIN)时使用,第二个定义是在测试时(phase:TEST)时使用。Caffe的BatchNorm对应Pytorch中的BatchNorm2d,定义如下:
BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
num_features是特征数量,数值和Conv2d的out_channels相同;eps是ϵ;affine默认为True,不要修改;track_running_stats=True 保持默认。所以bn1的对应的pytorch定义为:
bn1 = nn.BatchNorm2d(64)
64对应out_channels。
5、第一个Relu层relu1
layer {name: "relu1" # 层名type: "ReLU" # 类型:relubottom: "conv1" # 输入:conv1top: "conv1" # 输出:conv1
}
这是一个ReLU层的定义,也是一个in-Place层, 输入输出均为conv1。在Pytorch中,可以直接使用:
torch.nn.functional.relu(conv1)
6、第一部分的完整Pytorch代码
上面只是简单的介绍了三个层,实际上只完成了:
这一小部分。但是我不再重复举例说明,而是直接给出第一部分模型的代码,相信读者可以举一反三,完成第一部分模型的转换。请读者先自行完成conv1到conv3的Pytorch代码,再和下面的比对。
# coding=utf-8
from __future__ import absolute_import, division, print_function
import torch
import torchvision
import pickle as pkl
from torch import nn
import torch.nn.functional as Fclass SfSNet(nn.Module): # SfSNet = PS-Net in SfSNet_deploy.prototxtdef __init__(self):# C64super(SfSNet, self).__init__()# TODO 初始化器 xavierself.conv1 = nn.Conv2d(3, 64, 7, 1, 3)self.bn1 = nn.BatchNorm2d(64)# C128self.conv2 = nn.Conv2d(64, 128, 3, 1, 1)self.bn2 = nn.BatchNorm2d(128)# C128 S2self.conv3 = nn.Conv2d(128, 128, 3, 2, 1)def forward(self, inputs):# C64x = F.relu(self.bn1(self.conv1(inputs)))# C128x = F.relu(self.bn2(self.conv2(x)))# C128 S2conv3 = self.conv3(x)
二、SfSNet的残差块
观察网络的可视化结果,可以发现有很多重复的结构(下面框框里面包着的),SfSNet的作者说它们是残差块(ResidualBlock),其实还是有丢丢区别的,不过不影响我们转换模型。这些重复的结构层数相同,参数相同,就是名字不一样,因此我把每个框框里面的结构实现为一个“ResidualBlock”,以便于重复利用,还能减少出错。
1、Caffe的Eltwise层
layer {name: "nsum1" # 层名type: "Eltwise" # 类型:Eltwisebottom: "nconv1r" # 输入1:nconv1rbottom: "conv3" # 输入2:conv3top: "nsum1" # 输出:nsum1eltwise_param { # 操作类型operation: SUM # 求和}
}
顾名思义,Eltwise层就是逐元素的操作(相加、相减等等)。operation指定了类型是SUM(求和)。所以,此层就是将nconv1r和conv3求和,存到nsum1里面。
2、实现一个“ResidualBlock”
直接上代码:
class ResidualBlock(nn.Module):def __init__(self, in_channel, out_channel):super(ResidualBlock, self).__init__()# nbn1/nbn2/.../nbn5 abn1/abn2/.../abn5self.bn = nn.BatchNorm2d(in_channel)# nconv1/nconv2/.../nconv5 aconv1/aconv2/.../aconv5self.conv = nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=1, padding=1)# nbn1r/nbn2r/.../nbn5r abn1r/abn2r/.../abn5rself.bnr = nn.BatchNorm2d(out_channel)# nconv1r/nconv2r/.../nconv5r aconv1r/aconv2r/.../anconv5rself.convr = nn.Conv2d(out_channel, out_channel, kernel_size=3, stride=1, padding=1)def forward(self, x):out = self.conv(F.relu(self.bn(x)))out = self.convr(F.relu(self.bnr(out)))# num1/nsum2/.../nsum5 aum1/asum2/.../asum5out += x return out
上面就是一个框框内的所有层,都定义在一个ResidualBlock内,并且可以重复使用。Eltwise层使用“+=”替代了,ReLU层使用“F.relu”替代了。接下来就举个例子,说明ResidualBlock怎么用:
class SfSNet(nn.Module): # SfSNet = PS-Net in SfSNet_deploy.prototxtdef __init__(self):# C64super(SfSNet, self).__init__()# TODO 初始化器 xavierself.conv1 = nn.Conv2d(3, 64, 7, 1, 3)self.bn1 = nn.BatchNorm2d(64)# C128self.conv2 = nn.Conv2d(64, 128, 3, 1, 1)self.bn2 = nn.BatchNorm2d(128)# C128 S2self.conv3 = nn.Conv2d(128, 128, 3, 2, 1)# ------------RESNET for normals------------# RES1self.n_res1 = ResidualBlock(128, 128)# RES2self.n_res2 = ResidualBlock(128, 128)def forward(self, inputs):# C64x = F.relu(self.bn1(self.conv1(inputs)))# C128x = F.relu(self.bn2(self.conv2(x)))# C128 S2conv3 = self.conv3(x)# ------------RESNET for normals------------# RES1x = self.n_res1(conv3)# RES2x = self.n_res2(x)return x
三、反卷积层、Concat层、池化层以及全连接层
1、反卷积层
SfSNet中,还用到了反卷积层,分别为nup6和aup6。
在.prototxt中,一个反卷积层定义为:
#CD128
layer {name: "nup6" # 层名type: "Deconvolution" # 类型:Deconvolutionbottom: "nsum5" # 输入:nsum5top: "nup6" # 输出:nup6convolution_param { # 卷积参数kernel_size: 4 # 卷积核大小stride: 2 # 步长num_output: 128 # 输出特征数group: 128 # group大小,不理解是啥意思,不过Pytorch中的反卷积层有同名参数,不慌,小场面pad: 1 # padding大小weight_filler { # 权重初始化参数type: "bilinear" # 权重初始化器类型} bias_term: false # 使用偏置:false为不使用}param { # 权重学习参数lr_mult: 0 # 学习率:0decay_mult: 0 # 学习率衰减率:0}
}
Pytorch中,反卷积层的定义为:
ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1)
in_channels为输入通道数,在SfSNet中是128;out_channels为输出通道数,也为128;groups对应.prototxt中的“group: 128 ”;bias要设置为false,因为“bias_term: false”,其他参数保持默认,所以,nup6的Pytorch代码为:
aup6 = nn.ConvTranspose2d(128, 128, 4, 2, 1, groups=128, bias=False)
2、Concat层
#concat
layer {name: "lconcat1" # 层名bottom: "nsum5" # 输入1:nsum5bottom: "asum5" # 输入2:asum5top: "lconcat1" # 输出:lconcat1type: "Concat" # 类型:lconcat1concat_param { # Concat参数axis: 1 # 轴:连接(N, C, H, W)第二维,也就是C(channel)。当axis=0是,连接N,也就是一批的数量}
}
Pytorch里和Caffe中的Concat层对应的操作是:
torch.cat(tensors, dim=0, out=None) → Tensor
tensors是Tenor组成的列表;dim对应axis;out保持默认。所以,lconcat1对应的Python代码就是:
lconcat1 = torch.cat((nsum5, asum5), 1)
感觉没毛病吧?大错特错!!!,让我们再次打开网络可视化之后的结果:
可以看到,lconcat1的两个输入明明是nrelu6r和arelu6r啊,难道是可视化结果错了?为啥.prototxt文件中写成num5和asum5呢?因为nbn6r/abn6r和nrelu6r/arelu6r都是“In-Place”操作(输入输出都是num5/asum5),计算完之后又存回num5/asum5了,所以.prototxt文件写成num5和asum5,实际应为nrelu6r/arelu6r处理后的结果。所以,lconcat1的Pytorch代码应为:
lconcat1 = torch.cat((nrelu6r, arelu6r), 1)
需要特别注意那种“In-Place”操作,要结合可视化图和.prototxt文件来完成模型的转换,免得走弯路啊啊啊啊啊。
3、池化层
layer {name: "lpool2r" # 层名type: "Pooling" # 类型:Poolingbottom: "lconv1" # 输入:lconv1top: "lpool2r" # 输出:lpool2rpooling_param { # 池化层参数pool: AVE # 池化类型:AVE,对应AvgPool2d;还有MAX,对应MaxPool2d.kernel_size: 64 # 池化核大小}
}
上面就是池化层的定义。在Pytorch中,上述池化层的对应:
AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True)
kernel_size是池化核大小;其他参数不做说明。lpool2r对应的Pytorch代码为:
lpool2r = nn.AvgPool2d(64)
如果“pool: AVE”里的“AVE”换成“MAX”,那么lpool2r对应的Pytorch代码为:
lpool2r = nn.MaxPool2d(64)
4、全连接层
layer {name: "fc_light" # 层明type: "InnerProduct" # 类型:InnerProduct,也就是全连接层bottom: "lpool2r" # 输入:lpool2rtop: "fc_light" # 输出:fc_lightparam { # 权重参数lr_mult: 1decay_mult: 1}param { # 偏置参数lr_mult: 2decay_mult: 0}inner_product_param { # 全连接层参数num_output: 27 # 输出通道数:27weight_filler { # 权重初始化器参数type: "gaussian" # 类型:gaussianstd: 0.005 # 标准差:0.005}bias_filler { # 偏置初始化参数type: "constant" # 类型:constantvalue: 1 # 值:1}}
}
越写到后面越不想写,这篇博客都编辑了五六个小时了(哭,还是坚持写完吧)。上面就是全连接层fc_light的定义,在Pytorch中,对应的层是:
Linear(in_features, out_features, bias=True)
fc_light对应的Pytorch代码为:
fc_light = nn.Linear(128, 27)
等等!!!fc_light前面不是一个lpool2r层吗?
lpool2r层的输出形状是(1, 128, 1, 1),也就是四维啊,全连接层前面应该是一维的啊,所以在Pytorch的模型的forward函数里,还需要reshape(调整为[1, 128]) lpool2r的结果:
class SfSNet(nn.Module):def __init__(self):...def forward(self, inputs):...x = self.lpool2r(x)# 调整输出的shapex = x.view(-1, 128)# fc_lightlight = self.fc_light(x)
四、总结
- 不懂的多查多看!
- 转换模型要结合.prototxt和可视化结果。
把Caffe的模型转换为Pytorch模型相关推荐
- 将tensorflow模型转换为uff模型
将ckpt转换为pb模型 https://blog.csdn.net/guyuealian/article/details/82218092 将pb模型转换为uff模型 convert-to-uff ...
- 【数据库】逻辑设计-ER模型转换为关系模型
转载: https://blog.csdn.net/HaoDaWang/article/details/78098937?locationNum=4&fps=1 如何把ER模型转换为关系模型 ...
- er图的好处_如何把ER模型转换为关系模型(超详细,含例题)
本文转载自:http://blog.csdn.net/HaoDaWang/article/details/78098937?locationNum=4&fps=1 超级感谢博主分享 本篇博文中 ...
- er图转关系模式规则_将ER模型转换为关系模型的规则
将 ER 模型转换为关系模型的规则如下: (1) 一个实体型转换为一个关系模式, 实体的属性就是关系的属性, 实体的码 就是关系的码. (2)一个1 : 1的联系可以转换为一个独立的关系模式,也可以与 ...
- 数据库原理-ER模型转换为关系模型
1.ER图转换成关系模式集的算法(联系类型的转换):不同的情况做不同的处理.(二元联系类型的转换) ①若实体间联系是1:1,可以在两个实体类型转换成的两个关系模式中任意一个关系模式的属性中加入另一个关 ...
- E-R模型转换为关系模型
项目(实训)目标 能采用E-R模型进行概念结构设计 能实现E-R模型向关系模型的转化 项目(实训)中的具体任务 1.从应用背景的实体与实体关系,画出实体联系图(E-R图). 2.将得到的E-R图转化为 ...
- 如何将tensorflow模型转PYTORCH模型
将tensorflow版本的.ckpt模型转成pytorch的.bin模型 - 最咸的鱼 - 博客园
- 【.pth模型转换为.onnx模型】模型转换 英特尔神经计算棒 树莓派
转换代码 注意点:要根据你的代码进行修改,修改最初的包等 import torch from models.with_mobilenet import PoseEstimationWithMobile ...
- 【TensorRT】PyTorch模型转换为ONNX及TensorRT模型
文章目录 1. PyTorch模型转TensorRT模型流程 2. PyTorch模型转ONNX模型 3. ONNX模型转TensorRT模型 3.1 TensorRT安装 3.2 将ONNX模型转换 ...
- PyTorch参数模型转换为PT模型
当PyTorch模型需要部署到服务时,为了提升访问速度,需要转换为TRT模型,再进行部署.在转换为TRT模型之前,需要将PyTorch参数模型(如pth.tar)转换为pt模型,使用jit形式.pt模 ...
最新文章
- C++标准库中sstream和strstream的区别
- 嵌入式开发中使用DDD进行调试
- 超摩尔定律!OpenAI官宣AI算法效率每16个月翻一番
- 【Scratch】青少年蓝桥杯_每日一题_4.01_踢足球
- Beyond Compare启动出错解决方案
- typescript tslint rules config
- linux fstream open,fstream创建文件的问题
- 无符号有符号乘法_【编译笔记】变量除以常量的优化(一)——无符号除法
- centos 6.2安装mysql_centos6.2安装mysql
- 使用java Graphics 绘图工具生成顺丰快递电子面单
- mysql数据导出insert_mysql 数据导出
- 财富杂志推荐的75本必读书
- 计算机系统中引入虚拟内存的好处是什么,虚拟内存的作用是什么
- “内存型”网游外挂的刑事责任辨析
- Chromium下载地址
- 利用Euclid算法求解两个数的最大公约数及逆 matlab
- Mac重装系统出错的心路历程
- 《VALORANT》: 双塔迷城的诞生
- R语言绘制频率直方图
- Kafka 的 replica 同步机制(ISR与OSR列表数据相互转换)