使用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的理想准确率,那么就只要测试自己的模型有没有达到这个准确率

  • 我的结果如图使用caffe框架复现AlexNet

    • 因为输出数据太长,前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