hans

hans

【Caffe】快速上手训练自己的数据 《很认真的讲讲Caffe》


上手开始训练还是比较简单的,难得是如何调参。

1. 准备数据集#

如果你的数据集太少,会导致训练很快过拟合。表现为 test loss 趋于不变,而 train loss 还在下降。

可以看这篇博文增加数据集:

【Python】【Shell】【Caffe】训练集预处理 —— 数据增强 《很认真的讲讲 Caffe》

训练集,验证集和测试集。举个例子做场景识别,

需要准备的第一个文档是 words.txt:第一列是序号,第二列是类别。

0 阳台
1 浴室 淋浴
2 卧室 卧室
3 儿童房

第二个文档是 train.txt: 训练集文件路径和文件名 + 空格 + 类别序号。

第三个文档是 val.txt,格式同上,只不过变成了验证集文件。

分享个生成脚本:

#!/bin/sh

classes=(阳台 浴室 淋浴 卧室 卧室 儿童房)
num=0
for class in ${classes[@]}
do
    ls $class/* > $class.txt
    sed -i "s/$/ $num/g" $class.txt #末尾添加类别索引
    let num+=1
    cat $class.txt >> temp.txt
    rm $class.txt
done
cat temp.txt | awk 'BEGIN{srand()}{print rand()"\t"$0}' | sort -k1,1 -n | cut -f2- > train.txt #乱序

2. 准备 lmdb 文件#

这个要分别给 train,val 和 test 都弄一下。

找到 $ROOT/caffe/example/imagenet/create_imagenet.sh, 改路径。下面是我的版本。

#!/bin/sh
set -e

EXAMPLE=/path/to/lmdb   # 保存生成lmdb目录的位置
DATA=/path/to/train.txt & val.txt & test.txt 
TOOLS=/path/to/caffe/build/tools

TRAIN_DATA_ROOT=/absolute/path/to/calss #到上面阳台等类别目录的路径,和train.txt里面凑成图片的完整绝对路径
VAL_DATA_ROOT=/absolute/path/to/calss


echo "Creating train lmdb..."

GLOG_logtostderr=1 $TOOLS/convert_imageset \
    --resize_height=0 \ #如果你的数据集图片大小不一致,需要根据train_val.prototxt网络结构将所有图片进行缩放。
    --resize_width=0 \
    --shuffle=false \
    $TRAIN_DATA_ROOT \
    $DATA/train.txt \
    $EXAMPLE/train_lmdb

echo "Creating val lmdb..."

GLOG_logtostderr=1 $TOOLS/convert_imageset \
    --resize_height=0 \
    --resize_width=0 \
    --shuffle=false \  #改成true后自动shuffle

    $VAL_DATA_ROOT \
    $DATA/val.txt \
    $EXAMPLE/val_lmdb

echo "Done."

3. 准备均值文件(mean file)#

找到 $ROOT/caffe/example/imagenet/make_imagenet_mean.sh, 改路径。下面是我的版本。

#!/bin/sh

EXAMPLE=/path/to/lmdb
DATA=/path/to/your/train.txt & val.txt   # the path should be short
TOOLS=/path/to/caffe/build/tools

$TOOLS/compute_image_mean $EXAMPLE/train_lmdb $DATA/mean.binaryproto

echo "Done."

4. 修改配置文件#

我用的 alexnet。先修改 train_val_alexnet.prototxt

name: "AlexNet"
layer {
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    crop_size: 227
    mean_file: "/path/to/mean.binaryproto" #改
    mirror: true
  }
  data_param {
    source: "/path/to/train_lmdb" #改
    backend: LMDB
    batch_size: 32 #改
  }
}
layer {
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    mirror: false
    crop_size: 227
    mean_file: "/path/to/mean.binaryproto" #改
  }
  data_param {
    source: "/path/to/val_lmdb" #改
    batch_size: 16 #改
    backend: LMDB
  }

...

...

...

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: 5  #改成你的类别数
    weight_filler {
      type: "gaussian"
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}

...

...

...

然后修改超参数,solver_alexnet.prototxt

net: "/path/to/alexnet.prototxt"
test_iter: 500  #一次测试时迭代次数,这个数值乘以test batch size是你验证集所有样本的数量
test_interval: 200  #训练时迭代多少次进行一次测试
base_lr: 0.001  #基础学习率
lr_policy: "step"  #学习策略,步进式
gamma: 0.1 #学习率衰减系数
stepsize: 140000 #学习率衰减步长
display: 200 #训练时迭代多少次在terminal显示一次数据
max_iter: 420000 #最大训练迭代次数
momentum: 0.9 #冲量,不要改
weight_decay: 0.0005 #权重衰减量,不要改
snapshot: 1000 #训练迭代多少次保存一次快照结果
snapshot_prefix: "/path/" #快照路径
solver_mode: GPU #模式

5. 开始训练#

直接运行:

./build/tools/caffe train --solver=/path/to/solver.prototxt

----------[2017.07.20 更新]----------finetune-------------------------------

之前一直做类别不是很多的训练,直接训练效果就还好。

昨儿试了下 20+, 30+ 类别的训练,一晚上 10W 多次迭代 loss 居高不下,accuracy 稳定在很低的值。

改了两次学习率问题没解决后,脑子里就冒出 finetune 这个词。抱着尝试的心态,结果成功了。

我是在这里 http://dl.caffe.berkeleyvision.org/
下载的 bvlc_googlenet.caffemodel

模型参考 caffe/model/bvlc_googlenet/train_val.prototxt

超参数参考 caffe/model/bvlc_googlenet/solver.prototxt

需要修改的内容有:

  1. 删掉三行 mean_value, 改成 meanfile=/path/to/your/meanfile.prototxt

  2. 修改训练和测试 source 路径

  3. 删掉所有带有 loss1 的层,删掉所有带有 loss2 的层

  4. 删掉最后 name: "loss3/top-5" 这一层.

  5. 所有 loss3/loss3 改为 loss

  6. 所有 loss3/top-1 改为 loss

  7. num_output: 1000 将 1000 改成你的类别数

  8. 所有 loss3/classifier 随便改个名字,比如改成 loss3

以上内容修改好之后,就可以运行下面代码进行 finetune 了,准确率上升很快。

./build/tools/caffe train -solver /path/to/solver.prototxt -weights /path/to/bvlc_googlenet.caffemodel

#

6.loss 可视化#

运行:

script loss_acc.txt

./build/tools/caffe train --solver=/path/to/solver.prototxt

训练结束后记得 ctrl+d 退出屏幕录像。

下面是可视化用的 matlab 代码,这段代码参照了《深度学习:21 天实战 Caffe》中学习曲线可视化方法修改的。

clear;
clc;
close all;

train_log_file = 'loss_acc.txt';
display=20;  %对应solver中的值
test_interval=20;  %同上

[~, string_output] = dos(['cat ', train_log_file, ' | grep "Train net output #0" | awk ''{print $11}''']);
train_loss = str2num(string_output);
n = 1:length(train_loss);
idx_train = (n-1)*display;

[~, string_output] = dos(['cat ', train_log_file, ' | grep "Test net output #1" | awk ''{print $11}''']);
test_loss = str2num(string_output);
m = 1:length(test_loss);
idx_test = (m - 1)*test_interval;
figure;plot(idx_train, train_loss);
hold on;
plot(idx_test, test_loss,'--r');

grid on;
legend('训练损失', '测试损失');
xlabel('迭代次数');
ylabel('损失');
title(' 训练 & 测试损失曲线');

[~, acc_output] = dos(['cat ', train_log_file, ' | grep "Test net output #0" | awk ''{print $11}''']);
test_acc = str2num( acc_output);
k = 1:length(test_acc);
idx_acc = (k - 1)*test_interval;

figure;
plot(idx_acc, test_acc);

grid on;
box = legend('测试准确率');
set(box,'Location','southeast');
xlabel('迭代次数');
ylabel('准确率');
title(' 测试准确率曲线');

-------【2017.09.26】------ 更新 python 的可视化代码,不过还是用 digits 最方便 ------------

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 29 10:05:13 2017

@author: hans

http://blog.csdn.net/renhanchi
"""
import matplotlib.pyplot as plt
import numpy as np
import commands
import argparse

parser = argparse.ArgumentParser()
parser.add_argument(
    '-p',
    type = str,
    default = '',
    help = """\
    path to log file\
    """
)

FLAGS = parser.parse_args()

train_log_file = FLAGS.p

display = 10 #solver
test_interval = 100 #solver

train_output = commands.getoutput("cat " + train_log_file + " | grep 'Train net output #0' | awk '{print $11}'")  #train loss
test_output = commands.getoutput("cat " + train_log_file + " | grep 'Test net output #1' | awk '{print $11}'")  #test loss
accu_output = commands.getoutput("cat " + train_log_file + " | grep 'Test net output #0' | awk '{print $11}'") #test accuracy
train_loss = train_output.split("\n")
test_loss = test_output.split("\n")
test_accu = accu_output.split("\n")
l = len(test_accu)

_,ax1 = plt.subplots()
ax2 = ax1.twinx()

l1, = ax1.plot(display*np.arange(len(train_loss)), train_loss)
l2, = ax1.plot(test_interval*np.arange(l), test_loss, 'g')
l3, = ax2.plot(test_interval*np.arange(l), test_accu, 'r')

ax1.set_xlabel('迭代次数')
ax1.set_ylabel('损失')
ax2.set_ylabel('测试准确率')

plt.legend([l1, l2, l3], ['训练损失', '测试损失', '测试准确率'], loc = 'upper left')
plt.show()

上面这些都简单,最难的是调参!!调参!!!调参!!!

7. 测试模型#

运行:

./build/tools/caffe test \

--model=/path/to/train_val_alexnet.prototxt \

--weights=path/to/xxxxxxxxxx.caffemodel

这里注意一下,正常这么运行是用 val 数据集进行测试的。如果你准备了自己的测试集,按照上面步骤生成 test_lmdb 文件夹,然后在 train_val_alexnet.prototxt 文件中,将 val_lmdb 改成 test_lmdb。在运行面上命令就好了。

8. 使用 classify.py 调用模型进行分类#

参考 【Caffe】【场景分类】Places365 安装、docker 运行,以及调用本地 caffe 运行(Ubuntu14.04)

修改 132 行的 labels.txt 路径就好了。

但是!!!注意!!!!

运行命令中的 --model_def 我们不能直接选择 train_val_alexnet.prototxt,会报错的。

新建一个内容和 train_val_alexnet.prototxt 一样的文件名字叫 deploy_alexnet.prototxt

然后删掉 data 层,accuracy 层,修改 loss 层。具体看下面。

name: "AlexNet"
input: "data"
input_dim: 1
input_dim: 3
input_dim: 227
input_dim: 227
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
    decay_mult: 1
  }
  param {
    lr_mult: 2
    decay_mult: 0
  }
  convolution_param {
    num_output: 96
    kernel_size: 11
    stride: 4
    weight_filler {
      type: "gaussian"
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}

...

...

...

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: 10
    weight_filler {
      type: "gaussian"
      std: 0.01
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layer {
  name: "prob"
  type: "Softmax"
  bottom: "fc8"
  top: "prob"
}

修改好后,运行:

cd $ROOT/caffe/python

python classify.py /path/to/image.jpg result.npy \

--model_def  /path/to/deploy_alexnet.prototxt

--pretrained_model /path/to/xxxxxx.caffemodel

当然大家也可以通过修改 classify.py 中前面 model_def 和 pretrained_model 的默认值来简化运行命令。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。