【caffe速成】caffe图像分类从模型自定义到测试
文章首發于微信公眾號《與有三學AI》
【caffe速成】caffe圖像分類從模型自定義到測試
這是給大家準備的caffe速成例子
這一次我們講講 Caffe 這個主流的開源框架從訓練到測試出結果的全流程。到此,我必須假設大家已經有了深度學習的基礎知識并了解卷積網絡的工作原理。
相關的代碼、數據都在我們 Git 上,希望大家 Follow 一下這個 Git 項目,后面會持續更新不同框架下的任務。
https://github.com/longpeng2008/LongPeng_ML_Course
這一篇我們說一個分類任務,給大家準備了 500 張微笑的圖片、500 張非微笑的圖片,放置在 data 目錄下,圖片預覽如下,已經縮放到 60*60 的大小:
這是非微笑的圖片:
?
這是微笑的圖片:
?
?
01 Caffe 是什么
Caffe 是以 C++/CUDA 代碼為主,最早的深度學習框架之一,比 TensorFlow、Mxnet、Pytorch 等都更早,支持命令行、Python 和 Matlab 接口,單機多卡、多機多卡等都可以很方便的使用,CPU 和 GPU 之間無縫切換。
對于入門級別的任務,如圖像分類,Caffe 上手的成本最低,幾乎不需要寫一行代碼就可以開始訓練,所以我推薦 Caffe 作為入門學習的框架。
Caffe 相對于 TensorFlow 等使用 pip 一鍵安裝的方式來說,編譯安裝稍微麻煩一些,但其實依舊很簡單,我們以 Ubuntu 16.04 為例子,官網的安裝腳本足夠用了,它有一些依賴庫。
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 就可以進行編譯安裝,如果其中有任何問題,多 Google,還有什么問題,留言吧。當然,對于有 GPU 的讀者,還需要安裝 cuda 以及 Nvidia 驅動。
?
02 Caffe 訓練
Caffe 完成一個訓練,必要準備以下資料:一個是 train.prototxt 作為網絡配置文件,另一個是 solver.prototxt 作為優化參數配置文件,再一個是訓練文件 list。
另外,在大多數情況下,需要一個預訓練模型作為權重的初始化。
(1)準備網絡配置文件
我們準備了一個 3*3 的卷積神經網絡,它的 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 的這個網絡配置文件,每一個卷積層,都是以 layer{} 的形式定義,layer 的bottom、top 就是它的輸入輸出,type 就是它的類型,有的是數據層、有的是卷積層、有的是 loss 層。
我們采用 netscope 來可視化一下這個模型。
從上面看很直觀的看到,網絡的輸入層是 data 層,后面接了3個卷積層,其中每一個卷積層都后接了一個 relu 層,最后 ip1-mouth、fc-mouth 是全連接層。Loss 和 acc 分別是計算 loss 和 acc 的層。
各層的配置有一些參數,比如 conv1 有卷積核的學習率、卷積核的大小、輸出通道數、初始化方法等,這些可以后續詳細了解。
(2)準備訓練 list
我們看上面的 data layer,可以到?
image_data_param?里面有
source: "all_shuffle_train.txt"
它是什么呢,就是輸入用于訓練的 list,它的內容是這樣的:
../../../../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 用于圖片分類默認的輸入格式。
(3)準備優化配置文件:
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
介紹一下上面的參數。
net 是網絡的配置路徑。test_interval是指訓練迭代多少次之后,進行一次測試。test_iter是測試多少個batch,如果它等于 1,就說明只取一個 batchsize 的數據來做測試,如果 batchsize 太小,那么對于分類任務來說統計出來的指標也不可信,所以最好一次測試,用到所有測試數據。因為,常令test_iter*test_batchsize=測試集合的大小。
base_lr、momentum、type、lr_policy是和學習率有關的參數,base_lr和lr_policy決定了學習率大小如何變化。type 是優化的方法,以后再談。max_iter是最大的迭代次數,snapshot 是每迭代多少次之后存儲迭代結果,snapshot_prefix為存儲結果的目錄,caffe 存儲的模型后綴是 .caffemodel。solver_mode可以指定用 GPU 或者 CPU 進行訓練。
(4)訓練與結果可視化
我們利用 C++ 的接口進行訓練,命令如下:
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 就是指定訓練。我們可以利用腳本可視化一下訓練結果,具體參考git項目:
?
03 Caffe 測試
上面我們得到了訓練結果,下面開始采用自己的圖片進行測試。
train.prototxt 與 test.prototxt 的區別
訓練時的網絡配置與測試時的網絡配置是不同的,測試沒有 acc 層,也沒有 loss 層,取輸出的 softmax 就是分類的結果。同時,輸入層的格式也有出入,不需要再輸入 label,也不需要指定圖片 list,但是要指定輸入尺度,我們看一下 test.prototxt 和可視化結果。
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 進行測試
由于 Python 目前廣泛使用,下面使用 Python 來進行測試,它要做的就是導入模型、導入圖片、輸出結果。
下面是所有的代碼,我們詳細解釋下:
---代碼段1,這一段,我導入一些基本庫,同時導入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,這一段,我們添加一個參數解釋器,方便參數管理---
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,這一段,我們就完成了網絡的初始化---
? ?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,這一段,是讀取圖片并進行預處理,還記得我們之前的訓練,是采用 BGR 的輸入格式,減去了圖像均值吧,同時,輸入網絡的圖像,也需要 resize 到相應尺度。預處理是通過 caffe 的類,transformer 來完成,set_mean 完成均值,set_transpose 完成維度的替換,因為 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,這一段,就得到了輸出結果了,并做一些可視化顯示---
? ? ? 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)
經過前面的介紹,我們已經學會了 Caffe 的基本使用,但是我們不能停留于此。Caffe 是一個非常優秀的開源框架,有必要去細讀它的源代碼。
至于怎么讀 Caffe 的代碼,建議閱讀我寫的Caffe代碼閱讀系列內容。
?
04 總結
雖然現在很多人沒有從 Caffe 開始學,但是希望提升自己 C++ 水平和更深刻理解深度學習中的一些源碼的,建議從 Caffe 開始學起。
?
同時,在我的知乎專欄也會開始同步更新這個模塊,歡迎來交流
https://zhuanlan.zhihu.com/c_151876233
注:部分圖片來自網絡
—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圖像分類從模型自定義到測試
感謝各位看官的耐心閱讀,不足之處希望多多指教。后續內容將會不定期奉上,歡迎大家關注有三公眾號 有三AI!
?
總結
以上是生活随笔為你收集整理的【caffe速成】caffe图像分类从模型自定义到测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【模型解读】“不正经”的卷积神经网络
- 下一篇: 【tensorflow速成】Tensor