https://blog.csdn.net/tostq/article/details/51786315
上一節(jié)我們總體介紹項(xiàng)目并說明Minst手寫數(shù)字?jǐn)?shù)據(jù)庫(kù)的使用憾朴,這一節(jié)我們將重點(diǎn)介紹CNN網(wǎng)絡(luò)總體結(jié)構(gòu)膊毁。
上圖我們已經(jīng)非常熟悉富玷,其為Yann在1998年介紹的LeNet-5網(wǎng)絡(luò)的結(jié)構(gòu),其剛被提出淀零,就在學(xué)術(shù)和工業(yè)領(lǐng)域上得到廣泛應(yīng)用烛缔,而本文的CNN卷積網(wǎng)絡(luò)卻是如下圖所示(博主自己畫的,畫這個(gè)圖還是挺麻煩的:L砍鸠,不清晰請(qǐng)?jiān)彛蚅eNet-5相比主要有以下三點(diǎn)不同:
(1)LeNet-5給輸入圖像增加了一圈黑邊耕驰,使輸入圖像大小變成了32x32爷辱,這樣的目的是為了在下層卷積過程中保留更多原圖的信息。
(2)LeNet-5的卷積層C3只有16個(gè)模板朦肘,得到16個(gè)輸出饭弓,而本文的卷積層C3由于是全連接,所以有6*12個(gè)模板媒抠,得到12個(gè)輸出圖像
(3)LeNet-5多了兩種弟断,分別是C5到F6的全連接神經(jīng)網(wǎng)絡(luò)層,和F6到OUTPUT高斯連接網(wǎng)絡(luò)層趴生。而本文的直接由采樣層S4直接經(jīng)過一層全連接神經(jīng)網(wǎng)絡(luò)層到OUTPUT阀趴。
下面我們將重點(diǎn)介紹各層的結(jié)構(gòu)及數(shù)據(jù)的前向傳播。
一苍匆、各層的解釋
(1)卷積層C1
輸入為的灰度圖像刘急,灰度圖像分別同6個(gè)的模板進(jìn)行卷積操作,分別得到了6個(gè)的卷積圖像浸踩,圖像里的每個(gè)像素加上一個(gè)權(quán)重叔汁,并經(jīng)過一個(gè)激活函數(shù),得到該層的輸出检碗。
所以該層的相關(guān)參數(shù)為:6個(gè)的模板參數(shù)据块,6個(gè)模板對(duì)應(yīng)的權(quán)重參數(shù),共個(gè)參數(shù)
Tips:
關(guān)于激活函數(shù):激活函數(shù)我們?cè)趯W(xué)習(xí)神經(jīng)網(wǎng)絡(luò)時(shí)就已經(jīng)接觸過了后裸,其主要有兩個(gè)目的瑰钮,第一是將數(shù)據(jù)鉗制在一定范圍內(nèi)(如Sigmoid函數(shù)將數(shù)據(jù)壓縮在-1到1之間),不太高也不太低微驶,第二是用來加入非線性因素的浪谴,因?yàn)榫€性模型的表達(dá)能力不夠。傳統(tǒng)神經(jīng)網(wǎng)絡(luò)中最常用的兩個(gè)激活函數(shù)Sigmoid系和Tanh系因苹,而Sigmoid系(Logistic-Sigmoid苟耻、Tanh-Sigmoid)被視為神經(jīng)網(wǎng)絡(luò)的核心所在。本文的例子就是Sigmoid系扶檐。
近年來凶杖,在深度學(xué)習(xí)領(lǐng)域中效果最好,應(yīng)用更為廣泛的是ReLu激活函數(shù)款筑,其相較于Sigmoid系智蝠,主要變化有三點(diǎn):①單側(cè)抑制 ②相對(duì)寬闊的興奮邊界 ③稀疏激活性腾么。特別是在神經(jīng)科學(xué)方面,除了新的激活頻率函數(shù)之外杈湾,神經(jīng)科學(xué)家還發(fā)現(xiàn)了的稀疏激活性廣泛存在于大腦的神經(jīng)元解虱,神經(jīng)元編碼工作方式具有稀疏性和分布性。大腦同時(shí)被激活的神經(jīng)元只有1~4%漆撞。 從信號(hào)方面來看殴泰,即神經(jīng)元同時(shí)只對(duì)輸入信號(hào)的少部分選擇性響應(yīng),大量信號(hào)被刻意的屏蔽了浮驳,這樣可以提高學(xué)習(xí)的精度悍汛,更好更快地提取稀疏特征。而在經(jīng)驗(yàn)規(guī)則的初始化W之后至会,傳統(tǒng)的Sigmoid系函數(shù)同時(shí)近乎有一半的神經(jīng)元被激活离咐,這不符合神經(jīng)科學(xué)的研究,而且會(huì)給深度網(wǎng)絡(luò)訓(xùn)練帶來巨大問題奋献。Softplus照顧到了新模型的前兩點(diǎn)健霹,卻沒有稀疏激活性旺上。因而瓶蚂,校正函數(shù)即ReLu函數(shù)成了最大贏家。
(2)采樣層S2及S4(Pooling層)
采樣層S又名Pooling層宣吱,Pooling主要是為了減少數(shù)據(jù)處理的維度窃这,常見的pooling方法有max pooling和average pooling等。
max pooling 就是選擇當(dāng)前塊內(nèi)最大像素值來表示當(dāng)前局部塊
average pooling 就是選擇當(dāng)前塊的像素值平均值來代替
本文的選擇Pooling方法是average pooling征候,而使用廣泛效果較好的方法卻是max pooling杭攻。(看到這里,你可能會(huì)吐槽疤坝,為什么不用效果好兆解,因?yàn)槠骄?jì)算相比而言,有那么一丟丟簡(jiǎn)單E苋唷)
(3)卷積層C3
這里的卷積層是一個(gè)全連接的卷積層锅睛。輸出的卷積公式如下,這里表示圖像历谍,表示卷積模板现拒,表示偏重,表示激活函數(shù)望侈,表示輸入圖像序號(hào)(i=1~6)印蔬,j表示該層輸出圖像序號(hào)(j=1~12)
由此可以看到在卷積層C3中輸入為6個(gè)的圖像,輸出為12個(gè)的圖像
所需要訓(xùn)練的參數(shù)有個(gè)的卷積模板和12個(gè)偏重(每個(gè)模板對(duì)應(yīng)的偏重都是相同的)
而實(shí)際上由于神經(jīng)網(wǎng)絡(luò)的稀疏結(jié)構(gòu)和減少訓(xùn)練時(shí)間的需要脱衙,該卷積層一般不是利用全連接的侥猬,就比如前面介紹LeNet-5網(wǎng)絡(luò)例驹,只需要利用16個(gè)卷積模板就可以了,而不是全連接的個(gè)退唠,其連接方法如下眠饮,其最終得到16個(gè)輸出圖像。
這里X表示選擇卷積铜邮,比如第0張輸出圖像是由第0仪召、1、2張輸入圖像分別同第0個(gè)卷積模板卷積相加松蒜,再加上偏重扔茅,經(jīng)過激活函數(shù)得到的。而第15張圖像是由第0秸苗、1召娜、2、3惊楼、4玖瘸、5張輸入圖像分別同第15個(gè)卷積模板卷積相加得到的。
(4)輸出層O5:
采樣層S4后檀咙,我們將得到12張的圖像雅倒,將所有圖像展開成一維,就得到了位的向量弧可。
輸出層是由輸入192位蔑匣,輸出10位的全連接單層神經(jīng)網(wǎng)絡(luò),共有10個(gè)神經(jīng)元構(gòu)成棕诵,每個(gè)神經(jīng)元都同192位輸入相連裁良,即都有192位的輸入和1位輸出,其處理公式如下校套,這里表示輸出神經(jīng)元的序號(hào)价脾,表示輸入的序號(hào)。
所以該層參數(shù)共有個(gè)權(quán)重笛匙,和10個(gè)偏重侨把。
二、卷積神經(jīng)網(wǎng)絡(luò)的相關(guān)數(shù)據(jù)結(jié)構(gòu)
這個(gè)卷積網(wǎng)絡(luò)主要有五層網(wǎng)絡(luò)膳算,主要結(jié)構(gòu)是卷積層座硕、采樣層(Pooling)、卷積層涕蜂、采樣層(Pooling)和全連接的單層神經(jīng)網(wǎng)絡(luò)層(輸出層)华匾,所以我們建立了三個(gè)基本層的結(jié)構(gòu)及一個(gè)總的卷積網(wǎng)絡(luò)結(jié)構(gòu)。
這里結(jié)構(gòu)內(nèi)除了必要的權(quán)重參數(shù),而需要記錄該層輸入輸出數(shù)據(jù)蜘拉,及需要傳遞到下一層的局部梯度萨西。
(1)卷積層
// 卷積層
typedef struct convolutional_layer{
int inputWidth; //輸入圖像的寬
int inputHeight; //輸入圖像的長(zhǎng)
int mapSize; //特征模板的大小,模板一般都是正方形
int inChannels; //輸入圖像的數(shù)目
int outChannels; //輸出圖像的數(shù)目
// 關(guān)于特征模板的權(quán)重分布旭旭,這里是一個(gè)四維數(shù)組
// 其大小為inChannels*outChannels*mapSize*mapSize大小
// 這里用四維數(shù)組谎脯,主要是為了表現(xiàn)全連接的形式,實(shí)際上卷積層并沒有用到全連接的形式
// 這里的例子是DeapLearningToolboox里的CNN例子持寄,其用到就是全連接
float**** mapData; //存放特征模塊的數(shù)據(jù)
float**** dmapData; //存放特征模塊的數(shù)據(jù)的局部梯度
float* basicData; //偏置源梭,偏置的大小,為outChannels
bool isFullConnect; //是否為全連接
bool* connectModel; //連接模式(默認(rèn)為全連接)
// 下面三者的大小同輸出的維度相同
float*** v; // 進(jìn)入激活函數(shù)的輸入值
float*** y; // 激活函數(shù)后神經(jīng)元的輸出
// 輸出像素的局部梯度
float*** d; // 網(wǎng)絡(luò)的局部梯度,δ值
}CovLayer;
(2)采樣層
// 采樣層 pooling
typedef struct pooling_layer{
int inputWidth; //輸入圖像的寬
int inputHeight; //輸入圖像的長(zhǎng)
int mapSize; //特征模板的大小
int inChannels; //輸入圖像的數(shù)目
int outChannels; //輸出圖像的數(shù)目
int poolType; //Pooling的方法
float* basicData; //偏置
float*** y; // 采樣函數(shù)后神經(jīng)元的輸出,無激活函數(shù)
float*** d; // 網(wǎng)絡(luò)的局部梯度,δ值
}PoolLayer;
(3)全連接的單層神經(jīng)網(wǎng)絡(luò)
// 輸出層 全連接的神經(jīng)網(wǎng)絡(luò)
typedef struct nn_layer{
int inputNum; //輸入數(shù)據(jù)的數(shù)目
int outputNum; //輸出數(shù)據(jù)的數(shù)目
float** wData; // 權(quán)重?cái)?shù)據(jù)稍味,為一個(gè)inputNum*outputNum大小
float* basicData; //偏置废麻,大小為outputNum大小
// 下面三者的大小同輸出的維度相同
float* v; // 進(jìn)入激活函數(shù)的輸入值
float* y; // 激活函數(shù)后神經(jīng)元的輸出
float* d; // 網(wǎng)絡(luò)的局部梯度,δ值
bool isFullConnect; //是否為全連接
}OutLayer;
(4)各層共同組成一個(gè)完整的卷積網(wǎng)絡(luò)
typedef struct cnn_network{
int layerNum;
CovLayer* C1;
PoolLayer* S2;
CovLayer* C3;
PoolLayer* S4;
OutLayer* O5;
float* e; // 訓(xùn)練誤差
float* L; // 瞬時(shí)誤差能量
}CNN;
(5)另外還有一個(gè)用于存放訓(xùn)練參量的結(jié)構(gòu)
typedef struct train_opts{
int numepochs; // 訓(xùn)練的迭代次數(shù)
float alpha; // 學(xué)習(xí)速率
}CNNOpts;
三、卷積神經(jīng)網(wǎng)絡(luò)的初始化
卷積神經(jīng)網(wǎng)絡(luò)的初始化主要包含了各數(shù)據(jù)的空間初始化及權(quán)重的隨機(jī)賦值模庐,沒有什么復(fù)雜烛愧,按照結(jié)構(gòu)分配空間就可以了,這里不再詳細(xì)贅述了掂碱,可以直接參考代碼內(nèi)函數(shù)
四怜姿、卷積神經(jīng)網(wǎng)絡(luò)的前向傳播過程
前向傳播過程實(shí)際上就是指輸入圖像數(shù)據(jù),得到輸出結(jié)果的過程疼燥,而后向傳播過程就是將輸出結(jié)果的誤差由后向前傳遞給各層沧卢,各層依次調(diào)整權(quán)重的過程。所以前向傳播過程相比而是比較直觀悴了,而且簡(jiǎn)單的搏恤。
前向傳播過程在項(xiàng)目中主要是由函數(shù)完成违寿,下面我們將按層介紹其過程
(1)卷積層C1
卷積層C1共有6個(gè)卷積模板湃交,每個(gè)模板同輸入圖像卷積將會(huì)得到一個(gè)輸出,即共6個(gè)輸出藤巢,以下是圖像的卷積公式:
C1層的相關(guān)代碼搞莺,這里函數(shù)是卷積函數(shù),在是具體的定義掂咒,是激活函數(shù)
int outSizeW=cnn->S2->inputWidth;
int outSizeH=cnn->S2->inputHeight;
// 第一層的傳播
int i,j,r,c;
// 第一層輸出數(shù)據(jù)
nSize mapSize={cnn->C1->mapSize,cnn->C1->mapSize};
nSize inSize={cnn->C1->inputWidth,cnn->C1->inputHeight};
nSize outSize={cnn->S2->inputWidth,cnn->S2->inputHeight};
for(i=0;i<(cnn->C1->outChannels);i++){
for(j=0;j<(cnn->C1->inChannels);j++){
float** mapout=cov(cnn->C1->mapData[j][i],mapSize,inputData,inSize,valid);
addmat(cnn->C1->v[i],cnn->C1->v[i],outSize,mapout,outSize);
for(r=0;r<outSize.r;r++)
free(mapout[r]);
free(mapout);
}
for(r=0;r<outSize.r;r++)
for(c=0;c<outSize.c;c++)
cnn->C1->y[i][r][c]=activation_Sigma(cnn->C1->v[i][r][c],cnn->C1->basicData[i]);
}
(2)采樣層S2才沧,是平均Pooling函數(shù)
// 第二層的輸出傳播S2,采樣層
outSize.c=cnn->C3->inputWidth;
outSize.r=cnn->C3->inputHeight;
inSize.c=cnn->S2->inputWidth;
inSize.r=cnn->S2->inputHeight;
for(i=0;i<(cnn->S2->outChannels);i++){
if(cnn->S2->poolType==AvePool)
avgPooling(cnn->S2->y[i],outSize,cnn->C1->y[i],inSize,cnn->S2->mapSize);
}
(3)卷積層C3绍刮,同C1很類似
// 第三層輸出傳播,這里是全連接
outSize.c=cnn->S4->inputWidth;
outSize.r=cnn->S4->inputHeight;
inSize.c=cnn->C3->inputWidth;
inSize.r=cnn->C3->inputHeight;
mapSize.c=cnn->C3->mapSize;
mapSize.r=cnn->C3->mapSize;
for(i=0;i<(cnn->C3->outChannels);i++){
for(j=0;j<(cnn->C3->inChannels);j++){
float** mapout=cov(cnn->C3->mapData[j][i],mapSize,cnn->S2->y[j],inSize,valid);
addmat(cnn->C3->v[i],cnn->C3->v[i],outSize,mapout,outSize);
for(r=0;r<outSize.r;r++)
free(mapout[r]);
free(mapout);
}
for(r=0;r<outSize.r;r++)
for(c=0;c<outSize.c;c++)
cnn->C3->y[i][r][c]=activation_Sigma(cnn->C3->v[i][r][c],cnn->C3->basicData[i]);
}
(4)采樣層S4温圆,同S2很類似
// 第四層的輸出傳播
inSize.c=cnn->S4->inputWidth;
inSize.r=cnn->S4->inputHeight;
outSize.c=inSize.c/cnn->S4->mapSize;
outSize.r=inSize.r/cnn->S4->mapSize;
for(i=0;i<(cnn->S4->outChannels);i++){
if(cnn->S4->poolType==AvePool)
avgPooling(cnn->S4->y[i],outSize,cnn->C3->y[i],inSize,cnn->S4->mapSize);
}
(5)輸出層O5
// 輸出層O5的處理
// 首先需要將前面的多維輸出展開成一維向量
float* O5inData=(float*)malloc((cnn->O5->inputNum)*sizeof(float));
for(i=0;i<(cnn->S4->outChannels);i++)
for(r=0;r<outSize.r;r++)
for(c=0;c<outSize.c;c++)
O5inData[i*outSize.r*outSize.c+r*outSize.c+c]=cnn->S4->y[i][r][c];
nSize nnSize={cnn->O5->inputNum,cnn->O5->outputNum};
nnff(cnn->O5->v,O5inData,cnn->O5->wData,cnn->O5->basicData,nnSize);
for(i=0;i<cnn->O5->outputNum;i++)
cnn->O5->y[i]=activation_Sigma(cnn->O5->v[i],cnn->O5->basicData[i]); // 這里多加了一個(gè)bias, 感謝wydbyxr的指出!
free(O5inData);
}
項(xiàng)目代碼地址:https://github.com/tostq/DeepLearningC/tree/master/CNN