Caffe学习系列——3Fine-tuning微调网络

Contents

  1. 1. fine-tuning流程
  2. 2. Caffe调参

本文主要介绍如何在Caffe上微调网络,来适应我们自己特定的新任务。一般来说我们通常都是需要在一些特定的领域里来识别分类,比如服装类、标志等等,但是深度学习中数据集的准备一直是最令人头疼的一件事。我们很难拿到大量的数据。在这种情况下重新训练的一个新的网络是比较复杂,且参数也不好调整,因此数据集为图像时,通过fine-tuning微调是一个比较理想的选择。Fine-tuning的整体思路就是,ImageNet是一个千万级的图像数据库,现已经在ImageNet上训练了一个很牛的网络,我们可以把pretrain的网络拿过来,然后只重新训练最后几层。意思就是以前需要分成1000类,现在只需要识别是狗还是猫,或者衣服是上衣还是裤子。故就只需要把最后一层softmax从40961000的分类器变成40922的分类器。微调网络需要一个已初始化的模型参数文件,这里不同于用某某网络训练自己的数据集方法。后者在训练过程中,这些参数都被随机的初始化。而fine-tuning是在已训练好的参数的基础上,根据我们的分类识别任务进行特定的微调。总而言之,fine-tuning这种策略目前在应用中是非常好使的。接下来介绍一下fine-tuning流程以及简单介绍一下该如何调参。

fine-tuning流程

对网络进行微调的整个流程可以分为以下几步:

  1. 准备数据集(包括训练数据和测试数据)
  2. 计算数据集的均值文件,因为某个特定领域的图像均值文件会跟ImageNet上的数据均值不太一样
  3. 修改网络最后一层的输出类别数,以及最后一层网络的名称,加大最后一层的参数学习速率
  4. 调整solver的配置参数
  5. 加载预训练模型的参数,启动训练

1. 准备数据集(建立与下载)
(1)建立数据集:
这一步在上一篇的博文中已经详细说明,传送门在此(),因此这里就不再赘述。这里的数据格式依然是一个图像路径之后一个空格和类别的ID。再一次提醒,这里ID必须从0开始(即必须是0,1,2,3.。。,而不能是2,4,6),要连续,否则会出现loss不下降的情况。格式如下:
/home/linbiyuan/caffe/data/blogimg/streetsnap/Jillian-harris&_118db71d65c96133.jpg 0
也可以下载。
2. 计算数据集的均值文件
可以使用caffe下的convert_imagset工具

1
/home/linbiyuan/caffe/build/tools/convert_imageset    /home/linbiyuan/caffe/data/blog_img/    /home/linbiyuan/caffe/data/blog_img /train.txt     /home/linbiyuan/caffe/examples/blog_img/train_lmsb   -resize_width=227   -resize_height=227   -check_size    -shuffle true

其中第一个参数是图片的路径,用于拼接的,第二个是label文件,第三个是生成的数据库文件,支持lmdb(如何生成见上一篇博文)和leveldb,第四个和第五个参数是resize的大小,最后一个是是否随机图片的顺序。
当然,你也可以使用imagenet的均值,其均值文件路径是$Caffe_Root/data/ilsvrc12/imagenet_mean.binaryproto
3. 调整网络参数
参照caffe上的例程,可以使用CaffeNet,复制caffenet的train_val.prototxt到自己的目录下,或者复制官网给出的例子——Fine-tuning CaffeNet for Style Recognition on “Flickr Style” Data,下载完之后,模型会放在models的finetune_flickr_style中,可以参考里面的那些文件,适当修改一些地方即可。个人建议:不管训练什么模型,最好自己新建一个目录,然后把所有需要的文件拷贝到该目录下,然后根据自己的需求更改。本文是在examples/下新建了一个blog_img目录。该文件需要修改的地方如下:
train_val.prototxt
(1)修改source和meanfile

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
name: "BlogNet" #这里的名字可修改可不修改,根据自己的意愿,建议还是命名一下
layer {
name: "data"
type: "ImageData"
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param {
mirror: true
crop_size: 227
mean_file: "data/blog_img/blogimg_mean.binaryproto" #这里是第二步求得的均值文件所在的地方
}
image_data_param {
source: "data/blog_img/train.txt" #训练标签所在的地方
batch_size: 512 #可根据自己内存情况调整,最好是8的倍数
new_height: 256 #在finetuning的时候,新问题的图像大小不同于pretraining的图像大小时,只能缩放到同样的大小
new_width: 256
}
}
layer {
name: "data"
type: "ImageData"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
mirror: false
crop_size: 227
mean_file: "data/blog_img/blogimg_mean.binaryproto "
}
image_data_param {
source: "data/blog_img/test.txt" #测试集所在的地方
batch_size: 128
new_height: 256
new_width: 256
}
}

(2)修改输出层fc8

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
layer {
name: "fc8_bogimg" #修改名字,这样预训练模型赋值时会因为名字不匹配从而重新训练,也就达到了适应新任务的目的
type: "InnerProduct"
bottom: "fc7"
top: "fc8_blogimg"
# lr_mult is set to higher than for other layers, because this layer is starting from random while the others are already trained
param {
lr_mult: 10 #调整学习率,因为最后一层是重新学习的,因此需要比其他层更快的学习速率,因此将weight和bias都增加10倍,其他层为1或2,如果
decay_mult: 1 #数据集过小,可以将其他层的学习率置为0,只训练最后一层
}
param {
lr_mult: 20 #上同
decay_mult: 0
}
inner_product_param {
num_output: 5 #修改为你想识别的类别数
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
name: "accuracy"
type: "Accuracy"
bottom: "fc8_blogimg" #别忘了还有这里的名字(fc8层的名字)
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "fc8_blogimg" #别忘了还有这里的名字(fc8层的名字)

bottom: "label"
top: "loss"
}

solver.prototxt
微调,顾名思义微微调整,所以一般来说,相比较于用某网络结构直接训练自己的数据集来说,学习速率、步长、迭代次数都减小。修改地方如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
net: "examples/blog_img/train_val.prototxt"
test_iter: 100
test_interval: 1000
# lr for fine-tuning should be lower than when starting from scratch
base_lr: 0.001
lr_policy: "step"
gamma: 0.1
# stepsize should also be lower, as we're closer to being done
stepsize: 5000
display: 100
max_iter: 10000
momentum: 0.9
weight_decay: 0.0005
snapshot: 5000
snapshot_prefix: "examples/blog_img/img_type"
# uncomment the following to default to CPU mode solving
solver_mode: GPU

4. 开始训练
(1)下载训练好的模型参数文件
执行命令:
./scripts/download_model_binary.py models/bvlc_reference_caffenet
训练命令如下:
./build/tools/caffe train -solver examples/blog_img/solver.prototxt -weights models/bvlc_reference_caffenet /bvlc_reference_caffenet .caffemodel -gpu 0 > /home/linbiyuan/log/blogimgtrain.log 2>&1
最后照例是将控制台输出重定向到日志中,以便查看。
最后达到的accuray是0.92,效果还是不错的。

Caffe调参

一般fine tuning的方式,都是把learning rate(solver.prototxt)调低(为原来的十分之一),之后把训练模型的最后一层或者两层的学习速率调大一点————这就相当于,把模型的前面那些层的学习调低,使得参数更新的慢一点以达到微调的目的。但有可能会导致整个收敛速度变慢,因此还需要增加最大迭代次数。
关于调参,个人感觉很多时候都是经验,在这个情景下,增加一个模块效果是好的,在另一个场景可能就是不好的。没有一个很好的理论支持你,应该怎么调整。简单来讲就是修改学习速率、batchsize等参数。
需要调节的参数(solver.prototxt)主要包括:
base_lr:初始学习率,这个是非常重要的一个参数;momentum:一般设置为0.9,如果base_lr特别低的话也可以设置为0.99或0.999等 weight_decay:默认0.005,可以适当调整,类似于正则化项;
lr_policy:学习率变化策略,常见的有fixed(固定), inv,step等,详细的说明见http://stackoverflow.com/questions/30033096/what-is-lr-policy-in-caffe
最后如果loss出现不收敛的情况,可以尝试一下以下几种方法:

  1. 调小solver里的初始学习率
  2. 数据量小的话,可以只对最后几层的权重进行调整,前面权重保持不变,即把前面的学习率置为0,一定程度上限制了激活的大小,这样就限制了某一过大的误差的影响,这样可以避免迭代方向出现过大的变化。
  3. 在GPU可承受的范围内调大batch_size的大小(贾扬清的解释:理论上batch小是不会影响收敛的。小batch主要的问题是在FC层的计算可能会不是很efficient,如果实在无计可施,可以试试 ;网上其他博主的解释:batch比较小 ,导致样本覆盖面过低,产生了非常多的局部极小点,在步长和方向的共同作用下,导致数据产生了震荡,导致了不收敛 )

总而言之,一般策略是先试一试参数,把display设小一点,多看看结果,找到合适的参数,再整个跑。这里有几篇关于调参的论文(),可以看一下,基于理解和有合理解释的调参才不会变为无意义的工作。