Caffe模型转NCNN后生成param与bin文件及相关NCNN源码解析
Caffe转ncnn后生成param与bin文件,param是网络结构文件,bin是网络权重文件。
下图是Alexnet的param文件。
第1行(版本信息)
7767517
代表此param文件的版本,magic number(幻数)。
ncnn源码说明:
第2行(layer与blob数量)
24 24
第一个数:层(layer)数量 ,即下面有24行,每一行代表一层
第二个数:数据交换结构(blob)数量
ncnn源码说明:
根据Caffe官方文档,caffe大致可以分为三层结构:blob,layer,net。blob是caffe的标准数组结构,它提供了一个统一的内存接口。layer是caffe模型和计算的基本单元,net是一系列layers和其连接的集合。
2.1 Blob
Caffe内部数据存储和通讯都是通过Blob来完成,Blob提供统一的存储操作接口,可用来保存训练数据和模型参数等。
在数学上,一个blob就是一个N维的数组,按照c语言风格存储。Blob的维度可以表示为
(N, K, H, W),每个维度的意思分别是:
- N:数据的个数,例如batch_size的大小
- K:如果是图像,可以理解为通道数量;如果是网络中间,可以理解为feature map的数量
- H:图像或者滤波器的高度
- W:图像或者滤波器的宽度
以一张三通道480*640的图片为例子,如果转为Blob格式的数据,那么这个Blob可以表示为:(1*3*480*640)。
对于blob中的数据,我们关心的是values(值)和gradients(梯度),所以一个blob单元存储了两块数据——data和diff,前者是我们在网络中传送的普通数据,如图像像素数据之类的;后者指的是Back propagation,运算得到的梯度数据。
2.2 Layer
Layer是Caffe模型的本质内容和执行计算的基本单元。Layer可以进行很多运算,如:
- convolve(卷积)
- pool(池化)
- inner product(内积)
- rectified-linear和sigmoid等非线性运算
- 元素级的数据变换
- normalize(归一化)
- load data(数据加载)
- softmax和hinge等losses(损失计算)
一个layer通过bottom(底部)连接层接收数据,通过top(顶部)连接层输出数据。
每一个layer都定义了3种重要的运算:setup(初始化设置),forward(前向传播), backward(反向传播)。
第3行(输入层data)
Input data 0 1 data 0=227 1=227 2=3
前4个值的含义是固定的,分别是:
- 层的类型:Input
- 层的名称:data
- 输入数据结构(blob)数量bottom:0
- 输出数据结构(blob)数量top:1
Input层比较特殊,没有输入数据,所以bottom为0。输出是一个卷积层,所以top为1。
ncnn源码说明:
后面有三种类型的值(严格按照顺序排序):
- 第一种: 网络输入层名(一个层可能有多个输入,于是有多个网络输入层名) :没有
- 第二种: 网络输出层名(一个层可能有多个输出,于是有多个网络输出层名) :data
- 第三种(可能没有):特殊参数层,一是k=v的类型存在。二是k=len,v1,v2,v3….(数组类型)。此层在ncnn中是存放到paramDict结构中,不同类型层,各种参数意义不一样,需要具体分析。
0=227 1=227 2=3
特殊参数1(第一个k=v):w=227,输入图像的宽(w)
特殊参数2(第二个k=v):h=227,输入图像的高(h)
特殊参数3(第三个k=v):c=3,输入图像通道数(c)
Alexnet输入图像大小为227*227*3
ncnn源码说明:
第4行(卷积层conv1)
Convolution conv1 1 1 data conv1 0=96 1=11 2=1 3=4 4=0 5=1 6=34848
前6个值:
- 层的类型:Convolution
- 层的名称:conv1
- 输入数据结构(blob)数量bottom:1
- 输出数据结构(blob)数量top:1
- 网络输入层名:data
- 网络输出层名:conv1
0=96 1=11 2=1 3=4 4=0 5=1 6=34848
特殊参数1:0=96,num_output=96,卷积核的数量
特殊参数2:1=11, kernel_w=11,卷积核的宽
特殊参数3:2=1, dilation_w=1,卷积核rows方向的缩放系数,默认为1,一般不修改或配置
特殊参数4:3=4,stride_w=4,步长
特殊参数5:4=0,pad_w=0,填充大小
特殊参数6:5=1,bias_term=1,是否开启偏置项,默认为1, 开启
特殊参数7:6=34848,weight_data_size=34848,权重数据大小11 * 11 * 3 * 96=34848,如果加上bias的话,这一层的参数个数=(11 * 11 * 3 * 96)+96=34944
ncnn源码说明:
第5行(**层relu1)
ReLU relu1 1 1 conv1 conv1_relu1
5.1 什么是**函数
如下图,在神经元中,输入的 inputs 通过加权,求和后,还被作用了一个函数,这个函数就是**函数 Activation Function。
5.2 为什么要用**函数
如果不用**函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合。
如果使用的话,**函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。
例子:
这是一个单层的感知机,用它可以划出一条线, 把平面分割开
我们可以用多个感知机来进行组合, 获得更强的分类能力
但这样一个神经网络组合起来,输出的时候无论如何都还是一个线性方程,加上**函数的作用就是,输出变成了非线性函数,有了这样的非线性**函数以后, 神经网络的表达能力更加强大了。比如说,加上非线性**函数之后, 我们就有可能学习到这样的平滑分类平面。
5.3 有哪些**函数
在AlexNet之前,常见的**函数是用sigmoid和tanh两种,而AlexNet采用的是ReLU (Rectified Linear Unit,修正线性单元)。
5.3.1 sigmoid
公式:
图像:
能够把输入的连续实数值压缩到0和1之间
缺点:
- sigmoid函数饱和使梯度消失,sigmoid神经元在值为0或1的时候接近饱和,梯度几乎为 0。因此在反向传播时,这个局部梯度会与整个代价函数关于该单元输出的梯度相乘,结果也会接近为0。这样,几乎就没有信号通过神经元传到权重再到数据了,因此这时梯度就对模型的更新没有任何贡献。
-
sigmoid函数不是关于原点中心对称,因为在神经网络后面层中的神经元得到的数据将不是零中心的。这一情况将影响梯度下降的运作,因为如果输入神经元的数据总是正数(比如在
中每个元素都大于零),那么关于的梯度在反向传播的过程中,将会要么全部是正数,要么全部是负数(具体依整个表达式f而定)。这将会导致梯度下降权重更新时出现z字型的下降。然而,可以看到整个批量的数据的梯度被加起来后,对于权重的最终更新将会有不同的正负,这样就从一定程度上减轻了这个问题。因此,该问题相对于上面的神经元饱和问题来说只是个小麻烦,没有那么严重。
- 计算量大,反向传播求误差梯度时,求导涉及除法。
5.3.2 tanh双切正切函数
公式:
从下图看以看出tanh跟sigmoid很像,实际上,tanh 是sigmoid的变形:
图像:
tanh实数值压缩到[-1,1]之间,解决了sigmoid的输出不是零中心的问题,但仍然存在饱和问题。
5.3.3 ReLU
公式:
图像:
从图左可以看出,输入信号<0时,输出都是0,>0的情况下,输出等于输入。
优点:
- 相较于sigmoid和tanh函数,ReLU对于SGD随机梯度下降的收敛有巨大的加速作用
- sigmoid和tanh神经元含有指数运算等耗费计算资源的操作,而ReLU可以简单地通过对一个矩阵进行阈值计算得到
缺点:
在训练的时候,ReLU单元比较脆弱并且可能“死掉”。举例来说,当一个很大的梯度流过ReLU的神经元的时候,可能会导致梯度更新到一种特别的状态,在这种状态下神经元将无法被其他任何数据点再次**。如果这种情况发生,那么从此所以流过这个神经元的梯度将都变成0。也就是说,这个ReLU单元在训练中将不可逆转的死亡,因为这导致了数据多样化的丢失。例如,如果学习率设置得太高,可能会发现网络中40%的神经元都会死掉(在整个训练集中这些神经元都不会被**)。通过合理设置学习率,这种情况的发生概率会降低。
5.3.4 Leaky ReLU
公式:
Leaky ReLU是给所有负值赋予一个非零斜率,比如0.01,Leaky ReLU非线性函数图像如下图所示。这样做目的是使负轴信息不会全部丢失,解决了ReLU神经元“死掉”的问题。更进一步的方法是PReLU,负值部分的斜率是根据数据来定的,而非预先定义的。
图像:
5.3.5 Maxout
Maxout是对ReLU和leaky ReLU的一般化归纳,它的函数是:
ReLU和Leaky ReLU都是这个公式的特殊情况(比如ReLU就是当的时候)。这样Maxout神经元就拥有ReLU单元的所有优点(线性操作和不饱和),而没有它的缺点(死亡的ReLU单元)。然而和ReLU对比,它每个神经元的参数数量增加了一倍,这就导致整体参数的数量激增。
第6行(归一化层norm1)
LRN norm1 1 1 conv1_relu1 norm1 0=0 1=5 2=0.000100 3=0.750000
LRN(Local Response Normalization)局部响应归一化,使用Sigmoid和tanh这类传统的饱和型**函数,我们往往都会做归一化处理,避免出现神经元对较大的输入值不敏感的情况(较大的输入值经过Sigmoid和tanh后的输出总是一个接近1的值)。
实际上ReLU这样的非饱和型函数不做归一化也是可以的,但作者还是给出了一个叫做LRN的方法,有利于增加泛化能力。(后期其他网络VGG、GOOGLENET、YOLO等放弃了这个LRN层,认为效果并不显著。)
0=0 1=5 2=0.000100 3=0.750000
特殊参数1:0=0,region_type
特殊参数2:1=5,local_size,对应上面公式中的n
特殊参数3:2=0.000100,alpha,对应上面公式中的特殊参数4:3=0.750000,beta,对应上面公式中的
ncnn源码说明:
第7行(池化层pool1)
池化操作(Pooling)用于卷积操作之后,提取的是一小部分的代表性特征,减少冗余信息。其作用在于特征融合和降维,降维改变的是图像的宽高,而不改变通道数。其实也是一种类似卷积的操作,只是池化层的所有参数都是超参数,都是不用学习得到的。
上面(来源见底层水印)这张图解释了最大池化(Max Pooling)的操作过程,核的尺寸为2×2,步长为2,最大池化的过程是将2×2尺寸内的所有像素值取最大值,作为输出通道的像素值。
除了最大池化外,还有平均池化(Average Pooling),也就是将取最大改为取平均。
AlexNet中的pooling操作是有重叠的。常见的pooling操作都没有重叠,即pooling单元的边长和步长是相同的,但AlexNet采用了3×3的pooling单元,步长设置为2,相邻的两个pooling单元存在重叠。这样的设定使AlexNet的top-1和top-5错误率分别降低了0.4%和0.3%(和使用不重叠的池化相比)。
Pooling pool1 1 1 norm1 pool1 0=0 1=3 2=2 3=0 4=0
特殊参数1:0=0,pooling_type池化类型,0=最大池化。1=平均池化,2=随机
特殊参数2:1=3,kernel_w=3,池化核大小
特殊参数3:2=2,stride_w=2,池化核步长
特殊参数4:3=0,pad_left=0,填充大小
特殊参数5:4=0,global_pooling=0,是否全区域池化(将整幅图像降采样为1*1)
ncnn源码说明:
第8行(卷积层conv2)
ConvolutionDepthWise conv2 1 1 pool1 conv2 0=256 1=5 2=1 3=1 4=2 5=1 6=307200 7=2
由于当时的GPU运算性能有限,alexnet将第2、4、5三个卷积层分为两部分,分配至两个GPU并行计算。所以这三层的层名都还是卷积层,但层的类型是ConvolutionDepthWise。
前6个特殊参数跟普通的卷积层是一样的,不同的是最后多了一个参数7=2,表示分成两组。
特殊参数1:0=256,num_output=256,卷积核的数量
特殊参数2:1=5, kernel_w=5,卷积核的宽
特殊参数3:2=1, dilation_w=1,卷积核rows方向的缩放系数,默认为1,一般不修改或配置
特殊参数4:3=1,stride_w=1,步长
特殊参数5:4=2,pad_w=2,填充大小,Same padding,卷积后图像大小不变
特殊参数6:5=1,bias_term=1,是否开启偏置项,默认为1, 开启
特殊参数7:6=307200,weight_data_size=307200,权重数据大小5*5*48*128*2=307200
特殊参数8:7=2,group=2,分成两组在两个GPU上运行
ncnn源码说明:
第19行(全连接层fc6)
卷积取的是局部特征,全连接就是把前面的局部特征重新通过权值矩阵组装成完整的图。因为用到了所有的局部特征,所以叫全连接。
输入数据是第5层的输出,尺寸为6×6×256=9216。本层共有4096个卷积核,每个卷积核的尺寸为6×6×256,由于卷积核的尺寸刚好与待处理特征图(输入)的尺寸相同,即卷积核中的每个系数只与特征图(输入)尺寸的一个像素值相乘,一一对应,因此,该层被称为全连接层。由于卷积核与特征图的尺寸相同,卷积运算后只有一个值,因此,卷积后的像素层尺寸为4096×1×1,即有4096个神经元。
InnerProduct fc6 1 1 pool5 fc6 0=4096 1=1 2=37748736
特殊参数1:0=4096,num_output=4096,
特殊参数2:1=1,bias_term=1,是否开启偏置项,默认为1, 开启
特殊参数3:2=37748736,weight_data_size=37748736,(6*6*128*2)*4096=37748736
ncnn源码说明:
第21行(Dropout层drop6)
Dropout drop6 1 1 fc6_relu6 fc6_drop6
Dropout以一定概率随机让某些神经元输出设置为0,既不参与前向传播也不参与反向传播。
Dropout主要是为了防止过拟合,为什么有助于防止过拟合呢?可以简单地这样解释,运用了dropout的训练过程,相当于训练了很多个只有半数隐层单元的神经网络(后面简称为“半数网络”),每一个这样的半数网络,都可以给出一个分类结果,这些结果有的是正确的,有的是错误的。随着训练的进行,大部分半数网络都可以给出正确的分类结果,那么少数的错误分类结果就不会对最终结果造成大的影响。
第26行(Softmax层prob)
Softmax prob 1 1 fc8 prob 0=0
softmax可以理解为归一化,如目前图片分类有一百种,那经过 softmax 层的输出就是一个一百维的向量。向量中的第一个值就是当前图片属于第一类的概率值,向量中的第二个值就是当前图片属于第二类的概率值...这一百维的向量之和为1.
softmax的输入层和输出层的维度是一样的,如果不一样,就在输入至 softmax 层之前通过一层全连接层。
全连接层将权重矩阵与输入向量相乘再加上偏置,将n个(−∞,+∞)的实数映射为K个(−∞,+∞)的实数(分数);Softmax将K个(−∞,+∞)的实数映射为K个(0,1)的实数(概率),同时保证它们之和为1。
AlexNet网络结构: