【caffe速成】caffe图像分类从模型自定义到测试
文章首發(fā)于微信公眾號《與有三學(xué)AI》
【caffe速成】caffe圖像分類從模型自定義到測試
這是給大家準(zhǔn)備的caffe速成例子
這一次我們講講 Caffe 這個主流的開源框架從訓(xùn)練到測試出結(jié)果的全流程。到此,我必須假設(shè)大家已經(jīng)有了深度學(xué)習(xí)的基礎(chǔ)知識并了解卷積網(wǎng)絡(luò)的工作原理。
相關(guān)的代碼、數(shù)據(jù)都在我們 Git 上,希望大家 Follow 一下這個 Git 項目,后面會持續(xù)更新不同框架下的任務(wù)。
https://github.com/longpeng2008/LongPeng_ML_Course
這一篇我們說一個分類任務(wù),給大家準(zhǔn)備了 500 張微笑的圖片、500 張非微笑的圖片,放置在 data 目錄下,圖片預(yù)覽如下,已經(jīng)縮放到 60*60 的大小:
這是非微笑的圖片:
?
這是微笑的圖片:
?
?
01 Caffe 是什么
Caffe 是以 C++/CUDA 代碼為主,最早的深度學(xué)習(xí)框架之一,比 TensorFlow、Mxnet、Pytorch 等都更早,支持命令行、Python 和 Matlab 接口,單機(jī)多卡、多機(jī)多卡等都可以很方便的使用,CPU 和 GPU 之間無縫切換。
對于入門級別的任務(wù),如圖像分類,Caffe 上手的成本最低,幾乎不需要寫一行代碼就可以開始訓(xùn)練,所以我推薦 Caffe 作為入門學(xué)習(xí)的框架。
Caffe 相對于 TensorFlow 等使用 pip 一鍵安裝的方式來說,編譯安裝稍微麻煩一些,但其實(shí)依舊很簡單,我們以 Ubuntu 16.04 為例子,官網(wǎng)的安裝腳本足夠用了,它有一些依賴庫。
sudo apt-get install libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler
sudo apt-get install --no-install-recommends libboost-all-devsudo apt-get install libatlas-base-dev
sudo apt-get install libgflags-dev libgoogle-glog-dev liblmdb-dev
裝完之后,到?Git 上 clone 代碼,修改 Makefile.config 就可以進(jìn)行編譯安裝,如果其中有任何問題,多 Google,還有什么問題,留言吧。當(dāng)然,對于有 GPU 的讀者,還需要安裝 cuda 以及 Nvidia 驅(qū)動。
?
02 Caffe 訓(xùn)練
Caffe 完成一個訓(xùn)練,必要準(zhǔn)備以下資料:一個是 train.prototxt 作為網(wǎng)絡(luò)配置文件,另一個是 solver.prototxt 作為優(yōu)化參數(shù)配置文件,再一個是訓(xùn)練文件 list。
另外,在大多數(shù)情況下,需要一個預(yù)訓(xùn)練模型作為權(quán)重的初始化。
(1)準(zhǔn)備網(wǎng)絡(luò)配置文件
我們準(zhǔn)備了一個 3*3 的卷積神經(jīng)網(wǎng)絡(luò),它的 train.prototxt 文件是這樣的:
name: "mouth"
layer {
?name: "data"
?type: "ImageData"
?top: "data"
?top: "clc-label"
?image_data_param {
? ?source: "all_shuffle_train.txt"
? ?batch_size: 96
? ?shuffle: true
?}
?transform_param {
? ?mean_value: 104.008
? ?mean_value: 116.669
? ?mean_value: 122.675
? ?crop_size: 48
? ?mirror: true
?}
?include: { phase: TRAIN}
}
layer {
?name: "data"
?type: "ImageData"
?top: "data"
?top: "clc-label"
?image_data_param {
? ?source: "all_shuffle_val.txt"
? ?batch_size: 30
? ?shuffle: false
?}
?transform_param {
? ?mean_value: 104.008
? ?mean_value: 116.669
? ?mean_value: 122.675
? ?crop_size: 48
? ?mirror: false
?}
?include: { phase: TEST}
}
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: 12
? ?pad: 1
? ?kernel_size: 3
? ?stride: 2
? ?weight_filler {
? ? ?type: "xavier"
? ? ?std: 0.01
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0.2
? ?}
?}
}
layer {
?name: "relu1"
?type: "ReLU"
?bottom: "conv1"
?top: "conv1"
}
layer {
?name: "conv2"
?type: "Convolution"
?bottom: "conv1"
?top: "conv2"
?param {
? ?lr_mult: 1
? ?decay_mult: 1
?}
?param {
? ?lr_mult: 2
? ?decay_mult: 0
?}
?convolution_param {
? ?num_output: 20
? ?kernel_size: 3
? ?stride: 2
? ?pad: 1
? ?weight_filler {
? ? ?type: "xavier"
? ? ?std: 0.1
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0.2
? ?}
?}
}
layer {
?name: "relu2"
?type: "ReLU"
?bottom: "conv2"
?top: "conv2"
}
layer {
?name: "conv3"
?type: "Convolution"
?bottom: "conv2"
?top: "conv3"
?param {
? ?lr_mult: 1
? ?decay_mult: 1
?}
?param {
? ?lr_mult: 2
? ?decay_mult: 0
?}
?convolution_param {
? ?num_output: 40
? ?kernel_size: 3
? ?stride: 2
? ?pad: 1
? ?weight_filler {
? ? ?type: "xavier"
? ? ?std: 0.1
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0.2
? ?}
?}
}
layer {
?name: "relu3"
?type: "ReLU"
?bottom: "conv3"
?top: "conv3"
}
layer {
?name: "ip1-mouth"
?type: "InnerProduct"
?bottom: "conv3"
?top: "pool-mouth"
?param {
? ?lr_mult: 1
? ?decay_mult: 1
?}
?param {
? ?lr_mult: 2
? ?decay_mult: 0
?}
?inner_product_param {
? ?num_output: 128
? ?weight_filler {
? ? ?type: "xavier"
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0
? ?}
?}
}
layer {
? ?bottom: "pool-mouth"
? ?top: "fc-mouth"
? ?name: "fc-mouth"
? ?type: "InnerProduct"
? ?param {
? ? ? ?lr_mult: 1
? ? ? ?decay_mult: 1
? ?}
? ?param {
? ? ? ?lr_mult: 2
? ? ? ?decay_mult: 1
? ?}
? ?inner_product_param {
? ? ? ?num_output: 2
? ? ? ?weight_filler {
? ? ? ? ? ?type: "xavier"
? ? ? ?}
? ? ? ?bias_filler {
? ? ? ? ? ?type: "constant"
? ? ? ? ? ?value: 0
? ? ? ?}
? ?}
}
layer {
? ?bottom: "fc-mouth"
? ?bottom: "clc-label"
? ?name: "loss"
? ?type: "SoftmaxWithLoss"
? ?top: "loss"
}
layer {
? ?bottom: "fc-mouth"
? ?bottom: "clc-label"
? ?top: "acc"
? ?name: "acc"
? ?type: "Accuracy"
? ?include {
? ? ? ?phase: TRAIN
? ?}
? ?include {
? ? ? ?phase: TEST
? ?}
}
可以看出,Caffe 的這個網(wǎng)絡(luò)配置文件,每一個卷積層,都是以 layer{} 的形式定義,layer 的bottom、top 就是它的輸入輸出,type 就是它的類型,有的是數(shù)據(jù)層、有的是卷積層、有的是 loss 層。
我們采用 netscope 來可視化一下這個模型。
從上面看很直觀的看到,網(wǎng)絡(luò)的輸入層是 data 層,后面接了3個卷積層,其中每一個卷積層都后接了一個 relu 層,最后 ip1-mouth、fc-mouth 是全連接層。Loss 和 acc 分別是計算 loss 和 acc 的層。
各層的配置有一些參數(shù),比如 conv1 有卷積核的學(xué)習(xí)率、卷積核的大小、輸出通道數(shù)、初始化方法等,這些可以后續(xù)詳細(xì)了解。
(2)準(zhǔn)備訓(xùn)練 list
我們看上面的 data layer,可以到?
image_data_param?里面有
source: "all_shuffle_train.txt"
它是什么呢,就是輸入用于訓(xùn)練的 list,它的內(nèi)容是這樣的:
../../../../datas/mouth/1/182smile.jpg 1
../../../../datas/mouth/1/435smile.jpg 1
../../../../datas/mouth/0/40neutral.jpg 0
../../../../datas/mouth/1/206smile.jpg 1
../../../../datas/mouth/0/458neutral.jpg 0
../../../../datas/mouth/0/158neutral.jpg 0
../../../../datas/mouth/1/322smile.jpg 1
../../../../datas/mouth/1/83smile.jpg 1
../../../../datas/mouth/0/403neutral.jpg 0
../../../../datas/mouth/1/425smile.jpg 1
../../../../datas/mouth/1/180smile.jpg 1
../../../../datas/mouth/1/233smile.jpg 1
../../../../datas/mouth/1/213smile.jpg 1
../../../../datas/mouth/1/144smile.jpg 1
../../../../datas/mouth/0/327neutral.jpg 0
格式就是,圖片的名字 + 空格 + label,這就是 Caffe 用于圖片分類默認(rèn)的輸入格式。
(3)準(zhǔn)備優(yōu)化配置文件:
net: "./train.prototxt"
test_iter: 100
test_interval: 10
base_lr: 0.00001
momentum: 0.9
type: "Adam"
lr_policy: "fixed"
display: 100
max_iter: 10000
snapshot: 2000
snapshot_prefix: "./snaps/conv3_finetune"
solver_mode: GPU
介紹一下上面的參數(shù)。
net 是網(wǎng)絡(luò)的配置路徑。test_interval是指訓(xùn)練迭代多少次之后,進(jìn)行一次測試。test_iter是測試多少個batch,如果它等于 1,就說明只取一個 batchsize 的數(shù)據(jù)來做測試,如果 batchsize 太小,那么對于分類任務(wù)來說統(tǒng)計出來的指標(biāo)也不可信,所以最好一次測試,用到所有測試數(shù)據(jù)。因?yàn)?#xff0c;常令test_iter*test_batchsize=測試集合的大小。
base_lr、momentum、type、lr_policy是和學(xué)習(xí)率有關(guān)的參數(shù),base_lr和lr_policy決定了學(xué)習(xí)率大小如何變化。type 是優(yōu)化的方法,以后再談。max_iter是最大的迭代次數(shù),snapshot 是每迭代多少次之后存儲迭代結(jié)果,snapshot_prefix為存儲結(jié)果的目錄,caffe 存儲的模型后綴是 .caffemodel。solver_mode可以指定用 GPU 或者 CPU 進(jìn)行訓(xùn)練。
(4)訓(xùn)練與結(jié)果可視化
我們利用 C++ 的接口進(jìn)行訓(xùn)練,命令如下:
SOLVER=./solver.prototxt
WEIGHTS=./init.caffemodel
../../../../libs/Caffe_Long/build/tools/caffe train -solver $SOLVER -weights $WEIGHTS -gpu 0 2>&1 | tee log.txt
其中,caffe train 就是指定訓(xùn)練。我們可以利用腳本可視化一下訓(xùn)練結(jié)果,具體參考git項目:
?
03 Caffe 測試
上面我們得到了訓(xùn)練結(jié)果,下面開始采用自己的圖片進(jìn)行測試。
train.prototxt 與 test.prototxt 的區(qū)別
訓(xùn)練時的網(wǎng)絡(luò)配置與測試時的網(wǎng)絡(luò)配置是不同的,測試沒有 acc 層,也沒有 loss 層,取輸出的 softmax 就是分類的結(jié)果。同時,輸入層的格式也有出入,不需要再輸入 label,也不需要指定圖片 list,但是要指定輸入尺度,我們看一下 test.prototxt 和可視化結(jié)果。
name: "mouth"
layer {
?name: "data"
?type: "Input"
?top: "data"
?input_param { shape: { dim: 1 dim: 3 dim: 48 dim: 48 } }
}
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: 12
? ?pad: 1
? ?kernel_size: 3
? ?stride: 2
? ?weight_filler {
? ? ?type: "xavier"
? ? ?std: 0.01
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0.2
? ?}
?}
}
layer {
?name: "relu1"
?type: "ReLU"
?bottom: "conv1"
?top: "conv1"
}
layer {
?name: "conv2"
?type: "Convolution"
?bottom: "conv1"
?top: "conv2"
?param {
? ?lr_mult: 1
? ?decay_mult: 1
?}
?param {
? ?lr_mult: 2
? ?decay_mult: 0
?}
?convolution_param {
? ?num_output: 20
? ?kernel_size: 3
? ?stride: 2
? ?pad: 1
? ?weight_filler {
? ? ?type: "xavier"
? ? ?std: 0.1
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0.2
? ?}
?}
}
layer {
?name: "relu2"
?type: "ReLU"
?bottom: "conv2"
?top: "conv2"
}
layer {
?name: "conv3"
?type: "Convolution"
?bottom: "conv2"
?top: "conv3"
?param {
? ?lr_mult: 1
? ?decay_mult: 1
?}
?param {
? ?lr_mult: 2
? ?decay_mult: 0
?}
?convolution_param {
? ?num_output: 40
? ?kernel_size: 3
? ?stride: 2
? ?pad: 1
? ?weight_filler {
? ? ?type: "xavier"
? ? ?std: 0.1
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0.2
? ?}
?}
}
layer {
?name: "relu3"
?type: "ReLU"
?bottom: "conv3"
?top: "conv3"
}
layer {
?name: "ip1-mouth"
?type: "InnerProduct"
?bottom: "conv3"
?top: "pool-mouth"
?param {
? ?lr_mult: 1
? ?decay_mult: 1
?}
?param {
? ?lr_mult: 2
? ?decay_mult: 0
?}
?inner_product_param {
? ?num_output: 128
? ?weight_filler {
? ? ?type: "xavier"
? ?}
? ?bias_filler {
? ? ?type: "constant"
? ? ?value: 0
? ?}
?}
}
layer {
? ?bottom: "pool-mouth"
? ?top: "fc-mouth"
? ?name: "fc-mouth"
? ?type: "InnerProduct"
? ?param {
? ? ? ?lr_mult: 1
? ? ? ?decay_mult: 1
? ?}
? ?param {
? ? ? ?lr_mult: 2
? ? ? ?decay_mult: 1
? ?}
? ?inner_product_param {
? ? ? ?num_output: 2
? ? ? ?weight_filler {
? ? ? ? ? ?type: "xavier"
? ? ? ?}
? ? ? ?bias_filler {
? ? ? ? ? ?type: "constant"
? ? ? ? ? ?value: 0
? ? ? ?}
? ?}
}
layer {
? ?bottom: "fc-mouth"
? ?name: "loss"
? ?type: "Softmax"
? ?top: "prob"
}
使用 Python 進(jìn)行測試
由于 Python 目前廣泛使用,下面使用 Python 來進(jìn)行測試,它要做的就是導(dǎo)入模型、導(dǎo)入圖片、輸出結(jié)果。
下面是所有的代碼,我們詳細(xì)解釋下:
---代碼段1,這一段,我導(dǎo)入一些基本庫,同時導(dǎo)入caffe的路徑---
#_*_ coding:utf8
import sys
sys.path.insert(0, '../../../../../libs/Caffe_Long/python/')
import caffe
import os,shutil
import numpy as np
from PIL import Image as PILImage
from PIL import ImageMath
import matplotlib.pyplot as plt
import time
import cv2
---代碼段2,這一段,我們添加一個參數(shù)解釋器,方便參數(shù)管理---
debug=True
import argparse
def parse_args():
? ?parser = argparse.ArgumentParser(description='test resnet model for portrait segmentation')
? ?parser.add_argument('--model', dest='model_proto', help='the model', default='test.prototxt', type=str)
? ?parser.add_argument('--weights', dest='model_weight', help='the weights', default='./test.caffemodel', type=str)
? ?parser.add_argument('--testsize', dest='testsize', help='inference size', default=60,type=int)
? ?parser.add_argument('--src', dest='img_folder', help='the src image folder', type=str, default='./')
? ?parser.add_argument('--gt', dest='gt', help='the gt', type=int, default=0)
? ?args = parser.parse_args()
? ?return args
def start_test(model_proto,model_weight,img_folder,testsize):
---代碼段3,這一段,我們就完成了網(wǎng)絡(luò)的初始化---
? ?caffe.set_device(0)
? ?#caffe.set_mode_cpu()
? ?net = caffe.Net(model_proto, model_weight, caffe.TEST)
? ?imgs = os.listdir(img_folder)
? ?pos = 0
? ?neg = 0
? ?for imgname in imgs:
---代碼段4,這一段,是讀取圖片并進(jìn)行預(yù)處理,還記得我們之前的訓(xùn)練,是采用 BGR 的輸入格式,減去了圖像均值吧,同時,輸入網(wǎng)絡(luò)的圖像,也需要 resize 到相應(yīng)尺度。預(yù)處理是通過 caffe 的類,transformer 來完成,set_mean 完成均值,set_transpose 完成維度的替換,因?yàn)?caffe blob 的格式是 batch、channel、height、width,而 numpy 圖像的維度是 height、width、channel 的順序---
? ? ? imgtype = imgname.split('.')[-1]
? ? ? imgid = imgname.split('.')[0]
? ? ? if imgtype != 'png' and imgtype != 'jpg' and imgtype != 'JPG' and imgtype != 'jpeg' and imgtype != 'tif' and imgtype != 'bmp':
? ? ? ? ? print imgtype,"error"
? ? ? ? ? continue
? ? ? imgpath = os.path.join(img_folder,imgname)
? ? ? img = cv2.imread(imgpath)
? ? ? if img is None:
? ? ? ? ? print "---------img is empty---------",imgpath
? ? ? ? ? continue
? ? ? img = cv2.resize(img,(testsize,testsize))
? ? ? transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
? ? ? transformer.set_mean('data', np.array([104.008,116.669,122.675]))
? ? ? transformer.set_transpose('data', (2,0,1))
---代碼段5,這一段,就得到了輸出結(jié)果了,并做一些可視化顯示---
? ? ? out = net.forward_all(data=np.asarray([transformer.preprocess('data', img)]))
? ? ? result = out['prob'][0]
? ? ? print "---------result prob---------",result,"-------result size--------",result.shape
? ? ? probneutral = result[0]
? ? ? print "prob neutral",probneutral?
? ? ? probsmile = result[1]
? ? ? print "prob smile",probsmile
? ? ? problabel = -1
? ? ? probstr = 'none'
? ? ? if probneutral > probsmile:
? ? ? ? ? probstr = "neutral:"+str(probneutral)
? ? ? ? ? pos = pos + 1
? ? ? else:
? ? ? ? ? probstr = "smile:"+str(probsmile)
? ? ? ? ? neg = neg + 1
? ? ? if debug:
? ? ? ? ?showimg = cv2.resize(img,(256,256))
? ? ? ? ?cv2.putText(showimg,probstr,(30,50),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),1)
? ? ? ? ?cv2.imshow("test",showimg)
? ? ? ? ?k = cv2.waitKey(0)
? ? ? ? ?if k == ord('q'):
? ? ? ? ? ? ?break
? ?print "pos=",pos?
? ?print "neg=",neg?
if __name__ == '__main__':
? ? args = parse_args()
? ? start_test(args.model_proto,args.model_weight,args.img_folder,args.testsize)
經(jīng)過前面的介紹,我們已經(jīng)學(xué)會了 Caffe 的基本使用,但是我們不能停留于此。Caffe 是一個非常優(yōu)秀的開源框架,有必要去細(xì)讀它的源代碼。
至于怎么讀 Caffe 的代碼,建議閱讀我寫的Caffe代碼閱讀系列內(nèi)容。
?
04 總結(jié)
雖然現(xiàn)在很多人沒有從 Caffe 開始學(xué),但是希望提升自己 C++ 水平和更深刻理解深度學(xué)習(xí)中的一些源碼的,建議從 Caffe 開始學(xué)起。
?
同時,在我的知乎專欄也會開始同步更新這個模塊,歡迎來交流
https://zhuanlan.zhihu.com/c_151876233
注:部分圖片來自網(wǎng)絡(luò)
—END—
本系列完整文章:
第一篇:【caffe速成】caffe圖像分類從模型自定義到測試
第二篇:【tensorflow速成】Tensorflow圖像分類從模型自定義到測試
第三篇:【pytorch速成】Pytorch圖像分類從模型自定義到測試
第四篇:【paddlepaddle速成】paddlepaddle圖像分類從模型自定義到測試
第五篇:【Keras速成】Keras圖像分類從模型自定義到測試
第六篇:【mxnet速成】mxnet圖像分類從模型自定義到測試
第七篇:【cntk速成】cntk圖像分類從模型自定義到測試
第八篇:【chainer速成】chainer圖像分類從模型自定義到測試
第九篇:【DL4J速成】Deeplearning4j圖像分類從模型自定義到測試
第十篇:【MatConvnet速成】MatConvnet圖像分類從模型自定義到測試
第十一篇:【Lasagne速成】Lasagne/Theano圖像分類從模型自定義到測試
第十二篇:【darknet速成】Darknet圖像分類從模型自定義到測試
感謝各位看官的耐心閱讀,不足之處希望多多指教。后續(xù)內(nèi)容將會不定期奉上,歡迎大家關(guān)注有三公眾號 有三AI!
?
總結(jié)
以上是生活随笔為你收集整理的【caffe速成】caffe图像分类从模型自定义到测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【模型解读】“不正经”的卷积神经网络
- 下一篇: 【tensorflow速成】Tensor