眾所周知,圖像相比文字能夠提供更加生動,容易理解及更具藝術感的信息,是人們轉遞與交換信息的重要來源。
談到圖像分類,圖像分類是根據圖像的語義信息對不同類別圖像進行區分,是計算機視覺中重要的基礎問題,也是圖像檢測、圖像分割、物體跟蹤、行為分析等其他高層視覺任務的基礎,在許多領域都有着廣泛的應用。如:安防領域的人臉識別和智能視頻分析等,交通領域的交通場景識別,互聯網領域基於內容的圖像檢索和相冊自動歸類,醫學領域的圖像識別等。在本項目里,你將了解使用深度學習進行圖片分類的原理、圖片數據的一些處理技巧、深度神經網絡模型搭建以及訓練過程等,除此之外,還會加深你對Paddlepaddle深度學習框架的了解。
1.背景知識
圖像分類包括通用圖像分類,細粒度圖像分類等。
圖1展示了通用圖像分類效果,即模型可以正確識別圖像上的主要物體。
圖1 通用圖像分類展示
圖2展示了細粒度圖像分類 - 花卉識別的效果,要求模型可以正確識別花的類別。

圖2 細粒度圖像分類展示
一個好的模型既要對不同類別識別正確,同時也應該能夠對不同視角,光照,背景,變形或部分遮擋的圖像正確識別(這里我們統一稱作圖像擾動)。
圖3展示了一些圖像的擾動,較好的模型會像聰明的人類一樣能夠正確識別。

圖3 擾動圖片展示
2.項目說明
在本項目中,我們實現的是基於PaddlePaddle框架利用深度神經網絡進行桃子圖片的分類。
本項目使用數據集下載地址為:https://pan.baidu.com/s/17i8yYDVNqDDrO6qc4jjaxQ
關於數據集的補充說明:下載下來的數據集data文件里包括train與test兩個文件夾,需要進行以下操作:對於命名為train的文件夾 ,train下面有4個文件夾,將文件夾名分別
改為class0,class1,class2,class3。 class0下再建立一個文件夾命名為0,第一類訓練圖片放在這里,class1下再建立個命名為1的文件夾,class2下建個2,class3下建個3 。
test 文件夾 以此類推操作。然后就可以進行訓練測試了。

本實驗使用的桃子數據集展示
3.項目實現過程:
3.1 導入相關庫
1 import numpy as np 2 import sys 3 from PIL import Image 4 5 import paddle.v2 as paddle
3.2 數據預處理以及reader的構造
數據集中6400 張大桃照片按照紅、大、中、小等元素按照分檔建立圖片數據集合,實驗思路是將圖片數據集放入卷積神經網絡(CNN)中進行訓練,自動提取用於分級的影響要素並形成分類邏輯。
由於本項目的數據集是圖片,我們首先來了解一下圖片的CHW布局:
縮寫:C =通道,H =高度,W =寬度
由cv2或PIL打開的圖像的默認布局是HWC。
而PaddlePaddle僅支持CHW布局,而CHW只是HWC的轉置,故我們在輸入數據前要進行轉置操作將HWC布局調整為CHW。
1 def reader_creator(flag): 2 def reader(): 3 cnt = 0 4 if flag == 'train': 5 path = './train' 6 else: 7 path = './test' 8 for label_dir in os.listdir(path): 9 if('0' in label_dir or '1' in label_dir or '2' in label_dir or '3' in label_dir): 10 label = label_dir[-1:] 11 for dir in os.listdir(path+'/'+label_dir): 12 if('.' not in dir): 13 for image_name in os.listdir(path+'/'+label_dir+'/'+dir): 14 if('png' in image_name): 15 im = Image.open(path+'/'+label_dir+'/'+dir+'/'+image_name) 16 #if path == './test': 17 # print path+'/'+label_dir+'/'+dir+'/'+image_name 18 # print label 19 try: 20 pass 21 #im = im.resize((800, 600), Image.ANTIALIAS) 22 #im = im.resize((32, 32), Image.ANTIALIAS) 23 except: 24 print 'lose frame' 25 continue 26 # 由cv2或PIL打開的圖像的默認布局是HWC。 27 # PaddlePaddle僅支持CHW布局。而CHW只是HWC的轉置。 28 im = np.array(im).astype(np.float32) 29 im = im.transpose((2, 0, 1)) # 轉置操作,以得到CHW布局 30 im = im.flatten() 31 im = im / 255.0 32 if im.shape[0] != 230400: 33 continue 34 cnt = cnt+1 35 yield im, int(label) 36 print cnt 37 return reader
構造train reader 與test reader:
1 def train(): 2 return reader_creator('train'); 3 def test(): 4 return reader_creator('test');
3.3 搭建模型
在完成了數據預處理與Reader的構造等基礎工程之后,我們就可以正式進入神經網絡模型的搭建流程了。搭建模型的內容主要有以下幾點:
-
定義網絡結構
-
初始化PaddlePaddle
-
配置網絡結構和設置參數
- 配置網絡結構
- 定義損失函數cost
- 創建parameters
- 定義優化器optimizer
(1)定義網絡結構
兩個隱藏層激活函數選用Relu激活函數,各層均使用全連接層。
1 # 定義多層感知機結構 2 def multilayer_perceptron(img): 3 hidden1 = paddle.layer.fc(input=img, size=128, act=paddle.activation.Relu()) 4 hidden2 = paddle.layer.fc(input=hidden1, size=64, act=paddle.activation.Relu()) 5 predict = paddle.layer.fc(input=hidden2, size=4, act=paddle.activation.Softmax()) 6 return predict
(2)初始化PaddlePaddle
然后進行最基本的初始化操作,在PaddlePaddle中使用paddle.init(use_gpu=False, trainer_count=1)來進行初始化:
- use_gpu=False表示不使用gpu進行訓練
- trainer_count=1表示僅使用一個訓練器進行訓練
1 # PaddlePaddle 初始化 2 paddle.init(use_gpu=False, trainer_count=1)
(3)配置網絡結構及參數設置
定義參數及輸入:
設置算法參數(如數據維度、類別數目和batch size等參數),所用數據集是桃子圖片。桃子所分的種類是4,因此,classdim=4。
定義數據輸入層image和類別標簽lbl,本實驗使用image=paddle.layer.data(name=”image”, type=paddle.data_type.dense_vector_sequence(data_dim))函數,名稱為“image”,數據類型為data_dim維向量;
1 #數據格式 2 datadim = 3 * 320 *240 3 #圖片類別數目 4 classdim = 4 5 # 描述神經網絡的輸入,定義數據輸入層image 6 image = paddle.layer.data(name="image", height=320, width=240, type=paddle.data_type.dense_vector(datadim)) 7 # 定義類別標簽 8 lbl = paddle.layer.data(name="label", type=paddle.data_type.integer_value(classdim))
選用net模型,使用前面定義的multilayer_perceptron()
1 # 使用net模型為 multilayer_perceptron() 2 net = multilayer_perceptron(image)
獲得網絡最后的Softmax層
1 # 獲得網絡最后的Softmax層 2 out = paddle.layer.fc(input=net, size=classdim, act=paddle.activation.Softmax())
定義損失函數
在配置網絡結構之后,我們需要定義一個損失函數來計算梯度並優化參數。
1 # 定義損失函數 2 cost = paddle.layer.classification_cost(input=out, label=lbl)
創建參數
PaddlePaddle中提供了接口paddle.parameters.create(cost)來創建和初始化參數,參數cost表示基於我們剛剛創建的cost損失函數來創建和初始化參數。
1 # 利用cost創建 parameters Create parameters 2 parameters = paddle.parameters.create(cost)
創建優化器
通過 learning_rate_decay_a (簡寫a) 、learning_rate_decay_b (簡寫b) 和 learning_rate_schedule 指定學習率調整策略,這里采用離散指數的方式調節學習率。
計算公式如下, n 代表已經處理過的累計總樣本數,lr0 即為參數里設置的 learning_rate。
1 # 創建 optimizer Create optimizer 2 momentum_optimizer = paddle.optimizer.Momentum( 3 momentum=0.9, 4 regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128), 5 learning_rate=0.01 / 128.0, 6 learning_rate_decay_a=0.1, 7 learning_rate_decay_b=50000 * 100, 8 learning_rate_schedule='discexp')
定義事件處理程序
定義事件處理函數可以幫助我們在訓練時實時了解訓練進度
1 #定義事件處理函數 2 def event_handler(event): 3 if isinstance(event, paddle.event.EndIteration): 4 # 每隔5個batch 打印一次進度 5 if event.batch_id % 5 == 0: 6 print "\nPass %d, Batch %d, Cost %f, %s" % ( 7 event.pass_id, event.batch_id, event.cost, event.metrics) 8 else: 9 sys.stdout.write('.') 10 sys.stdout.flush() 11 if isinstance(event, paddle.event.EndPass): 12 # 每隔2個pass 保存一次模型參數parameters 13 if event.pass_id %2 == 0: 14 with open('params_pass_%d.tar' % event.pass_id, 'w') as f: 15 parameters.to_tar(f) 16 17 result = trainer.test( 18 reader=paddle.batch( 19 paddle.reader.shuffle( 20 test(), buf_size=50000), batch_size=128), 21 feeding={'image': 0, 22 'label': 1}) 23 print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
3.4 訓練模型
完成了模型搭建的過程,接下來我們就可以開始模型的訓練過程了,首先,我們需要用 paddle.trainer.SGD() 函數定義一個隨機梯度下降trainer,配置三個參數cost、parameters、update_equation,它們分別表示損失函數、參數和更新公式。
1 # 創建訓練器 2 trainer = paddle.trainer.SGD( 3 cost=cost, parameters=parameters, update_equation=momentum_optimizer)
訓練器創建完成后就可以正式開始訓練了,訓練參數設置如下:
- paddle.reader.shuffle(train(),buf_size=20000)表示trainer從train()這個reader中讀取了buf_size=20000大小的數據並打亂順序
- paddle.batch(reader(), batch_size=128)表示從打亂的數據中再取出batch_size=128大小的數據進行一次迭代訓練
- 參數feeding是feeding索引,其將數據層image和label輸入trainer,也就是訓練數據的來源。
- 參數event_handler是事件管理機制,讀者可以自定義event_handler,根據事件信息作相應的操作。
- 參數num_passes=20 表示迭代訓練20次后停止訓練。
當然,這些參數可以根據需求自行設置,讀者可以通過修改參數來觀察其對訓練結果的影響。
1 # 開始訓練 2 trainer.train( 3 reader=paddle.batch( 4 paddle.reader.shuffle( 5 train(), buf_size=20000), 6 batch_size=128), 7 num_passes=20, 8 event_handler=event_handler, 9 feeding={'image': 0, 10 'label': 1})
3.5 加載模型並進行預測
模型訓練完成以后,我們就可以加載訓練好的來進行分類預測並觀察預測效果了,首先我們需要加載訓練好的模型:
1 # 加載訓練的模型文件 params_pass_18.tar,可視訓練效果調整所要加載的模型文件 2 with open('params_pass_18.tar', 'r') as f: 3 parameters = paddle.parameters.Parameters.from_tar(f)
加載了訓練模型以后,我們同樣需要對測試集數據進行處理,使之成為預測模型可以接受的輸入格式,因此,測試集中的數據同樣要進行轉置操作,以達到PaddlePaddle輸入圖片CHW布局要求。
然后使用預測函數 paddle.infer(output_layer = out,parameters = parameters ,input = test_data) 進行預測,其中參數output_layer 表示輸出層,參數parameters表示模型參數,參數input表示輸入的測試數據。
1 for i in range(0,10000): 2 i = str(i) 3 # 選擇測試集所在路徑,這里選擇的是類別為3的測試集,可以調整為其他類別的繼續測試 4 file = './test/class3/3/'+i+'.png' 5 try: 6 im = Image.open(file) 7 except: 8 continue 9 im = np.array(im).astype(np.float32) 10 im = im.transpose((2, 0, 1)) # CHW 11 im = im.flatten() 12 im = im / 255.0 13 if im.shape[0] != 230400: 14 continue 15 # 先建立圖像列表文件 16 test_data = [] 17 test_data.append((im, )) 18 19 20 probs = paddle.infer( output_layer=out, parameters=parameters, input=test_data) 21 lab = np.argsort(-probs) # probs and lab are the results of one batch data 22 # np.argsort(x) 返回的是數組值從小到大的索引值 np.argosrt(-x)返回的是數組值從大到小的索引值 23 # 輸出預測結果 24 print "Label of image/%s.png is: %d" % (i,lab[0][0]) 25 print probs[0][lab[0][0]]
4.參考文獻
1.https://github.com/PaddlePaddle/book/tree/develop/03.image_classification
2.https://github.com/sorting4peach/sorting4peach