caffe是一个非常清晰且高效的深度学习框架,目前有着不少的用户,也渐渐的形成了自己的社区,社区上可以讨论相关的问题。
我从开始看深度学习的相关内容到能够用caffe训练测试自己的数据,看了不少网站,教程和博客,也走了不少弯路,在此把整个流程梳理和总结一遍,以期望可以可以仅仅通过这一篇文章就可以轻松的用caffe训练自己的数据,体验深度学习的乐趣。(本文中包含了很多的链接,链接中有我看过的详细的教程和讲解,以方便大家理解)
1.安装和配置caffe(包括安装CUDA)
caffe的安装和编译,官网上已经写的非常的详细了。官网上的安装链接 : 链接 只要按照官网的说明一步步的进行,一般都是不会出什么问题的。并且,官网给出来ubuntu,Mac,和Windows环境下安装和配置的链接。条件允许的话,还是尽量在Ubuntu下安装和配置吧,对于不熟悉Ubuntu的人来说可能比较麻烦,但是我感觉,在linux下安装,编译,包括后面的使用都是比较方便的。
这里需要提前说明的一点是,在官方给的安装文档里,下面几个是可以选择安装的
- OpenCV >= 2.4 including 3.0
- IO libraries:
lmdb
,leveldb
(note: leveldb requiressnappy
) - cuDNN for GPU acceleration (v5)
其中,cuDnn是专门针对CUDA的加速套件,可以为在GPU上跑深度学习的程序进行加速,所以这个还是安装一下吧,为以后跑程序能省些时间。
2. cuDNN的安装
这里单独强调一下cuDNN的安装(网上给的很多教程有问题,会导致后面的编译失败),cuDNN的安装需要在CUDA安装完成后进行。
cuDnn需要从Nvidia的官网上下载,并且需要先注册才行,注册的时候会让你填写一些调查问卷,注册完成后,选择正确的版本进行下载。我这里下载的是:Download cuDNN v5 (May 27, 2016), for CUDA 8.0 cuDNN v5 Library for Linux版本,然后解压缩,并且放到相应的文件夹下,最后修改一下权限
<span style="background-color: rgb(240, 240, 240);">tar xvzf cudnn-8.0-linux-x64-v4.tgz #这里要注意你下载的版本,需要解压你下载的对应版本的文件</span>解压后的文件夹名字是cuda
sudo cp cuda/include/cudnn.h /usr/local/cuda/include
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64
sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn*
网上的不少教程一般给到上面这些就没有了,我安装的时候,仅仅进行上面的步骤,在make test的时候会报错的。错误信息大致如下:
CXX/LD -o .build_release/tools/upgrade_net_proto_text.bin .build_release/lib/libcaffe.so: undefined reference to caffe::curandGetErrorString(curandStatus)
.build_release/lib/libcaffe.so: undefined reference to caffe::BaseConvolutionLayer::weight_gpu_gemm(double const*, double const*, double*)
.build_release/lib/libcaffe.so: undefined reference to caffe::BaseConvolutionLayer::forward_gpu_bias(double*, double const*)
找了好多资料,最后在stackoverflaw上找到了解决办法,我这里把链接贴出来:链接
其实方法就是,在/usr/local/cuda/lib64 目录下,执行如下操作(网上的大神说,复制过程破坏了软连接,所以要自己手动建立一下软连接)
sudo rm libcudnn.so.5 libcudnn.so
sudo ln -sf libcudnn.so.5 libcudnn.so
sudo ln -sf libcudnn.so.5.1.3 libcudnn.so.5
sudo ldconfig /usr/local/cuda/lib64
此时,再重新编译,运行 make all 然后,make test ,在然后make runtest 就可以了。
3.在MNIST上用caffe训练LeNet网络
MNIST是一个手写数字的数据集,LeNet 是一个比较简单的深度学习网络模型。
这个过程,官方是给了教程的,附上链接:链接
但是当时我按着步骤一步步的执行,最后也能够完全运行成功,但是程序是怎么运行成功的,怎么跑通的,原理是什么,我还是搞不懂。
为了弄清楚程序具体的执行过程,我尝试利用自己找的一些数据进行训练和测试,以便加深对caffe和深度学习的理解。
4.训练和测试自己的数据
这里要感谢这篇博客的给我的巨大帮助:点击打开链接。虽然博客写的很好,但是还是有一些不详细的地方。我在这篇博客的基础上进行完成和补充,尽量把整个过程描述的更加详细。
1)准备数据
要想训练自己的网络,首先要有图片数据,前期在学习阶段,为了节省时间,先从小的数据集开始训练和学习比较合适,博客的博主给了一个小数据集的链接,共500张图片,分为大巴,恐龙,大象,鲜花和马 五个类别,每个类别100张,数据集的下载链接为:http://pan.baidu.com/s/1nuqlTnN
编号为3,4,5,6,7开头的,分别为一类,每一类的100张图片,用80个作为训练,20个作为测试,图片放在caffe目录下的data目录下,即训练图片的目录为data/re/train,测试图片的目录为data/re/test
2)图片转换为lmdb格式
caffe中,不能直接拿jpg,png之类的图片文件进行训练,需要将图片格式转换为lmdb格式才可以使用。而且caffe提供了转换格式所需要的工具,我们只需要写几行代码就能转换完成。
在examples文件夹下新建一个myffle文件夹,这个文件用来存放配置文件,脚本文件和lmdb文件,然后编写一个脚本文件,create_filelist.sh用来生成train.txt和test.txt两个文本文件,这连个文本文件包含图片的列表清单。然后编辑这个脚本文件如下:
#!/usr/bin/env sh
DATA=data/re/
MY=examples/myfile
echo "Create train.txt..."
rm -rf $MY/train.txt
for i in 3 4 5 6 7
do
find $DATA/train -name $i*.jpg | cut -d '/' -f4-5 | sed "s/$/ $i/">>$MY/train.txt
done
echo "Create test.txt..."
rm -rf $MY/test.txt
for i in 3 4 5 6 7
do
find $DATA/test -name $i*.jpg | cut -d '/' -f4-5 | sed "s/$/ $i/">>$MY/test.txt
done
echo "All done"
然后运行这个脚本文件
sudo sh examples/myfile/create_filelist.sh
然后就可以得到train.txt和test.txt文件了,打开看看基本上就知道这个文件是干啥的了吧。(就是为每个图片做类别标记)
(其实,上述步骤的脚本文件如果看不懂或者觉得不方便,用c写个文件生成train.txt和test.txt也可以。我自己对脚本文件不太熟悉,所以这些操作我有时候会用c写。)
接着在编写一个脚本文件,调用convert_imageset命令来转换数据格式。(就是把图片格式的文件转换成了lmdb格式)
sudo vi examples/myfile/create_lmdb.sh
在其中插入
#!/usr/bin/env sh
MY=examples/myfile
echo "Create train lmdb.."
rm -rf $MY/img_train_lmdb
build/tools/convert_imageset \
--shuffle \
--resize_height=256 \
--resize_width=256 \
/home/xxx/caffe/data/re/ \
$MY/train.txt \
$MY/img_train_lmdb
echo "Create test lmdb.."
rm -rf $MY/img_test_lmdb
build/tools/convert_imageset \
--shuffle \
--resize_width=256 \
--resize_height=256 \
/home/xxx/caffe/data/re/ \
$MY/test.txt \
$MY/img_test_lmdb
echo "All Done.."
图片的大小不一致,所以统一转换成了256×256 的大小,运行成功后,会在examples/myfile下生成两个文件,img_train_lmdb和img_val_lmdb,这个两个文件就是图片数据集转换后的文件。
3)计算均值并保存
图片减去均值再训练,可以提高训练速度和精度。因此,一般都会有这个操作。(比如非常有名的描述AlexNet的论文中,也有这一步预操作)
同样的,减去均值的操作caffe也写好了,直接调用就行了,即compute_image_mean.cpp文件
sudo build/tools/compute_image_mean examples/myfile/img_train_lmdb examples/myfile/mean.binaryproto
compute_image_mean带两个参数,第一个参数是lmdb训练数据位置,第二个参数设定均值文件的名字及保存路径。
运行成功后,会在 examples/myfile/ 下面生成一个mean.binaryproto的均值文件。
4)创建神经网络模型并编写配置文件
这里,就使用caffe自带的caffenet模型进行训练,把该模型和相应的配置文件都拷贝过来,操作如下:
sudo cp models/bvlc_reference_caffenet/solver.prototxt examples/myfile/
sudo cp models/bvlc_reference_caffenet/train_val.prototxt examples/myfile/
solver.prototxt是整个网络的配置文件,将其修改如下:
net: "examples/myfile/train_val.prototxt"
test_iter: 2
test_interval: 50
base_lr: 0.001
lr_policy: "step"
gamma: 0.1
stepsize: 100
display: 20
max_iter: 500
momentum: 0.9
weight_decay: 0.005
# snapshot intermediate results
snapshot: 500
snapshot_prefix: "examples/myfile/results/my"
solver_mode: GPU
关于配置文件参数不理解的地方,可以看链接:链接
然后,修改 train_val.protxt,修改成如下形式
name: "CaffeNet"
layer {
name: "data"
type: "Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param {
mirror: true
crop_size: 227
mean_file: "examples/myfile/mean.binaryproto"
}
# mean pixel / channel-wise mean instead of mean image
# transform_param {
# crop_size: 227
# mean_value: 104
# mean_value: 117
# mean_value: 123
# mirror: true
# }
data_param {
source: "examples/myfile/img_train_lmdb"
batch_size: 50
backend: LMDB
}
}
layer {
name: "data"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
mirror: false
crop_size: 227
mean_file: "examples/myfile/mean.binaryproto"
}
# mean pixel / channel-wise mean instead of mean image
# transform_param {
# crop_size: 227
# mean_value: 104
# mean_value: 117
# mean_value: 123
# mirror: false
# }
data_param {
source: "examples/myfile/img_test_lmdb"
batch_size: 50
backend: LMDB
}
}
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 96
kernel_size: 11
stride: 4
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "conv1"
top: "conv1"
}
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
layer {
name: "norm1"
type: "LRN"
bottom: "pool1"
top: "norm1"
lrn_param {
local_size: 5
alpha: 0.0001
beta: 0.75
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "norm1"
top: "conv2"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 256
pad: 2
kernel_size: 5
group: 2
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 1
}
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "conv2"
top: "conv2"
}
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2"
top: "pool2"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
layer {
name: "norm2"
type: "LRN"
bottom: "pool2"
top: "norm2"
lrn_param {
local_size: 5
alpha: 0.0001
beta: 0.75
}
}
layer {
name: "conv3"
type: "Convolution"
bottom: "norm2"
top: "conv3"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 384
pad: 1
kernel_size: 3
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
name: "relu3"
type: "ReLU"
bottom: "conv3"
top: "conv3"
}
layer {
name: "conv4"
type: "Convolution"
bottom: "conv3"
top: "conv4"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 384
pad: 1
kernel_size: 3
group: 2
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 1
}
}
}
layer {
name: "relu4"
type: "ReLU"
bottom: "conv4"
top: "conv4"
}
layer {
name: "conv5"
type: "Convolution"
bottom: "conv4"
top: "conv5"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
group: 2
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 1
}
}
}
layer {
name: "relu5"
type: "ReLU"
bottom: "conv5"
top: "conv5"
}
layer {
name: "pool5"
type: "Pooling"
bottom: "conv5"
top: "pool5"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
layer {
name: "fc6"
type: "InnerProduct"
bottom: "pool5"
top: "fc6"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
inner_product_param {
num_output: 4096
weight_filler {
type: "gaussian"
std: 0.005
}
bias_filler {
type: "constant"
value: 1
}
}
}
layer {
name: "relu6"
type: "ReLU"
bottom: "fc6"
top: "fc6"
}
layer {
name: "drop6"
type: "Dropout"
bottom: "fc6"
top: "fc6"
dropout_param {
dropout_ratio: 0.5
}
}
layer {
name: "fc7"
type: "InnerProduct"
bottom: "fc6"
top: "fc7"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
inner_product_param {
num_output: 4096
weight_filler {
type: "gaussian"
std: 0.005
}
bias_filler {
type: "constant"
value: 1
}
}
}
layer {
name: "relu7"
type: "ReLU"
bottom: "fc7"
top: "fc7"
}
layer {
name: "drop7"
type: "Dropout"
bottom: "fc7"
top: "fc7"
dropout_param {
dropout_ratio: 0.5
}
}
layer {
name: "fc8"
type: "InnerProduct"
bottom: "fc7"
top: "fc8"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
inner_product_param {
num_output: 1000
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
name: "accuracy"
type: "Accuracy"
bottom: "fc8"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "fc8"
bottom: "label"
top: "loss"
}
这个文件其实就是定义了整个网络的模型,每一个layer就是一层,你在每一层中定义好这是卷积层还是池化层,神经元有多少等等,具体的参数的含义可以看链接:点击打开链接
5)训练和测试
只需要直接输入下面的命令就可以完成了。(这里需要注意的是,训练和测试整个的流程是一起执行的,因为上面的train_val.prototxt文件把train和test的参数都设定好了,所以程序训练完了以后就会直接进入测试数据的阶段,其实也可以写成两个文件分开执行。在solver.prototxt中,有一行为
snapshot_prefix: "examples/myfile/results/my"
这就表示,你训练好的权值都在results文件夹下面保存着,以后如果想要使用训练好的模型,直接调用rusult下的文件进行测试就可以。)
最后的输出结果中,
Test net output #0:accuracy:0.95
这一行,就表示在测试数据上,用自己训练的网络进行分类的正确率达到了95%。