训练MobileNet-SSD

Author Avatar
YaHei 8月 21, 2018

RK3399上Tengine平台搭建 | Hey~YaHei!》一文介绍了RK3399和Tengine并且尝试跑通了MobilNet-SSD网络,而随后又分别用《MobileNets v1模型解析 | Hey~YaHei!》、《SSD框架解析 | Hey~YaHei!》《MobileNet-SSD网络解析 | Hey~YaHei!》三篇文章分别介绍了MobileNet v1、SSD和MobileNet-SSD。
接下来,本文将尝试训练自己的MobileNet-SSD并且部署在Tengine平台上。


安装配置cuda、caffe

cuda的安装网上有非常多的教程,比如《Ubuntu16.04+cuda9.0安装教程 | 贝多芬的悲桑, cnblogs》和《安装cuda-8.0 | 代码小哥, csdn》,过程也很简单,在官网下载你需要的版本对应的.run文件,直接运行按提示安装即可。

caffe由于要使用SSD框架,所以要编译安装caffe的ssd分支——

下载caffe-ssd源码

  • 直接用git把仓库克隆到本地并切换到ssd分支
      git clone https://github.com/weiliu89/caffe.git
      cd caffe
      git checkout ssd
    
  • github服务器在海外,网络不是很稳定,你可以试着挂vpn下载源码。
    如果你的linux没有配置vpn,但windows或mac有,那也可以直接在 weiliu89/caffe at ssd | github 上下载zip压缩包,再拷贝到linux用unzip指令解压;
  • 如果你没有vpn,也可以到我的网盘上下载:blog-share/mobilenet_ssd/caffe-ssd.zip | 百度网盘

编译caffe-ssd

编译过程可以参照 Caffe | Installation 来进行;

export CAFFE_ROOT=/your/caffe/root/path
# 进入caffe源码的根目录
cd $CAFFE_ROOT
# 从模板拷贝一份编译的配置文件
cp Makefile.config.example Makefile.config
# 按需要修改编译配置
# vim Makefile.config
# 开始编译(参数j表示编译使用的线程数量,一般数值越大越快,取决于你cpu支持的线程数)
make -j8
# 修改环境变量
echo "export PYTHONPATH=$CAFFE_ROOT/python:$PYTHONPATH" >> ~/.bashrc
source ~/.bashrc
# 编译python包
make py
# 编译测试程序
make test -j8
# 测试
make runtest -j8

关于配置文件,一般直接用默认配置就行,

  • 如果你想用cudnn加速而不是caffe自己提供的加速库(caffe不建议用cudnn),给第5行解除注释。
      [4] # cuDNN acceleration switch (uncomment to build with cuDNN).
      [5] # USE_CUDNN := 1
    
  • 如果你不想用gpu,给第8行解除注释,
      [7] # CPU-only switch (uncomment to build without GPU support).
      [8] # CPU_ONLY := 1
    
  • 如果你的cuda没有安装在默认位置,你可能需要在第28行修改变量CUDA_DIR
      [27] # CUDA directory contains bin/ and lib/ directories that we need.
      [28] CUDA_DIR := /usr/local/cuda
      [29] # On Ubuntu 14.04, if cuda tools are installed via
      [30] # "sudo apt-get install nvidia-cuda-toolkit" then use this instead:
      [31] # CUDA_DIR := /usr
    
  • 如果你用的是anaconda,或者你要用python3接口,你可能需要修改
      [64] # NOTE: this is required only if you will compile the python interface.
      [65] # We need to be able to find Python.h and numpy/arrayobject.h.
      [66] PYTHON_INCLUDE := /usr/include/python2.7 \
      [67]         /usr/lib/python2.7/dist-packages/numpy/core/include
      [68] # Anaconda Python distribution is quite popular. Include path:
      [69] # Verify anaconda location, sometimes it's in root.
      [70] # ANACONDA_HOME := $(HOME)/anaconda2
      [71] # PYTHON_INCLUDE := $(ANACONDA_HOME)/include \
      [72]         $(ANACONDA_HOME)/include/python2.7 \
      [73]         $(ANACONDA_HOME)/lib/python2.7/site-packages/numpy/core/include \
      [74] 
      [75] # Uncomment to use Python 3 (default is Python 2)
      [76] # PYTHON_LIBRARIES := boost_python3 python3.5m
      [77] # PYTHON_INCLUDE := /usr/include/python3.5m \
      [78] #                 /usr/lib/python3.5/dist-packages/numpy/core/include
    

编译过程中如果出现 google::protobuf 或者 google::protoc 相关的报错,你可能需要到 google/protobuf | github 下载合适版本的protobuf到本地编译并且配置环境变量(可以用protoc --version指令查看当前使用的protobuf版本)

开始训练MobileNet-SSD

首先,先跑通默认的 MobileNet-SSD——

准备数据集

MobileNet-SSD默认使用Pascal VOC的2007和2012数据集,
下载以下数据集,并解压到同一个目录下:

解压后目录如下图所示:

然后用caffe-ssd提供的 VOC数据集处理工具 对数据集进行处理——

  1. 按实际情况修改并执行脚本$CAFFE_ROOT/data/VOC0712/create_list.sh
    第3行root_dir变量修改为你的VOC数据集目录,比如按照我的目录树,则设置为$HOME/data/VOCdevkit
     [1] #!/bin/bash
     [2] 
     [3] root_dir=$HOME/data/VOCdevkit/
     [4] sub_dir=ImageSets/Main
     [5] bash_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    
  2. 按实际情况修改并执行脚本$CAFFE_ROOT/data/VOC0712/create_data.sh
    第7行data_root_dir变量修改为你的VOC数据集目录,比如按照我的目录树,则设置为$HOME/data/VOCdevkit
     [1] cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
     [2] root_dir=$cur_dir/../..
     [3] 
     [4] cd $root_dir
     [5] 
     [6] redo=1
     [7] data_root_dir="$HOME/data/VOCdevkit"
     [8] dataset_name="VOC0712"
     [9] mapfile="$root_dir/data/$dataset_name/labelmap_voc.prototxt"
    

执行完毕后将会自动在$data_root_dir目录下生成VOC0712子目录,里边包含了从数据集VOC2007和VOC2012提取的图片和标记信息,并构建caffe能够高效读取的lmdb文件。
VOC0712子目录结构如下图所示:

训练

下载MobileNet-SSD源码:

创建数据集软链接:

export TRAINVAL_LMDB=$HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb/
export TEST_LMDB=$HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb/

cd $CAFFE_ROOT/examples/MobileNet-SSD
ln -s $TRAINVAL_LMDB ./trainval_lmdb
ln -s $TEST_LMDB ./test_lmdb

把VOC的标签映射文件复制过来:

cp $CAFFE_ROOT/data/VOC0712/labelmap_voc.prototxt $CAFFE_ROOT/examples/MobileNet-SSD/labelmap.prototxt

生成模型文件:

./gen_model.sh 21

这里21指的是VOC的21个类别(含负样本),生成的模型文件默认放置在example目录下;

如果需要修改训练参数和测试参数,可以分别修改目录下的solver_train.protxtsovler_test.protxt文件,
默认使用example目录下的训练模型和测试模型;

如果需要指定GPU和初始化权重,可以修改目录下的train.shtest.sh文件,以train.sh为例:

#!/bin/sh
if ! test -f example/MobileNetSSD_train.prototxt ;then
    echo "error: example/MobileNetSSD_train.prototxt does not exist."
    echo "please use the gen_model.sh to generate your own model."
        exit 1
fi
mkdir -p snapshot
../../build/tools/caffe train -solver="solver_train.prototxt" \
-weights="mobilenet_iter_73000.caffemodel" \
-gpu 0 

weights参数指定初始化的权重文件,这里用了chuanqi305预训练迭代了73000次的模型;
gpu参数指定使用的gpu,多个gpu可以用逗号隔开;
除此之外,如果需要继续之前中断的训练,还可以指定snapshot参数,
比如我想从最近的快照继续训练,可以这样修改train.sh——

#!/bin/sh
latest=$(ls -t snapshot/*.caffemodel | head -n 1)
if ! test -f example/MobileNetSSD_train.prototxt ;then
    echo "error: example/MobileNetSSD_train.prototxt does not exist."
    echo "please use the gen_model.sh to generate your own model."
        exit 1
fi
mkdir -p snapshot
../../build/tools/caffe train -solver="solver_train.prototxt" \
-snapshot=$latest \
-gpu 0 

部署

合并BN层:
训练后会在snapshot目录下产生一个相应的caffemodel文件;
按实际情况修改merge_bn.py文件并执行:

[ 1] import numpy as np  
[ 2] import sys,os  
[ 3] caffe_root = '/your/caffe/root/path/'
[ 4] sys.path.insert(0, caffe_root + 'python')  
[ 5] import caffe  
[ 6] 
[ 7] train_proto = 'example/MobileNetSSD_train.prototxt'       # 训练时所用的模型文件
[ 8] train_model = 'mobilenet_iter_73000.caffemodel'           # 训练后产生的caffemodel文件
[ 9] 
[10] deploy_proto = 'example/MobileNetSSD_deploy.prototxt'     # 部署时所要用的模型文件(去掉BN层)
[11] save_model = 'MobileNetSSD_deploy.caffemodel'             # 最终生成的caffemodel文件(合并BN层参数)

生成的合并BN层后的caffemodel就在MobileNet-SSD项目的根目录下;
编辑example/MobileNetSSD_deploy.prototxt修改输入层,即把

input: "data"
input_shape {
    dim: 1
    dim: 3
    dim: 300
    dim: 300
}

改为

layer {
    name: "input"
    type: "Input"
    top: "data"
    input_param {
        shape {
            dim: 1
            dim: 3
            dim: 300
            dim: 300
        }
    }
}

example/MobileNetSSD_deploy.prototxtMobileNetSSD_deploy.caffemodel 拷贝到Tengine平台的models目录下,此时运行mobilenet_ssd/MSSD用的就是新训练好的模型啦!


本文简单介绍了如何用chuanqi305的MobileNet-SSD训练出自己的网络。
下一篇文章《基于MobileNet-SSD的目标检测Demo(一) | Hey~YaHei!》将继续尝试根据实际情况删减多余类别进行训练。还可以注意到,

  • chuanqi305的MobileNet-SSD模型除了基础网络部分之外依旧保守的使用了Standard Conv,可以尝试将这一部分也改造为Depthwise Conv;
  • 同时,MobileNet-SSD使用带group的caffe原生Conv来进行Depthwise Conv操作,这是非常低效率的,下篇文章还将进一步比较Depthwise Conv和带group的原生Conv的效率。