上手開始訓練還是比較簡單的,難得是如何調參。
1. 準備數據集#
如果你的數據集太少,會導致訓練很快過擬合。表現為 test loss 趨於不變,而 train loss 還在下降.
可以看這篇博文增加數據集:
【Python】【Shell】【Caffe】訓練集預處理 —— 數據增強 《很認真的講講 Caffe》
訓練集,驗證集和測試集。舉個例子做場景識別,
需要準備的第一個文檔是 words.txt:第一列是序號,第二列是類別。
0 balcony
1 bathroom shower
2 bedroom bedchamber
3 childs_room
第二個文檔是 train.txt: 訓練集文件路徑和文件名 + 空格 + 類別序號。
第三個文檔是 val.txt,格式同上,只不過變成了驗證集文件。
分享個生成腳本:
#!/bin/sh
classes=(balcony bathroom shower bedroom bedchamber childs_room)
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 #到上面balcony等類別目錄的路徑,和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
需要修改的內容有:
-
刪掉三行 mean_value, 改成 meanfile=/path/to/your/meanfile.prototxt
-
修改訓練和測試 source 路徑
-
刪掉所有帶有 loss1 的層,刪掉所有帶有 loss2 的層
-
刪掉最後 name: "loss3/top-5" 這一層.
-
所有 loss3/loss3 改為 loss
-
所有 loss3/top-1 改為 loss
-
num_output: 1000 將 1000 改成你的類別數
-
所有 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('Train Loss', 'Test Loss');
xlabel('iterations');
ylabel('loss');
title(' Train & Test Loss Curve');
[~, 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('Test Accuracy');
set(box,'Location','southeast');
xlabel('iterations');
ylabel('Accuracy');
title(' Test Accuracy Curve');
-------【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('Iteration')
ax1.set_ylabel('Loss')
ax2.set_ylabel('Test Accuracy')
plt.legend([l1, l2, l3], ['Train Loss', 'Test Loss', 'Test Accu'], 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 的默認值來簡化運行命令。