Caffe学习小结

为什么relu比其他函数收敛快,并能保持同样的效果?

RELU层支持in-place计算,这意味着bottom的输出和输入相同以避免内存的消耗。

train.prototxt的多部学习策略net配置文件可合二为一

前后两个配置文件就是学习率 (base_lr) 和最大迭代次数 (max_iter) 不一样,其它都是一样。如果你对配置文件比较熟悉以后,实际上是可以将两个配置文件合二为一的,设置 lr_policy 为 multistep 就可以了。如下:

base_lr: 0.001
momentum: 0.9
weight_decay: 0.004
lr_policy: "multistep"
gamma: 0.1
stepvalue: 4000
stepvalue: 5000

Caffe 中经常使用的数据类型是lmdb/leveldb,从原始图片文件转换成 Caffe 中能够运行的leveldb/lmdb

在 Caffe 中,作者为我们提供了这样一个文件:convert_imageset.cpp,存放在根目录下的 tools 文件夹下。编译之后,生成对应的可执行文件放在 buile/tools/ 下面,这个文件的作用就是用于将图片文件转换成 Caffe 框架中能直接使用的 db 文件。
该文件的使用格式:

convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME
需要带四个参数:
FLAGS: 图片参数组,后面详细介绍
ROOTFOLDER/: 图片存放的绝对路径,从 linux 系统根目录开始
LISTFILE: 图片文件列表清单,一般为一个 txt 文件,一行一张图片
DB_NAME: 最终生成的 db 文件存放目录,如果图片已经下载到本地电脑上了,那么我们首先需要创建一个图片列表清单,保存为txt。

python格式的均值计算

如果我们要使用 python 接口,或者我们要进行特征可视化,可能就要用到 python 格式的均值文件了。首先,我们用 lmdb 格式的数据,计算出二进制格式的均值,然后,再转换成 python 格式的均值。我们可以编写一个 python 脚本来实现:

 #!/usr/bin/env python
import numpy as np
import sys,caffe
if len(sys.argv)!=3:
    print "Usage: python convert_mean.py mean.binaryproto mean.npy"
    sys.exit()
blob = caffe.proto.caffe_pb2.BlobProto()
bin_mean = open( sys.argv[1] , 'rb' ).read()
blob.ParseFromString(bin_mean)
arr = np.array( caffe.io.blobproto_to_array(blob) )
npy_mean = arr[0]
np.save( sys.argv[2] , npy_mean )

caffemodel 文件和*.solverstate 文件区别

.caffemodel 文件里面存放的就是各层的参数,即 net.params,里面没有数据 net.blobs。顺带还生成了一个相应的 .solverstate 文件,这个和 .caffemodel 差不多,但它多了一些数据,如模型名称、当前迭代次数等。两者的功能不一样,训练完后保存起来的 .caffemodel,是在测试阶段用来分类的,而 .*solverstate 是用来恢复训练的,防止意外终止而保存的快照。

绘制网络模型

python/draw_net.py 这个文件,就是用来绘制网络模型的,也就是将网络模型由 prototxt 变成一张图片。

训练样本的一些参数设置指导

假设训练样本总共有 121368 个,如果我们设置 batch_size = 256。将所有训练样本都处理完一次(称为一代,即 epoch),需要 121368 / 256 = 475 次迭代完成。一般我们需要处理完一次所有的训练样本之后,才去进行测试,所以我们一般设置 test_interval >= 475。如果我们想要迭代 100 epoch,我们设置 max_iter = 47500。测试样本如果有 1000 个,训练过程的 batch_size = 25,那么需要 40 次才能完整地测试完所有测试样本,所以我们一般设置 test_iter >= 40。学习率的变化规律,我们设置为随着迭代次数的增加,慢慢降低。总共迭代 max_iter = 47500,我们将变化 5 次,所以设置 stepsize = 47500 / 5 = 9500, 即每迭代 9500 次,我们就降低一次学习率。注意 step_size 不能太小,否则会使学习率迅速降低,模型达不到充分训练的效果。

solver参数含义

net: "examples/AAA/train_val.prototxt"   #训练或者测试配置文件
test_iter: 40   #完成一次测试需要的迭代次数
test_interval: 475  #测试间隔
base_lr: 0.01  #基础学习率
lr_policy: "step"  #学习率变化规律
gamma: 0.1  #学习率变化指数
stepsize: 9500  #学习率变化频率
display: 20  #屏幕显示间隔
max_iter: 47500 #最大迭代次数
momentum: 0.9 #动量
weight_decay: 0.0005 #权重衰减
snapshot: 5000 #保存模型间隔
snapshot_prefix: "models/A1/caffenet_train" #保存模型的前缀
solver_mode: GPU #是否使用GPU

LMDB提供键值对的存储方式,其中每一个键值对就是我们数据集的一个样本。一般而言,键代表ID字符串,值代表序列化后的Caffe框架的Datum类(使用protobuf编译)。
我们几乎总是初始化模型的权重为高斯或均匀分布中随机抽取的值。高斯或均匀分布的选择似乎不会有很大的差别,但也没有被详尽地研究。然而,初始分布的 大小确实对优化过程的结果和网络泛化能力都有很大的影响,随着bn等新的层的提出对于初始化的方法现在网络有更强的容忍度了,不过还是有影响的。主要介绍几种常用的初始化方法:
Gaussian,高斯初始化给定均值与标准差 比如均值一般为0,标准差为 0.01 或0.001
uniform,取均匀分布的数值比如从0到1之间随机取 Xavier也是属于均匀分布的一种
Xavier,为了保证前传和反传过程中不会遇到输出方差越来越大或者越来越小的情况,将其初始化为均匀分布:U(-sqrt(6/(m+n)),sqrt(6/(m+n)))
除此之外也有很多对于初始化的探索,比如将权重初始化为正交矩阵等等

Caffe中的Datum数据结构

Caffe中并不是把向量和矩阵直接放进数据库,而是将数据通过caffe.proto里定义的一个Datum类来封装,数据库中存放的是一个个Datum对象序列化成的字符串。Datum的定义如下:

message Datum {
  optional int32 channels = 1;
  optional int32 height = 2;
  optional int32 width = 3;
  // the actual image data, in bytes
  optional bytes data = 4;
  optional int32 label = 5;
  // Optionally, the datum could also hold float data.
  repeated float float_data = 6;
  // If true data contains an encoded image that need to be decoded
  optional bool encoded = 7 [default = false];
}

网络中间结果及参数可视化

网络不仅仅是一个黑盒子,我们还能查看网络中的某些参数和中间的激活状态。我们可以通过这样的可视化技术,来理解网络到底学习到了什么:

# net.blobs 类型为 OrderedDict
    for layer_name, blob in net.blobs.iteritems():
        print layer_name + '\t' + str(blob.data.shape)

输出网络的参数尺寸,即权重尺寸为 (output_channel, input_channel, filter_height, filter_width); 偏置尺寸为 (output_channel, )

# param[0] 是权重参数;param[1]是偏置参数
    for layer_name, param in net.params.iteritems():
        print layer_name + '\t' + str(param[0].data.shape), str(param[1].data.shape)

数据层

层类型(layer type):Data数据来自于数据库(如LevelDB和LMDB)
层类型:MemoryData数据来自于内存
层类型:HDF5Data数据来自于HDF5
层类型:ImageData数据来自于图片
层类型:WindowData这种格式的训练数据是从一张大图片中滑窗裁剪而来的。滑窗的时候,存在着重叠的问题。
fg_threshold: 0.5 #前景(目标对象)的重叠阈值
bg_threshold: 0.5 #背景(非目标对象)的重叠阈值
fg_fraction: 0.25 #每个批次中作为前景目标的比例值
context_pad: 16 #滑窗上下文的填充值
crop_mode: “warp” #滑窗的类型,warp表示滑窗过程中是固定大小和长宽比的
数据层其中的data_param部分,就是根据数据的来源不同,来进行不同的设置

其他方面

机器学习过程需要训练出模型来,而这个模型其实就是权重。因此solver是一个自动化的寻找权重的方法。 Caffe的更新权重的方法如下:
六种优化算法来求解最优参数
Stochastic Gradient Descent (type: “SGD”),
AdaDelta (type: “AdaDelta”),
Adaptive Gradient (type: “AdaGrad”),
Adam (type: “Adam”),
Nesterov’s Accelerated Gradient (type: “Nesterov”) and
RMSprop (type: “RMSProp”)
1. sigmoid在压缩数据幅度方面有优势,对于深度网络,使用sigmoid可以保证数据幅度不会有问题,这样数据幅度稳住了就不会出现太大的失误。
2. 但是sigmoid存在梯度消失的问题,在反向传播上有劣势,所以在优化的过程中存在不足
3. relu不会对数据做幅度压缩,所以如果数据的幅度不断扩张,那么模型的层数越深,幅度的扩张也会越厉害,最终会影响模型的表现。
4. 但是relu在反向传导方面可以很好地将“原汁原味”的梯度传到后面,这样在学习的过程中可以更好地发挥出来。(这个“原汁原味”只可意会,不必深究)这么来看,sigmoid前向更靠谱,relu后向更强。
生成的特征图个数,如下。如果设置stride为1,前后两次卷积部分存在重叠。如果设置pad=(kernel_size-1)/2,则运算后,宽度和高度不变。

w1=(w0+2*pad-kernel_size)/stride+1;
h1=(h0+2*pad-kernel_size)/stride+1;

pool: 池化方法,默认为MAX。目前可用的方法有MAX, AVE, 或STOCHASTIC
Local Response Normalization (LRN)层:此层是对一个输入的局部区域进行归一化,达到“侧抑制”的效果。可去搜索AlexNet或GoogLenet,里面就用到了这个功能
在caffe中,卷积运算就是先对数据进行im2col操作,再进行内积运算(inner product)。这样做,比原始的卷积操作速度更快。

其他层

Absolute Value层
求每个输入数据的绝对值。
f(x)=Abs(x)
Power层:
对每个输入数据进行幂运算
f(x)= (shift + scale * x) ^ power
BNLL层:
binomial normal log likelihood的简称
f(x)=log(1 + exp(x))
Reshap层:
在不改变数据的情况下,改变输入的维度
假设原数据为:64*3*28*28, 表示64张3通道的28*28的彩色图片
经过reshape变换:

reshape_param {
      shape {
        dim: 0 
        dim: 0
        dim: 14
        dim: -1 
      }
    }

输出数据为:64*3*14*56

全连接层参数Blob和data Blob的区别

全连接层,把输入当作成一个向量,输出也是一个简单向量(把输入数据blobs的width和height全变为1)。全连接层实际上也是一种卷积层,只是它的卷积核大小和原数据大小一致。因此它的参数基本和卷积层的参数一样。输入: n*c0*h*w—输出: n*c1*1*1,在一个卷积层中,输入一张3通道图片,有96个卷积核,每个核大小为11*11,因此这个参数Blob是96*3*11*11. 而在一个全连接层中,假设输入1024通道图片,输出1000个数据,则Blob为1000*1024,因为卷积核大小为图片本身,即全连接层参数blob为2维shape为1000*(1024*h*w即特征图会展平),输出blob为1*1000*1*1,第二个全连接层的参数blob会变为236*1000。另外注意!参数blob没有batchsize项!!!,卷积的参数blob和全连接的参数blobs的shape不一样!
输出分类(预测)精确度,只有test阶段才有,因此需要加入include参数。

Caffe常用命令行解析:

文件的路径要从caffe的根目录开始,其它的所有配置都是这样。也可用train_net和test_net来对训练模型和测试模型分别设定。例如:

train_net: "examples/hdf5_classification/logreg_auto_train.prototxt"
test_net: "examples/hdf5_classification/logreg_auto_test.prototxt"

lr_policy可以设置为下面这些值,相应的学习率的计算为:

- fixed:保持base_lr不变.
- step: 如果设置为step,则还需要设置一个stepsize,  返回 base_lr * gamma ^ (floor(iter / stepsize)),其中iter表示当前的迭代次数
- exp: 返回base_lr * gamma ^ iter, iter为当前迭代次数
- inv: 如果设置为inv,还需要设置一个power, 返回base_lr * (1 + gamma * iter) ^ (- power)
- multistep: 如果设置为multistep,则还需要设置一个stepvalue。这个参数和step很相似,step是均匀等间隔变化,而multistep则是根据stepvalue值变化
- poly: 学习率进行多项式误差, 返回 base_lr (1 - iter/max_iter) ^ (power)
- sigmoid: 学习率进行sigmod衰减,返回 base_lr ( 1/(1 + exp(-gamma * (iter - stepsize))))

参数设置说明:
最大迭代次数。这个数设置太小,会导致没有收敛,精确度很低。设置太大,会导致震荡,浪费时间,权重衰减项,防止过拟合的一个参数默认为L2正则化。
napshot_prefix设置保存路径。还可以设置snapshot_diff,是否保存梯度值,默认为false,不保存。也可以设置snapshot_format,保存的类型。有两种选择:HDF5 和BINARYPROTO ,默认为BINARYPROTO。设置运行模式,默认为GPU,如果你没有GPU,则需要改成CPU,否则会出错。在深度学习中使用SGD,比较好的初始化参数的策略是把学习率设为0.01左右(base_lr: 0.01),在训练的过程中,如果loss开始出现稳定水平时,对学习率乘以一个常数因子(gamma),这样的过程重复多次。对于momentum,一般取值在0.5–0.99之间。通常设为0.9,momentum可以让使用SGD的深度学习方法更加稳定以及快速。如果学习的时候出现diverge(比如,你一开始就发现非常大或者NaN或者inf的loss值或者输出),此时你需要降低base_lr的值(比如,0.001),然后重新训练,这样的过程重复几次直到你找到可以work的base_lr。


 # ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt
    -gpu: 可选参数。该参数用来指定用哪一块gpu运行,根据gpu的id进行选择,如果设置为'-gpu all'则使用所有的gpu运行。如使用第二块gpu运行:
# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 2
-snapshot:可选参数。该参数用来从快照(snapshot)中恢复训练。可以在solver配置文件设置快照,保存solverstate。如:
# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -snapshot examples/mnist/lenet_iter_5000.solverstate
-weights:可选参数。用预先训练好的权重来fine-tuning模型,需要一个caffemodel,不能和-snapshot同时使用。如:
# ./build/tools/caffe train -solver examples/finetuning_on_flickr_style/solver.prototxt -weights models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel
-iterations: 可选参数,迭代次数,默认为50。 如果在配置文件文件中没有设定迭代次数,则默认迭代50次。

-model:可选参数,定义在protocol buffer文件中的模型。也可以在solver配置文件中指定。

-sighup_effect:可选参数。用来设定当程序发生挂起事件时,执行的操作,可以设置为snapshot, stop或none, 默认为snapshot

-sigint_effect: 可选参数。用来设定当程序发生键盘中止事件时(ctrl+c), 执行的操作,可以设置为snapshot, stop或none, 默认为stop
test参数用在测试阶段,用于最终结果的输出,要模型配置文件中我们可以设定需要输入accuracy还是loss. 假设我们要在验证集中验证已经训练好的模型,就可以这样写
# ./build/tools/caffe test -model examples/mnist/lenet_train_test.prototxt -weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 100

这个例子比较长,不仅用到了test参数,还用到了-model, -weights, -gpu和-iteration四个参数。意思是利用训练好了的权重(-weight),输入到测试模型中(-model),用编号为0的gpu(-gpu)测试100次(-iteration)。

caffe程序提供了一个计算均值的文件compute_image_mean.cpp,我们直接使用就可以了,图片减去均值再训练,会提高训练速度和精度。因此,一般都会有这个操作。实际上就是计算所有训练样本的平均值,计算出来后,保存为一个均值文件,在以后的测试中,就可以直接使用这个均值来相减,而不需要对测试图片重新计算。切换到caffe根目录下,命令行运行:

# sudo build/tools/compute_image_mean examples/myfile/img_train_lmdb examples/myfile/mean.binaryproto

compute_image_mean带两个参数,第一个参数是lmdb训练数据位置,第二个参数设定均值文件的名字及保存路径。运行成功后,会在 examples/myfile/ 下面生成一个mean.binaryproto的均值文件。
其他方面:
参数有两种类型:权值参数和偏置项。分别用params[“conv1”][0] 和params[“conv1”][1] 表示 。
我们只显示权值参数,因此用params[“conv1”][0] 数据选得不太好,很吃显存,batch_size设置为16—32之间,运行得不错,但好像batchsize调到64以上才能完全发挥GPU的性能

自定义caffe C++层:

相比于博客中旧版caffe对层的分类,现在的caffe中层的分类有所改变,去掉了vision层,直接由layer层派生。除此之外还有loss层,neuron层,以及data层。
- loss层和data层顾名思义,不加赘述
- 输入blob和输出blob的大小一样,从neuron层派生。例如激活层ReLU,以及逐点操作的exp层和power层。需要实现虚函数SetUp,Forward_cpu,Backward_cpu。
- 输入blob和输出blob的大小不一样,直接从layer层派生,例如conv层。需要实现虚函数SetUp,Reshape,Forward_cpu,Backward_cpu
从layer层派生一定得实现四个虚函数SetUp,Reshape,Forward_cpu,Backward_cpu;从neuron派生则不需要实现Reshape函数。虚函数的函数名,函数返回值,函数参数以及参数类型必须得和基类中的定义一致,建议直接从基类中拷贝。
具体实现自定义层步骤:
1.首先需要在caffe.proto中添加相应的ID号和层用到的参数,分别在:message LayerParameter和message YourNameLayerParameter中,建立cpp中类名和类成员参数与protxt文件中YourName类和your_name_parameter(主要是定制化参数)参数的映射!
2.建立相应的头文件.hpp和实现文件.cpp
3.如果是某一些特殊的layer,可能对CUDA有要求,不过对于Loss这种层直接用CPP来实现是不太会影响模型训练的速度
4.最后添加的layer 最好是在Caffe\src\test目录下再新建一个test文件,确保网络可以test通过,因为一般代码偶尔还是会带点逻辑问题的,到最后训练的时候出问题得不偿失

caffe.proto操作:
message LayerParameter包含layer name,layer type,the name of each bottom blob,the name of each top blob,另外主要是各定义层的实例ID,即YourNameParameter对应your_name_parameter.在message LayerParameter中添加ID,如optional YourNameParameter recurrent_param = 146(即添加和cpp文件);同时更新标记 LayerParameter的available ID!

message YourNameParameter {
参数描述:添加和cpp文件成员函数对应的名字参数ID

}  

这一步不确定是否必须:在message V1LayerParameter中的enum LayerType添加YourName层的层名:YourName = 40;同时添加:optional YourNameParameter your_name_param = 43。

include/caffe/layers/ 文件夹中增加该layer的类的声明your_name_layer.h文件(一般继承自基础类,如Layer、BasePrefetchingDataLayer等)
source/caffe/layers/ 文件夹中增加该layer的类的声明your_name_layer.cpp文件,自己实现的cpp一定要添加注册!在 Layer 工厂注册新 Layer 加工函数

NSTANTIATE_CLASS(YourNameLayer);  
REGISTER_LAYER_CLASS(YourName);  

source/caffe/layers/ 文件夹中增加该layer的类的声明your_name_layer.cu文件,自己实现的cpp一定要添加注册!(根据需要,可不写该文件,即该layer不适用GPU)

YourNameParameter your_name_param = this->layer_param_.your_name_param();  
#这个语句常用于层的初始化操作,从caffe.proto文件和prototxt文件导入相关参数设定
或:num_output_ = this->layer_param_.maxout_param().num_output();#读取prototxt预设值,将prototxt中对应层的设定的参数num_output由caffe.proto解析,装入c++类的成员变量
template <typename Dtype>
void YourNameLayer<Dtype>:: SetUp(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>* top)LayerSetup用于初始化层,开辟空间,填充初始值什么的
Reshape是对输入值进行维度变换,比如pooling接全连接层的时候要先拉成一个向量再计算。

以下是层的输入blobs的属性成员读入到对应层的类的成员函数中用于数据处理或初始化操作,
num_ = bottom[0]->num();
channels_ = bottom[0]->channels();
height_ = bottom[0]->height();
width_ = bottom[0]->width();

以下是层的输入和输出blobs的数据成员读入到对应层的类的成员函数中用于数据处理,Blob是caffe基本的数据结构,用四维矩阵 Batch×Channel×Height×Weight表示,存储了网络的神经元激活值和网络参数,以及相应的梯度(激活值的残差和dW、db)。其中包含有cpu_data、gpu_data、cpu_diff、gpu_diff、mutable_cpu_data、mutable_gpu_data、mutable_cpu_diff、mutable_gpu_diff这一堆很像的东西,分别表示存储在CPU和GPU上的数据(印象中二者的值好像是会自动同步成一致的),其中带data的里面存储的是激活值和W、b,diff中存储的是残差和dW、db,另外带mutable和不带mutable的一对指针所指的位置是相同的,只是不带mutable的只读,而带mutable的可写。

const Dtype* bottom_data = bottom[i]->cpu_data();//cpu_data()只读访问cpu data,如果这个层前后有多个输入输出层,就会有bottom[1],比如accuracy_layer就有两个输入,fc8和label。
const Dtype* top_diff = top[0]->cpu_diff();
Dtype* bottom_data = (*bottom)[i]->mutable_cpu_data(); //mutable_cpu_data读写访问cpu data
Dtype* bottom_diff = (*bottom)[0]->mutable_cpu_diff();  
Dtype* top_diff = (*bottom)[0]->mutable_cpu_diff();  
  CHECK_EQ(channels_, bottom[bottom_id]->channels())  
        << "Inputs must have same channels.";  

每层的参数会存在this->blobs_里,一般this->blobs_[0]存W,this->blobs_[1]存b,this->blobs_[0]->cpu_data()存的是W的值,this->blobs_[0]->cpu_diff()存的梯度dW,b和db也类似,然后换成gpu是存在GPU上的数据,再带上mutable就可写了,每层在Backward中只是计算dW和db,而W和b的更新是在Net的Update函数里最后一起更新的。solver主要就是两个步骤反复迭代:
①不断利用ComputeUpdateValue计算迭代相关参数,比如计算learning rate,把weight decay加上什么的,
②调用Net的Update函数对整个网络进行更新。
.cu文件中运算时基本都调用了cuda核函数,可以在math_function.cu中查
.cpp文件中需要实现的函数说明:
SetUp: 进行check
Reshape: 更改top blob的大小
Forward_cpu: 实现正向传播
Backward_cpu: 实现反向传播
REGISTER_LAYER_CLASS: 最后注册层。

在device_alternate.hpp中,通过#ifdef CPU_ONLY定义了一些宏来取消GPU的调用:

#define STUB_GPU(classname)
#define STUB_GPU_FORWARD(classname, funcname)
#define STUB_GPU_BACKWARD(classname, funcname)
vector<bool> param_propagate_down_; // 这个bool表示是否计算各个blob参数的diff,即传播误差Backward里面有个propagate_down参数,用来表示该Layer是否反向传播参数

总结:新添加的layer继承自其它layer(如Layer等)时,必须实现其所有纯虚函数(如Forward_cpu()、Backward_cpu()等函数),出现该错误,可能是在添加新的layer时,没有实现其基类中有关CPU操作纯虚函数; 若没有实际应用的函数,可以没有具体实现,此时在函数内部只需写NOT_IMPLEMENTED;在Forward和Backward的具体实现里,会根据Caffe::mode()进行对应的操作,即,使用cpu或者gpu进行计算,两个都实现了对应的接口Forward_cpu、Forward_gpu和Backward_cpu、Backward_gpu,这些接口都是virtual,具体还是要根据layer的类型进行对应的计算(注意:有些layer并没有GPU计算的实现,所以封装时加入了CPU的计算作为后备)。另外,还实现了ToProto的接口,将Layer的参数写入到protocol buffer文件中

Caffe python layer的自定义

Caffe通过Boost中的Boost.Python模块来支持使用Python定义Layer!!!caffe官方的python layer应用实例在caffe/examples/pycaffe/layers路径下,有pyloss.py自定义层模块和pascal_multilabel_datalayers.py自定义层模块,一个是新增loss层的定义,另一个是数据层的定义。在应用自定义Python层的demo中,须指明模块的路径和import引入。

自定义网络和自定义文件:

1.写应用自定义层的demo-mynet.prototxt,包含自定义层:
Python层
layer {
name: 'MyPythonLayer'
type: 'Python'
top: 'output'
python_param {
module: 'mypythonlayer'
layer: 'MyLayer'
param_str: "{\'data_dir\':\'../../images\',\'num\': 100}"
}
# set loss weight so Caffe knows this is a loss layer
loss_weight: 1
}

2.在某路径下写Python模块mypythonlayer.py,对应自定义网络conv.prototxt中的python_param中的module: ‘mypythonlayer’,py文件中定义的类名为MyLayer,对应python_param中的layer: ‘MyLayer’

3.编写应用新定义python层的demo,须要注意的是加入语句:sys.path.append(“/home/sunlibo/softwares/caffe/examples/pycaffe/”)(因为mypythonlayer.py在该路径下) 和import mypythonlayer(引入mypythonlayer.py模块)

分析官方的pyloss定义层

1.在编译的时候加上WITH_PYTHON_LAYER的选项,如果没有加可以先make clean删除编译后的文件,再重新编译
2.python的自定义层文件在写网络定义时,在prototxt中(如下),模块名是pyloss,对应的pyloss.py文件,注意自定义的所有的Python层的type都是Python类型!

layer {
  type: 'Python'
  name: 'loss'
  top: 'loss'
  bottom: 'ipx'
  bottom: 'ipy'
  python_param {
    # 模块名 -- 通常也是文件名 -- 若在caffe路径的$PYTHONPATH下,则不需要再用sys.path.append("。。。")引入自定义层的文件路径。
    module: 'pyloss'
    # 层名 -- 对应pyloss.py模块里的类名
    layer: 'EuclideanLossLayer'
  }
  # set loss weight so Caffe knows this is a loss layer
  loss_weight: 1
}

3.pyloss.py文件编写

# pyloss.py
import caffe
import numpy as np

class EuclideanLossLayer(caffe.Layer):

    def setup(self, bottom, top):
        # check input pair
        if len(bottom) != 2:
            raise Exception("Need two inputs to compute distance.")

    def reshape(self, bottom, top):
        # check input dimensions match
        if bottom[0].count != bottom[1].count:
            raise Exception("Inputs must have the same dimension.")
        # difference is shape of inputs
        self.diff = np.zeros_like(bottom[0].data, dtype=np.float32)
        # loss output is scalar
        top[0].reshape(1)

    def forward(self, bottom, top):
        self.diff[...] = bottom[0].data - bottom[1].data
        top[0].data[...] = np.sum(self.diff**2) / bottom[0].num / 2.

    def backward(self, top, propagate_down, bottom):
        for i in range(2):
            if not propagate_down[i]:
                continue
            if i == 0:
                sign = 1
            else:
                sign = -1
            bottom[i].diff[...] = sign * self.diff / bottom[i].num

4.阅读caffe源码pythonlayer.hpp可以知道,类PythonLayer继承自Layer,并且新增私有变量boost::python::object self来表示我们自己定义的python layer的内存对象。类PythonLayer类的成员函数LayerSetUP, Reshape, Forward_cpu和Backward_cpu分别是对我们自己定义的python layer中成员函数setup, reshape, forward和backward的封装调用
类,直接继承的是caffe.Layer,然后必须重写setup(),reshape(),forward(),backward()函数,其他的函数可以自己定义,没有限制。
setup()是类启动时该做的事情,比如层所需数据的初始化。
reshape()就是取数据然后把它规范化为四维的矩阵。每次取数据都会调用此函数。
forward()就是网络的前向运行,这里就是把取到的数据往前传递,因为没有其他运算。
backward()就是网络的反馈,data层是没有反馈的,所以这里就直接pass。

源码解析

DummyData这个是用来开发和测试的层
1.初始化的过程:train函数先生成solver类的对象,solver对象中有net类的内部变量,所以solver对象生成的同时生成了net类对象,net调用layer_factory循环生成每个层,solver对象是根据train函数中传入的prototxt文件来初始化的。同理net类对象里有一个layer类的vector容器,里面实际将会装进各个不同的层,net类对象会通过传入solver类对象的初始化量来找到xxx.prototxt这样的层描述文件中的内容,来初始化这个layer类的vector容器,最后根据读入model或是filler来初始化参数。然后训练的过程:train函数执行solver的solve函数,solve函数会根据设定的参数循环调用net类的forwardbackward函数,net类的forforward和backward函数主要负责控制前向和反相的顺序,按照顺序调用不同的layer前向和反向的函数,而layer类中的forward和backward函数负责不同层实现的细节。

2.主线程在Net Init的时候执行各个layer的setup函数,执行第一个数据层的setup函数时,开启一个线程InternalThread,此线程从LMDB中读取数据。数据线程和主线程之间的通信采用两个不定向数组来实现线程安全。主线程申请4个(默认)batch空间,将batch指针push到prefetch_free_数组,然后在数据线程,逐个从prefetch_free_中pop出来,调用load_batch,从lmdb中读取数据填充至该batch,然后将该batch push到prefetch_full_数组;直至prefetch_free_数组中元素为空时,交出CPU,阻塞等待。

3.layer的setup相关具体操作是,先根据param找到module的位置,再加载module,再根据层名加载层,然后前向计算反向计算什么的。setup和reshape是在caffe构造网络结构的时候调用的,这个时候bottom[I].data的shape显示应该是不确定的,因为这个时候并不知道batch_size是多少。forward和backward是在train阶段调用的,这个时候bottom[I].data的shape显示的就是batch_size *C*h*w的了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值