数据准备
首先从MNIST网站下载数据集并转换获取的数据格式,运行以下简单的命令脚本
cd $CAFFE_ROOT #$CAFFE_ROOT指的是caffe所在根目录
./data/mnist/get_mnist.sh #从脚本中提供的网址下载数据(.gz格式)并解压、删除已下载的原件,仅保留解压后的数据(如train-images-idx3-ubyte、train-labels-idx1-ubyte)
./examples/mnist/create_mnist.sh #将上述数据转化成lmdb格式
lmdb格式讲解
简述:Caffe中的lmdb数据大约有两类:1. 输入 DataLayer的训练/测试数据集;2. extract_feature输出的特征数据。
lmdb,文件结构简单,一个文件夹里面包含一个数据文件,一个锁文件。数据随意复制,随意传输。访问简单,不需要运行单独的数据库管理进程,只要在访问数据的代码里引用LMDB库,访问时给文件路径即可。
运行完上述的脚本文件后,会在/examples/mnist目录下面得到mnist_train_lmdb和mnist_test_lmdb两个文件夹,每个文件夹下包含有一个数据文件data.lmdb和一个锁文件lock.lmdb。
LeNet:MNIST分类模型
在初始版本的LeNet上稍稍改变:用ReLU替换sigmoid激活函数。已经在$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt
中定义了网络结构。
caffe中的protobuf定义,见$CAFFE_ROOT/src/caffe/proto/caffe.proto,官方的protobuf documents参考Google Protobuf,另参考博客protobuf文件的简单使用
定义MNIST网络
在LeNet的示例中,lenet_train_test.prototxt文件描述了LeNet网络的结构:
首先,给网络一个名字
name:"LeNet"
数据层Data Layer
layer {
name: "mnist" #本层的名字自取
type: "Data" #层类型
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param {
scale: 0.00390625
}
data_param {
source: "examples/mnist/mnist_train_lmdb"
batch_size: 64
backend: LMDB
}
}
从所指定的lmdb中读取数据;batch size为64,scal输入的像素值到[0,1)256分之1=0.00390625;最后产生(输出)两个blob(data blob和label blob)用于下层。
卷积层Convolution Layer
第一个卷积层:
layer {
name: "conv1" # 本层的名字自取
type: "Convolution" # 层类型
bottom: "data" # 接受来自data layer的data
top: "conv1" # 输出/产生conv1 blob
param {
lr_mult: 1#学习率
}
param {
lr_mult: 2
}
convolution_param {
num_output: 20 #输出的channels数为20
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
fillers可以随机的初始化weights和bias值。在本层中,对于weights fillers,使用
xavier算法根据输入输出的神经节点数目自动决定初始化的尺度;对于bisa fillers,采用
constant常数填充,这里用0填充。
lr_mults是层中可学习参数的学习率。在该例子中,weight的学习率与运行期间由solver给定的学习率相同,bias学习率是weight学习率的两倍大,这样做通常有更好的收敛率。
池化层Pooling Layer
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
pooling层相对简单,这里的解释是对来自conv1的blob以max-pooling的方式处理,然后产生名为pool1的blob。如此,可以近似地编写conv2和pool2层。
全连接层Fully Connected Layer
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"
}
}
}
这样就定义了对来自pool2的blob进行
InnerProduct以得到一个有500单元输出的blob ip1。
ReLU处理层
layer {
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}
以相同的名字来命名bottom和top,即对输入进行一次
ReLU变换。同理,可以构建
InnerProduct layer ip2层。
Loss Layer
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}
softmax_loss层实现softmax和多项logistic损失(省时并提高数值稳定性)。它需要两个blob,第一个是预测,第二个是数据层提供的标签。计算损失函数值,在反向传播开始时报告它,并针对ip2启动梯度。
额外注意点:编写层规则
layer {
// ...layer definition...
include: { phase: TRAIN }
}
图层的定义可以包含是否以及何时包含在网络定义中的规则中。示例描述的是一个基于当前网络状态来控制网络中的层包含的规则。规则TRAIN说明该层只包含在训练阶段,默认情况下(无规则时)层包含在整个网络中。因此,lenet_train_test.prototxt有两个具有不同batch_size的Data Layers:一个用于训练,另一个用于测试。
layer {
name: "accuracy"
type: "Accuracy"
bottom: "ip2"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
此外,还有一个Accurry Layer,仅包含在测试阶段,用于每100次迭代报告模型精度。当然,这里所说的100次是在$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt中描述的。
定义MNIST的Solver
具体描述见$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt
# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100
# Carry out testing every 500 training iterations.
test_interval: 500
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# The learning rate policy
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# Display every 100 iterations
display: 100
# The maximum number of iterations
max_iter: 10000
# snapshot intermediate results
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
# solver mode: CPU or GPU
solver_mode: GPU
选择GPU训练,solver_model:GPU
训练和测试模型
运行$CAFFE_ROOT/examples/mnist/train_lenet.sh脚本,训练的主要工具就是caffe中的train操作,并以solver prototxt文件作为它的参数。
#!/usr/bin/env sh
set -e
./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt $@
由图可知,每层的连接和输出规模,并且可在对应的源文件中追踪,如[... net.cpp:84]。此外,我们还能知道哪些层需要反向传播计算,有无输出loss。在初始化完成后,开始训练过程。
根据solver的设置,每100 iterations打印输出训练损失,以及每500 iterations测试一次网络,那么可以看到:
I1208 10:02:38.242851 4482 solver.cpp:218] Iteration 100 (653.884 iter/s, 0.152932s/100 iters), loss = 0.21386
I1208 10:02:38.242880 4482 solver.cpp:237] Train net output #0: loss = 0.21386 (* 1 = 0.21386 loss)
I1208 10:02:38.242887 4482 sgd_solver.cpp:105] Iteration 100, lr = 0.00992565
...
...
I1208 10:02:38.758451 4482 solver.cpp:330] Iteration 500, Testing net (#0)
I1208 10:02:38.809602 4491 data_layer.cpp:73] Restarting data prefetching from start.
I1208 10:02:38.810797 4482 solver.cpp:397] Test net output #0: accuracy = 0.9726
I1208 10:02:38.810818 4482 solver.cpp:397] Test net output #1: loss = 0.0893905 (* 1 = 0.0893905 loss)
...
...
I1208 10:02:51.497777 4482 solver.cpp:447] Snapshotting to binary proto file examples/mnist/lenet_iter_10000.caffemodel
I1208 10:02:51.501615 4482 sgd_solver.cpp:273] Snapshotting solver state to binary proto file examples/mnist/lenet_iter_10000.solverstate
I1208 10:02:51.503881 4482 solver.cpp:310] Iteration 10000, loss = 0.00287347
I1208 10:02:51.503899 4482 solver.cpp:330] Iteration 10000, Testing net (#0)
I1208 10:02:51.553721 4491 data_layer.cpp:73] Restarting data prefetching from start.
I1208 10:02:51.555130 4482 solver.cpp:397] Test net output #0: accuracy = 0.9907
I1208 10:02:51.555150 4482 solver.cpp:397] Test net output #1: loss = 0.0314041 (* 1 = 0.0314041 loss)
I1208 10:02:51.555155 4482 solver.cpp:315] Optimization Done.
I1208 10:02:51.555158 4482 caffe.cpp:259] Optimization Done.
最终的模型,以2进制protobuf文件的形式存储在lenet_iter_10000.caffemodel中,运行测试文件test_lenet.sh.
#!/usr/bin/env sh
./build/tools/caffe test --model=examples/mnist/lenet_train_test.prototxt \
--weights=examples/mnist/lenet_iter_10000.caffemodel -gpu=0
如果遇到权限问题,加上权限:
$ chmod +x ./examples/mnist/test_lenet.sh