Skip to content

Latest commit

 

History

History
1133 lines (911 loc) · 41.8 KB

caffe学习总结.org

File metadata and controls

1133 lines (911 loc) · 41.8 KB

一般把数据源放在 $CAFFE_ROOT/data 文件夹下面。 处理后的数据和模型文件等放在 $CAFFE_ROOT/examples文件夹下。($CAFFE_ROOT 表示你电脑上的caffe代码路径。)

caffe支持的数据格式

  • 数据库格式 (LEVELDB or LMDB) $CAFFEROOT/build/tools/convert_imageset 可以用来做把原始图片转换为LevelDB或者 Lmdb格式。

Image Data - read raw images.

Database - read data from LEVELDB or LMDB.

HDF5 Input - read HDF5 data, allows data of arbitrary dimensions.

HDF5 Output - write data as HDF5.

Input - typically used for networks that are being deployed.

Window Data - read window data file.

Memory Data - read data directly from memory.

Dummy Data - for static data and debugging.

第一个样例

准备数据

MNIST数据: $CAFFE_ROOT/data/mnist/get_mnist.sh

运行这个脚本就可以帮我们下载MNIST数据。

cd $CAFFE_ROOT
./data/mnist/get_mnist.sh #得到MNIST数据

运行成功后,在 data/mnist/ 目录下有四个文件:

文件名内容大小
train-images-idx3-ubyte训练集样本9912422bytes
train-labels-idx1-ubyte训练集对应标注28881bytes
t10k-images-idx3-ubyte测试集图片1648877bytes
t10k-labels-idx1-ubyte测试集对应标注4542bytes

C:\Users\123\AppData\Roaming\Typora\typora-user-imagesℑ-20200102145223534.png

但是这些数据不能在 Caffe 中直接使用,需要转换成 LMDB 数据

./examples/mnist/create_mnist.sh #官方给出的专门转换MNIST数据格式的脚本
#这条命令必须从$CAFFE_ROOT这个目录下执行,否则会出现找不到文件的错误:build/examples/mnist/convert_mnist_data.bin: not found

如果想运行 leveldb 数据,请运行 examples/siamese/ 文件夹下面的程序。 examples/mnist/ 文件夹是运行 lmdb 数据

转换成功后,会在 `examples/mnist/` 目录下,生成两个文件夹

C:\Users\123\AppData\Roaming\Typora\typora-user-imagesℑ-20200102145650650.png

配置网络结构

官网提供了定义好的网络文件 $CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt

内容如下:

name: "LeNet"
layer {
  name: "mnist"
  type: "Data" #数据层
  top: "data" 
  top: "label"
  include {
    phase: TRAIN #训练时才加载
  }
  transform_param {
    scale: 0.00390625 #每个像素乘以改值做归一化(1/255 = 0.00390625)
  }
  data_param {
    source: "examples/mnist/mnist_train_lmdb" #前面生成的训练数据集
    batch_size: 64 # 每一批训练集大小
    backend: LMDB #数据格式
  }
}
layer {
  name: "mnist"
  type: "Data" #数据层
  top: "data"
  top: "label"
  include {
    phase: TEST #测试时才加载
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "examples/mnist/mnist_test_lmdb" #前面生成的测试数据集
    batch_size: 100
    backend: LMDB
  }
}
layer {
  name: "conv1"
  type: "Convolution" #卷积层
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1 #weights学习率
  }
  param {
    lr_mult: 2 #bias学习率
  }
  convolution_param {
    num_output: 20 #输出多少个特征图(对应卷积核数量)
    kernel_size: 5 #卷积核大小
    stride: 1 #步长
    weight_filler {
      type: "xavier" #权重初始化算法
    }
    bias_filler {
      type: "constant" #基值初始化算法
    }
  }
}
layer {
  name: "pool1"
  type: "Pooling" #池化层
  bottom: "conv1" 
  top: "pool1"
  pooling_param {
    pool: MAX #池化方法
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "ip1"
  type: "InnerProduct" #全链接层
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1 #weights学习率
  }
  param {
    lr_mult: 2 #bias学习率
  }
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "relu1"
  type: "ReLU" #relu层
  bottom: "ip1"
  top: "ip1"
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "accuracy"
  type: "Accuracy" #输出精度
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}
layer {
  name: "loss" 
  type: "SoftmaxWithLoss" #输出损失
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

可以用 官方自带的python绘图工具绘制出网络图:

~/caffe/python/draw_net.py ~/caffe/examples/mnist/lenet_train_test.prototxt ~/lenet_train_test.png
#最后一个参数是输出图片的路径

配置网络求解文件

官网给出了一个求解文件:$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt:

# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100
# Carry out testing every 500 training iterations. 设置每500次测试一下网络 精度 损失
test_interval: 500
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# The learning rate policy
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# Display every 100 iterations  设置每100次迭代训练显示当前状态 lr loss
display: 100
# The maximum number of iterations
max_iter: 10000
# snapshot intermediate results 中间结果快照每5000次保存一次
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet" 
# solver mode: CPU or GPU
solver_mode: GPU

训练

cd $CAFFE_ROOT
./examples/mnist/train_lenet.sh

其内容如下:

`#!/usr/bin/env sh
set -e

./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt $@`

可见只是调用命令行接口。

然后得到一堆的输出信息:

#solver文件设置每100次迭代训练显示当前状态 lr loss
I1203 solver.cpp:204] Iteration 100, lr = 0.00992565  #学习率
I1203 solver.cpp:66] Iteration 100, loss = 0.26044 #损失
...
#solver文件设置每500次测试一下网络 精度 损失
I1203 solver.cpp:84] Testing net
I1203 solver.cpp:111] Test score #0: 0.9785 #精度
I1203 solver.cpp:111] Test score #1: 0.0606671 #损失

训练结束后,输出信息可以看到最终的精度和损失。 在 $CAFFE_ROOT/examples/mnist 文件夹下可以看到 如下文件。 迭代5000次的中间状态快照 (.solverstate文件,可用于恢复网络训练)和模型 (.caffemodel文件,可用于下一步测试),迭代10000次的中间状态快照和模型https://img-blog.csdnimg.cn/20191224101300626.png

备注:由于我们在solver中设置了每500做一下测试。所以实际上上面 ./build/tools/caffe train –solver=examples/mnist/lenet_solver.prototxt 的过程包含了训练和间隔测试。这样做有助于我们对网络训练的中间过程有直观感受。

测试

参数包括: 网络结构模型文件(.prototxt 注意不是求解文件solover) 训练好的模型参数(.caffemodel) 迭代测试100次(前面训练和间隔测试时是在solver文件中定义的 此处则用命令行写明)

cd $CAFFE_ROOT
build/tools/caffe test -model examples/mnist/lenet_train_test.prototxt -weights examples/mnist/lenet_iter_10000.caffemodel  -iterations 100

第二个样例

准备数据

cifar10 数据训练样本50000张,测试样本10000张,每张为32*32的彩色三通道图片,共分为10类。

下载数据:

sh data/cifar10/get_cifar10.sh

运行成功后,会在 data/cifar10/ 文件夹下生成一堆 bin 文件

转换数据格式为 lmdb:

sh examples/cifar10/create_cifar10.sh

注意:这些命令必须在caffe 主目录下执行,否则会报错

转换成功后,会在 examples/cifar10/ 文件夹下生成两个文件夹, cifar10_train_lmdbcifar10_test_lmdb 里面的文件就是我们需要的文件。

为了节省时间,我们进行快速训练 (train_quick),训练分为两个阶段,第一个阶段(迭代4000次)调用配置文件 cifar10_quick_solver.prototxt , 学习率 (base_lr) 为0.001。第二阶段(迭代1000次)调用配置文件 cifar10_quick_solver_lr1.prototxt , 学习率 (base_lr) 为0.0001。

前后两个配置文件就是学习率 (base_lr) 和最大迭代次数 (max_iter) 不一样,其它都是一样。如果你对配置文件比较熟悉以后,实际上是可以将两个配置文件合二为一的,设置 lr_policymultistep 就可以了。

base_lr: 0.001
momentum: 0.9
weight_decay: 0.004
lr_policy: "multistep"
gamma: 0.1
stepvalue: 4000
stepvalue: 5000

运行例子:

sh examples/cifar10/train_quick.sh

prototxt文件的设置

net: "examples/mnist/lenet_train_test.prototxt"  
test_iter: 100  
test_interval: 500  
base_lr: 0.01  
momentum: 0.9  
type: SGD  
weight_decay: 0.0005  
lr_policy: "inv"  
gamma: 0.0001  
power: 0.75  
display: 100  
max_iter: 20000  
snapshot: 5000  
snapshot_prefix: "examples/mnist/lenet"  
solver_mode: CPU 
net: "examples/mnist/lenet_train_test.prototxt" 

设置网络模型。每一个模型就是一个net,需要在一个专门的配置文件中对net进行配置,每个net由许多的layer所组成。

需要注意的是:文件的路径要从caffe的根目录开始,其它的所有配置都是这样。

test_iter: 100

测试集迭代次数。

这个要与 test layer 中的 batch_size 结合起来理解。mnist数据中测试样本总数为10000,一次性执行全部数据效率很低,因此我们将测试数据分成几个批次来执行,每个批次的数量就是 batch_size 。假设我们设置 batch_size 为100,则需要迭代100次才能将10000个数据全部执行完。因此 test_iter 设置为100。执行完一次全部数据,称之为一个 epoch

test_interval: 500

测试间隔。也就是每训练500次,才进行一次测试。

base_lr: 0.01  
lr_policy: "inv"  
gamma: 0.0001  
power: 0.75

这四行可以放在一起理解,用于学习率的设置。只要是梯度下降法来求解优化,都会有一个学习率,也叫步长。 base_lr 用于设置基础学习率

lr_policy 设置学习率的调整策略。

lr_policy 可以设置为下面这些值,相应的学习率的计算为:

  • fixed:   保持base_lr不变.
  • step:    如果设置为step,则还需要设置一个stepsize, 返回 base_lr * gamma ^ (floor(iter / stepsize)),其中iter表示当前的迭代次数
  • exp:   返回base_lr * gamma ^ iter, iter为当前迭代次数
  • inv:   如果设置为inv,还需要设置一个power, 返回base_lr * (1 + gamma * iter) ^ (- power)
  • multistep: 如果设置为multistep,则还需要设置一个stepvalue。这个参数和step很相似,step是均匀等间隔变化,而multistep则是根据 stepvalue值变化
  • poly:    学习率进行多项式误差, 返回 base_lr (1 - iter/max_iter) ^ (power)
  • sigmoid: 学习率进行sigmod衰减,返回 base_lr ( 1/(1 + exp(-gamma * (iter - stepsize))))

multistep示例:

base_lr: 0.01  
momentum: 0.9  
weight_decay: 0.0005  
# The learning rate policy  
lr_policy: "multistep"  
gamma: 0.9  
stepvalue: 5000  
stepvalue: 7000  
stepvalue: 8000  
stepvalue: 9000  
stepvalue: 9500  

接下来的参数:

momentum :0.9  

指上一次梯度更新的权重

type: SGD  

优化算法选择。这一行可以省掉,因为默认值就是SGD。

caffe提供了六种优化算法来求解最优参数,在solver配置文件中,通过设置 type 类型来选择

  • Stochastic Gradient Descent (type:”SGD”),
  • AdaDelta (type:”AdaDelta”),
  • Adaptive Gradient (type:”AdaGrad”),
  • Adam (type: “Adam”),
  • Nesterov’s Accelerated Gradient (type: “Nesterov”) and
  • RMSprop (type:”RMSProp”)
weight_decay: 0.0005  

权重衰减项,防止过拟合的一个参数。

display: 100  

每训练100次,在屏幕上显示一次。如果设置为0,则不显示。

max_iter: 20000 

最大迭代次数。这个数设置太小,会导致没有收敛,精确度很低。设置太大,会导致震荡,浪费时间

snapshot: 5000  
snapshot_prefix: "examples/mnist/lenet"  

快照。将训练出来的model和solver状态进行保存,`snapshot`用于设置训练多少次后进行保存,默认为0,不保存。

snapshot_prefix 设置保存路径。 还可以设置`snapshot_diff`,是否保存梯度值,默认为 false ,不保存。

也可以设置 snapshot_format ,保存的类型。有两种选择: HDF5BINARYPROTO ,默认为 BINARYPROTO

solver_mode: CPU  

设置运行模式。默认为GPU,如果你没有GPU,则需要改成CPU,否则会出错。

注意:以上的所有参数都是可选参数,都有默认值。根据solver方法(type)的不同,还有一些其它的参数,在此不一一列举。

命令行解析

Caffe的 C++ 主程序(caffe.cpp) 放在根目录下的 tools 文件夹内, 当然还有一些其它的功能文件,如: convert_imageset.cpp , train_net.cpp, test_net.cpp 等也放在这个文件夹内。经过编译后,这些文件都被编译成了可执行文件,放在了 ./build/tools/ 文件夹内。因此我们要执行caffe程序,都需要加 ./build/tools/ 前缀。

如: sh ./build/tools/caffe train --solver=examples/mnist/train_lenet.sh

Caffe 程序的命令行执行格式

命令格式

caffe <command> <args>

其中<command>有四种

command功能
train训练或微调模型
test测试模型
time显示程序执行时间

其中的<args>参数有

commandrequired功能
-solver必选参数指定一个模型的配置文件
-gpu可选参数指定执行 Caffe 的 GPU 编号
-snapshot可选参数从快照文件(*.solverstate)恢复训练
-weights可选参数从权重文件 (*.caffemodel)微调模型
-iterations可选参数设定迭代次数
-model可选参数指定模型结构描述文件
-sighup_effect可选参数用来设定当程序发生挂起事件时,执行的操作
-sigint_effect可选参数用来设定当程序发生键盘中止事件时(Ctrl + C )

操作例子

  1. -solver:一个 protocol buffer 类型的文件,即模型的配置文件。 ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt
  2. -gpu: 该参数用来指定用哪一块 GPU 运行,根据 GPU 的 ID 进行选择,如果设置为 -gpu all 则使用所有的 GPU 运行。如使用第二块 GPU 运行: ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 1
  3. -snapshot: 该参数用来从快照 (snapshot)中恢复训练。可以在 solver 配置文件设置快照,保存 solverstate。如: ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -snapshot examples/mnist/lenet_iter_5000.solverstate
  4. -weights: 用预先训练好的权重来 fine-tuning 模型,需要一个 caffemodel,不能和 -snapshot 同时使用。如: ./build/tools/caffe train -solver examples/finetuning_on_flickr_style/solver.prototxt -weights models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel
  5. -iterations: 指定迭代次数,默认为50。如果在配置文件文件中没有设定迭代次数,则默认迭代50次。
  6. -model: 指定 protocol buffer 文件中的模型。也可以在 solver 配置文件中指定。
  7. -sighup_effect:用来设定当程序发生挂起事件时,执行的操作,可以设置为 snapshot, stop 或 none, 默认为 snapshot
  8. -sigint_effect: 用来设定当程序发生键盘中止事件时 (Ctrl + C), 执行的操作,可以设置为 snapshot, stop 或 none, 默认为 stop

刚才举例了一些 `train` 参数的例子,现在我们来看看其它三个`<command>`:

  1. test test 参数用在测试阶段,用于最终结果的输出,要模型配置文件中我们可以设定需要输入 accuracy 还是 loss. 假设我们要在验证集中验证已经训练好的模型,就可以这样写
./build/tools/caffe test -model examples/mnist/lenet_train_test.prototxt -weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 100

这个例子比较长,不仅用到了 test 参数,还用到了-model, -weights, -gpu 和 -iterations 四个参数。意思是利用训练好了的权重 (-weight),输入到测试模型中 (-model),用编号为0的 gpu (-gpu) 测试100次 (-iteration)。

  1. time time 参数用来在屏幕上显示程序运行时间。如
./build/tools/caffe time -model examples/mnist/lenet_train_test.prototxt -iterations 10
#这个例子用来在屏幕上显示 lenet 模型迭代10次所使用的时间。包括每次迭代的 forward 和 backward 所用的时间,也包括每层 forward 和 backward 所用的平均时间。

./build/tools/caffe time -model examples/mnist/lenet_train_test.prototxt -gpu 0
#这个例子用来在屏幕上显示 lenet 模型用 gpu 迭代50次所使用的时间。

./build/tools/caffe time -model examples/mnist/lenet_train_test.prototxt -weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 10
#利用给定的权重,利用第一块 gpu,迭代10次 lenet 模型所用的时间。
  1. device_query device_query 参数用来诊断 gpu 信息。
./build/tools/caffe device_query -gpu 0
  1. 关于GPU的例子
./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 0,1
./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu all

这两个例子表示: 用两块或多块 GPU 来平行运算,这样速度会快很多。但是如果你只有一块或没有 GPU, 就不要加 -gpu 参数了,加了反而慢。 最后,在 linux 下,本身就有一个 time 命令,因此可以结合进来使用,因此我们运行mnist例子的最终命令是(一块 gpu ):

time ./build/toos/caffe train -solver examples/mnist/lenet_solver.prototxt

图像数据转成 db (leveldb/lmdb)文件

脚本

#!/usr/bin/en sh
# /usr/bin/env sh
echo "Create train.txt..."
rm -rf caffemodel/train.txt
find caffemodel/train/cardboard -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 0/" >> caffemodel/train.txt
find caffemodel/train/glass -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 1/" >> caffemodel/train.txt
find caffemodel/train/metal -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 2/" >> caffemodel/train.txt
find caffemodel/train/paper -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 3/" >> caffemodel/train.txt
find caffemodel/train/plastic -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 4/" >> caffemodel/train.txt
find caffemodel/train/trash -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 5/" >> caffemodel/train.txt

echo "Create val.txt..."
rm -rf caffemodel/val.txt
find caffemodel/val/cardboard -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 0/" >> caffemodel/val.txt
find caffemodel/val/glass -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 1/" >> caffemodel/val.txt
find caffemodel/val/metal -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 2/" >> caffemodel/val.txt
find caffemodel/val/paper -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 3/" >> caffemodel/val.txt
find caffemodel/val/plastic -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 4/" >> caffemodel/val.txt
find caffemodel/val/trash -name *.jpg | cut -d '/' -f3-4 | sed "s/$/ 5/" >> caffemodel/val.txt

echo "All Done.."

rm -rf caffemodel/img_train_lmdb
build/tools/convert_imageset --shuffle \
--resize_height=224 --resize_width=224 \
caffemodel/train/ caffemodel/train.txt  caffemodel/img_train_lmdb/


rm -rf caffemodel/img_val_lmdb
build/tools/convert_imageset --shuffle \
--resize_height=224 --resize_width=224 \
caffemodel/val/ caffemodel/val.txt  caffemodel/img_val_lmdb/

详细步骤

在 Caffe 中,作者为我们提供了这样一个文件:`convert_imageset.cpp`,存放在根目录下的 `tools` 文件夹下。编译之后,生成对应的可执行文件放在 `buile/tools/` 下面,这个文件的作用就是用于将图片文件转换成 Caffe 框架中能直接使用的 db 文件。

convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME

需要带四个参数:

  • FLAGS: 图片参数组
  • ROOTFOLDER/: 图片存放的绝对路径,从 linux 系统根目录开始
  • LISTFILE: 图片文件列表清单,一般为一个 txt 文件,一行一张图片
  • DB_NAME: 最终生成的 db 文件存放目录 如果图片已经下载到本地电脑上了,那么我们首先需要创建一个图片列表清单,保存为 txt。本文以 Caffe 程序中自带的图片为例,进行讲解,图片目录是 example/images/, 两张图片,一张为 cat.jpg, 另一张为 fish_bike.jpg ,表示两个类别。

我们创建一个 sh 脚本文件,调用 linux 命令来生成图片清单: 

vim examples/images/create_filelist.sh

编辑这个文件,输入下面的代码并保存

# /usr/bin/env sh
DATA=examples/images
echo "Create train.txt..."
rm -rf $DATA/train.txt
find $DATA -name *cat.jpg | cut -d '/' -f3 | sed "s/$/ 1/" >> $DATA/train.txt
find $DATA -name *bike.jpg | cut -d '/' -f3 | sed "s/$/ 2/" >> $DATA/tmp.txt
cat $DATA/tmp.txt >> $DATA/train.txt
rm -rf $DATA/tmp.txt
echo "Done.."

这个脚本文件中,用到了 rm, find, cut, sed, cat 等 linux 命令。

  • rm: 删除文件
  • find: 寻找文件
  • cut: 截取路径
  • sed: 在每行的最后面加上标注。本例中将找到的 *cat.jpg 文件加入标注为1,找到的 *bike.jpg 文件加入标注为2
  • cat: 将两个类别合并在一个文件里。

最终生成如下的一个 train.txt 文件:

cat.jpg 1
fish-bike.jpg 2

当然,图片很少的时候,手动编写这个列表清单文件就行了。但图片很多的情况,就需要用脚本文件来自动生成了。在以后的实际应用中,还需要生成相应的 `val.txt` 和 `test.txt` 文件,方法是一样的。生成的这个 `train.txt` 文件,就可以作为第三个参数,直接使用了。 接下来,我们来了解一下 `FLAGS` 这个参数组,有些什么内容:

  • -gray: 是否以灰度图的方式打开图片。程序调用 opencv 库中的 imread() 函数来打开图片,默认为 false
  • -shuffle: 是否随机打乱图片顺序。默认为 false
  • -backend: 需要转换成的 db 文件格式,可选为 leveldb 或 lmdb,默认为 lmdb
  • -resize_width/resize_height: 改变图片的大小。在运行中,要求所有图片的尺寸一致,因此需要改变图片大小。 程序调用 opencv 库的 resize() 函数来对图片放大缩小,默认为0,不改变
  • -check_size: 检查所有的数据是否有相同的尺寸。默认为 false, 不检查
  • -encoded: 是否将原图片编码放入最终的数据中,默认为false
  • -encode_type: 与前一个参数对应,将图片编码为哪一个格式:‘png’, ‘jpg’……

知道这些参数后,我们就可以调用命令来生成最终的 lmdb 格式数据了。由于参数比较多,因此我们可以编写一个 sh 脚本来执行命令: 首先,创建 sh 脚本文件:

vim examples/images/create_lmdb.sh

编辑,输入下面的代码并保存

#!/usr/bin/en sh
DATA=examples/images
rm -rf $DATA/img_train_lmdb
build/tools/convert_imageset --shuffle \
--resize_height=256 --resize_width=256 \
$CAFFE_ROOT/examples/images/ $DATA/train.txt  $DATA/img_train_lmdb

设置参数 `-shuffle`,打乱图片顺序。设置参数 -resize_height-resize_width 将所有图片尺寸都变为256*256。 $CAFFE_ROOT/examples/images/ 为图片保存的绝对路径($CAFFE_ROOT 是 Caffe 工程的根目录)。

最后,运行这个脚本文件

sh examples/images/create_lmdb.sh

就会在 examples/images/ 目录下生成一个名为 img_train_lmdb 的文件夹,里面的文件就是我们需要的 db 文件了。

数据可视化环境(Python 接口) 配置

Caffe 程序是由 C++ 语言写的,本身是不带数据可视化功能的。只能借助其它的库或接口,如 opencv, python 或 matlab。大部分人使用 python 接口来进行可视化,因为 python 出了个比较强大的东西: ipython notebook , 现在的最新版本改名叫 jupyter notebook ,它能将python代码搬到浏览器上去执行,以富文本方式显示,使得整个工作可以以笔记的形式展现、存储,对于交互编程、学习非常方便。

计算图片数据的均值

图片减去均值后,再进行训练和测试,会提高速度和精度。因此,一般在各种模型中都会有这个操作。这个均值实际上就是计算所有训练样本的平均值,计算出来后,保存为一个均值文件,在以后的测试中,就可以直接使用这个均值来相减,而不需要对测试图片重新计算。

1、二进制格式的均值计算

Caffe 中使用的均值数据格式是 binaryproto, 作者为我们提供了一个计算均值的文件 compute_image_mean.cpp ,放在 Caffe 根目录下的 tools 文件夹里面。编译后的可执行体放在 build/tools/ 下面,我们直接调用就可以了。

./build/tools/compute_image_mean examples/mnist/mnist_train_lmdb examples/mnist/mean.binaryproto

参数解释:

  • 第一个参数:examples/mnist/mnist_train_lmdb, 表示需要计算均值的数据,格式为lmdb的训练数据。
  • 第二个参数:examples/mnist/mean.binaryproto,计算出来的结果保存文件。

2、python 格式的均值计算

如果我们要使用 python 接口,或者我们要进行特征可视化,可能就要用到 python 格式的均值文件了。首先,我们用 lmdb 格式的数据,计算出二进制格式的均值,然后,再转换成 python 格式的均值。 我们可以编写一个 python 脚本来实现:

#!/usr/bin/env python
import numpy as np
import sys,caffe
if len(sys.argv)!=3:
    print "Usage: python convert_mean.py mean.binaryproto mean.npy"
    sys.exit()
blob = caffe.proto.caffe_pb2.BlobProto()
bin_mean = open( sys.argv[1] , 'rb' ).read()
blob.ParseFromString(bin_mean)
arr = np.array( caffe.io.blobproto_to_array(blob) )
npy_mean = arr[0]
np.save( sys.argv[2] , npy_mean )

将这个脚本保存为 convert_mean.py。调用格式为:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

“`bash python convert_mean.py mean.binaryproto mean.npy “`

其中的 `mean.binaryproto` 就是经过前面步骤计算出来的二进制均值。`mean.npy` 就是我们需要的python格式的均值。

CaffeModel 可视化

通过前面的学习,我们已经能够正常训练各种数据了。设置好 `solver.prototxt` 后,我们可以把训练好的模型保存起来,如 `lenet_iter_10000.caffemodel`。训练多少次就自动保存一下,这个是通过 `snapshot` 进行设置的,保存文件的路径及文件名前缀是由 `snapshot_prefix` 来设定的。   `*.caffemodel` 文件里面存放的就是各层的参数,即 `net.params`,里面没有数据 `net.blobs`。   顺带还生成了一个相应的 `*.solverstate` 文件,这个和 `*.caffemodel` 差不多,但它多了一些数据,如模型名称、当前迭代次数等。两者的功能不一样,训练完后保存起来的 `*.caffemodel`,是在测试阶段用来分类的,而 `.*solverstate` 是用来恢复训练的,防止意外终止而保存的快照。

  既然我们知道了 `*.caffemodel` 里面保存的就是模型各层的参数,因此我们可以把这些参数提取出来,进行可视化,看一看究竟长什么样。   参数有两种类型:权值参数和偏置项,分别用 params[“conv1”][0] 和 params[“conv1”][1] 表示。

1、绘制网络模型

  `python/draw_net.py` 这个文件,就是用来绘制网络模型的,也就是将网络模型由 prototxt 变成一张图片。 draw_net.py 执行的时候带三个参数

  • 第一个参数:网络模型的 prototxt 文件
  • 第二个参数:保存的图片路径及名字
  • 第二个参数:–rankdir=x , x 有四种选项,分别是 LR, RL, TB, BT 。用来表示网络的方向,分别是从左到右,从右到左,从上到小,从下到上。默认为LR。

2、绘制 loss 和 accuracy 曲线

[绘制 loss 和 accuracy 曲线](http://blog.leanote.com/post/braveapple/Caffe-%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7)

使用训练好的模型

  1. 下载caffemodel文件 可以手动下载,也可以使用脚本下载

“`bash ./scripts/download_model_binary.py models/bvlc_reference_caffenet “`

  1. 下载均值文件 有了 caffemodel 文件,就需要对应的均值文件,在测试阶段,需要把测试数据减去均值。这个文件我们用脚本来下载,在 caffe 根目录下执行:

“`bash sh ./data/ilsvrc12/get_ilsvrc_aux.sh “`

执行并下载后,均值文件放在 data/ilsvrc12/ 文件夹里。

  1. synset_words.txt 文件 在调用脚本文件下载均值的时候,这个文件也一并下载好了。里面放的是1000个类的名称。数据准备好了,我们就可以开始分类了。这里提供两个版本的分类方法: 3.1 C++ 方法 在 Caffe 根目录下的 `examples/cpp-classification/` 文件夹下面,有个 `classification.cpp`文件,就是用来分类的。当然编译后,放在 `/build/examples/cpp_classification/` 下面。

  我们就直接运行命令:

“`bash ./build/examples/cpp_classification/classification.bin \ models/bvlc_reference_caffenet/deploy.prototxt \ models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel \ data/ilsvrc12/imagenet_mean.binaryproto \ data/ilsvrc12/synset_words.txt \ examples/images/cat.jpg “`

命令很长,用了很多的 \ 符号来换行。可以看出,从第二行开始就是参数,每行一个,共需要4个参数运行成功后,输出 top-5 结果:

“`bash ---------- Prediction for examples/images/cat.jpg ---------- 0.3134 - “n02123045 tabby, tabby cat” 0.2380 - “n02123159 tiger cat” 0.1235 - “n02124075 Egyptian cat” 0.1003 - “n02119022 red fox, Vulpes vulpes” 0.0715 - “n02127052 lynx, catamount” “`

即有0.3134的概率为 `tabby cat`, 有0.2380的概率为 `tiger cat`。 3.2、python 方法 python 接口可以使用 jupyter notebook 来进行可视化操作,因此推荐使用这种方法。编写一个 py 文件,命名为 py-classify.py

“`python *coding=utf-8 *加载必要的库 import numpy as np import sys,os *设置当前目录 caffe_root = ’home/xxx/caffe’ sys.path.insert(0, caffe_root + ‘python’) import caffe os.chdir(caffe_root) net_file=caffe_root + ‘models/bvlc_reference_caffenet/deploy.prototxt’ caffe_model=caffe_root + ‘models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel’ mean_file=caffe_root + ‘python/caffe/imagenet/ilsvrc_2012_mean.npy’ net = caffe.Net(net_file,caffe_model,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(caffe_root+’examples/images/cat.jpg’) net.blobs[‘data’].data[…] = transformer.preprocess(‘data’,im) out = net.forward() imagenet_labels_filename = caffe_root + ‘data/ilsvrc12/synset_words.txt’ labels = np.loadtxt(imagenet_labels_filename, str, delimiter=’\t’) top_k = net.blobs[‘prob’].data[0].flatten().argsort()[-1:-6:-1] for i in np.arange(top_k.size): print top_k[i], labels[top_k[i]] “`

执行这个文件,输出:

“`bash 281 n02123045 tabby, tabby cat 282 n02123159 tiger cat 285 n02124075 Egyptian cat 277 n02119022 red fox, Vulpes vulpes 287 n02127052 lynx, catamount “`

 Caffe开发团队实际上也编写了一个 python 版本的分类文件,路径为 `python/classify.py`。运行这个文件必需两个参数,一个输入图片文件,一个输出结果文件。而且运行必须在 `python` 目录下。假设当前目录是 caffe 根目录,则运行:  

“`bash cd python python classify.py ../examples/images/cat.jpg result.npy “`

分类的结果保存为当前目录下的 `result.npy`文件里面,是看不见的。而且这个文件有错误,运行的时候,会提示 `Mean shape incompatible with input shape`的错误。因此,要使用这个文件,我们还得进行修改:

  • 修改均值计算: 定位到

“`bash mean = np.load(args.mean_file) “`

这一行,在下面加上一行:

“`bash mean=mean.mean(1).mean(1) “`

则可以解决报错的问题。

  • 修改文件,使得结果显示在命令行下: 定位到

“`python

Classify.

start = time.time() predictions = classifier.predict(inputs, not args.center_only) print(“Done in %.2f s.” % (time.time() - start)) “`

这个地方,在后面加上几行,如下所示:

“`python

Classify.

start = time.time() predictions = classifier.predict(inputs, not args.center_only) print(“Done in %.2f s.” % (time.time() - start)) imagenet_labels_filename = ‘../data/ilsvrc12/synset_words.txt’ labels = np.loadtxt(imagenet_labels_filename, str, delimiter=’\t’) top_k = predictions.flatten().argsort()[-1:-6:-1] for i in np.arange(top_k.size): print top_k[i], labels[top_k[i]] “`

就样就可以了。运行不会报错,而且结果会显示在命令行下面。

如何将别人训练好的model用到自己的数据上

我们把最后一层的输出类别改一下,然后把层的名称改一下,最后用别人的参数、修改后的 network 和我们自己的数据,再进行训练,使得参数适应我们的数据,这样一个过程,通常称之为微调 (fine tuning)

  1. 下载`.caffemodel`文件
  2. 准备数据

> 这些数据共有500张图片,分为大巴车、恐龙、大象、鲜花和马五个类,每个类100张。编号分别以0,1,2,3,4开头,各为一类。我从其中每类选出20张作为测试,其余80张作为训练。因此最终训练图片400张(放在 train 文件夹内,每个类一个子文件夹),测试图片100张(放在 test 文件夹内,每个类一个子文件夹)。

caffenet的网络配置文件,放在 caffe/models/bvlc_reference_caffenet/ 这个文件夹里面,名字叫 train_val.prototxt。

修改 train 阶段的 data 层为:

“`c layer { name: “data” type: “Data” top: “data” top: “label” include { phase: TRAIN } transform_param { mean_file: “examples/finetune/mean.binaryproto” mirror: true crop_size: 227 } data_param { source: “examples/finetune/finetune_train_lmdb” batch_size: 100 backend: LMDB } } “`

把均值文件(mean_file)、数据源文件 (source)、批次大小 (batch_size)和数据源格式 (backend) 这四项作相应的修改。 修改 test 阶段的 data 层:

“`c layer { name: “data” type: “Data” top: “data” top: “label” include { phase: TEST } transform_param { mean_file: “examples/finetune/mean.binaryproto” mirror: false crop_size: 227 } data_param { source: “examples/finetune/finetune_test_lmdb” batch_size: 100 backend: LMDB } } “`

修改最后一个全连接层 (fc8):

“`c layer { name: “fc8-my” *原来为”fc8” type: “InnerProduct” bottom: “fc7” top: “fc8” param { lr_mult: 1.0 decay_mult: 1.0 } param { lr_mult: 2.0 decay_mult: 0.0 } inner_product_param { num_output: 5 *原来为”1000” weight_filler { type: “gaussian” std: 0.01 } bias_filler { type: “constant” value: 0.0 } } } “`

看注释的地方,就只有两个地方修改,其它不变。设置好后,就可以开始微调了(fine tuning)。训练结果就是一个新的 model,可以用来单张图片和多张图片测试。

参考文档

[Caffe 使用教程](http://blog.leanote.com/post/braveapple/Caffe-%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7) [caffe学习系列:训练自己的图片集](https://blog.csdn.net/qq_27923041/article/details/54139887) [caffe 有关prototxt文件的设置解读](https://blog.csdn.net/greenlight_74110/article/details/78640916)

我的实践操作

我用的模型是[deploy_resnet10-1x32d.prototxt](https://github.com/soeaver/caffe-model/tree/master/cls/resnet) 这个模型的输入数据格式为

“`bash input_shape { dim: 1 dim: 3 dim: 224 dim: 224 } “`

我用的数据集合是[这个](https://pan.baidu.com/s/1nuqlTnN) 这个数据集有500张图片,分为大巴车、恐龙、大象、鲜花和马五个类,每个类100张。 编号分别以3,4,5,6,7开头,各为一类。我从其中每类选出20张作为测试,其余80张作为训练。因此最终训练图片400张,测试图片100张,共5类。 首先在examples下面创建一个myfile的文件夹,来用存放配置文件和脚本文件。 我将图片放在`/root/caffe/examples/myfile/re`下。即训练图片目录:`/root/caffe/examples/myfile/re/train/`,测试图片目录:`/root/caffe/examples/myfile/re/test/` 然后编写一个脚本create_filelist.sh,用来生成train.txt和test.txt清单文件 编辑create_filelist.sh文件,并写入如下代码,并保存

“`bash *!/usr/bin/env sh DATA=/root/caffe/examples/myfile/re MY=/root/caffe/examples/myfile

echo “Create train.txt…” rm -rf $MY/train.txt for i in 3 4 5 6 7 do j=`expr $i - 3` find $DATA/train -name $i*.jpg | cut -d ’’ -f7-8 | sed “s/$ $j/”>>$MY/train.txt done echo “Create test.txt…” rm -rf $MY/test.txt for i in 3 4 5 6 7 do j=`expr $i - 3` find $DATA/test -name $i*.jpg | cut -d ’’ -f7-8 | sed “s/$ $j/”>>$MY/test.txt done “`

然后,运行此脚本 在这个脚本里,我们通过查找替换功能,将train.txt文件和test.txt文件里面的图片编号依次由3,4,5,6,7改为0,1,2,3,4。之所以要改编号的原因是,编号从3~7会影响训练精度。修改完后,train.txt内容如下图所示 打开train.txt可以看到如下内容,同样test.txt文件里面的内容也是如此,二者仅数量不同。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191224203626639.png) 接着再编写一个脚本文件create_lmdb.sh,调用convert_imageset命令来转换数据格式

“`bash *!/usr/bin/en sh My=/root/caffe/examples/myfile echo “Create train lmdb..” rm -rf $My/img_train_lmdb /root/caffe/build/tools/convert_imageset –shuffle \ –resize_height=256 –resize_width=256 \ $My/re/train/ $My/train.txt $My/img_train_lmdb

echo “Create test lmdb..” rm -rf $My/img_test_lmdb /root/caffe/build/tools/convert_imageset –shuffle \ –resize_height=256 –resize_width=256 \ $My/re/test/ $My/test.txt $My/img_test_lmdb

echo “All Done” “`

代码中的shuffle为,打乱图片顺序。`/root/caffe/examples/myfile/re`为下载的图像数据保存的绝对路径。 执行该脚本即可。运行成功后,会在`examples/myfile`下面生成两个`img_test_lmdb`和`img_train_lmdb`,分别用于保存图片转换后的lmdb文件。

图片减去均值再训练,会提高训练速度和精度。因此,一般都会有这个操作。 caffe程序提供了一个计算均值的文件`compute_image_mean.cpp`,我们直接使用就可以了。

“`bash /root/caffe/build/tools/compute_image_mean /root/caffe/examples/myfile/img_train_lmdb /root/caffe/examples/myfile/mean.binaryproto “`

第一个参数为上一步得到的`img_train_lmdb`,第二个参数为转换后的文件保存位置。

这一步我失败了

创建模型并编写配置文件 配置文件`solver.prototxt`的内容为

“`bash net: “/root/caffe/examples/myfile/deploy_resnet10-1x32d.prototxt” test_iter: 2 test_interval: 50 base_lr: 0.01 lr_policy: “step” gamma: 0.1 stepsize: 100 display: 20 max_iter: 500 momentum: 0.9 weight_decay: 0.0005 solver_mode: GPU “`

100个测试数据,batch_size为50,因此test_iter设置为2,就能全覆盖了。在训练过程中,调整学习率,逐步变小。