數學 · CNN · 從 NN 到 CNN

類似的東西在Python · 神經網路(七)· CNN裡面說過,但我覺得還是單獨開一章來講會更清晰一點;以及我接觸 CNN 的時間非常有限,如果本文有講得不對的地方、還望觀眾老爺們不吝指出 ( σω)σ

(感謝評論區@齊顯東 的建議,我把一些我認為比較關鍵的地方進行了加粗處理)

CNN 的思想

從名字也可以看出、卷積神經網路(CNN)其實是神經網路(NN)的一種拓展,而事實上從結構上來說,樸素的 CNN 和樸素的 NN 沒有任何區別(當然,引入了特殊結構的、複雜的 CNN 會和 NN 有著比較大的區別)。本章我們主要會說一下 CNN 的思想以及它到底在 NN 的基礎上做了哪些改進,同時也會說一下 CNN 能夠解決的任務類型

CNN 的主要思想可以概括為如下兩點:

  • 局部連接(Sparse Connectivity)
  • 權值共享(Shared Weights)

它們有很好直觀。舉個栗子,我們平時四處看風景時,都是「一塊一塊」來看的、信息也都是「一塊一塊」地接收的(所謂的【局部感受野】)。在這個過程中,我們的思想在看的過程中通常是不怎麼變的、而在看完後可能會發出「啊這風景好美」的感慨、然後可能會根據這個感慨來調整我們的思想。在這個栗子中,那「一塊一塊」的風景就是局部連接,我們的思想就是權值。我們在看風景時用的都是自己的思想,這就是權值共享的生物學意義(註:這個栗子是我開腦洞開出來的、完全不能保證其學術嚴謹性、還請各位觀眾老爺們帶著批判的眼光去看待它……如果有這方面專長的觀眾老爺發現我完全就在瞎扯淡、還望不吝指出 ( σω)σ)

光用文字敘述可能還是有些懵懂,我來畫張圖(參考了一張被引用爛了的圖;但由於原圖有一定的誤導性、所以還是打算自己畫一個)(雖然很醜):

這張圖比較了 NN 和 CNN 的思想差別。左圖為 NN,可以看到它在處理輸入時是全連接的、亦即它採用的是全局感受野,同時各個神經元又是相對獨立的、這直接導致它難以將原數據樣本翻譯成一個「視野」。而正如上面所說,CNNn 採用的是局部感受野 + n共享權值,這在右圖中的表現為它的神經元可以看成是「一整塊」的「視野」,這塊視野的每一個組成部分都是共享的權值(右圖中那些又粗又長的綠線)在原數據樣本的某一個局部上「看到」的東西

用上文中看風景的例子來說的話,CNN 的行為比較像一個正常人的表現、而 NN 的行為就更像是很多個能把整個風景都看在眼底的人同時看了同一個風景、然後分別感慨了一下並把這個感慨傳遞下去這種表現(???)

再次感謝評論區@齊顯東 舉的小栗子,這裡我就做一個搬運工吧:

為什麼可以這樣?- 因為比如一隻貓的頭部,一個車的稜角"pattern"只在3*3的像素格就可以表現出來,不必用全連接所有像素點去"獲取信息量"

CNN 好處呢? - 共享參數能夠顯著減少參數的數量

其中第二點在理解了下述前向傳導演算法後應該能比較清晰地認識到,我這裡就偷個懶、不舉具體的栗子了(不過如果有需求的話我還是會補上的 ( σω)σ)

前向傳導演算法

與 NN 相對應的,CNN 也有前向傳導演算法、而且與 NN 的相似之處也很多(至少從實現的層面來說它們的結構幾乎一模一樣)。它們之間的不同之處則主要體現在如下兩點:

  • 接收的輸入的形式不同
  • 層與層之間的連接方式不同

先看第一點:對於 NN 而言、輸入是一個Ntimes n的矩陣X=left( x_1,...,x_Nright)^T,其中x_1,...,x_N都是ntimes1的列向量;當輸入是圖像時,NN 的處理方式是將圖像拉直成一個列向量。以3times3times3的圖像為例(第一個 3 代指RGB通道,後兩個 3 分別是高和寬),NN 會先把各個圖像變成27times1的列向量(亦即n=3times3times3),然後再把它們合併、轉置成一個Ntimes27的大矩陣以當作輸入nn

CNN 則不會這麼大費周章——它會直接以原始的數據作為輸入。換句話說、CNN 接收的輸入是Ntimes3times3times3的矩陣

nn可以用下圖來直觀認知一下該區別:

所以兩者的前向傳導演算法就可以用以下兩張圖來進行直觀說明了(NN 在上,CNN 在下):

(我已經盡我全力來畫得好看一點了……)

下面進行進一步的說明:

  • 對於一個3times3times3的輸入,我們可以把它拆分成 3 個3times3的輸入的堆疊(如果把3times3times3的輸入看成是一個「圖像」的話,我們可以把拆分後的 3 個輸入看成是該圖像的 3 個「頻道」;對於原始輸入來講,這 3 個頻道通常就是 RGB 通道)
  • 由於 NN 是全連接的,所以輸入的所有信息都會直接輸入給下一層的某個神經元
  • 由於 CNN 是局部連接、共享權值的,一個合理的做法就是給拆分後的每個「頻道」分配一個共享的「局部視野」(注意上面 CNN 那張圖中三個「頻道」中間都有 4 個相同顏色的正方形、且三個頻道中正方形的顏色彼此不同,這就是局部共享視野的意義)(誰注意得到啊喂)。我們通常會把這三個局部視野視為一個整體並把它稱作一個 Kernel 或一個 Filter
  • 上面 CNN 那張圖裡面我們用的是2times2的局部視野,該局部視野從相應頻道左上看到右上、然後看左下、最後看右下,這個過程中一共看了四次、每看一次就會生成一個輸出。所以三個局部視野會分別在對應的頻道上生成四個輸出、亦即一個 Kernel(或說一個 Filter)會生成 3 個2times2的輸出,將它們直接相加就得到了該 Kernel 的最終輸出——一個2times2的頻道nn

上面最後提到的「左上rightarrow右上rightarrow左下rightarrow右下」這個「看」的過程其實就是所謂的「卷積」,這也正是卷積神經網路名字的由來。卷積本身的數學定義要比上面這個簡單的描述要繁複得多,但幸運的是、實現和應用 CNN 本身並不需要具備這方面的數學理論知識(當然如果想開發更好的 CNN 結構與演算法的話、是需要進行相關研究的,不過這些都已超出我們討論的範圍了)

注意:上面 CNN 那張圖中的情形為只有一個 Kernel 的情形,通常來說在實際應用中、我們會使用幾十甚至幾百個 Kernel 以期望網路能夠學習出更好的特徵——這是因為一個 Kernel 會生成一個頻道,幾十、幾百個 Kernel 就意味著會生成幾十、幾百個頻道,由此可以期待這大量不同的頻道能夠對數據進行足夠強的描述(要知道原始數據可只有 3 個頻道)

不難根據這些內容總結出 NN 和 CNN 目前為止的異同:

  • NN 和 CNN 的主要結構都是層,但是 NN 的層結構是一維的、CNN 的層結構是高維的
  • NN 處理的一般是「線性」的數據,CNN 則從直觀上更適合處理「結構性的」數據
  • NN 層結構間會有權值矩陣作為連接的橋樑,CNN 則沒有層結構之間的權值矩陣、取而代之的是層結構本身的局部視野。該局部視野會在前向傳導演算法中與層結構進行卷積運算來得到結果、並會直接將這個結果(或將被激活函數作用後的結果)傳給下一層。因此我們常稱 NN 中的層結構為「普通層」、稱 CNN 中擁有局部視野的層結構為「卷積層」

可以看出、CNN 與 NN 區別之關鍵正在於「卷積」二字。雖然卷積的直觀形式比較簡單、但是它的實現卻並不平凡。常用的解決方案有如下兩種:

  • 將卷積步驟變換成比較大規模的矩陣相乘(cs231n 裡面的 stride trick 把我看哭了……)
  • 利用快速傅里葉變換(Fast Fourier Transform,簡稱FFT)求解(只聽說過,沒實踐過)

然後介紹一下 Stride 和 Padding 的概念。Stride 可以翻譯成「步長」,它描述了局部視野在頻道上的「瀏覽速度」。設想現在有一個5times5的頻道而我們的局部視野是2times2的,那麼不同 Stride 下的表現將如下面兩張圖所示(只以第一排為例):

(……)

可以看到上圖中局部視野每次前進「一步」而下圖中每次會前進「三步」

Padding 可以翻譯成「填充」、其存在意義有許多種解釋,一種最好理解的就是——它能保持輸入和輸出的頻道形狀一致。注意目前為止展示過的栗子中,輸入頻道在被卷積之後、輸出的頻道都會「縮小」一點。這樣在經過相當有限的卷積操作後、輸入就會變得過小而不適合再進行卷積,從而就會大大限制了整個網路結構的深度。Padding 正是這個問題的一種解決方案:它會在輸入頻道進行卷積之前、先在頻道的周圍「填充」上若干圈的「0」。設想現在有一個3times3的頻道而我們的局部視野也是3times3的,如果按照之前所說的卷積來做的話、不難想像輸出將會是1times1的頻道;不過如果我們將 Padding 設置為 1、亦即在輸入的頻道周圍填充一圈 0 的話,那麼卷積的表現將如下圖所示:

可以看到當我們在輸入頻道外面 Pad 上一圈 0 之後、輸出就變成3times3的了,這為超深層 CNN 的搭建創造了可能性(比如有名的 ResNet)

在 cs231n 的這篇文章裡面有一張很好很好很好的動圖(大概位於頁面中央),請允許我偷個懶不自己動手畫了…… ( σω)σ

全連接層(Fully Connected Layer)

全連接層常簡稱為 FC,它是可能會出現在 CNN 中的、一個比較特殊的結構;從名字就可以大概猜想到、FC 應該和普通層息息相關,事實上也正是如此。直觀地說、FC 是連接卷積層和普通層的普通層,它將從父層(卷積層)那裡得到的高維數據鋪平以作為輸入、進行一些非線性變換(用激活函數作用)、然後將結果輸進跟在它後面的各個普通層構成的系統中:

上圖中的 FC 一共有n_1=3times2times2=12個神經元,自 FC 之後的系統其實就是 NN。換句話說、我們可以把 CNN 拆分成如下兩塊結構:

  • 自輸入開始、至 FC 終止的「卷積塊」,組成卷積塊的都是卷積層
  • 自 FC 開始、至輸出終止的「NN 塊」,組成 NN 塊的都是普通層

注意:值得一提的是,在許多常見的網路結構中、NN 塊里都只含有 FC 這個普通層

nn

那麼為什麼 CNN 會有 FC 這個結構呢?或者問得更具體一點、為什麼要將總體分成卷積塊和 NN 塊兩部分呢?這其實從直觀上來說非常好解釋:卷積塊中的卷積的基本單元是局部視野,用它類比我們的眼睛的話、就是將外界信息翻譯成神經信號的工具,它能將接收的輸入中的各個特徵提取出來;至於 NN(神經網路)塊、則可以類比我們的神經網路(甚至說、類比我們的大腦),它能夠利用卷積塊得到的信號(特徵)來做出相應的決策。概括地說、CNN 視卷積塊為「眼」而視 NN 塊為「腦」,眼腦結合則決策自成(???)。用機器學習的術語來說、則卷積塊為「特徵提取器」而 NN 塊為「決策分類器」

nn而事實上,CNN 的強大之處其實正在於其卷積塊強大的特徵提取能力上、NN 塊甚至可以說只是用於分類的一個附屬品而已。我們完全可以利用 CNN 將特徵提取出來後、用之前介紹過的決策樹、支持向量機等等來進行分類這一步而無須使用 NN 塊

池化(Pooling)

池化是 NN 中完全沒有的、只屬於 CNN 的特殊演算。雖然名字聽上去可能有些高大上的感覺,但它的本質其實就是「對局部信息的總結」。常見的池化有如下兩種:

  • 極大池化(Max Pooling),它會輸出接收到的所有輸入中的最大值
  • 平均池化(Average Pooling),它會輸出接收到的所有輸入的均值

池化過程其實與卷積過程類似、可以看成是局部視野對輸入信息的轉換,只不過卷積過程做的是卷積運算、池化過程做的是極大或平均(或其它)運算而已

nn

不過池化與卷積有一點通常是差異較大的——池化的 Stride 通常會比卷積的 Stride 要大。比如對於一個3times3的輸入頻道和一個3times3的局部視野而言:

  • 卷積常常選取 Stride 和 Padding 都為 1,從而輸出頻道是3times3
  • 池化常常選取 Stride 為 2、Padding 為 1,從而輸出頻道是2times2

將 Stride 選大是符合池化的內涵的:池化是對局部信息的總結、所以自然希望池化能夠將得到的信息進行某種「壓縮處理」。如果將 Stride 選得比較小的話、總結出來的信息就很可能會產生「冗餘」,這就違背了池化的本意。

nn

不過為什麼最常見的兩種池化——極大池化和平均池化確實能夠壓縮信息呢?這主要是因為 CNN 一般處理的都是圖像數據。由經驗可知、圖像在像素級間隔上的差異是很小的,這就為上述兩種池化提供了一定的合理性

以上就比較泛地說了一下 CNN 的諸多概念,可以看到我沒有講(也不打算講)(喂)最重要的反向傳播演算法。這是因為 Tensorflow 能夠幫我們處理梯度,所以單就使用 Tensorflow 來實現 CNN 而言、完全不用管反向傳播演算法應該怎麼推 ( σω)σ

不過如果我有空的話,可能會在帶星號的章節中講講反向傳播演算法……

希望觀眾老爺們能夠喜歡~


推薦閱讀:

深度學習入門系列,用白話文的方式讓你看得懂學的快(第七章、第八章)
機器學習系列:遞歸神經網路
基於TensorFlow一次簡單的RNN實現

TAG:数学 | 机器学习 | 卷积神经网络CNN |