1. 程式人生 > >零基礎入門深度學習(4)

零基礎入門深度學習(4)

往期回顧

在前面的文章中,我們介紹了全連線神經網路,以及它的訓練和使用。我們用它來識別了手寫數字,然而,這種結構的網路對於影象識別任務來說並不是很合適。本文將要介紹一種更適合影象、語音識別任務的神經網路結構——卷積神經網路(Convolutional Neural Network, CNN)。說卷積神經網路是最重要的一種神經網路也不為過,它在最近幾年大放異彩,幾乎所有影象、語音識別領域的重要突破都是卷積神經網路取得的,比如谷歌的GoogleNet、微軟的ResNet等,打敗李世石的AlphaGo也用到了這種網路。本文將詳細介紹卷積神經網路以及它的訓練演算法,以及動手實現一個簡單的卷積神經網路

一個新的啟用函式——Relu

最近幾年卷積神經網路中,啟用函式往往不選擇sigmoid或tanh函式,而是選擇relu函式。Relu函式的定義是:

Relu函式影象如下圖所示:

Relu函式作為啟用函式,有下面幾大優勢:

  • 速度快 和sigmoid函式需要計算指數和倒數相比,relu函式其實就是一個max(0,x),計算代價小很多。
  • 減輕梯度消失問題 回憶一下計算梯度的公式。其中,是sigmoid函式的導數。在使用反向傳播演算法進行梯度計算時,每經過一層sigmoid神經元,梯度就要乘上一個。從下圖可以看出,函式最大值是1/4。因此,乘一個會導致梯度越來越小,這對於深層網路的訓練是個很大的問題。而relu函式的導數是1,不會導致梯度變小。當然,啟用函式僅僅是導致梯度減小的一個因素,但無論如何在這方面relu的表現強於sigmoid。使用relu啟用函式可以讓你訓練更深的網路。

  • 稀疏性 通過對大腦的研究發現,大腦在工作的時候只有大約5%的神經元是啟用的,而採用sigmoid啟用函式的人工神經網路,其啟用率大約是50%。有論文聲稱人工神經網路在15%-30%的啟用率時是比較理想的。因為relu函式在輸入小於0時是完全不啟用的,因此可以獲得一個更低的啟用率。

全連線網路 VS 卷積網路

全連線神經網路之所以不太適合影象識別任務,主要有以下幾個方面的問題:

  • 引數數量太多 考慮一個輸入1000*1000畫素的圖片(一百萬畫素,現在已經不能算大圖了),輸入層有1000*1000=100萬節點。假設第一個隱藏層有100個節點(這個數量並不多),那麼僅這一層就有(1000*1000+1)*100=1億引數,這實在是太多了!我們看到影象只擴大一點,引數數量就會多很多,因此它的擴充套件性很差。
  • 沒有利用畫素之間的位置資訊 對於影象識別任務來說,每個畫素和其周圍畫素的聯絡是比較緊密的,和離得很遠的畫素的聯絡可能就很小了。如果一個神經元和上一層所有神經元相連,那麼就相當於對於一個畫素來說,把影象的所有畫素都等同看待,這不符合前面的假設。當我們完成每個連線權重的學習之後,最終可能會發現,有大量的權重,它們的值都是很小的(也就是這些連線其實無關緊要)。努力學習大量並不重要的權重,這樣的學習必將是非常低效的。
  • 網路層數限制 我們知道網路層數越多其表達能力越強,但是通過梯度下降方法訓練深度全連線神經網路很困難,因為全連線神經網路的梯度很難傳遞超過3層。因此,我們不可能得到一個很深的全連線神經網路,也就限制了它的能力。

那麼,卷積神經網路又是怎樣解決這個問題的呢?主要有三個思路:

  • 區域性連線 這個是最容易想到的,每個神經元不再和上一層的所有神經元相連,而只和一小部分神經元相連。這樣就減少了很多引數。
  • 權值共享 一組連線可以共享同一個權重,而不是每個連線有一個不同的權重,這樣又減少了很多引數。
  • 下采樣 可以使用Pooling來減少每層的樣本數,進一步減少引數數量,同時還可以提升模型的魯棒性。

對於影象識別任務來說,卷積神經網路通過儘可能保留重要的引數,去掉大量不重要的引數,來達到更好的學習效果。

接下來,我們將詳述卷積神經網路到底是何方神聖。

卷積神經網路是啥

首先,我們先獲取一個感性認識,下圖是一個卷積神經網路的示意圖:

圖1 卷積神經網路

網路架構

圖1所示,一個卷積神經網路由若干卷積層Pooling層全連線層組成。你可以構建各種不同的卷積神經網路,它的常用架構模式為:

INPUT -> [[CONV]*N -> POOL?]*M -> [FC]*K

也就是N個卷積層疊加,然後(可選)疊加一個Pooling層,重複這個結構M次,最後疊加K個全連線層。

對於圖1展示的卷積神經網路:

INPUT -> CONV -> POOL -> CONV -> POOL -> FC -> FC

按照上述模式可以表示為:

INPUT -> [[CONV]*1 -> POOL]*2 -> [FC]*2

也就是:N=1, M=2, K=2

三維的層結構

圖1我們可以發現卷積神經網路的層結構和全連線神經網路的層結構有很大不同。全連線神經網路每層的神經元是按照一維排列的,也就是排成一條線的樣子;而卷積神經網路每層的神經元是按照三維排列的,也就是排成一個長方體的樣子,有寬度高度深度

對於圖1展示的神經網路,我們看到輸入層的寬度和高度對應於輸入影象的寬度和高度,而它的深度為1。接著,第一個卷積層對這幅影象進行了卷積操作(後面我們會講如何計算卷積),得到了三個Feature Map。這裡的"3"可能是讓很多初學者迷惑的地方,實際上,就是這個卷積層包含三個Filter,也就是三套引數,每個Filter都可以把原始輸入影象卷積得到一個Feature Map,三個Filter就可以得到三個Feature Map。至於一個卷積層可以有多少個Filter,那是可以自由設定的。也就是說,卷積層的Filter個數也是一個超引數。我們可以把Feature Map可以看做是通過卷積變換提取到的影象特徵,三個Filter就對原始影象提取出三組不同的特徵,也就是得到了三個Feature Map,也稱做三個通道(channel)

繼續觀察圖1,在第一個卷積層之後,Pooling層對三個Feature Map做了下采樣(後面我們會講如何計算下采樣),得到了三個更小的Feature Map。接著,是第二個卷積層,它有5個Filter。每個Fitler都把前面下采樣之後的3個Feature Map卷積在一起,得到一個新的Feature Map。這樣,5個Filter就得到了5個Feature Map。接著,是第二個Pooling,繼續對5個Feature Map進行下采樣,得到了5個更小的Feature Map。

圖1所示網路的最後兩層是全連線層。第一個全連線層的每個神經元,和上一層5個Feature Map中的每個神經元相連,第二個全連線層(也就是輸出層)的每個神經元,則和第一個全連線層的每個神經元相連,這樣得到了整個網路的輸出。

至此,我們對卷積神經網路有了最基本的感性認識。接下來,我們將介紹卷積神經網路中各種層的計算和訓練。

卷積神經網路輸出值的計算

卷積層輸出值的計算

我們用一個簡單的例子來講述如何計算卷積,然後,我們抽象出卷積層的一些重要概念和計算方法。

假設有一個5*5的影象,使用一個3*3的filter進行卷積,想得到一個3*3的Feature Map,如下所示:

為了清楚的描述卷積計算過程,我們首先對影象的每個畫素進行編號,用表示影象的第行第列元素;對filter的每個權重進行編號,用表示第行第列權重,用表示filter的偏置項;對Feature Map的每個元素進行編號,用表示Feature Map的第行第列元素;用表示啟用函式(這個例子選擇relu函式作為啟用函式)。然後,使用下列公式計算卷積:

例如,對於Feature Map左上角元素來說,其卷積計算方法為:

計算結果如下圖所示:

接下來,Feature Map的元素的卷積計算方法為:

計算結果如下圖所示:

可以依次計算出Feature Map中所有元素的值。下面的動畫顯示了整個Feature Map的計算過程:

圖2 卷積計算

上面的計算過程中,步幅(stride)為1。步幅可以設為大於1的數。例如,當步幅為2時,Feature Map計算如下:

我們注意到,當步幅設定為2的時候,Feature Map就變成2*2了。這說明影象大小、步幅和卷積後的Feature Map大小是有關係的。事實上,它們滿足下面的關係:

式式

在上面兩個公式中,是卷積後Feature Map的寬度;是卷積前影象的寬度;是filter的寬度;是Zero Padding數量,Zero Padding是指在原始影象周圍補幾圈0,如果的值是1,那麼就補1圈0;是步幅;是卷積後Feature Map的高度;是卷積前影象的寬度。式2式3本質上是一樣的。

以前面的例子來說,影象寬度,filter寬度,Zero Padding步幅,則

說明Feature Map寬度是2。同樣,我們也可以計算出Feature Map高度也是2。

前面我們已經講了深度為1的卷積層的計算方法,如果深度大於1怎麼計算呢?其實也是類似的。如果卷積前的影象深度為D,那麼相應的filter的深度也必須為D。我們擴充套件一下式1,得到了深度大於1的卷積計算公式:

式4中,D是深度;F是filter的大小(寬度或高度,兩者相同);表示filter的第層第行第列權重;表示影象的第層第行第列畫素;其它的符號含義和式1是相同的,不再贅述。

我們前面還曾提到,每個卷積層可以有多個filter。每個filter和原始影象進行卷積後,都可以得到一個Feature Map。因此,卷積後Feature Map的深度(個數)和卷積層的filter個數是相同的。

下面的動畫顯示了包含兩個filter的卷積層的計算。我們可以看到7*7*3輸入,經過兩個3*3*3filter的卷積(步幅為2),得到了3*3*2的輸出。另外我們也會看到下圖的Zero padding是1,也就是在輸入元素的周圍補了一圈0。Zero padding對於影象邊緣部分的特徵提取是很有幫助的。

以上就是卷積層的計算方法。這裡面體現了區域性連線權值共享:每層神經元只和上一層部分神經元相連(卷積計算規則),且filter的權值對於上一層所有神經元都是一樣的。對於包含兩個3*3*3的fitler的卷積層來說,其引數數量僅有(3*3*3+1)*2=56個,且引數數量與上一層神經元個數無關。與全連線神經網路相比,其引數數量大大減少了。

用卷積公式來表達卷積層計算

不想了解太多數學細節的讀者可以跳過這一節,不影響對全文的理解。

式4的表達很是繁冗,最好能簡化一下。就像利用矩陣可以簡化表達全連線神經網路的計算一樣,我們利用卷積公式可以簡化卷積神經網路的表達。

下面我們介紹二維卷積公式

設矩陣,,其行、列數分別為、、、,則二維卷積公式如下:

且,滿足條件。

我們可以把上式寫成

如果我們按照式5來計算卷積,我們可以發現矩陣A實際上是filter,而矩陣B是待卷積的輸入,位置關係也有所不同:

從上圖可以看到,A左上角的值與B對應區塊中右下角的值相乘,而不是與左上角的相乘。因此,數學中的卷積和卷積神經網路中的『卷積』還是有區別的,為了避免混淆,我們把卷積神經網路中的『卷積』操作叫做互相關(cross-correlation)操作。

卷積互相關操作是可以轉化的。首先,我們把矩陣A翻轉180度,然後再交換A和B的位置(即把B放在左邊而把A放在右邊。卷積滿足交換率,這個操作不會導致結果變化),那麼卷積就變成了互相關

如果我們不去考慮兩者這麼一點點的區別,我們可以把式5代入到式4

其中,是卷積層輸出的feature map。同式4相比,式6就簡單多了。然而,這種簡潔寫法只適合步長為1的情況。

Pooling層輸出值的計算

Pooling層主要的作用是下采樣,通過去掉Feature Map中不重要的樣本,進一步減少引數數量。Pooling的方法很多,最常用的是Max PoolingMax Pooling實際上就是在n*n的樣本中取最大值,作為取樣後的樣本值。下圖是2*2 max pooling:

除了Max Pooing之外,常用的還有Mean Pooling——取各樣本的平均值。

對於深度為D的Feature Map,各層獨立做Pooling,因此Pooling後的深度仍然為D。

全連線層

全連線層輸出值的計算和上一篇文章零基礎入門深度學習(3) - 神經網路和反向傳播演算法講過的全連線神經網路是一樣的,這裡就不再贅述了。

卷積神經網路的訓練

全連線神經網路相比,卷積神經網路的訓練要複雜一些。但訓練的原理是一樣的:利用鏈式求導計算損失函式對每個權重的偏導數(梯度),然後根據梯度下降公式更新權重。訓練演算法依然是反向傳播演算法。

  1. 前向計算每個神經元的輸出值(表示網路的第個神經元,以下同);
  2. 反向計算每個神經元的誤差項,在有的文獻中也叫做敏感度(sensitivity)。它實際上是網路的損失函式對神經元加權輸入的偏導數,即;
  3. 計算每個神經元連線權重的梯度(表示從神經元連線到神經元的權重),公式為,其中,表示神經元的輸出。

最後,根據梯度下降法則更新每個權重即可。

對於卷積神經網路,由於涉及到區域性連線下采樣的等操作,影響到了第二步誤差項的具體計算方法,而權值共享影響了第三步權重梯度的計算方法。接下來,我們分別介紹卷積層和Pooling層的訓練演算法。

卷積層的訓練

對於卷積層,我們先來看看上面的第二步,即如何將誤差項傳遞到上一層;然後再來看看第三步,即如何計算filter每個權值的梯度

卷積層誤差項的傳遞

最簡單情況下誤差項的傳遞

我們先來考慮步長為1、輸入的深度為1、filter個數為1的最簡單的情況。

假設輸入的大小為3*3,filter大小為2*2,按步長為1卷積,我們將得到2*2的feature map。如下圖所示:

在上圖中,為了描述方便,我們為每個元素都進行了編號。用表示第層第行第列的誤差項;用表示filter第行第列權重,用表示filter的偏置項;用表示第層第行第列神經元的輸出;用表示第行神經元的加權輸入;用表示第層第行第列的誤差項;用表示第層的啟用函式。它們之間的關係如下:

上式中,、、都是陣列,是由組成的陣列,表示卷積操作。

在這裡,我們假設第中的每個值都已經算好,我們要做的是計算第層每個神經元的誤差項

根據鏈式求導法則:

我們先求第一項。我們先來看幾個特例,然後從中總結出一般性的規律。

例1,計算,僅與的計算有關:

因此:

例2,計算,與和的計算都有關:

因此:

例3,計算,與、、和的計算都有關:

因此:

從上面三個例子,我們發揮一下想象力,不難發現,計算,相當於把第層的sensitive map周圍補一圈0,在與180度翻轉後的filter進行cross-correlation,就能得到想要結果,如下圖所示:

因為卷積相當於將filter旋轉180度的cross-correlation,因此上圖的計算可以用卷積公式完美的表達:

上式中的表示第層的filter的權重陣列。也可以把上式的卷積展開,寫成求和的形式:

現在,我們再求第二項。因為

所以這一項極其簡單,僅求啟用函式的導數就行了。

將第一項和第二項組合起來,我們得到最終的公式:

也可以將式7寫成卷積的形式:

其中,符號表示element-wise product,即將矩陣中每個對應元素相乘。注意式8中的、、都是矩陣

以上就是步長為1、輸入的深度為1、filter個數為1的最簡單的情況,卷積層誤差項傳遞的演算法。下面我們來推導一下步長為S的情況。

卷積步長為S時的誤差傳遞

我們先來看看步長為S與步長為1的差別。

如上圖,上面是步長為1時的卷積結果,下面是步長為2時的卷積結果。我們可以看出,因為步長為2,得到的feature map跳過了步長為1時相應的部分。因此,當我們反向計算誤差項時,我們可以對步長為S的sensitivity map相應的位置進行補0,將其『還原』成步長為1時的sensitivity map,再用式8進行求解。

輸入層深度為D時的誤差傳遞

當輸入深度為D時,filter的深度也必須為D,層的通道只與filter的通道的權重進行計算。因此,反向計算誤差項時,我們可以使用式8,用filter的第通道權重對第層sensitivity map進行卷積,得到第層通道的sensitivity map。如下圖所示:

filter數量為N時的誤差傳遞

filter數量為N時,輸出層的深度也為N,第個filter卷積產生輸出層的第個feature map。由於第層每個加權輸入都同時影響了第層所有feature map的輸出值,因此,反向計算誤差項時,需要使用全導數公式。也就是,我們先使用第個filter對第層相應的第個sensitivity map進行卷積,得到N個層的偏sensitivity map,然後再將N個偏sensitivity map**按元素相加**,得到最終的層的sensitivity map:

以上就是卷積層誤差項傳遞的演算法,如果讀者還有所困惑,可以參考後面的程式碼實現來理解。

卷積層filter權重梯度的計算

我們要在得到第層sensitivity map的情況下,計算filter的權重的梯度,由於卷積層是權重共享的,因此梯度的計算稍有不同。

如上圖所示,是第層的輸出,是第層filter的權重,是第層的sensitivity map。我們的任務是計算的梯度,即。

為了計算偏導數,我們需要考察權重對的影響。權重項通過影響的值,進而影響。我們仍然通過幾個具體的例子來看權重項對的影響,然後再從中總結出規律。

例1,計算:

從上面的公式看出,由於權值共享,權值對所有的都有影響。是、、...的函式,而、、...又是的函式,根據全導數公式,計算就是要把每個偏導數都加起來:

例2,計算:

通過檢視與的關係,我們很容易得到:

實際上,每個權重項都是類似的,我們不一一舉例了。現在,是我們再次發揮想象力的時候,我們發現計算規律是:

也就是用sensitivity map作為卷積核,在input上進行cross-correlation,如下圖所示:

最後,我們來看一看偏置項的梯度。通過檢視前面的公式,我們很容易發現:

也就是偏置項梯度就是sensitivity map所有誤差項之和。

對於步長為S的卷積層,處理方法與傳遞**誤差項*是一樣的,首先將sensitivity map『還原』成步長為1時的sensitivity map,再用上面的方法進行計算。

獲得了所有的梯度之後,就是根據梯度下降演算法來更新每個權重。這在前面的文章中已經反覆寫過,這裡就不再重複了。

至此,我們已經解決了卷積層的訓練問題,接下來我們看一看Pooling層的訓練。

Pooling層的訓練

無論max pooling還是mean pooling,都沒有需要學習的引數。因此,在卷積神經網路的訓練中,Pooling層需要做的僅僅是將誤差項傳遞到上一層,而沒有梯度的計算。

Max Pooling誤差項的傳遞

如下圖,假設第層大小為4*4,pooling filter大小為2*2,步長為2,這樣,max pooling之後,第層大小為2*2。假設第層的值都已經計算完畢,我們現在的任務是計算第層的值。

我們用表示第層的加權輸入;用表示第層的加權輸入。我們先來考察一個具體的例子,然後再總結一般性的規律。對於max pooling:

也就是說,只有區塊中最大的才會對的值產生影響。我們假設最大的值是,則上式相當於:

那麼,我們不難求得下面幾個偏導數:

因此:

而:

現在,我們發現了規律:對於max pooling,下一層的誤差項的值會原封不動的傳遞到上一層對應區塊中的最大值所對應的神經元,而其他神經元的誤差項的值都是0。如下圖所示(假設、、、為所在區塊中的最大輸出值):

Mean Pooling誤差項的傳遞

我們還是用前面屢試不爽的套路,先研究一個特殊的情形,再擴充套件為一般規律。

如上圖,我們先來考慮計算。我們先來看看如何影響。

根據上式,我們一眼就能看出來:

所以,根據鏈式求導法則,我們不難算出:

同樣,我們可以算出、、:

現在,我們發現了規律:對於mean pooling,下一層的誤差項的值會平均分配到上一層對應區塊中的所有神經元。如下圖所示:

上面這個演算法可以表達為高大上的克羅內克積(Kronecker product)的形式,有興趣的讀者可以研究一下。

其中,是pooling層filter的大小,、都是矩陣。

至此,我們已經把卷積層Pooling層的訓練演算法介紹完畢,加上上一篇文章講的全連線層訓練演算法,您應該已經具備了編寫卷積神經網路程式碼所需要的知識。為了加深對知識的理解,接下來,我們將展示如何實現一個簡單的卷積神經網路

卷積神經網路的實現

現在,我們親自動手實現一個卷積神經網路,以便鞏固我們所學的知識。

首先,我們要改變一下程式碼的架構,『層』成為了我們最核心的元件。這是因為卷積神經網路有不同的層,而每種層的演算法都在對應的類中實現。

這次,我們用到了在python中編寫演算法經常會用到的numpy包。為了使用numpy,我們需要先將numpy匯入:

  1. import numpy as np

卷積層的實現

卷積層初始化

我們用ConvLayer類來實現一個卷積層。下面的程式碼是初始化一個卷積層,可以在建構函式中設定卷積層的超引數

  1. classConvLayer(object):
  2. def __init__(self, input_width, input_height,
  3. channel_number, filter_width,
  4. filter_height, filter_number,
  5. zero_padding, stride, activator,
  6. learning_rate):
  7. self.input_width = input_width
  8. self.input_height = input_height
  9. self.channel_number = channel_number
  10. self.filter_width = filter_width
  11. self.filter_height = filter_height
  12. self.filter_number = filter_number
  13. self.zero_padding = zero_padding
  14. self.stride = stride
  15. self.output_width = \
  16. ConvLayer.calculate_output_size(
  17. self.input_width, filter_width, zero_padding,
  18. stride)
  19. self.output_height = \
  20. ConvLayer.calculate_output_size(
  21. self.input_height, filter_height, zero_padding,
  22. stride)
  23. self.output_array = np.zeros((self.filter_number,
  24. self.output_height, self.output_width))
  25. self.filters =[]
  26. for i in range(filter_number):
  27. self.filters.append(Filter(filter_width,
  28. filter_height, self.channel_number))
  29. self.activator = activator
  30. self.learning_rate = learning_rate

calculate_output_size函式用來確定卷積層輸出的大小,其實現如下:

  1. @staticmethod
  2. def calculate_output_size(input_size,
  3. filter_size, zero_padding, stride):
  4. return(input_size - filter_size +
  5. 2* zero_padding)/ stride +1

Filter類儲存了卷積層的引數以及梯度,並且實現了用梯度下降演算法來更新引數。

  1. classFilter(object):
  2. def __init__(self, width, height, depth):
  3. self.weights = np.random.uniform(-1e-4,1e-4,
  4. (depth, height, width))
  5. self.bias =0
  6. self.weights_grad = np.zeros(
  7. self.weights.shape)
  8. self.bias_grad =0
  9. def __repr__(self):
  10. return'filter weights:\n%s\nbias:\n%s'%(
  11. repr(self.weights), repr(self.bias))
  12. def get_weights(self):
  13. return self.weights
  14. def get_bias(self):
  15. return self.bias
  16. def update(self, learning_rate):
  17. self.weights -= learning_rate * self.weights_grad
  18. self.bias -= learning_rate * self.bias_grad

我們對引數的初始化採用了常用的策略,即:權重隨機初始化為一個很小的值,而偏置項初始化為0。

Activator類實現了啟用函式,其中,forward方法實現了前向計算,而backward方法則是計算導數。比如,relu函式的實現如下:

  1. classReluActivator(object):
  2. def forward(self, weighted_input):
  3. #return weighted_input
  4. return max(0, weighted_input)
  5. def backward(self, output):
  6. return1if output >0else0

卷積層前向計算的實現

ConvLayer類的forward方法實現了卷積層的前向計算(即計算根據輸入來計算卷積層的輸出),下面是程式碼實現:

  1. def forward(self, input_array):
  2. '''
  3. 計算卷積層的輸出
  4. 輸出結果儲存在self.output_array
  5. '''
  6. self.input_array = input_array
  7. self.padded_input_array = padding(input_array,
  8. self.zero_padding)
  9. for f in range(self.filter_number):
  10. filter = self.filters[f]
  11. conv(self.padded_input_array,
  12. filter.get_weights(), self.output_array[f],
  13. self.stride, filter.get_bias())
  14. element_wise_op(self.output_array,
  15. self.activator.forward)

上面的程式碼裡面包含了幾個工具函式。element_wise_op函式實現了對numpy陣列進行按元素操作,並將返回值寫回到陣列中,程式碼如下:

  1. # 對numpy陣列進行element wise操作
  2. def element_wise_op(array, op):
  3. for i in np.nditer(array,
  4. op_flags=['readwrite']):
  5. i[...]= op(i)

conv函式實現了2維和3維陣列的卷積,程式碼如下:

  1. def conv(input_array,
  2. kernel_array,
  3. output_array,
  4. stride, bias):
  5. '''
  6. 計算卷積,自動適配輸入為2D和3D的情況
  7. '''
  8. channel_number = input_array.ndim
  9. output_width = output_array.shape[1]
  10. output_height = output_array.shape[0]
  11. kernel_width = kernel_array.shape[-1]
  12. kernel_height = kernel_array.shape[-2]
  13. for i in range(output_height):
  14. for j in range(output_width):
  15. output_array[i][j]=(
  16. get_patch(input_array, i, j, kernel_width,
  17. kernel_height, stride)* kernel_array
  18. ).sum()+ bias

padding函式實現了zero padding操作:

  1. # 為陣列增加Zero padding
  2. def padding(input_array, zp):
  3. '''
  4. 為陣列增加Zero padding,自動適配輸入為2D和3D的情況
  5. '''
  6. if zp ==0:
  7. return input_array
  8. else:
  9. if input_array.ndim ==3:
  10. input_width = input_array.shape[2]
  11. input_height = input_array.shape[1]
  12. input_depth = input_array.shape[0]
  13. padded_array = np.zeros((
  14. input_depth,
  15. input_height +2* zp,
  16. input_width +2* zp))
  17. padded_array[:,
  18. zp : zp + input_height,
  19. zp : zp + input_width]= input_array
  20. return padded_array
  21. elif input_array.ndim ==2:
  22. input_width = input_array.shape[1]
  23. input_height = input_array.shape[0]
  24. padded_array = np.zeros((
  25. input_height +2* zp,
  26. input_width +2* zp))
  27. padded_array[zp : zp + input_height,
  28. zp : zp + input_width]= input_array
  29. return padded_array

卷積層反向傳播演算法的實現

現在,是介紹卷積層核心演算法的時候了。我們知道反向傳播演算法需要完成幾個任務:

  1. 誤差項傳遞到上一層。
  2. 計算每個引數梯度
  3. 更新