caffe自定义网络模型实现图像识别

前言

caffe是个很好用的机器学习工具,尤其是在图像处理做卷积神经网络方面。要掌握caffe,最主要的要掌握:

  • 数据生成
  • 网络结构定义
  • solver配置文件参数设置

接下来我逐一为大家介绍。

生成LMDB数据

数据下载

这次项目是实现500张5个类别.jpg图片的训练和测试识别。在这里为大家提供了数据源的链接:https://pan.baidu.com/s/1MotUe
这些数据共有500张图片,分为大巴车、恐龙、大象、鲜花和马五个类,每个类100张。编号分别以0,1,2,3,4开头,各为一类。

生成图片清单文件

创建一个sh脚本文件,调用linux命令生成图片清单(当然你喜欢的话,你也可以自己写):
sudo vi examples/images/create_filelist.sh

在这个文件中输入如下代码并保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# /usr/bin/env sh
DATA=/home/hero/caffe/examples/my_first/train
echo "Create train.txt..."
rm -rf $DATA/train.txt
find $DATA -name '*3??.jpg' | cut -d '/' -f8 | sed "s/$/ 1/">>$DATA/train.txt
find $DATA -name '*4??.jpg' | cut -d '/' -f8 | sed "s/$/ 2/">>$DATA/tmp1.txt
find $DATA -name '*5??.jpg' | cut -d '/' -f8 | sed "s/$/ 3/">>$DATA/tmp2.txt
find $DATA -name '*6??.jpg' | cut -d '/' -f8 | sed "s/$/ 4/">>$DATA/tmp3.txt
find $DATA -name '*7??.jpg' | cut -d '/' -f8 | sed "s/$/ 0/">>$DATA/tmp4.txt
cat $DATA/tmp1.txt>>$DATA/train.txt
rm -rf $DATA/tmp1.txt
cat $DATA/tmp2.txt>>$DATA/train.txt
rm -rf $DATA/tmp2.txt
cat $DATA/tmp3.txt>>$DATA/train.txt
rm -rf $DATA/tmp3.txt
cat $DATA/tmp4.txt>>$DATA/train.txt
rm -rf $DATA/tmp4.txt
echo "Done.."

运行

sudo sh examples/images/create_filelist.sh

类似的生成相应的测试图片清单(其中相关了命令不在此介绍,可自行百度)。
最终生成train.txt文件如下(test.txt类似):

1
2
3
4
5
6
7
370.jpg 1
386.jpg 1
398.jpg 1
364.jpg 1
389.jpg 1
352.jpg 1
...

生成lmdb数据文件

由于参数较多,我们还是创建sh脚本文件运行(这样也比较酷):
sudo vi examples/images/create_lmdb.sh

编辑并保存:

1
2
3
4
5
6
7
#!/usr/bin/en sh
DATA=/home/hero/caffe/examples/my_first/train
rm -rf $DATA/img_train_lmdb
rm -rf $DATA/img_test_lmdb
/home/hero/caffe/build/tools/convert_imageset --shuffle=true \
--resize_height=38 --resize_width=38 \
/home/hero/caffe/examples/my_first/train/ $DATA/train.txt $DATA/img_train_lmdb

其中设置参数shuffle打乱图片顺序,为了快速学习,将图片大小resize成38*38(硬件受限,这样程序跑的快一点)。然后输入
sudo sh examples/images/create_lmdb.sh
运行程序,生成所要的数据文件。

生成图片均值文件

为了提高速度和精度,我们一般还会计算并让图片减去均值,这个用caffe自带的工具函数就能实现:

1
sudo build/tools/compute_image_mean examples/mnist/mnist_train_lmdb examples/mnist/mean.binaryproto

第一个参数:examples/mnist/mnist_train_lmdb, 表示需要计算均值的数据,格式为lmdb的训练数据。
第二个参数:examples/mnist/mean.binaryproto, 计算出来的结果保存文件。

网络层layer介绍

这个部分我们就简绍卷积神经网络中最主要的两个层卷积(convolution)层和池化(pooling)层。

Convolution层

就是卷积层,是卷积神经网络(CNN)的核心层。
示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 20
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}

主要参数介绍:

  • num_output: 卷积核(filter)的个数

  • kernel_size: 卷积核的大小。如果卷积核的长和宽不等,需要用kernel_h和kernel_w分别设定

其它参数:

  • stride: 卷积核的步长,默认为1。也可以用stride_h和stride_w来设置。

  • pad: 扩充边缘,默认为0,不扩充。 扩充的时候是左右、上下对称的,比如卷积核的大小为5*5,那么pad设置为2,则四个边缘都扩充2个像素,即宽度和高度都扩充了4个像素,这样卷积运算之后的特征图就不会变小。也可以通过pad_h和pad_w来分别设定。

  • weight_filler: 权值初始化。 默认为“constant”,值全为0,很多时候我们用”xavier”算法来进行初始化,也可以设置为”gaussian”

  • bias_filler: 偏置项的初始化。一般设置为”constant”,值全为0。

  • bias_term: 是否开启偏置项,默认为true, 开启

  • group: 分组,默认为1组。如果大于1,我们限制卷积的连接操作在一个子集内。如果我们根据图像的通道来分组,那么第i个输出分组只能与第i个输入分组进行连接。

Pooling层

也叫池化层,为了减少运算量和数据维度而设置的一种层。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}

主要参数介绍:

  • kernel_size: 池化的核大小。也可以用kernel_h和kernel_w分别设定。

其它参数:

  • pool: 池化方法,默认为MAX。目前可用的方法有MAX, AVE, 或STOCHASTIC

  • pad: 和卷积层的pad的一样,进行边缘扩充。默认为0

  • stride: 池化的步长,默认为1。一般我们设置为2,即不重叠。也可以用stride_h和stride_w来设置。

pooling层的运算方法基本是和卷积层是一样的。

我的网络模型

包括一个数据层,两个卷积层,两个池化层和两个全连接层。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
name: "MY_FIRST_quick"
layer {
name: "my_first"
type: "Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param {
scale: 0.00390625
mean_file: "examples/my_first/mean.binaryproto"
}
data_param {
source: "examples/my_first/train/img_train_lmdb"
batch_size: 10
backend: LMDB
}
}
layer {
name: "my_first"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
scale: 0.00390625
mean_file: "examples/my_first/mean.binaryproto"
}
data_param {
source: "examples/my_first/test/img_test_lmdb"
batch_size: 10
backend: LMDB
}
}
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 55
pad: 2
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "pool1"
top: "conv2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 27
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2"
top: "pool2"
pooling_param {
pool: AVE
kernel_size: 3
stride: 2
}
}
layer {
name: "ip1"
type: "InnerProduct"
bottom: "pool2"
top: "ip1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 500
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}
layer {
name: "ip2"
type: "InnerProduct"
bottom: "ip1"
top: "ip2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 5
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "accuracy"
type: "Accuracy"
bottom: "ip2"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}

solver文件介绍

solver文件可以算是caffe里最核心的文件了,它就相当于是电脑的CPU,控制着整个网络的运行。
在每一次的迭代过程中,solver做了这几步工作:

  • 1、调用forward算法来计算最终的输出值,以及对应的loss
  • 2、调用backward算法来计算每层的梯度
  • 3、根据选用的slover方法,利用梯度进行参数更新
  • 4、记录并保存每次迭代的学习率、快照,以及对应的状态。

以我的solver文件为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# The train/test net protocol buffer definition
net: "examples/my_first/my_first_quick_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
test_iter: 50
# Carry out testing every 1000 training iterations.
test_interval: 1000
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.0001
momentum: 0.9
weight_decay: 0.004
# The learning rate policy
lr_policy: "fixed"
# Display every 100 iterations
display: 100
# The maximum number of iterations
max_iter: 10000
# snapshot intermediate results
#snapshot: 5000
#snapshot_format: HDF5
#snapshot_prefix: "examples/my_first/my_first_quick"
# solver mode: CPU or GPU
solver_mode: CPU

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))))

训练及结果

同样编写sh脚本文件在终端运行程序,可以看到网络模型的运行过程。

1
2
3
4
5
6
7
#!/usr/bin/env sh
set -e
TOOLS=./build/tools
$TOOLS/caffe train \
--solver=examples/my_first/my_first_quick_solver.prototxt $@

结果如下图所示:
运行结果

心得体会

caffe本身是简单易用的,但是若对神经网络本身算法原理一窍不通,就无法设置合适的参数,来提高图片识别的准确度。因此,在熟练使用工具的基础上,还要加强对算法原理的了解掌握,这两者是相辅相成的关系。

参考链接