天天看點

深度學習_卷積神經網絡CNN

1. 引入

卷積神經網絡(CNN)是一種專門用來處理具有網格結構資料的神經網絡.它屬于前饋神經網絡,它被定義為:至少在某一層用卷積代替了矩陣乘法的神經網絡.最常見的應用場景是圖像識别.

前篇我們自己動手,用Python實作了一個BP神經網絡,本篇我們在Keras架構之下實作卷積神經網絡(Keras架構詳見《深度學習_工具》篇).Keras幾乎是搭建CNN最簡單的工具了,然而原理并不簡單:除了基本的神經網絡中用的誤差函數,激活函數等概念以外(具體詳見《深度學習_BP神經網絡》),CNN還用到了卷積,池化,DropOut等方法.将在本文中逐一介紹.

2. 原理

1) 圖像識别

先來看看圖形學中的圖像識别是如何實作的.

深度學習_卷積神經網絡CNN

圖檔.png

我們拿到了原圖(圖上左),一般先将其轉換成灰階圖(圖上中).然後進行邊緣檢測,圖像進行中常使用計算梯度方法(判斷某像素與它相鄰像素的內插補點)檢測邊緣.在CNN中我們用卷積來檢測:先設計一個卷積核計算相鄰像素的內插補點,然後用ReLU(f(x)=max(0,x))激活函數将那些內插補點小的置為0,即識别為非邊緣(看到激活函數的厲害了吧,它把低于門檻值的都扔掉了,這可是一般線性變換做不到的).于是得到了邊緣圖(圖上右).處理後的圖像是個稀疏的矩陣(多數點值都為0).

¬為了簡化計算,我們希望将圖檔縮小,但在縮放的過程中,細的邊緣線就會被弱化,甚至消失(圖下左).在CNN中用池化解決這一問題,如果把3*3個點縮放到1點,”最大池化”會将這9個點中最大的值,作為新點的值,于是無論強邊緣還是弱邊緣都被保留了下來(圖下右).

邊緣檢測隻是圖像識别的第一步,之後還将識别一些更上層的特征,比如拐角,車輪,汽車的輪廓等等,它們都可由卷積實作.越往後的層越抽象,最終一個神經網絡模型建起來和圖像進行中的"金字塔"有類似的結構,雖然下層的每個點都是局部的,但是上層的點具有全局性.

2) 卷積

下面來看看卷積到底是什麼,它與之前學習的全連接配接神經網絡有什麼不同.

卷積定義是:通過兩個函數f 和g 生成第三個函數的一種數學算子.呵呵,很抽象啊,形象地說,處理圖像時,卷積就好像拿着一小塊濾鏡,把圖像從左到右,從上到下,一格一格地掃一遍,如果在濾鏡中看到想要的,就在下一層做個标記.看看下圖就明白了.

深度學習_卷積神經網絡CNN

(圖檔引自《深度學習》”花書”)

圖中是一個二維平面上的卷積運算,輸入是一個4x3的矩陣,卷積核(Kernel)是2x2的矩陣,輸出是一個3x2的矩陣(卷積不都是2維的).這就是卷積——一種特殊的線性運算,考慮以下幾種情況:

如果上圖中的w,x,y,z都為0.25,則該卷積實作了均值運算(圖檔的模糊處理)

如果上圖中w+x+y+z=1,則該卷積實作了實作了權重平均.

這個運算是不是有點眼熟,如果把Input中各點排着一列,把Kernel看成權值參數,則它是一個神經網絡的連接配接.輸入層有12個元素,隐藏層1有6個元素(先不管其它層)

深度學習_卷積神經網絡CNN

卷積網絡與全連接配接網絡

如圖所示,左則是卷積層,右則是全連接配接的神經網絡,全連接配接時,共有126=72個連接配接(輸入輸出),72種權值;而卷積層隻有24個連接配接(核大小*輸出),4個權值w,x,y,z,分别用四種顔色标出.此處,引出了兩個概念:

共享權值(參數共享):共享權值就是多個連接配接使用同一權值,卷積神經網絡中共享的權值就是卷積核的内容,這樣不但減小了學習難度.還帶來的”平移等變”的性質,比如集體合影中每張臉都可以使用相同的卷積核(一組權值)識别出來,無論它在圖中的什麼位置,這樣學習一張臉後,就能對每張臉應用相同的處理了.此技術多用于同一狀态重複出現的情況下.(若圖中隻有一張臉,臉相關的卷積核共享作用就不大了)

全連接配接,局部連接配接與卷積:全連接配接就是上一層的每個點都與下一層的每個點連接配接,每個連接配接都有其自已的權值;局部連接配接是隻有部分點互相連續;卷積是在局部連續的基礎上又共享了權值.

卷積核可以是指定的,也可以是用梯度向前推出來的,可以是聚類算出來的(無監督學習),還可以先取一小塊訓練,然後用這小塊訓練的結果定義卷積的核.這取決于我們設計的不同算法.

由此可見,卷積層是兩層之間的連接配接方法,與全連接配接相比,它大大簡化了運算的複雜度,還節省了存儲空間.之是以能這樣簡化是因為圖像中距離越近的點關系越大(序列處理也同理:離得越近關系越大).

3) 池化

池化是使用某一位置的相鄰輸出的總體統計特征來代替網絡在該位置的輸出,和卷積差不多,它也是層到層之間的運算,經過池化處理,層中節點可能不變,也可能變小(常用它降采樣,以減少計算量).

最大池化就是将相鄰的N個點作為輸入,将其中最大的值輸出到下一層.除了最大池化,池化算法還有:取平均值,權重平均等等.

池化具有平移不變性:若對圖檔進行少量平移,經過池化函數後的大多數輸出并不會發生改變,比如最大池化,移動一像素後,區域中最大的值可能仍在該區域内.

同理,在一個稀疏的矩陣中(不一定是圖像),假設對n點做最大池化,那麼隻要其中有一點非0,則池化結果非0.如下圖所示,隻要手寫資料5符合其中一個判斷标準,則将其識别為數字5.

深度學習_卷積神經網絡CNN

(圖檔引用自《深度學習》"花書")

池化還經常用于處理不同尺寸的圖檔:比如有800x600和200*150兩張圖,想對它們做同一處理,可通過設定池化的輸出為100x75來實作保留特征的縮放,以至擴充到任意大小的圖檔.

基本上卷積網絡都用使用池化操作.

4) DropOut

深度學習_卷積神經網絡CNN

再舉個例子,現在我們來識别老鼠,一般老鼠都有兩隻耳朵,兩隻眼睛,一個鼻子和一個嘴,它們有各自的形狀且都是從上到下排列的.如果嚴格按照這個規則,那麼"一隻耳"就不會被識别成老鼠.

為提高魯棒性,使用了DropOut方法.它随機地去掉神經網絡中的一些點,用剩餘的點訓練,假設有一些訓練集中的老鼠,被去掉的正是耳朵部分,那麼"一隻耳"最終也可能由于其它特征都對而被識别.

除了提高魯棒性,DropOut還有其它一些優點,比如在卷積神經網絡中,由于共享參數,我們訓練一小部分的子網絡(由DropOut剪出),參數共享會使得剩餘的子網絡也能有同樣好的參數設定。保證學習效果的同時,也大大減少了計算量.

DropOut就如同在層與層之間故意加入了一些噪聲,雖然避免了過拟合,但它是一個有損的算法,小的網絡使用它可能會丢失一些有用的資訊.一般在較大型的網絡中使用DropOut.

5) 總結

卷積,池化,DropOut都是設計者根據資料的性質,采取的對全連接配接網絡的優化和簡化,雖然它們都是有損的,但是對計算和存儲的優化也非常明顯,使我們有機會将神經網絡擴充到成百上千層.

3. 代碼

1) 說明

代碼的主要功能是根據Mnist庫的圖像資料訓練手寫數字識别.核心代碼在後半部分,它使用了卷積層,池化層,Dropout層,Flatten層,和全連接配接層.

2) 代碼

(為了讓大家複制粘貼就能運作,還是附上了全部代碼)

# -*- coding: utf-8 -*-
from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K

batch_size = 128 # 批尺寸
num_classes = 10 # 0-9十個數字對應10個分類
epochs = 12 # 訓練12次

# input image dimensions
img_rows, img_cols = 28, 28 # 訓練圖檔大小28x28

# the data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()

if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
    x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
    input_shape = (1, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
    input_shape = (img_rows, img_cols, 1)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

# 下面model相關的是關鍵部分
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3), #加卷積層,核大小3x3,輸出次元32
                 activation='relu', #激活函數為relu
                 input_shape=input_shape)) #傳入資料
model.add(Conv2D(64, (3, 3), activation='relu')) #又加一個卷積層
model.add(MaxPooling2D(pool_size=(2, 2))) # 以2x2為一塊池化
model.add(Dropout(0.25)) # 随機斷開25%的連接配接
model.add(Flatten()) # 扁平化,例如将28x28x1變為784的格式
model.add(Dense(128, activation='relu')) # 加入全連接配接層
model.add(Dropout(0.5)) # 再加一層Dropout
model.add(Dense(num_classes, activation='softmax')) # 加入到輸出層的全連接配接

model.compile(loss=keras.losses.categorical_crossentropy, # 設損失函數
              optimizer=keras.optimizers.Adadelta(), # 設學習率
              metrics=['accuracy'])

model.fit(x_train, y_train,
          batch_size=batch_size, # 設批大小
          epochs=epochs, # 設學習次數
          verbose=1,
          validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0) # 用測試集評測
print('Test loss:', score[0])
print('Test accuracy:', score[1])      

4. CNN的曆史

看CNN的發展,從1998年LeCun的經典之作LeNet, 到将ImageNet圖像識别率提高了10個百分點的AlexNet, VGG(加入更多卷積層), GoogleNet(使用了Inception一種網中網的結構), 再到RssNet(使用殘差網絡),ImageNet的Top-5錯誤率已經降到3.57%,低于人眼識别的錯誤率5.1%,并且仍在不斷進步.這些不斷提高的成績以及在更多領域的應用讓神經變得越來越熱門.

深度學習_卷積神經網絡CNN

從圖中可見,從AlexNet的3層全連接配接神經網絡,到ResNet的152層神經網絡,全連接配接層越來越少,卷積層越來越多.除了算法的進步,人的知識也越來越多越來越細化地溶入了神經網絡.

5. 參考

1) CNN的發展史

技術文章定時推送

繼續閱讀