開局一張圖,內容全靠編。
python
上圖引用自 【卷積神經網絡-進化史】從LeNet到AlexNet. 目前經常使用的卷積神經網絡git
深度學習如今是百花齊放,各類網絡結構層出不窮,計劃梳理下各個經常使用的卷積神經網絡結構。 目前先梳理下用於圖像分類的卷積神經網絡github
本文是關於卷積神經網絡的開山之做LeNet的,以前想着論文較早,一直沒有細讀,仔細看了一遍收穫滿滿啊。
本文有如下內容:算法
LeNet-5可謂是第一個卷積神經網絡,而且在手寫數字識別上取得了很好的效果。
對於圖像像素做爲神經網絡的輸入數據面臨的一些問題:網絡
其提出了卷積神經網絡的概念,並應用機器學習
來解決上述問題。ide
1998年的誕生的LeNet(LeCun et al. Gradient-Based Learning Applied to Document Recognition)可謂是如今各類卷積神經網絡的始祖了,其網絡結構雖然只有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\)測試
第二個卷積層 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是在1998年提出的,用於手寫體數字的識別, 首次提出了卷積神經網絡的基本組成:卷積層,池化層和全鏈接層以及權值共享,感覺野等概念。 雖然時間比較久i,可是做爲卷積神經網絡的開山之做,仍是值得入門者研讀一番的。
論文提出了:
可是將像素獨自的輸入到神經元中則有如下問題:
有了以上的問題,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,重要的是直線-點-直線之間的相對位置。由於圖像的平移,形變是很常見的,因此圖像特徵的精確的位置信息,在分類識別中甚至是有害的。 經過下降圖像分辨率的方式來下降圖像特徵位置的精度,使用池化函數(均值或者最大)對圖像進行下采樣,下降網絡的輸出對圖像形變和平移的敏感程度。若是對圖像作平移,那麼對應於高層特徵的平移(由於權值共享);若是對圖像作局部旋轉,小範圍旋轉/扭曲會被局部感覺野消除,大範圍扭曲會由於降採樣而模糊掉其影響。
並無,精確的實現論文中描述的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