使用caffe框架复现AlexNet
使用caffe复现AlexNet
1.caffe安装
-
caffe 坑最多的就是编译安装了,每台机的情况不一样,只能是遇到什么问题去查对应的方法了
-
前置需求包
-
cuda, cudnn, opencv, etc... 这几个应该是最麻烦的了
-
makefilelist, makefile.configure 选择需要的
-
编译 pycaffe
2.数据准备
-
ILSVRC2012
-
因为是做分类,所以要利用xml文件切割出目标对象(图片)
-
最终会有 500k+ 个训练图片(没有使用ILSVRC2012额外训练集), 50k个校验图片
-
-
label文件,方便以后测试时得到index后找到对应的label
-
train 文件, 包含图片在磁盘的位置以及label(int 整形的label)
-
val 文件,类似 train 文件
-
(可选)利用$CAFFE_ROOT/build/tools/convert_imageset 建立lmdb文件
-
根据论文所述 要把图片resize 成 256 × 256
-
可以使用python代码生成lmdb文件用python读写lmdb数据库 既然是决定用python接口 那就多用python实现比较好。用图片生成lmdb的那段代码,需要修改label的生成方式。另外它做了直方图均衡化,我不太明白有什么用,如果测试数据没有做均衡化的话准确率会很低,loss会很高,所以删掉了那一段
-
因为可以直接使用图片作为输入,所以这一步可选
-
区别:lmdb的io更快,batch_size(下面解释)可以设得更大
-
-
-
(可选)使用$CAFFE_ROOT/build/tools/compute_image_mean 得到mean.binaryproto均值文件
-
要使用这一步的话就必须生成lmdb文件了
-
2.1 prototxt编写
-
train_val.prototxt
-
使用$CAFFE_ROOT/models/bvlc_alexnet/train_val.prototxt 即可(最好得备份一份)
-
修改 data 层 source 位置
-
修改 data 层 mean_file 位置
-
如果前一步没有生成mean.binaryproto文件的话 直接使用[104, 117, 123]作为均值
-
-
如果使用原始图片作为输入的话,在这一步resize图片,而batch_size大概只能设32或者16(2的指数倍有加速), 不然训练时会有大量的Waiting for data, 大部分时间都浪费在io上,如果数据存在固态硬盘的话会快一点。会快多少我也没试过,然而要从随机初始化训练ilsvrc的话是需要上百个epoch的,所以最好还是使用lmdb的格式训练减少io的时间浪费
-
(可选)可以把 Data 层中的TEST的部分删掉,TEST的作用是看校验集的loss变化能够帮助判断模型是否过拟合
-
关于 crop_size 当输入图片的size比crop_size大时会随机裁剪,注:这跟论文讲的crop的方法不一样,想要得到论文的效果就要改,具体要改底层,自己写一个层,修改crop的方式,重新编译caffe,也可以在训练图片之前就用论文所说的crop方法处理好。这里不推荐,因为这是针对ilsvrc数据集有用的trick,对于其他数据集不一定有用,而且最终能提高的准确率不多。
-
-
deploy.prototxt
-
直接使用$CAFFE_ROOT/models/bvlc_alexnet/deploy.prototxt 即可 这个不用改
-
-
solver.prototxt
-
大部分超参数是不用改的,都是AlexNet当时的训练参数
-
可以修改 test_iter 这个参数指(校验)测试时要跑多少个迭代,train_val.prototxt中,data TEST层中的batch_size 是50, 这2个默认值相乘等于50k,即校验集中所有图片的数量,但为了训练加速,可以把这个值设小10倍,这样就只校验了10%的数据
-
修改 test_interval 这个参数指每多少次迭代校验一次,默认值为1000, 是很频繁的,实际训练时,大部分时间都浪费在校验,特别是训练前期,校验根本没有意义,可以改成10000。如果有更智能调整这个参数的方法就好了,因为前期的校验没有意义,而后期需要更频繁的盯着校验集loss的变化防止过拟合,后面会提到一个trick可以达到这个效果
-
修改 snapshot_prefix 这个参数指模型的保存位置
-
stepsize 这个参数指每到 {stepsize} 次迭代后学习率 lr 会减小 这个值就不改了
-
snapshot 这个参数值每到 {snapshot} 次迭代后,保存一次模型和当前状态 这个参数也不改了
-
3.训练
-
注意随机打乱训练样本
-
可以在编写train文件的时候就打乱(推荐这种方法,这样用多种网络训练时可以保证它们的遍历顺序是一致,直接对比网络的效果,消除打乱的偶然性)
-
如果调用convert_imageset的话 加上shuffle参数就可以打乱
-
如果直接使用图片作为网络数据,在imagedataparam那里设置shuffle: true也可以打乱
-
-
断点训练,可以注意到每{snapshot}次迭代后会保存一份.solverstate文件,这个文件包含了当前状态还有模型参数,可以恢复到.solverstate的状态继续训练,不用从头再来,再也不怕断电死机了!
-
链接中还有关于.solverstate和.caffemodel文件关系的讨论
-
可以利用这个断点恢复功能,一开始先调较大的test_interval 等到迭代了100k次以后再把test_interval调小;对 display 的参数设置也是同理,一开始可以调小一点,确定模型在训练,训练速度是否合理(如果要好几个星期才能跑完,就去查查资料看看这个模型别人需要多长时间,如果差距较远肯定是设置有问题,或者你的机器就不适合跑这个,注:使用速度较快的GPU的话AlexNet的训练时间约为1天 Slide181 )
-
其他论文难以复现到论文所属准确率的原因大概也是研究者用了这个trick,他们观察train_loss, val_loss曲线有异样的时候立刻修改超参数,也即他们训练模型时超参数可能在每个阶段都不一样。
-
4.校验测试
-
在 $CAFFE_ROOT/models/bvlc_alexnet/readme.md 中给出了这个网络、solver的理想准确率,那么就只要测试自己的模型有没有达到这个准确率
-
我的结果如图
-
因为输出数据太长,前30k次迭代的log数据丢失; 断电中断,缺少了100k到120k次迭代的log数据,比较好的处理方法是把输出信息输出到一个文件中,如 python.py > train_log.txt
-
校验信息如下,有如readme.md中所说的 360,000 次迭代左右的校验loss较低,准确率最高的情况,由于为了训练加速,校验的迭代也不是数据集
Iteration accuracy loss 35000 0.45704 2.42176 36000 0.46422 2.38812 37000 0.46158 2.41665 38000 0.46306 2.39794 39000 0.46296 2.3979 40000 0.46828 2.36422 41000 0.46748 2.38362 42000 0.4658 2.39916 43000 0.4658 2.40238 44000 0.46992 2.36938 45000 0.4802 2.3184 46000 0.4668 2.38267 47000 0.48022 2.32808 48000 0.46352 2.39329 49000 0.47526 2.34462 50000 0.47644 2.34458 51000 0.4751 2.36333 52000 0.4834 2.30024 53000 0.47906 2.32603 54000 0.47286 2.35124 55000 0.4724 2.3462 56000 0.472701 2.33267 57000 0.47768 2.3229 58000 0.47238 2.35458 59000 0.48064 2.31515 60000 0.47758 2.3379 61000 0.48032 2.29905 62000 0.48348 2.30252 63000 0.48478 2.2796 64000 0.48156 2.31686 65000 0.4816 2.30085 66000 0.48342 2.28999 67000 0.4815 2.32701 68000 0.48592 2.29178 69000 0.4873 2.27052 70000 0.4818 2.30355 71000 0.490739 2.25992 72000 0.47574 2.34744 73000 0.48046 2.30828 74000 0.4852 2.28685 75000 0.48344 2.30387 76000 0.47864 2.32754 77000 0.4872 2.27676 78000 0.49032 2.2646 79000 0.48294 2.30437 80000 0.48428 2.27301 81000 0.47848 2.33244 82000 0.49182 2.24329 83000 0.49142 2.26756 84000 0.49262 2.2492 85000 0.48958 2.26518 86000 0.4886 2.28078 87000 0.49106 2.25509 88000 0.49072 2.25313 89000 0.49508 2.24154 90000 0.49152 2.26005 91000 0.48406 2.30251 92000 0.49448 2.24071 93000 0.49156 2.26402 94000 0.49138 2.25638 95000 0.49016 2.29438 96000 0.49256 2.24939 97000 0.49278 2.24709 98000 0.4937 2.25992 99000 0.4937 2.23724 100000 0.4883 2.27509 120000 0.5888 1.75249 130000 0.6218 1.68968 140000 0.597 1.75793 150000 0.601 1.73024 160000 0.6138 1.70104 170000 0.608 1.75883 180000 0.6156 1.72881 190000 0.5984 1.78794 200000 0.6024 1.79929 210000 0.6054 1.81473 220000 0.6208 1.76362 230000 0.642 1.75042 240000 0.6068 1.80905 250000 0.6174 1.76998 260000 0.6248 1.74679 270000 0.6168 1.80042 280000 0.625 1.76384 290000 0.612 1.79752 300000 0.6156 1.79072 310000 0.6098 1.8497 320000 0.6258 1.79119 330000 0.6454 1.7682 340000 0.6106 1.84174 350000 0.6152 1.78539 360000 0.628 1.76083 370000 0.6206 1.8153 380000 0.6276 1.76262 390000 0.6148 1.79527 400000 0.6234 1.78415 410000 0.6096 1.85402 420000 0.6256 1.79461 430000 0.6444 1.76951 440000 0.6116 1.84176 -
最终的top-5错误率为 16.36%(450k次迭代, 把360k迭代的模型文件弄丢了……)比论文的15.3%高一点,因为不是按论文的crop方法,但是比readme中的19.8%要低不少,这点还挺意外,大概是它是没有用xml切图直接训练的吧。
-
5.总结
-
本次实验旨在使用caffe框架的python接口训练一个分类网络,但在实现过程中,由于轻视直接给网络传原图片造成的大量io损耗,只能一时终止训练,把图片转成lmdb格式,而这个时候没有去查找lmdb格式转化的python实现,就直接调用了caffe提供的脚本,有点与本意相悖了。但也因此对比到了直接输入图片和输入lmdb的io速度差异极大。然而后面更复杂的网络参数变多,显存不足,batch_size只能设小,这个io速度的影响又变小了,所以这个发现倒是有点鸡肋。
-
本次实验的经验和套路完全可以套用在其他分类网络的训练上,但由于后面网络参数多、batch_size小,训练时间极长,不太适合做复现,倒是可以直接用来做实际应用,这就要使用fine-tunning的技巧,也就是读取别人训练好的.caffemodel文件提取网络权值作为初始化再训练,这个步骤在本次实验中没有使用。
Reference
-
Caffe 框架(包含提供AlexNet的网络、solver)