LeNet-5 詳解
一、前言
LeNet-5出自論文Gradient-Based Learning Applied to Document Recognition,是一種用于手寫體字符識(shí)別的非常高效的卷積神經(jīng)網(wǎng)絡(luò)涛救。
本文將從卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)的基礎(chǔ)說起,詳細(xì)地講解每個(gè)網(wǎng)絡(luò)層埋同。
論文下載:請(qǐng)到文章結(jié)尾處下載州叠。
二、卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural Network, CNN)
在講解LeNet-5之前凶赁,讓我們先看下CNN咧栗。卷積神經(jīng)網(wǎng)絡(luò)能夠很好的利用圖像的結(jié)構(gòu)信息。LeNet-5是一個(gè)較簡(jiǎn)單的卷積神經(jīng)網(wǎng)絡(luò)虱肄。下圖顯示了其結(jié)構(gòu):輸入的二維圖像致板,先經(jīng)過兩次卷積層到池化層,再經(jīng)過全連接層咏窿,最后使用softmax分類作為輸出層斟或。下面我們主要介紹卷積層和池化層。
1集嵌、卷積層
卷積層是卷積神經(jīng)網(wǎng)絡(luò)的核心基石萝挤。在圖像識(shí)別里我們提到的卷積是二維卷積,即離散二維濾波器(也稱作卷積核)與二維圖像做卷積操作根欧,簡(jiǎn)單的講是二維濾波器滑動(dòng)到二維圖像上所有位置怜珍,并在每個(gè)位置上與該像素點(diǎn)及其領(lǐng)域像素點(diǎn)做內(nèi)積。卷積操作被廣泛應(yīng)用與圖像處理領(lǐng)域凤粗,不同卷積核可以提取不同的特征酥泛,例如邊沿、線性嫌拣、角等特征柔袁。在深層卷積神經(jīng)網(wǎng)絡(luò)中,通過卷積操作可以提取出圖像低級(jí)到復(fù)雜的特征异逐。
上圖給出一個(gè)卷積計(jì)算過程的示例圖捶索,輸入圖像大小為H=5,W=5,D=3,即5×5大小的3通道(RGB灰瞻,也稱作深度)彩色圖像腥例。這個(gè)示例圖中包含兩(用K表示)組卷積核燥筷,即圖中濾波器W0和W1。在卷積計(jì)算中院崇,通常對(duì)不同的輸入通道采用不同的卷積核肆氓,如圖示例中每組卷積核包含(D=3)個(gè)3×3(用F×F表示)大小的卷積核。另外底瓣,這個(gè)示例中卷積核在圖像的水平方向(W方向)和垂直方向(H方向)的滑動(dòng)步長(zhǎng)為2(用S表示)谢揪;對(duì)輸入圖像周圍各填充1(用P表示)個(gè)0,即圖中輸入層原始數(shù)據(jù)為藍(lán)色部分,灰色部分是進(jìn)行了大小為1的擴(kuò)展,用0來進(jìn)行擴(kuò)展旧烧。經(jīng)過卷積操作得到輸出為3×3×2(用Ho×Wo×K表示)大小的特征圖,即3×3大小的2通道特征圖患民,其中Ho計(jì)算公式為:Ho=(H?F+2×P)/S+1,Wo同理垦梆。 而輸出特征圖中的每個(gè)像素匹颤,是每組濾波器與輸入圖像每個(gè)特征圖的內(nèi)積再求和,再加上偏置bo托猩,偏置通常對(duì)于每個(gè)輸出特征圖是共享的印蓖。輸出特征圖o[:,:,0]中的最后一個(gè)?2計(jì)算如上圖右下角公式所示。
記住這幾個(gè)符號(hào):
H:圖片高度京腥;
W:圖片寬度赦肃;
D:原始圖片通道數(shù),也是卷積核個(gè)數(shù)公浪;
F:卷積核高寬大兴稹;
P:圖像邊擴(kuò)充大星菲厅各;
S:滑動(dòng)步長(zhǎng)。
在卷積操作中卷積核是可學(xué)習(xí)的參數(shù)晃琳,經(jīng)過上面示例介紹讯检,每層卷積的參數(shù)大小為D×F×F×K琐鲁。卷積層的參數(shù)較少卫旱,這也是由卷積層的主要特性即局部連接和共享權(quán)重所決定。
局部連接:每個(gè)神經(jīng)元僅與輸入神經(jīng)元的一塊區(qū)域連接围段,這塊局部區(qū)域稱作感受野(receptive field)顾翼。在圖像卷積操作中,即神經(jīng)元在空間維度(spatial dimension奈泪,即上圖示例H和W所在的平面)是局部連接适贸,但在深度上是全部連接灸芳。對(duì)于二維圖像本身而言,也是局部像素關(guān)聯(lián)較強(qiáng)拜姿。這種局部連接保證了學(xué)習(xí)后的過濾器能夠?qū)τ诰植康妮斎胩卣饔凶顝?qiáng)的響應(yīng)烙样。局部連接的思想,也是受啟發(fā)于生物學(xué)里面的視覺系統(tǒng)結(jié)構(gòu)蕊肥,視覺皮層的神經(jīng)元就是局部接受信息的谒获。
權(quán)重共享:計(jì)算同一個(gè)深度切片的神經(jīng)元時(shí)采用的濾波器是共享的。例上圖中計(jì)算o[:,:,0]的每個(gè)每個(gè)神經(jīng)元的濾波器均相同壁却,都為W0批狱,這樣可以很大程度上減少參數(shù)。共享權(quán)重在一定程度上講是有意義的展东,例如圖片的底層邊緣特征與特征在圖中的具體位置無關(guān)赔硫。但是在一些場(chǎng)景中是無意的,比如輸入的圖片是人臉盐肃,眼睛和頭發(fā)位于不同的位置爪膊,希望在不同的位置學(xué)到不同的特征 。請(qǐng)注意權(quán)重只是對(duì)于同一深度切片的神經(jīng)元是共享的砸王,在卷積層惊完,通常采用多組卷積核提取不同特征,即對(duì)應(yīng)不同深度切片的特征处硬,不同深度切片的神經(jīng)元權(quán)重是不共享小槐。另外,偏重對(duì)同一深度切片的所有神經(jīng)元都是共享的荷辕。
通過介紹卷積計(jì)算過程及其特性凿跳,可以看出卷積是線性操作,并具有平移不變性(shift-invariant)疮方,平移不變性即在圖像每個(gè)位置執(zhí)行相同的操作控嗜。卷積層的局部連接和權(quán)重共享使得需要學(xué)習(xí)的參數(shù)大大減小,這樣也有利于訓(xùn)練較大卷積神經(jīng)網(wǎng)絡(luò)骡显。
整體計(jì)算過程如下(與上圖中的數(shù)據(jù)不同疆栏,但是計(jì)算過程相同):
2、池化層
池化是非線性下采樣的一種形式惫谤,主要作用是通過減少網(wǎng)絡(luò)的參數(shù)來減小計(jì)算量壁顶,并且能夠在一定程度上控制過擬合。通常在卷積層的后面會(huì)加上一個(gè)池化層溜歪。池化包括最大池化若专、平均池化等。其中最大池化是用不重疊的矩形框?qū)⑤斎雽臃殖刹煌膮^(qū)域蝴猪,對(duì)于每個(gè)矩形框的數(shù)取最大值作為輸出層调衰,如上圖所示膊爪。
三、Lenet-5
LeNet5 這個(gè)網(wǎng)絡(luò)雖然很小嚎莉,但是它包含了深度學(xué)習(xí)的基本模塊:卷積層米酬,池化層,全鏈接層趋箩。是其他深度學(xué)習(xí)模型的基礎(chǔ)淮逻, 這里我們對(duì)LeNet5進(jìn)行深入分析。同時(shí)阁簸,通過實(shí)例分析爬早,加深對(duì)與卷積層和池化層的理解。
LeNet-5共有7層启妹,不包含輸入筛严,每層都包含可訓(xùn)練參數(shù);每個(gè)層有多個(gè)Feature Map饶米,每個(gè)FeatureMap通過一種卷積濾波器提取輸入的一種特征桨啃,然后每個(gè)FeatureMap有多個(gè)神經(jīng)元。
各層參數(shù)詳解:
1檬输、INPUT層-輸入層
首先是數(shù)據(jù) INPUT 層照瘾,輸入圖像的尺寸統(tǒng)一歸一化為32*32。
注意:本層不算LeNet-5的網(wǎng)絡(luò)結(jié)構(gòu)丧慈,傳統(tǒng)上析命,不將輸入層視為網(wǎng)絡(luò)層次結(jié)構(gòu)之一。
2逃默、C1層-卷積層
輸入圖片:32*32
卷積核大芯榉摺:5*5
卷積核種類:6
輸出featuremap大小:28*28 (32-5+1)=28
神經(jīng)元數(shù)量:28286
可訓(xùn)練參數(shù):(55+1) * 6(每個(gè)濾波器55=25個(gè)unit參數(shù)和一個(gè)bias參數(shù)完域,一共6個(gè)濾波器)
連接數(shù):(55+1)62828=122304
詳細(xì)說明:對(duì)輸入圖像進(jìn)行第一次卷積運(yùn)算(使用 6 個(gè)大小為 55 的卷積核)软吐,得到6個(gè)C1特征圖(6個(gè)大小為2828的 feature maps, 32-5+1=28)。我們?cè)賮砜纯葱枰嗌賯€(gè)參數(shù)吟税,卷積核的大小為55凹耙,總共就有6(55+1)=156個(gè)參數(shù),其中+1是表示一個(gè)核有一個(gè)bias肠仪。對(duì)于卷積層C1肖抱,C1內(nèi)的每個(gè)像素都與輸入圖像中的55個(gè)像素和1個(gè)bias有連接,所以總共有1562828=122304個(gè)連接(connection)藤韵。有122304個(gè)連接虐沥,但是我們只需要學(xué)習(xí)156個(gè)參數(shù)熊经,主要是通過權(quán)值共享實(shí)現(xiàn)的泽艘。
3欲险、S2層-池化層(下采樣層)
輸入:28*28
采樣區(qū)域:2*2
采樣方式:4個(gè)輸入相加,乘以一個(gè)可訓(xùn)練參數(shù)匹涮,再加上一個(gè)可訓(xùn)練偏置天试。結(jié)果通過sigmoid
采樣種類:6
輸出featureMap大小:14*14(28/2)
神經(jīng)元數(shù)量:14146
連接數(shù):(22+1)61414
S2中每個(gè)特征圖的大小是C1中特征圖大小的1/4然低。
詳細(xì)說明:第一次卷積之后緊接著就是池化運(yùn)算喜每,使用 22核 進(jìn)行池化,于是得到了S2雳攘,6個(gè)1414的 特征圖(28/2=14)带兜。S2這個(gè)pooling層是對(duì)C1中的2*2區(qū)域內(nèi)的像素求和乘以一個(gè)權(quán)值系數(shù)再加上一個(gè)偏置,然后將這個(gè)結(jié)果再做一次映射吨灭。同時(shí)有5x14x14x6=5880個(gè)連接刚照。
4、C3層-卷積層
輸入:S2中所有6個(gè)或者幾個(gè)特征map組合
卷積核大行帧:5*5
卷積核種類:16
輸出featureMap大形夼稀:10*10 (14-5+1)=10
C3中的每個(gè)特征map是連接到S2中的所有6個(gè)或者幾個(gè)特征map的,表示本層的特征map是上一層提取到的特征map的不同組合
存在的一個(gè)方式是:C3的前6個(gè)特征圖以S2中3個(gè)相鄰的特征圖子集為輸入吠冤。接下來6個(gè)特征圖以S2中4個(gè)相鄰特征圖子集為輸入浑彰。然后的3個(gè)以不相鄰的4個(gè)特征圖子集為輸入。最后一個(gè)將S2中所有特征圖為輸入拯辙。
則:可訓(xùn)練參數(shù):6(355+1)+6(455+1)+3(455+1)+1(655+1)=1516
連接數(shù):10101516=151600
詳細(xì)說明:第一次池化之后是第二次卷積郭变,第二次卷積的輸出是C3,16個(gè)10x10的特征圖涯保,卷積核大小是 55. 我們知道S2 有6個(gè) 1414 的特征圖饵较,怎么從6 個(gè)特征圖得到 16個(gè)特征圖了? 這里是通過對(duì)S2 的特征圖特殊組合計(jì)算得到的16個(gè)特征圖遭赂。具體如下:
C3的前6個(gè)feature map(對(duì)應(yīng)上圖第一個(gè)紅框的6列)與S2層相連的3個(gè)feature map相連接(上圖第一個(gè)紅框)循诉,后面6個(gè)feature map與S2層相連的4個(gè)feature map相連接(上圖第二個(gè)紅框),后面3個(gè)feature map與S2層部分不相連的4個(gè)feature map相連接撇他,最后一個(gè)與S2層的所有feature map相連茄猫。卷積核大小依然為55,所以總共有6(355+1)+6(455+1)+3(455+1)+1(655+1)=1516個(gè)參數(shù)困肩。而圖像大小為1010划纽,所以共有151600個(gè)連接。
C3與S2中前3個(gè)圖相連的卷積結(jié)構(gòu)如下圖所示:
上圖對(duì)應(yīng)的參數(shù)為 355+1锌畸,一共進(jìn)行6次卷積得到6個(gè)特征圖勇劣,所以有6(35*5+1)參數(shù)。 為什么采用上述這樣的組合了?論文中說有兩個(gè)原因:1)減少參數(shù)比默,2)這種不對(duì)稱的組合連接的方式有利于提取多種組合特征幻捏。
5、S4層-池化層(下采樣層)
輸入:10*10
采樣區(qū)域:2*2
采樣方式:4個(gè)輸入相加命咐,乘以一個(gè)可訓(xùn)練參數(shù)篡九,再加上一個(gè)可訓(xùn)練偏置。結(jié)果通過sigmoid
采樣種類:16
輸出featureMap大写椎臁:5*5(10/2)
神經(jīng)元數(shù)量:5516=400
連接數(shù):16(22+1)55=2000
S4中每個(gè)特征圖的大小是C3中特征圖大小的1/4
詳細(xì)說明:S4是pooling層榛臼,窗口大小仍然是2*2,共計(jì)16個(gè)feature map窜司,C3層的16個(gè)10x10的圖分別進(jìn)行以2x2為單位的池化得到16個(gè)5x5的特征圖沛善。有5x5x5x16=2000個(gè)連接。連接的方式與S2層類似塞祈。
6路呜、C5層-卷積層
輸入:S4層的全部16個(gè)單元特征map(與s4全相連)
卷積核大小:5*5
卷積核種類:120
輸出featureMap大兄帧:1*1(5-5+1)
可訓(xùn)練參數(shù)/連接:120(165*5+1)=48120
詳細(xì)說明:C5層是一個(gè)卷積層胀葱。由于S4層的16個(gè)圖的大小為5x5,與卷積核的大小相同笙蒙,所以卷積后形成的圖的大小為1x1抵屿。這里形成120個(gè)卷積結(jié)果。每個(gè)都與上一層的16個(gè)圖相連捅位。所以共有(5x5x16+1)x120 = 48120個(gè)參數(shù)轧葛,同樣有48120個(gè)連接。C5層的網(wǎng)絡(luò)結(jié)構(gòu)如下:
7艇搀、F6層-全連接層
輸入:c5 120維向量
計(jì)算方式:計(jì)算輸入向量和權(quán)重向量之間的點(diǎn)積尿扯,再加上一個(gè)偏置,結(jié)果通過sigmoid函數(shù)輸出焰雕。
可訓(xùn)練參數(shù):84*(120+1)=10164
詳細(xì)說明:6層是全連接層衷笋。F6層有84個(gè)節(jié)點(diǎn),對(duì)應(yīng)于一個(gè)7x12的比特圖矩屁,-1表示白色辟宗,1表示黑色,這樣每個(gè)符號(hào)的比特圖的黑白色就對(duì)應(yīng)于一個(gè)編碼吝秕。該層的訓(xùn)練參數(shù)和連接數(shù)是(120 + 1)x84=10164泊脐。ASCII編碼圖如下:
F6層的連接方式如下:
8、Output層-全連接層
Output層也是全連接層烁峭,共有10個(gè)節(jié)點(diǎn)容客,分別代表數(shù)字0到9秕铛,且如果節(jié)點(diǎn)i的值為0,則網(wǎng)絡(luò)識(shí)別的結(jié)果是數(shù)字i缩挑。采用的是徑向基函數(shù)(RBF)的網(wǎng)絡(luò)連接方式但两。假設(shè)x是上一層的輸入,y是RBF的輸出调煎,則RBF輸出的計(jì)算方式是:
上式w_ij 的值由i的比特圖編碼確定镜遣,i從0到9己肮,j取值從0到7*12-1士袄。RBF輸出的值越接近于0,則越接近于i谎僻,即越接近于i的ASCII編碼圖娄柳,表示當(dāng)前網(wǎng)絡(luò)輸入的識(shí)別結(jié)果是字符i。該層有84x10=840個(gè)參數(shù)和連接艘绍。
上圖是LeNet-5識(shí)別數(shù)字3的過程赤拒。
四、總結(jié)
LeNet-5是一種用于手寫體字符識(shí)別的非常高效的卷積神經(jīng)網(wǎng)絡(luò)诱鞠。
卷積神經(jīng)網(wǎng)絡(luò)能夠很好的利用圖像的結(jié)構(gòu)信息挎挖。
卷積層的參數(shù)較少,這也是由卷積層的主要特性即局部連接和共享權(quán)重所決定航夺。
Code
#coding:utf-8
import tensorflow as tf
import MNIST_data.input_data as input_data
import time
"""
權(quán)重初始化
初始化為一個(gè)接近0的很小的正數(shù)
"""
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev = 0.1)
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape = shape)
return tf.Variable(initial)
"""
卷積和池化蕉朵,使用卷積步長(zhǎng)為1(stride size),0邊距(padding size)
池化用簡(jiǎn)單傳統(tǒng)的2x2大小的模板做max pooling
"""
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding = 'SAME')
# tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
# x(input) : [batch, in_height, in_width, in_channels]
# W(filter) : [filter_height, filter_width, in_channels, out_channels]
# strides : The stride of the sliding window for each dimension of input.
# For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1]
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize = [1, 2, 2, 1],
strides = [1, 2, 2, 1], padding = 'SAME')
# tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
# x(value) : [batch, height, width, channels]
# ksize(pool大小) : A list of ints that has length >= 4. The size of the window for each dimension of the input tensor.
# strides(pool滑動(dòng)大小) : A list of ints that has length >= 4. The stride of the sliding window for each dimension of the input tensor.
start = time.clock() #計(jì)算開始時(shí)間
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) #MNIST數(shù)據(jù)輸入
"""
第一層 卷積層
x_image(batch, 28, 28, 1) -> h_pool1(batch, 14, 14, 32)
"""
x = tf.placeholder(tf.float32,[None, 784])
x_image = tf.reshape(x, [-1, 28, 28, 1]) #最后一維代表通道數(shù)目,如果是rgb則為3
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
# x_image -> [batch, in_height, in_width, in_channels]
# [batch, 28, 28, 1]
# W_conv1 -> [filter_height, filter_width, in_channels, out_channels]
# [5, 5, 1, 32]
# output -> [batch, out_height, out_width, out_channels]
# [batch, 28, 28, 32]
h_pool1 = max_pool_2x2(h_conv1)
# h_conv1 -> [batch, in_height, in_weight, in_channels]
# [batch, 28, 28, 32]
# output -> [batch, out_height, out_weight, out_channels]
# [batch, 14, 14, 32]
"""
第二層 卷積層
h_pool1(batch, 14, 14, 32) -> h_pool2(batch, 7, 7, 64)
"""
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
# h_pool1 -> [batch, 14, 14, 32]
# W_conv2 -> [5, 5, 32, 64]
# output -> [batch, 14, 14, 64]
h_pool2 = max_pool_2x2(h_conv2)
# h_conv2 -> [batch, 14, 14, 64]
# output -> [batch, 7, 7, 64]
"""
第三層 全連接層
h_pool2(batch, 7, 7, 64) -> h_fc1(1, 1024)
"""
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
"""
Dropout
h_fc1 -> h_fc1_drop, 訓(xùn)練中啟用阳掐,測(cè)試中關(guān)閉
"""
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
"""
第四層 Softmax輸出層
"""
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
"""
訓(xùn)練和評(píng)估模型
ADAM優(yōu)化器來做梯度最速下降,feed_dict中加入?yún)?shù)keep_prob控制dropout比例
"""
y_ = tf.placeholder("float", [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y_conv)) #計(jì)算交叉熵
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) #使用adam優(yōu)化器來以0.0001的學(xué)習(xí)率來進(jìn)行微調(diào)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) #判斷預(yù)測(cè)標(biāo)簽和實(shí)際標(biāo)簽是否匹配
accuracy = tf.reduce_mean(tf.cast(correct_prediction,"float"))
sess = tf.Session() #啟動(dòng)創(chuàng)建的模型
sess.run(tf.initialize_all_variables()) #舊版本
#sess.run(tf.global_variables_initializer()) #初始化變量
for i in range(5000): #開始訓(xùn)練模型始衅,循環(huán)訓(xùn)練5000次
batch = mnist.train.next_batch(50) #batch大小設(shè)置為50
if i % 100 == 0:
train_accuracy = accuracy.eval(session = sess,
feed_dict = {x:batch[0], y_:batch[1], keep_prob:1.0})
print("step %d, train_accuracy %g" %(i, train_accuracy))
train_step.run(session = sess, feed_dict = {x:batch[0], y_:batch[1],
keep_prob:0.5}) #神經(jīng)元輸出保持不變的概率 keep_prob 為0.5
print("test accuracy %g" %accuracy.eval(session = sess,
feed_dict = {x:mnist.test.images, y_:mnist.test.labels,
keep_prob:1.0})) #神經(jīng)元輸出保持不變的概率 keep_prob 為 1,即不變缭保,一直保持輸出
end = time.clock() #計(jì)算程序結(jié)束時(shí)間
print("running time is %g s") % (end-start)