卷積神經網絡之LeNet

開局一張圖,內容全靠編。
python

上圖引用自 【卷積神經網絡-進化史】從LeNet到AlexNet. 目前經常使用的卷積神經網絡git

深度學習如今是百花齊放,各類網絡結構層出不窮,計劃梳理下各個經常使用的卷積神經網絡結構。 目前先梳理下用於圖像分類的卷積神經網絡github

  • LeNet
  • AlexNet
  • VGG
  • GoogLeNet
  • ResNet

本文是關於卷積神經網絡的開山之做LeNet的,以前想着論文較早,一直沒有細讀,仔細看了一遍收穫滿滿啊。
本文有如下內容:算法

  • LeNet 網絡結構
  • LeNet 論文
  • LeNet keras實現,並應用CIFAR10進行測試

LeNet的貢獻

LeNet-5可謂是第一個卷積神經網絡,而且在手寫數字識別上取得了很好的效果。
對於圖像像素做爲神經網絡的輸入數據面臨的一些問題:網絡

  1. 圖像數據量較大,單獨像素獨立輸入神經元中,須要很大的網絡結構,訓練參數過多
  2. 圖像的形變,形變引發的圖像特徵位置變化的問題
  3. 圖像的局部相關性。

其提出了卷積神經網絡的概念,並應用機器學習

  • 局部感覺野
  • 權值共享
  • 下采樣(池化)

來解決上述問題。ide

LeNet網絡結構

1998年的誕生的LeNet(LeCun et al. Gradient-Based Learning Applied to Document Recognition)可謂是如今各類卷積神經網絡的始祖了,其網絡結構雖然只有5層,卻包含了卷積神經網絡的基本組件(卷積層,池化層,全鏈接層)
函數

  • 輸入層 INPUT
    輸入\(32\times32\times1\)的圖像
  • 第一個卷積層 CONV1
    使用6個尺寸爲\(5 \times 5\)的濾波器,在卷積的過程當中不作邊緣填充,步長爲\(stride=1\)。單個核的卷積輸出大小爲\((32 - 5 + 1) \times (32 - 5 + 1) = 28 \times 28\)。因爲有6個卷積核,因此整個卷積層輸出獲得爲Feature Map爲 \(28 \times 28 \times 6\)。該層的參數
  • 濾波器的核: \((5 \times 5 + 1) \times 6 = 156\),每一個濾波器除了\(5 \times 5\)的核之外,還有一個偏置。
  • 神經元的個數:\(28 \times 28 \times 6\)在卷積層中,卷積核每移動一步產生一個神經元的輸出,也就至關於一個神經元。卷積的過程當中,不作邊緣填充,對於\(32 \times 32\)的輸入,一個\(5\times5\)的卷積核產生\(28 \times 28\)個輸出,共有6個卷積核,有\(28 \times 28 \times 6\)個神經元。
  • 總共的鏈接數: \((5 \times 5 + 1) \times 28 \times 28 \times 6 = 122304\)。在卷積層,每一個輸出的\(28 \times 28\)的Feature Map,都和一個\(5 \times 5\)卷積核相連。每一個卷積核都有一個偏置。
  • 參數個數:按照神經網絡的全鏈接設計,每一個像素間都有鏈接的話,輸入層和卷積層之間就要有122304個參數。可是在卷積神經網絡中,引入了權值共享,就大大的減小了卷積層的參數的個數。對\(32\times32\)的輸入,使用\(5\times5\)的核進行卷積,步長爲1,卷積核每移動一步,就獲得一個輸出,也就須要一個權值參數(有一個鏈接),這就須要\((5 \times 5 + 1) \times 28 \times 28\)個參數。引入權值共享,也就是一個卷積核在卷積的產生輸出的過程當中,每移動一步產生輸出,都使用相同的權值參數,這時候參數的個數爲\((5 \times 5 + 1) \times 1\)。共有6個卷積核,\((5 \times 5 + 1) \times 6 = 156\)個參數。學習

    設輸入尺寸爲\(W \times W\),卷積核尺寸爲\(F \times F\),卷積步長爲\(S\),填充寬度爲\(P\),則卷積後輸出的尺寸爲\((W - F + 2P) / S + 1\)測試

  • 池化層 POOL1
    池化層用於降採樣,其輸入是上一卷積層的輸出爲\(28 \times 28 \times 6\)。在本層採用尺寸\(2 \times 2\)的池化單元,池化時的沿長和寬的步長均爲2。 池化後的輸出爲\((28 - 2) / 2 + 1 = 14\)。深度爲6,則池化的輸出爲\(14 \times 14 \times 6\)
    • 鏈接個數: \((2 \times 2 + 1 ) \times 14 \times14 \times 6 = 5880\)
    • 參數個數:和卷積層相似,每一個池化單元進行池化的時候使用相同的權值,則訓練參數爲$ (1 + 1) \times 6 = 12\(。 其中,一個是池化單元的權值\)w\(,另外一個是偏置參數。 > 池化層輸入尺寸爲\)W \times W\(,池化尺寸爲\)F \times F\(,池化步長爲\)S\(,則池化後的輸出尺寸爲\)(W - F)/S + 1$
  • 第二個卷積層 CONV2
    該層的輸入爲\(14 \times 14 \times 6\),使用16個核爲\(5\times5\),步長爲1。則其輸出尺寸爲\(14 - 5 + 1 = 10\)。整個層的輸出爲\(10 \times 10 \times 16\)。 這裏要注意的本層的輸入有6個Feature Map,而輸出有16個Feature Map。 輸入的FeatureMap和輸出的FeatureMap之間並非全鏈接的,而是局部鏈接的,具體規則以下:

上圖所示,輸出的6個FeatureMap只和輸入的3個FeatureMap相鏈接;輸出的9個FeatureMap和輸入的4個FeatureMap相鏈接;而後1個和6個鏈接。
- 參數個數: 因爲權值共享,每一個卷積核產生的輸出共享同一個權值。例如,對於和3個Feature Map相鏈接的輸出的FeatureMap,在卷積的時候,使用3個不一樣的卷積核分別對三個輸入的FeatureMap進行卷積,而後將卷積的結果進行求和,因此其權值參數的個數是\(5 \times 5 \times + 1\)。因此,總的參數個數是\(6 \times (5 \times 5 \times 3 + 1) + 9 \times ( 5 \times 5 \times 4 + 1) + 1 \times (5 \times 5 \times 6 + 1) = 1516\)
- 鏈接個數:輸出的Feature Map的尺寸是\(10 \times 10\),則鏈接個數是: \(1516 \times 10 \times = 151600\)

  • 第二個池化層 POOL2
    輸入是\(10 \times 10 \times 16\),池化參數和第一個池化層同樣採用尺寸\(2 \times 2\)的池化單元,池化時的沿長和寬的步長均爲2,其輸出爲\(5 \times 5 \times 16\),參數個數是\((1 + 1) \times 16 = 32\),鏈接數\((2 \times 2 + 1) \times 5 \times 5 \times 16 = 2000\)

  • 第三個卷積層 CONV3
    120個\(5\times5\)的卷積核,輸入的是\(5 \times 5 \times 16\),輸入的Feature Map和輸出的Feature Map是全鏈接,輸出的尺寸爲\(1\times1 \times 120\)(卷積核和輸入尺寸相同,且沒有邊緣填充)。其卷積核的參數是\(5\times5 \times 16 + 1\)。 而輸出的120個神經元每一個都和上一層的相連,則鏈接和參數的個數相同爲\((5\times5 \times 16 + 1) \times 120 = 48120\)

  • 全鏈接層
    輸入是120維的向量,本層有84個神經元,參數個數是\((120 + 1) \times 84 = 10164\)

  • 輸出層
    全鏈接層,10個神經元,表明10個不一樣的數字。參數/鏈接個數爲\(84 \times 10 = 840\)

LeNet論文

LeNet是在1998年提出的,用於手寫體數字的識別, 首次提出了卷積神經網絡的基本組成:卷積層,池化層和全鏈接層以及權值共享,感覺野等概念。 雖然時間比較久i,可是做爲卷積神經網絡的開山之做,仍是值得入門者研讀一番的。

論文提出了:

  • 傳統的機器學習進行圖像分類,須要手工的設計特徵提取器從圖像集中提取特徵,而後輸入到機器學習算法中進行學習。
  • 使用梯度降低的方法訓練的多層神經網絡,能夠直接從大量的數據集中學習到複雜的,高維度以及非線性的特徵,這些特徵遠比人工設計的特徵要好不少。
  • 最後,能夠在神經網絡的全鏈接層直接利用前面網絡層提取到的特徵進行分類,不用像傳統的機器學習分類那樣,分紅兩個步,而是進行end-to-end的學習。

可是將像素獨自的輸入到神經元中則有如下問題:

  • 訓練難。圖像的數據由一個個的像素組成,若是將每一個像素都輸入到一個的單獨的神經元中,那神經網絡的尺寸,權值參數將是很是的大的。這麼多的參數,須要消耗很是多的計算資源,以及須要很是大的數據集。
  • 沒法應對圖像內容位置的變化。對於手寫體的數字圖像,其每一個字符的大小,傾斜度,書寫的位置都會致使特徵位置的變化。若是將每一個像素輸入到一個單獨的神經元中,則須要大量的不一樣位置的訓練數據輸入到神經元中。
  • 忽略了圖像的局部相關性。 圖像的相鄰像素有很強的相關性。單獨一個像素輸入到神經元中則丟失了這種相關性。

有了以上的問題,LeCun就提出了大名鼎鼎的卷積神經網絡(CNN)

  • 利用卷積覈對輸入進行卷積後輸入到神經元中,來保持圖像局部相關性
  • 使用局部感覺野,權值共享,池化(下采樣)來實現圖像的平移,縮放和形變的不變性。

局部感覺野
在卷積神經網絡的卷積層中,神經元的輸入是上一層中一個像素鄰域(也就是一個卷積核卷積後的結果,稱爲局部感覺野)。 使用局部感覺野,在淺層網絡中神經元能夠提取到圖像的邊緣,角點等視覺特徵,這些特徵在後面的網絡中進行結合,組成更高層的特徵。(在人工設計的特徵提取器中,則很難提取圖像的高層特徵)。

感覺野的定義: 感覺野是卷積神經網絡(CNN)每一層輸出的特徵圖(feature map)上的像素點在原始輸入圖像上映射的區域大小。
一個局部感覺野能夠看做是一個卷積核進行一次卷積的結果,一個\(5\times5\)的卷積覈對輸入圖像的\(5\times5\)鄰域進行卷積獲得一個輸出\(P\),輸入到神經元中。 在當前的卷積層中,這個\(P\)就能夠表明上一層的\(5\times5\)鄰域。 我的理解。

權值共享
引入權值共享的一個緣由是爲了解決圖像的形變和平移致使的圖像顯著特徵位置的變化。將同一個卷積核獲得的結果設爲相同的權值,能夠有效的下降其位置不一樣帶來的影響。權值共享的另外一個依據是,在一個位置可以提取到有效的特徵,在另外的位置也能提取到(特別是基礎的點線特徵)。另外,使用權值共享也大大的下降的網絡的參數。

一個卷積核就至關於一個特徵提取器,每一個卷積和和輸入圖像進行卷積獲得輸出稱爲Feature Map,一個FeatureMap中全部的像素點和上一層的鏈接,使用相同的權值參數,即爲權值共享。

每一層中全部的神經元造成一個平面,這個平面中全部神經元共享權值。神經元(unit)的全部輸出構成特徵圖,特徵圖中全部單元在圖像的不一樣位置執行相同的操做(同一個特徵圖是使用贊成給卷積核獲得),這樣他們能夠在輸入圖像的不一樣位置檢測到一樣的特徵,一個完整的卷積層由多個特徵圖組成(使用不一樣的權值向量),這樣每一個位置能夠提取多種特徵。
一個具體的示例就是圖2 LeNet-5中的第一層,第一層隱藏層中的全部單元造成6個平面,每一個是一個特徵圖。一個特徵圖中的一個單元對應有25個輸入,這25個輸入鏈接到輸入層的5x5區域,這個區域就是局部感覺野。每一個單元有25個輸入,所以有25個可訓練的參數加上一個偏置。因爲特徵圖中相鄰單元之前一層中連續的單元爲中心,因此相鄰單元的局部感覺野是重疊的。好比,LeNet-5中,水平方向連續的單元的感覺野存在5行4列的重疊,以前提到過,一個特徵圖中全部單元共享25個權值和一個偏置,因此他們在輸入圖像的不一樣位置檢測相同的特徵,每一層的其餘特徵圖使用不一樣的一組權值和偏置,提取不一樣類型的局部特徵。LeNet中,每一個輸入位置會提取6個不一樣的特徵。特徵圖的一種實現方式就是使用一個帶有感覺野的單元,掃面整個圖像,而且將每一個對應的位置的狀態保持在特徵圖中,這種操做等價於卷積,後面加入一個偏置和一個函數,所以,取名爲卷積網絡,卷積核就是鏈接的權重。卷積層的核就是特徵圖中全部單元使用的一組鏈接權重。卷積層的一個重要特性是若是輸入圖像發生了位移,特徵圖會發生相應的位移,不然特徵圖保持不變。這個特性是CNN對位移和形變保持魯棒的基礎。

一個卷積核提取輸入數據的某種特徵輸出一個Feature Map。 既然提取的是同一種特徵,那麼使用同一個權值也是應該的。

下采樣,池化
在圖像分類中,起主導做用的是圖像特徵的相對位置,如圖像中的數字7從左上角移到右下角,仍然是數字7,重要的是直線-點-直線之間的相對位置。由於圖像的平移,形變是很常見的,因此圖像特徵的精確的位置信息,在分類識別中甚至是有害的。 經過下降圖像分辨率的方式來下降圖像特徵位置的精度,使用池化函數(均值或者最大)對圖像進行下采樣,下降網絡的輸出對圖像形變和平移的敏感程度。若是對圖像作平移,那麼對應於高層特徵的平移(由於權值共享);若是對圖像作局部旋轉,小範圍旋轉/扭曲會被局部感覺野消除,大範圍扭曲會由於降採樣而模糊掉其影響。

Keras實現

並無,精確的實現論文中描述的LeNet-5的網絡結構,只是照着實現了一個簡單的卷積神經網絡,網絡結構以下:

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 32, 32, 20)        1520
_________________________________________________________________
activation_1 (Activation)    (None, 32, 32, 20)        0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 16, 20)        0
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 16, 16, 50)        25050
_________________________________________________________________
activation_2 (Activation)    (None, 16, 16, 50)        0
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 8, 8, 50)          0
_________________________________________________________________
flatten_1 (Flatten)          (None, 3200)              0
_________________________________________________________________
dense_1 (Dense)              (None, 500)               1600500
_________________________________________________________________
activation_3 (Activation)    (None, 500)               0
_________________________________________________________________
dense_2 (Dense)              (None, 10)                5010
_________________________________________________________________
activation_4 (Activation)    (None, 10)                0
=================================================================
Total params: 1,632,080
Trainable params: 1,632,080
Non-trainable params: 0
_________________________________________________________________

調用Keras API能夠很容易的實現上述的結構

# -*- coding:utf-8 -*-

from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras import backend as K 

class LeNet:
    @staticmethod
    def build(width,height,depth,classes):
        model = Sequential()
        inputShape = (height,width,depth)

        if K.image_data_format() == "channels_first":
            inputShape = (depth,height,width)

        # first set of CONV => RELU => POOL
        model.add(Conv2D(20,(5,5),padding="same",input_shape=inputShape))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2)))

        # second set of CONV => RELU => POOL_layers
        model.add(Conv2D(50,(5,5),padding="same"))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2)))

        # set of FC => RELU layers
        model.add(Flatten())
        model.add(Dense(500))
        model.add(Activation("relu"))

        # softmax classifier
        model.add(Dense(classes))
        model.add(Activation("softmax"))

        return model

將上述網絡應用於CIFAR10數據集進行分類

測試代碼,首先使用scikit-learn加載CIFAR10數據,並進行歸一化

print("[INFO] loading CIFAR-10 data...")
((trainX,trainY),(testX,testY)) = cifar10.load_data()
trainX = trainX.astype("float")  /  255.0
testX = testX.astype("float")  /  255.0

對CIFAR10的類別進行編碼

lb =  LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.fit_transform(testY)

處理完數據後,調用上面的LeNet創建LeNet網絡結構並使用trainX數據集進行訓練

print("[INFO] compiling model...")
opt =  SGD(lr=0.05)
model = lenet.LeNet.build(width=width,height=height,depth=depth,classes=classes)
model.compile(loss="categorical_crossentropy",optimizer=opt,metrics=["accuracy"])

# train
print("[INFO] training network...")
H = model.fit(trainX,trainY,validation_data=(testX,testY),batch_size=32,epochs=epochs,verbose=1)

最後使用testX數據集進行評估

# evaluate the network
print("[INFO] evaluating networking...")
predictions = model.predict(testX,batch_size=32)
print(classification_report(testY.argmax(axis=1),predictions.argmax(axis=1),
    target_names=labelNames))

很明顯的過擬合了,這裏就不關注這個精度了,只是簡單的測試下。

更詳細的測試代碼,能夠Start/Fork GitHub上https://github.com/brookicv/machineLearningSample 以及 https://github.com/brookicv/imageClassification

相關文章
相關標籤/搜索