引言
本文主要以ORL_64x64人臉數據庫識別為例,介紹如何使用基于matlab的CDBN工具箱。至于卷積深度置信網絡(CDBN,Convolutional Deep Belief Network)的理論知識,只給出筆者整理的一些學習資源。
卷積深度置信網絡理論知識
參考以下學習資料
- CSDN博客---受限玻爾茲曼機(RBM)學習筆記
- CSDN博客---深度信念網絡(Deep Belief Network)
- 知乎---卷積神經網絡工作原理直觀的解釋
- 量子位公眾號---一文了解各種卷積結構原理及優劣
- 中國知網博碩論文---基于卷積深度置信網絡的歌手識別
- CDBN paper(全英)
CDBN工具箱簡介
據筆者了解,目前,比較流行的深度學習框架,如TensorFlow、DeepLearning4j等不支持CDBN。GitHub上有基于Matlab的CDBN工具箱:CDBN工具箱下載鏈接
下面簡要介紹該工具箱。
從GitHub上下載的壓縮包解壓后再打開,文件目錄如下:
其中,最為重要的肯定是toolbox。toolbox里面有三個lib,分別是CDBN,DBN,Softmax庫。本文將用到CDBN和Softmax兩個庫。
需要注意的是,由于這個工具箱不是官方版的,因此可能存在某些bug,后面會涉及到筆者使用工具箱過程中的一些經驗。
神經網絡結構
介紹一下本文搭建的進行人臉識別的卷積深度置信網絡的結構。
主體結構:兩個卷積受限玻爾茲曼機(CRBM,Convolutional Restricted Boltzmann Machine)堆疊(每個CRBM后都接有池化層),頂層采用Softmax,實現分類。
第一個CRBM:
- 第二個CRBM:
Softmax層
神經元個數40個,最大迭代次數maxIter=1000,代價函數為交叉熵代價函數(Cross-Entropy Error)其他參數
其他諸如學習速率等的參數使用CDBN-master\toolbox\CDBNLIB\default_layer2D.m中的默認值。
編程
以下講解編程步驟。
步驟一:安裝工具箱
只需運行setup_toolbox.m即可。
安裝工具箱其實只是把用到的一些函數添加到matlab的搜索路徑,因此你完全可以把工具箱內所有的文件都復制到你當前的路徑下,不過肯定麻煩啦!步驟二:加載和矩陣化數據
%load data
dataFortrain=load('ORL_64x64\StTrainFile1.txt');%注意修改路徑
train_data=dataFortrain(:,1:end-1)';%訓練樣本
train_data=reshape(train_data,[64,64,1,360]);%矩陣化訓練樣本
trainL=dataFortrain(:,end);%訓練樣本標簽
dataFortest=load('ORL_64x64\StTestFile1.txt');%注意修改路徑
test_data=dataFortest(:,1:end-1)';%測試樣本
test_data=reshape(test_data,[64,64,1,40]);%注意修改路徑
testL=dataFortest(:,end);%測試樣本標簽
重點講一下第四行。
StTrainFile1.txt中有360行,4097列。每一行是一幅人臉圖像(像素為64X64=4096)的4096個灰度值,最后一列是該幅人臉圖像的標簽(1-40),表明其屬于哪個人的(共40人,即分類數目為40)。由此可見,一幅二維圖像(矩陣)被拉成了向量進行存儲,因此在數據輸入CDBN前,我們要對向量進行矩陣化,調用matlab的reshape方法,最終生成一個4維的矩陣,四個維度分別是64,64,1,360(樣本數)。倒數第二行同理。
- 步驟三:定義層參數
工具箱把一層layer定義為一個struct對象。
%INITIALIZE THE PARAMETERS OF THE NETWORK
%first layer setting
layer{1} = default_layer2D();
layer{1}.inputdata=train_data;%輸入訓練樣本
layer{1}.n_map_v=1;
layer{1}.n_map_h=9;
layer{1}.s_filter=[7 7];
layer{1}.stride=[1 1];
layer{1}.s_pool=[2 2];
layer{1}.batchsize=90;
layer{1}.n_epoch=1;
%second layer setting
layer{2} = default_layer2D();
layer{2}.n_map_v=9;
layer{2}.n_map_h=16;
layer{2}.s_filter=[5 5];
layer{2}.stride=[1 1];
layer{2}.s_pool=[2 2];
layer{2}.batchsize=10;
layer{2}.n_epoch=1;
需要注意的是,layer{i}=default_layer2D()這條語句是必須的,且必須位于所有層參數定義語句的最前面。原因:如果layer{i}=default_layer2D()這條語句不位于最前面的話,在這條語句前面的參數賦值語句實質不起作用,這些參數還是取默認值。特別是對于第一層,因為default_layer2D()方法中是沒有定義inputdata字段的,如果layer{1}.inputdata=train_data這條語句位于layer{1}=default_layer2D()前面,則會出現“使用未定義字段”的錯誤。
補充:要注意根據自己使用的數據集的情況設定層的輸入類型,對[0,1]之間的數據集,應該使用二值神經網絡,設定 layer{i}.type_input = 'Binary'(程序默認);其他數據集,應該設為layer{i}.type_input = 'Gaussian';至于二者的區別,自行百度,這里不展開了。
- 步驟四:訓練CDBN網絡
這個過程是無監督學習,只需調用cdbn2D方法即可。
在調用cdbn2D方法之前,CDBN-master\toolbox\CDBNLIB\mex中的crbm_forward2D_batch_mex.c要先用mex命令編譯生成crbm_forward2D_batch_mex.mexw64文件才能供matlab調用
mex crbm_forward2D_batch_mex.c
在編譯前,crbm_forward2D_batch_mex.c要先修改:128行的out_id要改成在最開始的位置定義,否則編譯時會出現“缺少:在類型前面’”的報錯信息(PS:第一次遇到這么奇葩的報錯,當時懷疑C語言是不是白學了),原因:VS2010的C編譯器只支持C89標準,對C99標準支持不完全,而在C89標準中,變量需要放到函數體的前面聲明,先聲明再使用。
%% ----------- GO TO 2D CONVOLUTIONAL DEEP BELIEF NETWORKS ------------------%%
tic;
[model,layer] = cdbn2D(layer);
save('model_parameter','model','layer');
toc;
trainD = model{1}.output;%訓練樣本的第一個CRBM的輸出,是一個4維矩陣
trainD1 = model{2}.output;%訓練樣本的第二個CRBM的輸出,是一個4維矩陣
我們來比較一下train_data、trainD、trainD1的大小
現在再看看卷積神經網絡的圖示,是不是很好理解了呢?
- 步驟五:將測試樣本輸入訓練好的CDBN網絡,提取高維特征
這段代碼可以直接copy,修改好變量名即可!
%% ------------ TESTDATA FORWARD MODEL WITH THE PARAMETERS ------------------ %%
% FORWARD MODEL OF NETWORKS
H = length(layer);
layer{1}.inputdata = test_data;
fprintf('output the testdata features:>>...\n');
tic;
if H >= 2
% PREPROCESSS INPUTDATA TO BE SUITABLE FOR TRAIN
layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);
for i = 2:H
layer{i}.inputdata = model{i-1}.output;
layer{i} = preprocess_train_data2D(layer{i});
model{i}.output = crbm_forward2D_batch_mex(model{i},layer{i},layer{i}.inputdata);
end
else
layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);
end
testD = model{1}.output;%訓練樣本的第一個CRBM的輸出,是一個4維矩陣
testD1 = model{2}.output;%訓練樣本的第二個CRBM的輸出,是一個4維矩陣
toc;
同樣的,我們來看一下test_data、testD、testD1的大小:
- 步驟六:訓練Softmax分類器,同時進行識別
這里我們用到 softmaxExercise(inputData,labels,inputData_t,labels_t)這個函數
參數說明:
- inputdata:訓練樣本的CDBN輸出,要求是二維矩陣
-labels:訓練樣本的標簽
-inputData_t:測試樣本的CDBN輸出,要求是二維矩陣
-labels_t:測試樣本的標簽
由于CDBN的輸出是4維矩陣,因此在訓練Softmax分類器前,需要把矩陣拉成向量(和之前的過程相反)。代碼如下,可直接copy,修改變量名即可!
%% ------------------------------- Softmax ---------------------------------- %%
fprintf('train the softmax:>>...\n');
tic;
% TRANSLATE THE OUTPUT TO ONE VECTOR
trainDa = [];
trainLa=trainL;
for i= 1:size(trainD,4)
a1 = [];
a2 = [];
a3 = [];
for j = 1:size(trainD,3)
a1 = [a1;reshape(trainD(:,:,j,i),size(trainD,2)*size(trainD,1),1)];
end
for j = 1:size(trainD1,3)
a2 = [a2;reshape(trainD1(:,:,j,i),size(trainD1,2)*size(trainD1,1),1)];
end
a3 = [a3;a1;a2];
trainDa = [trainDa,a3];
end
testDa = [];
testLa=testL;
for i= 1:size(testD,4)
b1 = [];
b2 = [];
b3 = [];
for j = 1:size(testD,3)
b1 = [b1;reshape(testD(:,:,j,i),size(testD,2)*size(testD,1),1)];
end
for j =1:size(testD1,3)
b2 = [b2;reshape(testD1(:,:,j,i),size(testD1,2)*size(testD1,1),1)];
end
b3 = [b3;b1;b2];
testDa = [testDa,b3];
end
我們來看一下拉成向量后的trainDa以及testDa的大小
對比一下,train_data和test_data在矩陣化之前的大小:
可見,CDBN作為特征提取器,將4096維特征映射到了9873維特征,提高了Softmax的分類能力!
softmaxExercise.m中有這樣一段注釋:
因此在調用softmaxExercise方法前,要做以下4個工作:
- 修改softmaxExercise.m第22行的numClasses,如本文改為40
- 修改softmaxExercise.m第96行的maxIter,本文取1000
PS:個人覺得softmaxExercise方法應該增加兩個入口參數,即numClasses和maxIter,如此才能更好體現封裝的思想。
- softmaxCost.m中定義需要的損失函數,只需要改第90行
cost = -(1. / numCases) * sum(sum(groundTruth .* log(p))) + (lambda / 2.) * sum(sum(theta.^2));
這條語句即可,原文件使用的是交叉熵代價函數。
- 有必要的話可以修改 softmaxPredict.m中內容,個人覺得完全沒必要,保留即可。
最后調用softmaxExercise方法
softmaxExercise(trainDa,trainLa,testDa,testLa);
toc;
完整代碼
FaceRecognitionDemo.m
clear;
%load data
dataFortrain=load('ORL_64x64\StTrainFile1.txt');
train_data=dataFortrain(:,1:end-1)';
train_data=reshape(train_data,[64,64,1,360]);
trainL=dataFortrain(:,end);
dataFortest=load('ORL_64x64\StTestFile1.txt');
test_data=dataFortest(:,1:end-1)';
test_data=reshape(test_data,[64,64,1,40]);
testL=dataFortest(:,end);
%INITIALIZE THE PARAMETERS OF THE NETWORK
%first layer setting
layer{1} = default_layer2D();
layer{1}.inputdata=train_data;
layer{1}.n_map_v=1;
layer{1}.n_map_h=9;
layer{1}.s_filter=[7 7];
layer{1}.stride=[1 1];
layer{1}.s_pool=[2 2];
layer{1}.batchsize=90;
layer{1}.n_epoch=1;
%second layer setting
layer{2} = default_layer2D();
layer{2}.n_map_v=9;
layer{2}.n_map_h=16;
layer{2}.s_filter=[5 5];
layer{2}.stride=[1 1];
layer{2}.s_pool=[2 2];
layer{2}.batchsize=10;
layer{2}.n_epoch=1;
%% ----------- GO TO 2D CONVOLUTIONAL DEEP BELIEF NETWORKS ------------------ %%
tic;
[model,layer] = cdbn2D(layer);
save('model_parameter','model','layer');
toc;
trainD = model{1}.output;
trainD1 = model{2}.output;
%% ------------ TESTDATA FORWARD MODEL WITH THE PARAMETERS ------------------ %%
% FORWARD MODEL OF NETWORKS
H = length(layer);
layer{1}.inputdata = test_data;
fprintf('output the testdata features:>>...\n');
tic;
if H >= 2
% PREPROCESSS INPUTDATA TO BE SUITABLE FOR TRAIN
layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);
for i = 2:H
layer{i}.inputdata = model{i-1}.output;
layer{i} = preprocess_train_data2D(layer{i});
model{i}.output = crbm_forward2D_batch_mex(model{i},layer{i},layer{i}.inputdata);
end
else
layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);
end
testD = model{1}.output;
testD1 = model{2}.output;
toc;
%% ------------------------------- Softmax ---------------------------------- %%
fprintf('train the softmax:>>...\n');
tic;
% TRANSLATE THE OUTPUT TO ONE VECTOR
trainDa = [];
trainLa=trainL;
for i= 1:size(trainD,4)
a1 = [];
a2 = [];
a3 = [];
for j = 1:size(trainD,3)
a1 = [a1;reshape(trainD(:,:,j,i),size(trainD,2)*size(trainD,1),1)];
end
for j = 1:size(trainD1,3)
a2 = [a2;reshape(trainD1(:,:,j,i),size(trainD1,2)*size(trainD1,1),1)];
end
a3 = [a3;a1;a2];
trainDa = [trainDa,a3];
end
testDa = [];
testLa=testL;
for i= 1:size(testD,4)
b1 = [];
b2 = [];
b3 = [];
for j = 1:size(testD,3)
b1 = [b1;reshape(testD(:,:,j,i),size(testD,2)*size(testD,1),1)];
end
for j =1:size(testD1,3)
b2 = [b2;reshape(testD1(:,:,j,i),size(testD1,2)*size(testD1,1),1)];
end
b3 = [b3;b1;b2];
testDa = [testDa,b3];
end
softmaxExercise(trainDa,trainLa,testDa,testLa);
toc;
運行截圖及準確率
97.5%的識別率,還是可以接受的,一方面是數據集好,另一方面是搭建得網絡好。
讀者可以試一試調整CDBN網絡的參數,比如增大epoch(本文取1),看能否獲得更高的識別率。
為了方便讀者研究,附上所有文件。
本Demo文件匯總下載鏈接(原鏈接失效,此為新版連接),提取碼:7f6i
以下是使用此工具箱的幾點提示:
- 原始工具箱只在LINUX系統測試過,由于LINUX系統和WINDOWS系統的文件分隔符不同,
因此DemoCDBN_Binary_2D.m的第83行、
cdbn2D.m的第15、24行、 setup_toolbox.m的文件分隔符要修改。 - 源程序存在bug,即若樣本個數不是batchsize的整數倍的話,會出錯,因此在此bug排除前,應將batchsize設置為樣本數目的因數
- 類別標簽不要用負數或0,比如進行二分類,標簽不要設為-1和1,可以設為1和2,這是因為softmaxCost.m文件中的第18行建立稀疏矩陣時會以標簽作為矩陣的索引,如果設為0或負數,肯定會報錯:矩陣索引必須為正數