机器学习CNN之step by step

关于机器学习原理的介绍比比皆是,而基于这些原理该如何操作才能应用机器学习完成一些工作任务呢。本文旨在尽可能详细地介绍如何利用现在比较通用的框架如caffe、TensorFlow来实现简单的分类任务。

本文目标:一步一步操作训练一个二分类的CNN模型

caffe版本

默认已经安装好caffe环境,如果未安装好,mac可参考
MAC安装指引

第一步:准备数据集

先转换为lmdb

你需要训练图片和对应的标签索引txt文件。其中标签索引txt文件中一行代表一张图片,内容是图片路径和对应分类的标签,中间用空格隔开,train.txt如下所示:

/Users/fanchun/Desktop/视频文件分析/分屏花屏/huapin/flurglitch/page1/Num1.jpg_0.jpg 0
/Users/fanchun/Desktop/视频文件分析/分屏花屏/huapin/flurglitch/page1/Num10.jpg_0.jpg 0
/Users/fanchun/Desktop/视频文件分析/分屏花屏/huapin/flurglitch/page1/Num100.jpg_1.jpg 1
/Users/fanchun/Desktop/视频文件分析/分屏花屏/huapin/flurglitch/page1/Num11.jpg_1.jpg 1
/Users/fanchun/Desktop/视频文件分析/分屏花屏/huapin/flurglitch/page1/Num14.jpg_1.jpg 1
/Users/fanchun/Desktop/视频文件分析/分屏花屏/huapin/flurglitch/page1/Num15.jpg_0.jpg 0

准备好标签索引txt文件后,可以从编译好的caffe文件中选择可执行文件convert_imageset调用来转换lmdb文件,调用方式如下:

#!/usr/bin/sh
DATA=examples/images
rm -rf $DATA/img_train_lmdb
rm -rf $DATA/img_test_lmdb
制作训练集lmdb
build/tools/convert_imageset --shuffle \
--resize_height=256 --resize_width=256 \
/home/xxx/caffe/examples/images/ $DATA/train.txt  $DATA/img_train_lmdb
制作测试集lmdb
build/tools/convert_imageset --shuffle \
--resize_height=256 --resize_width=256 \
/home/xxx/caffe/examples/images/ $DATA/test.txt  $DATA/img_test_lmdb

其中调用参数含义如下:

  • --shuffle 表示打乱顺序
  • --resize_height--resize_width表示图片是否需要以及resize的高和宽
  • 倒数第二个参数是标签索引txt文件
  • 最后一个参数是lmdb保存路径

至此,你会生成img_train_lmdbimg_test_lmdb的训练集和测试集,后面训练会用到

然后计算得到均值文件

图片减去均值后,再进行训练和测试,会提高速度和精度,因此训练时候需要有对应的均值文件,均值文件可从训练集计算出来。

从编译好caffe的路径下找到compute_image_mean的可执行文件,对其调用得到均值文件,调用方式如下

build/tools/compute_image_mean examples/images/img_train_lmdb examples/images/mean.binaryproto

注意这里只要用训练集计算出均值文件即可,测试的时候直接用训练集的均值文件。至此,我们获得了对应的均值文件mean.binaryproto

第二步:配置模型文件protxt

这一步我们可以使用现成的网络模型文件,比如lenet、alexnet、resnet、mobilenet等模型(这里有回顾介绍中文版,英文版),这些模型都是比较成熟的已经经过验证的模型,拿它们训练imagenet 1000分类数据时top 5的数据准确率可以达到90%多,模型本身提取和描述特征的能力非常强,因此我们完全可以将这些模型应用到我们实际的任务中,这些模型对于我们任务的未必是最优的,但效果绝对不会很差,甚至比我们自己写的一些模型效果会更好,所以建议在这些已有的模型上进行微调

微调模型

以AlexNet为例,这里介绍如何将其改造为2分类模型,主要修改用于训练和鉴别准确率的train_val.prototxt文件。

  1. 改输入----修改数据层的配置

    分别修改TRAIN数据层和TEST数据层的source为上面得到的训练集和测试集lmdb的路径,mean_file为上面得到均值文件的路径。
    还可以对应修改crop_size为训练时截取图片的大小,其他配置修改可参考文章

    name: "AlexNet"
    layer {
      name: "data"
      type: "Data"
      top: "data"
      top: "label"
      include {
        phase: TRAIN
      }
      transform_param {
        mirror: true
        crop_size: 227
        mean_file: "./examples/images/img_train_lmdb examples/images/mean.binaryproto"
      }
      data_param {
        source: "./examples/images/img_train_lmdb examples/images/img_train_lmdb"
        batch_size: 64
        backend: LMDB
      }
    }
    layer {
      name: "data"
      type: "Data"
      top: "data"
      top: "label"
      include {
        phase: TEST
      }
      transform_param {
        mirror: false
        crop_size: 227
        mean_file: "./examples/images/img_train_lmdb examples/images/mean.binaryproto"
      }
      data_param {
        source: "./examples/images/img_train_lmdb examples/images/mean.binaryproto"
        batch_size: 1
        backend: LMDB
      }
    }
    
  1. 改输出----修改最后一层全连接层的配置
    将模型最后一层,Alexnet中是名为fc8的全连接层的输出num_output个数改为2个,表示输出为2分类,其他使用原有参数即可。如果想修改,可参考文章

    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: 2
        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"
    }
    

第三步:配置求解文件solver.prototxt

如下,按照自己需要配置各个参数,更多参数修改参考 文章

* 该配置为上面配置好的模型文件train_val.prototxt的路径
net: "examples/train_val.prototxt"

* 测试时运算的迭代数test_iter和测试间隔的迭代数test_interval
test_iter: 100
test_interval: 500

* 学习率 base_lr 和衰减系数momentum 等学习策略相关
base_lr: 0.01
momentum: 0.9

* 最大训练的迭代数max_iter和保存模型的间隔迭代数snapshot
max_iter: 20000
snapshot: 5000

* 模型保存路径和前缀
snapshot_prefix: "examples/mymodel"

* 训练使用CPU或GPU
solver_mode: CPU

第四步:执行命令进行训练

配置好以上模型文件和求解文件后,即可进行训练了,这里可以选择两种训练方式,第一种是从头开始训练,即模型参数权重初始化取值为随机值;第二种是基于已经训练好的模型继续训练的finetune训练,即模型参数权重初始化取值是从其他已经训练好的模型来读取的。

----从头训练

如果是从头训练,直接执行下面命令即可。

./build/tools/caffe train --solver=examples/solver.prototxt

其中--solver是必选参数,用于配置求解文件的路径。
-snapshot为可选参数,如果之前训练中断了,该参数可用来从之前的快照(snapshot)中恢复训练,可以在solver配置文件设置快照,保存solverstate。

----finetune训练

这种训练方式非常重要,因为深度学习网络需要大量带有准确标签的训练数据才能训练出有好的泛化能力的模型,而实际应用中往往我们很难得到这么多训练数据,这时候就可以考虑采用finetune的训练方式,即通过对ImageNet上训练出来的模型(如CaffeNet,VGGNet,ResNet)进行微调,然后应用到我们自己的数据集上。因为ImageNet以百万计的带标签的数据使得这些已经训练出的模型具有非常强的泛华能力,这些训练好的模型中间层包含很多描述一般视觉元素的基本特征,只要将模型后面几层对我们任务中进行微调,即可有很好的效果。参考文章

具体操作上需要在训练命令行中加上--weights的参数,该参数配置finetune所需pretrained模型文件的路径。即

./build/tools/caffe train --solver=examples/solver.prototxt --weights=examples/pretrained.caffemodel

这里你可能有两点疑问:

  1. 如何保证只训练模型的后面几层?

    将模型前面不需要训练的层的学习率lr_mult配置为0即可

  2. ImageNet训练的模型输出都是1000分类,如何用在我们的2分类模型上?

    将模型最后一层的名字修改下,如Alexnet的fc8改为my_fc8,这样读取参数时由于名字不匹配便不会从pretrained模型读取最后一层输出层的参数了

无论是从头训练还是finetune训练,我们都需要一个时机来终止训练,当然在达到solver.prototxt中配置的max_iter的迭代次数后会自动终止训练,但实际上往往不需要等这么久,我们只需要判断当日志中loss足够小了,测试集的accuracy准确率收敛到最高值即可终止训练。

关于日志,我们可以通过重定向的方式来将训练的日志保存下来,如下面shell脚本所示

#!/bin/bash
LOG=log/train-`date +%Y-%m-%d-%H-%M-%S`.log
CAFFE=~/caffe/build/tools/caffe

$CAFFE train --solver=solver.prototxt 2>&1 | tee $LOG

保存了日志我们就可以根据日志画出训练过程中的一些曲线,比如loss曲线或者accuracy曲线,caffe自带了解析日志的脚本,参考编译caffe路径下的caffe/tools/extra/plot_training_log.py.example的脚本。

第五步:用训练好的模型进行预测

这一步即是使用训练好的模型了,可以直接通过调用编译caffe生成的可执行文件来预测,也可通过python或者c++的接口来调用。这里需要提前准备deploy.prototxt,即模型预测时候的配置文件,与train_val.prototxt相比,主要是修改了data数据层,同时删除了训练时测试的层。

以Alexnet而言,将TRAIN数据层和TEST数据层修改如下:

layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 10 dim: 3 dim: 227 dim: 227 } }
}

删除了train_val.prototxt中下面的层

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"
}

至此便得到了deploy.prototxt文件。

c++命令行使用

从编译好caffe路径下找到可执行文件classification.bin,执行如下命令即可

./build/examples/cpp_classification/classification.bin \
  examples/deploy.prototxt \
  examples/mymodel.caffemodel \
  examples/mean.binaryproto \
  examples/index.txt \
  examples/images/cat.jpg

python接口

按照如下代码调用执行即可

import caffe

DEPLOY_FILE = "examples/deploy.prototxt"
WEIGHTS_FILE = "examples/mymodel.caffemodel"
MEAN_FILE = "examples/mean.binaryproto"
testimage = "/Users/fanchun/Desktop/视频文件分析/分屏花屏/splitscreen_picsrc/page4/Num4.jpg_1.jpg"

net = caffe.Net(DEPLOY_FILE, WEIGHTS_FILE, caffe.TEST)
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_transpose('data', (2, 0, 1))
transformer.set_mean('data', np.load(MEAN_FILE).mean(1).mean(1))
transformer.set_raw_scale('data', 255)
transformer.set_channel_swap('data', (2, 1, 0))

im = caffe.io.load_image(testimage)
net.blobs['data'].data[...] = transformer.preprocess('data', im)
out = net.forward()

至此,你已经完成了用caffe训练一个二分类模型并拿图片预测。

你可能感兴趣的:(机器学习CNN之step by step)