Neural Networks and Deep Learning(神經網路與深度學習) - 學習筆記

Andrew.Hann發表於2017-04-23

catalogue

0. 引言
1. 感知器及啟用函式
2. 代價函式(loss function)
3. 用梯度下降法來學習-Learning with gradient descent
4. 用反向傳播調整神經網路中逐層所有神經元的超引數
5. 過擬合問題
6. IMPLEMENTING A NEURAL NETWORK FROM SCRATCH IN PYTHON – AN INTRODUCTION

 

0. 引言

0x1: 神經網路的分層神經元意味著什麼

為了解釋這個問題,我們先從一個我們熟悉的場景開始說起,電子電路的設計

如上圖所示,在實踐中,在解決線路設計問題(或者大多數其他演算法問題)時,我們通常先考慮如何解決子問題,然後逐步地整合這些子問題的解。換句話說,我們通過多層的抽象來獲得最終的解答,回到上圖的電路,我們可以看到,不論多麼複雜的電路功能,在最底層的底層,都是由最簡單的"與、或、非"門通過一定的邏輯關係組成

這就很自然地讓我麼聯想到深度神經網路的一張膾炙人口的架構圖

深度神經網路中間的隱層可以理解為是一種逐層抽象封裝的思想,這麼說可能並沒有嚴格的理論依據,但是卻十分符合我自己直覺上的理解,例如,如果我們在進行視覺模式識別

1. 第一層的神經元可能學會識別邊
2. 第二層的神經元可以在邊的基礎上學會識別更加複雜的形狀(例如三角形或者矩形)
3. 第三層能夠識別更加複雜的形狀
4. 以此類推,這些多層的抽象看起來能夠賦予深度網路一種學習解決複雜模式識別問題的能力

藉著這個話題,我們引申出一個很有趣的論點

用4個引數可以描繪出一個大象,如果給我5個引數,我甚至可以讓它卷鼻子

通過線性方程組來擬合一個資料集,本質是說世界上的所有的事物都可以用線性方程組來描繪

https://publications.mpi-cbg.de/Mayer_2010_4314.pdf

0x2: 神經網路的普遍性(神經網路可以計算任何函式)

神經網路的一個最顯著的事實就是它可以計算任何的函式,不管目標資料集對應的函式是什麼樣的,總會確保有一個神經網路能夠對任何可能的輸入x,其值f(x)或者某個足夠準確的近似是網路的輸出。這表明神經網路擁有一種"普遍性",普遍性是指,在原理上,神經網路可以做所有的事情

1. 兩個預先宣告

在討論普遍性定理成立之前,我們先定義下兩個預定宣告,即"神經網路可以計算任何函式"

1. 這句話不是說一個網路可以被用來準確地計算任何函式,而是說,我們可以獲得儘可能好的一個"近似"(即擬合),同時通過增加隱藏元的數量,我們可以提升近似的精度
2. 只要連續函式才可以被神經網路按照上面的方式去近似

總的來說,關於普遍性定理的表述應該是: 包含一個及以上隱藏層的神經網路可以被用來按照任意給定的精度來近似任何連續函式

2. 啟用函式輸出值在各個區間的累加和抵消

這樣寫標題可能有些奇怪,但這是我個人這個現象的理解,這個情況在我們高中數學中並不少見,即每個函式都有自己的定義域和值區間,當把兩個函式相加時需要同時考慮它們的定義域區間,最終得到"累加和抵消"後的函式結果,而我們網路中每個神經元對應的啟用函式輸出值都可以看作是一個函式,我們回到我們的sigmoid S型函式,把它的w權重設定為一個較大的值,想象一下它的函式曲線會是下面這個樣子(接近階躍函式)

可以看到,S型函式的階躍點為 s = -b / w: 和b成正比,和w成反比,在這種前提下,隱藏層的加權輸出(w1a1 + w2a2)就可以近似看成是一組階躍函式的輸出,那問題就好辦了

這些事情就變得很有趣的,由於b不同,導致階躍點不同,每個神經元的啟用值輸出函式的階躍點是錯開的,這就允許我們通過調整b(當然階躍點也受w的影響)來構造任意的突起。在注意第二點,這個突起的高度是誰決定的?答案也很明顯,它是由這一層的啟用值輸出乘上下一層的權重的累加值決定的。以上2點達成後,我們具備了一個能力: 可以在一個隱藏層中通過有效地調整w和b,來構造任意寬、任意高的"塔形凸起"

當繼續增加神經元的組合時,我們可以構造出更復雜的塔形突起

總的來說,通過改變權重和偏置,我們實際上是在設計這個擬合函式

Relevant Link:

http://neuralnetworksanddeeplearning.com/
http://neuralnetworksanddeeplearning.com/chap4.html
https://github.com/ty4z2008/Qix/blob/master/dl.md
http://neuralnetworksanddeeplearning.com/chap1.html
http://neuralnetworksanddeeplearning.com/

 

1. 感知器及啟用函式

為了更好的理解神經網路的"決策過程",我麼需要先了解啟用函式以及它的理論前身: 感知器,請注意我這裡用詞,決策過程,不管是多少層的神經網路,每一層/每一層上的每一個神經元都不不斷進行"決策",在深度神經網路中這通過啟用函式來支援(我們稍後再詳細討論啟用函式,現在只要知道啟用函式為這個決策提供了輸入)

0x1: 感知器

我們剛才提到決策這個概念,一個感知器接受幾個二進位制輸入x1、x2、x3...,併產生一個二進位制輸出

同時,對每個二進位制輸入(對應圖上左邊的3根箭頭)都定義了權重w1、w2、w3,表示相應輸入對於輸出(決策)重要性的實數

可以看到,這裡就包含了一個最簡單樸素的決策器思想,並且隨著權重w和閾值threshold的變化,你可以得到不同的"決策模型",我們把這些感知器組成一個層狀網路

這個感知器網路能做出一些很"微秒"的決策

1. 第一層感知器通過權衡輸入依據做出3個非常簡單的決定
2. 第二層感知器可以比第一層做出更復雜和抽象的決策
3. 第三層中的感知器甚至能進行更復雜的決策
4. 以這種方式,一個多層的感知器網路可以從事複雜巧妙的決策

再觀察一個細節,每一層的感知器都和上一層的所有感知器的輸入進行了"全連線",這意味著從第二層開始每一個層的所有感知器都是一個獨立的決策者。

我們把每一層的權重累加改寫為,這裡的w和x對應權重和輸入的向量,然後把閾值threshold移到不等式的另一邊,並用b = -threshold代替,用偏置而不是閾值,那麼感知器的決策規則可以重寫為

我麼可以把偏置看作一種表示讓感知器輸出1(或者用生物學的術語即"啟用感知器"),即輸入和權重和乘積累加加上這個偏置到達甚至超過這個感知器的"啟用點",讓它達到"啟用態"

0x2: S型神經元(sigmoid啟用函式)

感知器很好,它體現了一個多路輸入綜合決策的思想,但是我們仔細看一下感知器的函式圖,它是一個階躍函式,它最大的一個問題就是階躍點附近會產生巨大的翻轉,體現在感知器網路上就是單個感知器的許可權或者偏置的微小改動有時候會引起那個感知器的輸出的完全翻轉。這也很容易想象,因為在階躍點附近,感知器的輸出是瞬間從0->1或者1->0的,這本質是感知輸入決策是一種階躍函式,它不具備函式連續性,不具備連續性的函式自然也無法對輸入的微小改變做出相應的微小改變

為了解決非連續性的問題,我麼可以引入一個稱為S型神經元的人工神經元,S型神經元最大的特點就是輸入權重和偏置的微小改動只會引入輸出的微小變化,這對讓神經網路學習起來是很關鍵的

上面被稱為S型函式,這實際上也就是sigmoid啟用函式的單個形式,權重和輸入的乘積累加,和偏置的和的輸出是

可以看到,S型函式是一個連續函式

這個函式可以看成是感知器階躍函式平滑後的版本,Sigmoid啟用函式的平滑意味著權重和偏置的微小變化,會從神經元產生一個微小的輸出變化

0x3: tanh啟用函式(雙曲正切  hyperbolic tangent函式)

除了S型啟用函式之外,還有很多其他型別的啟用函式,tanch函式的輸入為,通過簡單的代數運算,我們可以得到: 。可以看出,tanh是S型函式的按比例變化版本

tanh在特徵相差明顯時的效果會很好,在迴圈過程中會不斷擴大特徵效果。與 sigmoid 的區別是,tanh 是 0 均值的,因此實際應用中 tanh 會比 sigmoid 更好

0x4: ReLu啟用函式(修正線性神經元 rectified linear neuron)

輸入為x,權重向量為w,偏置為b的ReLU神經元的輸出是: ,函式的形態是這樣的

ReLU 得到的 SGD 的收斂速度會比 sigmoid/tanh 快很多,因為它不存在在"在接近0或1時學習速率大幅下降"的問題

0x5: softmax啟用函式(柔性最大值)

softmax 的想法其實就是為神經網路定義一種新式的輸出層。開始時和 sigmoid 層一樣的,首先計算帶權輸入: ,不過,這裡我們不會使用 sigmoid 函式來獲得輸出。而是,會應用一種叫做 softmax 函式

分母是對所有的輸出神經元進行求和。該方程同樣保證輸出啟用值都是正數,因為指數函式是正的。將這兩點結合起來,我們看到 softmax 層的輸出是一些相加為 1 正數的集合。換言之,softmax 層的輸出可以被看做是一個概率分佈。這樣的效果很令人滿意。在很多問題中,將這些啟用值作為網路對於某個輸出正確的概率的估計非常方便。所以,比如在 MNIST 分類問題中,我們可以將 輸出值解釋成網路估計正確數字分類為 j 的概率

Relevant Link:

http://www.jianshu.com/p/22d9720dbf1a 

 

2. 代價函式(loss function)

我們已經瞭解了組成神經網路的基本單元S型神經單元,並且瞭解了它的基本組成架構

現在我們將注意力從單個神經元擴充套件到整個網路整體,從整體的角度來看待所有神經元對最後輸入"最終決策"的影響,網路中每一層的所有的權重和偏置在輸入的作用下,最終在輸出層得到一個輸出向量,這個時候問題來了,網路預測的輸出結果和我們預期的結果(label)一致嗎?它們差距了多少?以及是哪一層的哪一個神經元(或者多個神經元)的引數沒調整好導致了這種偏差(這個問題在之後的梯度下降會詳細解釋),為了量化這些偏差,我們需要為神經網路定義一個代價函式,代價函式有很多形式

下面只會簡單的介紹並給出對應代價函式的數學表示,而不是詳細討論,因為代價函式本身沒啥可以討論的,它的真正用途在於對各層神經元的w/b進行偏微分求導,從而得到該如何調整修正各個神經元w/b的最優化指導,這是一種被稱為梯度下降的技術

0x1: 二次代價函式(均方誤差 MSE)

y(x) - a是目標值和實際輸出值的差,可以看到,當對於所有的訓練輸入x,y(x)都接近於輸出a時(即都預測正確時),代價函式C(w, b)的值相當小,換句話說,如果我們的學習演算法能找到合適的權重和偏置,使得C(w, b) = 0,則該網路就是一個很好的網路,因此這就表明,我們訓練的目的,是最小化權重和偏置的代價函式,我們後面會說道將使用梯度下降來達到這個目的

1. 神經元飽和問題(僅限S型神經元這類啟用函式)

在開始探討這個問題前,先來解釋下什麼是神經元飽和,當然這裡依然需要下面將要講到的梯度下降的相關知識

1. 我們知道,迴圈迭代訓練神經網路的根本目的是尋找到一組w和b的向量,讓當前網路能儘量準確地近似我們的目標資料集
2. 而要達到這個目的,其中最關鍵的一個"反饋",最後一層輸出層的結果和目標值能夠計算出一個代價函式,根據這個代價函式對權重和偏置計算偏微分(求導),得到一個最佳下降方向
3. 知道第二步之前都沒問題,問題在於"二次代價函式+Sigmoid啟用函式"的組合,得到的偏導數和啟用函式本身的導致有關,這樣,啟用函式的曲線緩急就直接影響了代價函式偏導數的緩急,這樣是很不高效的,常常會遇到"神經元飽和"問題,即如果一個S型神經元的值接近1或者0,它認為自己已經接近優化完畢了,它會降低自己的啟用導致,讓自己的w和b趨於穩定,但是如果恰好這個w和b是隨機初始的錯誤值或者不小心進入了一個錯誤的調整,則很難再糾正這個錯誤,有點撞了南牆不回頭的意思

這麼說可能會很抽象,我們通過數學公式推導和視覺化函式影象來說明這點,首先,我們的二次代價函式方程如下

我們有,其中。使用鏈式法則來求權重和偏置的偏導數有

我們發現,公式中包含了這一項,仔細回憶下的函式影象

從這幅圖可以看出,當神經元的輸出接近1的時候(或者0),曲線變得相當平,所以就很小了,帶回上面的方程,則w和b的偏導數方程也就很小了,這就導致了神經元飽和時學習速率緩慢的原因。如果正確調整了倒還好,如果是因為初始化或者錯誤的調整導致進入了一個錯誤的方向,則要調整回來就變得很緩慢很困難

對著這個這題,我們再延伸出去思考一下,導致學習速率緩慢的罪魁禍首是S型神經元的這種曲線特性導致的是吧?那如果不用S型呢,用ReLU呢,是不是就不存在這種情況呢?答案是肯定的,至少不存在學習速率緩慢的問題(雖然ReLU也有自己的缺點)

所以嚴格來說,並不是二次代價函式MSE導致的神經元飽和,是二次代價函式和S型的組合存在神經元飽和的問題

2. 輸出層使用線性神經元時使用二次代價函式不存在學習速率慢的問題

如果我們輸出層的神經元都是線性神經元,而不再是S型函式的話,即使我麼繼續使用二次代價函式,最終關於權重和偏置的偏導數為

這表明如果輸出神經元是線性的那麼二次代價函式不再會導致學習速率下降的問題,在此情形下,二次代價函式就是一種合適的選擇

0x2: 交叉熵代價函式

有句話說得好: "失敗是成功之母",如果我們能及時定義自己犯得錯誤並及時改正,那麼我們的學習速度會變得很快,同樣的道理也適用在神經網路中,我們希望神經網路可以從錯誤中快速地學習。我們定義如下的代價函式

通過數學分析我們同樣可以看出

1. C > 0: 非負
2. 在實際輸出和目標輸出之間的差距越小,最終的交叉熵的值就越低

這其實就是我們想要的代價函式的特性

1. 交叉熵代價函式解決神經元飽和問題

同時它也避免了學習速度下降的問題,我們來看看它的偏導數情況,我們將代入前式的鏈式偏導數中

這個公式很有趣也很優美,它告訴我們權重學習的速度受到,也就是輸出中的誤差的控制,更大的誤差,更快的學習速率,這是一種非常符合我們直覺認識的一種現象,類似的,我們也可以計算出關於偏置的偏導數

這正是我們期待的當神經元開始出現嚴重錯誤時能以最快速度學習。事實上,如果在輸出神經元是S型神經元時,交叉熵一般都是更好的選擇

2. 交叉熵究竟表示什麼

交叉熵是一種源自資訊理論的解釋,它是"不確定性"的一種度量,交叉熵衡量我們學習到目標值y的正確值的平均起來的不確定性

1. 如果輸出我們期望的結果,不確定性就會小一些
2. 反之,不確定性就會大一些 

0x3: multiclass svm loss(hinge loss)

http://vision.stanford.edu/teaching/cs231n-demos/linear-classify/

Relevant Link:

https://hit-scir.gitbooks.io/neural-networks-and-deep-learning-zh_cn/content/chap3/c3s1.html

 

3. 用梯度下降法來學習-Learning with gradient descent

在上面討論清楚了代價函式的相關概念之後,我們接下來可以毫無障礙地繼續討論梯度下降的這個知識了。我們再次重申一下我們的目標,我們需要不斷調整w和b向量組,找到一組最佳的向量組,使之最終計算得到的代價函式C最小(C最小也就意味著最大化的擬合)

我們先從一個最簡單的情況切入話題,我們設計一個神經網路,它只有一個輸入、一個輸出,權重w = 1,偏置b = 2,代價函式為 C = || (a - y) ||

我們的輸入x  = 1,可以看到,神經元輸出的值為3,代價函式為3,我們的目標是讓C降為0(x是不變的),為此我們需要調整w和b的值,當讓這個例子太簡單了,我們知道怎麼做,但是如果這裡的神經元有多個呢,層數有多層呢,更重要的是,我們需要讓計算機自動完成這個過程。為此回答這個問題,我們需要引入梯度這個概念

1. 我們需要根據這裡的代價函式計算出對w和b的偏導數,這裡都是-1
2. 引入一個學習速率的概念,它表示一個學習調整的步長,設定為1
3. 接著我們每次把w和b分別進行: w = w - 1 * -1,b = b - 1 * -1
4. 很容易想象,在進行了2次學習後,此時w = -1,b = 1,此時代價函式C = 0,學習完成

這裡根據代價函式C求權重和偏置的偏導數,並根據學習速率,逐輪調整w和b的思想,就是梯度下降。同樣的問題推廣到二次函式也是類似的

0x1: 使用梯度下降來尋找C的全域性最小點

再次重申,我們訓練神經網路的目的是找到能最小化二次代價函式C(w, b)的權重和偏置,拋開所有細節不談,現在讓我們想象我們只要最小化一個給定的多元函式(各個神經元上的w和b就是它的變元): C(v),它可以是任意的多元實值函式,想象C是一個只有兩個變數v1和v2的函式

我們想要找到C的全域性最小值,一種解決這個問題的方式是用微積分來解析最小值,我們可以計算導數(甚至二階導數)去尋找C的極值點,這裡引入一個額外的話題: 隨機梯度下降(SGD)

1. 對於一次訓練來說,輸入的x是固定值,代表了所有樣本的輸入值
2. 梯度下降要做的是計算所有輸入值的"累加平均梯度"(這裡會有一個累加符號),並根據最終總的累加平均梯度來反饋到各層的w和b上,讓其作出相應調整
3. 問題就來了,當輸入樣本量十分巨大的時候,計算所有這些累加平均梯度是一件十分耗時的工作,所以為了改進這個問題,使用了隨機mini_batch技術,即從這批樣本中隨機選出一定數目的樣本作為代表,代表這批樣本進行計算累加平均梯度,計算得到的梯度再反饋給所有整體樣本,用這種方式提高運算效率
4. 假設我麼總共樣本是100,我們選的mini-batch = 10,則我們每次隨機選取10個樣本計算梯度,然後繼續重複10次,把所有樣本都輪一邊(當然隨機抽樣不能保證一定所有樣本都覆蓋到),這被稱為完成了一個"訓練迭代(epoch)"

我們把我們的代價函式C想象成一個山谷,我們當前的w/b想象成在山谷中的某個點,我們的梯度下降就是當前小球沿重力作用要滾落的方向,學習率可以看成是速度v,而每次w/b調整的的大小可以看成是位移

微積分告訴我們C將會有如下變化: ,進一步重寫為: 。這個表示式解釋了為什麼被稱為梯度向量,因為它把v的變化關聯為C的變化,整個方程讓我們看到了如何選取才能讓C下降: ,這裡的是個很小的正數(稱為學習速率)。至此,C的變數的變化形式我們得到了: 。然後我們用它再次更新規則計算下一次移動,如果我們反覆持續這樣做,我們將持續減小C直到獲得一個全域性最小值。總結一下

梯度下降演算法工作的方式就是重複計算梯度然後沿著相反的方向移動,沿著山谷"滾落"

從舉例中回到權重w和偏置的情況也是一樣的,我們用w和b代替變數v

0x2: 基於momentum的梯度下降

為了理解momentum技術,想想我麼關於梯度下降的原始圖片,其中我們研究了一個球滾向山谷的場景,我們稱之為梯度下降,momentum技術修改了梯度下降的兩處使之更加貼近於真實物理場景

1. 為我們想要優化的引數引入了一個稱為速度(velocity)的概念,梯度的作用就是改變速度(物理中力的作用是改變速度,而不是位移)
2. momentum方法引入了一種摩擦力的項,用來逐步減少速度

我們將梯度下降更新規則: 改為

我們來仔細看一看上面這個方程,它真正變化的量是v,每一輪計算新的v之前都要乘上一個因子u,然後減去學習率乘以代價函式差值(這個和普通梯度是一樣的),唯一的區別就在那個因子u

在上面方程中是用來控制阻礙或者摩擦力的量的超引數,為了理解這個引數的意義,我麼可以考慮一下

1. u = 1的時候,對應於沒有任何摩擦力,此時我們看到代價函式差值直接改變速度v,速度v隨後再控制w的變化率。直覺上想象一下,我們朝著梯度的方向不斷下降,由於v的關係,當我們到達谷底的時候,還會繼續越過去,這個時候如果梯度本該快速改變而沒有改變,我們會發現我們在錯誤的方向上移動太多了,雖然此時代價函式已經翻轉了,但是不能完全抵消v的影響
2. 前面提到,u可以控制系統中摩擦力的大小,更加準確的說,我們應該將"1 - u"看作是摩擦力的量
  1) 當u = 1沒有摩擦,速度完全由梯度導數決定
  2) 當u = 0就存在很大摩擦,速度無法疊加,上訴公式就退化成了普通的梯度下降公式

0x3: rmsprop

SGD的問題在於如果我們把learning rate設定的很小,則SGD會花費一個相當長的過程,為此,我們有很多對SGD的改進梯度下降方法,接下來逐一研究

The basic idea behind rmsprop is to adjust the learning rate per-parameteraccording to the a (smoothed) sum of the previous gradients. Intuitively this means that

1. frequently occurring features get a smaller learning rate (because the sum of their gradients is larger): 梯度越大,一次引數調整的size就要越小,這相當於速度很快了,時間s就要動態調整小,防止一次移動的距離s過大(步子邁的太大扯著蛋)
2. and rare features get a larger learning rate: 梯度越小,一個引數調整的size就要越大

The implementation of rmsprop is quite simple. For each parameter we keep a cache variable and during gradient descent we update the parameter and the cache as follows (example for W):

cacheW = decay * cacheW + (1 - decay) * dW ** 2
W = W - learning_rate * dW / np.sqrt(cacheW + 1e-6)

The decay is typically set to 0.9 or 0.95 and the 1e-6 term is added to avoid division by 0.

可以看到,RMSPROP的做法和momentum的梯度下降核心思想是一樣的,這是一種對不同的梯度進行動態補償調整的機制,目的是讓網路在大梯度情況下慢慢收斂,而在平緩梯度的時候儘快前進

0x4: (Nesterov) Momentum Method

0x5: AdaGrad

AdaGrad是RMSPROP的"old version",RMSPROP是在AdaGrad的基礎上改進而來的,我們來看看AdaGrad的定義

historical_grad += g^2
adjusted_grad = grad / (fudge_factor + sqrt(historical_grad))
w = w - master_stepsize*adjusted_grad

缺點是因為公式中分母上會累加梯度平方,這樣在訓練中持續增大的話,會使學習率非常小,甚至趨近無窮小

0x6: AdaDelta

0x7: Adam

Adam可以理解為momutum SGD和RMSPROP的綜合改進版本,同時引入了動態調整特性以及動量V特性。Adam(Adaptive Moment Estimation)本質上是帶有動量項的RMSprop,它利用梯度的一階矩估計和二階矩估計動態調整每個引數的學習率。Adam的優點主要在於經過偏置校正後,每一次迭代學習率都有個確定範圍,使得引數比較平穩。公式如下

Relevant Link:

http://www.wildml.com/2015/09/implementing-a-neural-network-from-scratch/
http://blog.csdn.net/yc461515457/article/details/50498266
https://tigerneil.gitbooks.io/neural-networks-and-deep-learning-zh/content/chapter3b.html
http://blog.csdn.net/u012759136/article/details/52302426

 

4. 用反向傳播調整神經網路中逐層所有神經元的超引數 

這個話題是針對整個神經網路的所有神經元而言的,是一個整體優化的話題,在開始討論這個話題前,我們先看一張多層神經網路的架構圖

我們設想一下,我們在當前超引數的前提下,根據一組輸入值x,得到了一個代價函式,接下來我們要怎麼把這種誤差傳遞給每一層的所有神經元呢,答案就是對所有神經元反向計算梯度,即要對每一層每一個神經元的w/b計算偏導數

這張圖實際上為了說明接下來要討論的"消失的梯度"的問題的,但是我認為它同樣表達出了反向傳播的核心思想,仔細看這個方程,我們會發現幾點

1. 越前面層的神經元相對於C的偏導數,可以通過後面層的神經元的偏導數推演而得,直觀上就像從最後一層反向傳播到了第一層一樣,故名反向傳播
2. C對逐層神經元的w/b的偏導數,隨著越往前,偏導數乘的因子越多

0x2: 反向傳播演算法標準化流程

仔細看這個演算法,我麼可以看到為何它被稱為反向傳播,我們從最後一層開始向後計算誤差向量,這種反向移動其實是代價函式是網路輸出的函式的結果。說動這裡引入一個題外話

1. 前饋網路: 輸入x通過一層一層的w/b逐步把影響傳遞到最後一層輸出層
2. 反向傳播: 這個時候輸入可以看作是代價函式C,通過鏈式求導反向逐層把C的誤差值傳遞給前面每一層的每一個神經元

這2點讓我影響深刻,充滿了哲學思想

0x3: 不穩定的梯度問題(梯度激增/消失)

我們從一個現象引出這個話題: 多層神經網路並不能顯著提高整體的精確度,在一個網路框架的基礎上,再增加新的一層神經元,網路的整體精確度並沒有顯著提升?這是為什麼呢?新增加的隱層沒有增加網路對抽象問題的決策能力嗎?

我們來看一個多層神經網路各個層的學習速率的變化曲線對比圖

我們可以發現,每層的神經元的學習速率都差了一個數量級,越前面層的神經元,獲得的學習速率越小,這種現象也被稱為"消失的梯度(vanishing gradient problem)",同時值得注意的是,這種情況也存在反例,即前面層的神經元學習速率比後面的大,即"激增的梯度問題(exploiding gradient problem)"。更一般的說,在深度神經網路中的梯度是不穩定的,在前面的層中會消失或激增

這種現象背後的數學原理是啥呢?我麼再次來看一下前面那張代價函式對各層神經元的偏導數

我們知道,越前面的神經元,代價函式C對w/b的偏導數的公式中,乘積因子越多,所以接下來問題就是這些多出來的乘積因子對結果產生什麼影響了呢?為了理解每個項的行為,先看看下面的sigmoid函式導數的影象

該導數在時達到最高值。現在,如果我們使用標準方法來初始化網路中的權重,那麼會使用一個均值為0標準差為1的高斯分佈,因此所有的權重會滿足 |wj|<1。有了這些資訊,我們發現會有 wjσ(zj)<1/4。並且在我們進行了所有這些項的乘積時,最終結果肯定會指數級下降:項越多,乘積的下降的越快。這就是消失的梯度問題的合理解釋。

Relevant Link:

https://tigerneil.gitbooks.io/neural-networks-and-deep-learning-zh/content/chapter5.html
http://blog.csdn.net/yc461515457/article/details/50499515

 

5. 過擬合問題

在科學領域有一種說法,一個擁有大量引數的模型能夠描述特別神奇的現象,但即使這樣的模型能夠很好地擬合已有的資料,但並不表示它就是一個好的模型,因為這可能只是模型中足夠的自由度使得它可以描述幾乎所有給定大小的資料集,而不需要真正洞察現象背後的本質。所以發生這種情形時,模型對已有的資料會表現的很好,但是對新的資料很難泛化。事實上,對一個模型真正的檢驗就是它對沒有見過的場景的"預測能力"

0x1: 過擬合(overfitting)/過度訓練(overtrainning)在神經網路訓練中表現出的幾種現象

1. 訓練集和測試集的準確率曲線沒有一致收斂

網路幾乎是在單純記憶訓練集合,而沒有對數字本質進行理解能夠泛化到測試資料集上

2. 訓練資料集準確度曲線在到達一定迭代次數後維持在一個數值周圍上線劇烈浮動

0x2: 如何規避過擬合問題

1. 在train_set/test_set的基礎上,增加validate_set驗證資料集判斷是否需要提前停止

我們使用validate_set來防止過度擬合,在每個迭代週期的最後都計算在validate_set上的分類準確度,一旦分類準確度已經飽和,就停止訓練,這個策略被稱為"提前停止"

在網路根據訓練資料集進行訓練的過程中,我們藉助validate_set來不斷驗證各種超引數,然後一旦獲得了想要的超引數,最終我們就使用test_set進行準確率測量,這給了我們在test_set上的結果是一個網路泛化能力真正的度量。換言之,你可以將驗證集看成一種特殊的訓練資料集能夠幫助我們學習好的超引數。這種尋找好的超引數的方法有時被稱為"hold out"方法(因為validate_set是從training_set中留出或者拿出一部分)

2. 增加訓練樣本的數量

思考一個最簡單的問題,我們有1000個超引數,但是訓練樣本只有100個,那麼平均下來就是10個超引數去擬合一個樣本,這給引數擬合帶來了很大的自由度,也就很容易造成過擬合。一般來說,最好的降低過擬合度的方式之一就是增加訓練樣本的量。有了足夠的訓練資料,就算是一個規模非常大的網路也不大容易過度擬合

3. 規範化

規範化能夠幫助我們解決過度擬合的問題,規範化有很多種方式,例如L1規範化、L2規範化,本小節我們重點討論L2規範化,他在實際的Tensorflow或者thoeno中用的最多

L2規範化,也叫權重衰減(weight decay),它的思想是增加一個額外的項到代價函式上,這個項叫做規範化項,下面就是規範化的交叉熵

注意到第二個項是新加入的所有權重的平方的和。然後使用一個因子 λ/2n 進行量化調整,其中 λ>0 可以成為 規範化引數,而 n 就是訓練集合的大小。當然,對其他的代價函式也可以進行規範化,例如二次代價函式。類似的規範化的形式如下

兩者都可以寫成這樣

直覺上看,規範化的效果是讓網路傾向於學習小一點的權重,換言之,規範化可以當作一種尋找小的權重和最小化原始代價函式之間的折中,這兩部分之前相對的重要性就由 λ 的值來控制了:λ 越小,就偏向於最小化原始代價函式,反之,傾向於小的權重。

思考一個問題,為什麼規範化能夠幫助減輕過度擬合?我們要明白,規範化只是一種術語說法,它的本質是通過改變原始代價函式的方程式,讓代價函式有的新的屬性,即誘導網路學習儘量小的權重,這背後的道理可以這麼理解:

小的權重在某種程度上,意味著更低的複雜性,也就對資料給出了一種更簡單卻更強大的解釋,因此應該優先選擇

讓我們從抗噪音干擾的角度來思考這個問題,假設神經網路的大多數引數有很小的權重,這最可能出現在規範化的網路中。更小的權重意味著網路的行為不會因為我們隨便改變一個輸入而改變太大。這會讓規範化網路學習區域性噪聲的影響更加困難。將它看作是一種讓單個的證據不會影響整個網路輸出太多的方式。相對的,規範化網路學習去對整個訓練集中經常出現的證據進行反應,對比看,大權重的網路可能會因為輸入的微小改變而產生比較大的行為改變

4. 棄權(Dropout)技術

棄權是一種相當激進的技術,和L1/L2規範化不同,棄權技術並不依賴對代價函式的修改,而是在棄權中,我們改變了網路本身,假設我們嘗試訓練一個網路

特別地,假設我們有一個訓練資料 x 和 對應的目標輸出 y。通常我們會通過在網路中前向傳播 x ,然後進行反向傳播來確定對梯度的共現。使用 dropout,這個過程就改了。我們會從隨機(臨時)地刪除網路中的一半的神經元開始,讓輸入層和輸出層的神經元保持不變。在此之後,我們會得到最終的網路。注意那些被 dropout 的神經元,即那些臨時性刪除的神經元,用虛圈表示在途中

我們前向傳播輸入,通過修改後的網路,然後反向傳播結果,同樣通過這個修改後的網路。在 minibatch 的若干樣本上進行這些步驟後,我們對那些權重和偏差進行更新。然後重複這個過程,首先重置 dropout 的神經元,然後選擇新的隨機隱藏元的子集進行刪除,估計對一個不同的minibatch的梯度,然後更新權重和偏差

為了解釋所發生的事,我希望你停下來想一下沒有 dropout 的訓練方式。特別地,想象一下我們訓練幾個不同的神經網路,使用的同一個訓練資料。當然,網路可能不是從同一初始狀態開始的,最終的結果也會有一些差異。出現這種情況時,我們可以使用一些平均或者投票的方式來確定接受哪個輸出。例如,如果我們訓練了五個網路,其中三個被分類當做是 3,那很可能它就是 3。另外兩個可能就犯了錯誤。這種平均的方式通常是一種強大(儘管代價昂貴)的方式來減輕過匹配。原因在於不同的網路可能會以不同的方式過匹配,平均法可能會幫助我們消除那樣的過匹配。

那麼這和 dropout 有什麼關係呢?啟發式地看,當我們丟掉不同的神經元集合時,有點像我們在訓練不同的神經網路。所以,dropout 過程就如同大量不同網路的效果的平均那樣。不同的網路以不同的方式過匹配了,所以,dropout 的網路會減輕過匹配。

一個相關的啟發式解釋在早期使用這項技術的論文中曾經給出

因為神經元不能依賴其他神經元特定的存在,這個技術其實減少了複雜的互適應的神經元。所以,強制要學習那些在神經元的不同隨機子集中更加健壯的特徵

換言之,如果我們就愛那個神經網路看做一個進行預測的模型的話,我們就可以將 dropout 看做是一種確保模型對於證據丟失健壯的方式。這樣看來,dropout 和 L1、L2 規範化也是有相似之處的,這也傾向於更小的權重,最後讓網路對丟失個體連線的場景更加健壯

5. 人為擴充套件訓練資料

我們前面說過,減少過擬合的一個最好的手段就是增加訓練樣本量,但這在很多場景是很難做到的,在影象識別領域,為了彌補樣本量不夠的問題,人們想出了一種擴大訓練樣本量的方法,即資料擴充套件,例如

1. 影象翻轉
2. 影象平移
3. 模擬人手肌肉的影象線條抖動
..

Relevant Link:

https://tigerneil.gitbooks.io/neural-networks-and-deep-learning-zh/content/chapter3a.html

 

6. IMPLEMENTING A NEURAL NETWORK FROM SCRATCH IN PYTHON – AN INTRODUCTION

0x1: LOGISTIC REGRESSION(使用邏輯迴歸分類器分類兩類圓環點集)

import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt


def generate_data():
    np.random.seed(0)
    X, y = datasets.make_moons(200, noise=0.20)
    return X, y


def visualize(X, y, clf):
    # plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral)
    # plt.show()
    plot_decision_boundary(lambda x: clf.predict(x), X, y)
    plt.title("Logistic Regression")


def plot_decision_boundary(pred_func, X, y):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
    plt.show()


def classify(X, y):
    clf = linear_model.LogisticRegressionCV()
    clf.fit(X, y)
    return clf


def main():
    X, y = generate_data()
    # visualize(X, y)
    clf = classify(X, y)
    visualize(X, y, clf)


if __name__ == "__main__":
    main()

可以看到,邏輯迴歸儘可能地忽略噪音(從直線擬合的角度看的噪音),用一條直線對資料集進行了分類,但是很明顯,邏輯迴歸沒有"理解"資料背後真正的"含義",沒有把圓環給分類出來

0x2: TRAINING A NEURAL NETWORK

Let’s now build a 3-layer neural network with one input layer, one hidden layer, and one output layer. The number of nodes in the input layer is determined by the dimensionality of our data, 2. Similarly, the number of nodes in the output layer is determined by the number of classes we have, also 2. (Because we only have 2 classes we could actually get away with only one output node predicting 0 or 1, but having 2 makes it easier to extend the network to more classes later on). The input to the network will be x- and y- coordinates and its output will be two probabilities, one for class 0 (“female”) and one for class 1 (“male”). It looks something like this:

1. HOW OUR NETWORK MAKES PREDICTIONS

Our network makes predictions using forward propagation, which is just a bunch of matrix multiplications and the application of the activation function(s) we defined above. If x is the 2-dimensional input to our network then we calculate our prediction \hat{y} (also two-dimensional) as follows:

2. LEARNING THE PARAMETERS

Learning the parameters for our network means finding parameters (W_1, b_1, W_2, b_2) that minimize the error on our training data. But how do we define the error? We call the function that measures our error the loss function. A common choice with the softmax output is the categorical cross-entropy loss (also known as negative log likelihood). If we have N training examples and C classes then the loss for our prediction \hat{y} with respect to the true labels y is given by:

We can use gradient descent to find the minimum and I will implement the most vanilla version of gradient descent, also called batch gradient descent with a fixed learning rate. Variations such as SGD (stochastic gradient descent) or minibatch gradient descent typically perform better in practice. So if you are serious you’ll want to use one of these, and ideally you would also decay the learning rate over time.

3. IMPLEMENTATION

import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt


class Config:
    nn_input_dim = 2  # input layer dimensionality
    nn_output_dim = 2  # output layer dimensionality
    # Gradient descent parameters (I picked these by hand)
    epsilon = 0.01  # learning rate for gradient descent
    reg_lambda = 0.01  # regularization strength


def generate_data():
    np.random.seed(0)
    X, y = datasets.make_moons(200, noise=0.20)
    return X, y


def visualize(X, y, model):
    # plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral)
    # plt.show()
    plot_decision_boundary(lambda x:predict(model,x), X, y)
    plt.title("Logistic Regression")


def plot_decision_boundary(pred_func, X, y):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
    plt.show()


# Helper function to evaluate the total loss on the dataset
def calculate_loss(model, X, y):
    num_examples = len(X)  # training set size
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # Forward propagation to calculate our predictions
    z1 = X.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    # 得到實際預測值,用於和目標值計算代價函式
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    # Calculating the loss(交叉熵)
    corect_logprobs = -np.log(probs[range(num_examples), y])
    # 針對每一個輸入樣本都要計算一個代價函式,C = 總的代價累加結果的平均值
    data_loss = np.sum(corect_logprobs)
    # Add regulatization term to loss (optional)
    data_loss += Config.reg_lambda / 2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    # 除以樣本數,得到平均代價函式值
    return 1. / num_examples * data_loss


def predict(model, x):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # Forward propagation
    z1 = x.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    # 根據當前網路的w/b向量組,根據啟用函式softmax得到一組預測值輸出向量
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    # 因為softmax輸出中各項和為1,所以其中值最大的那個代表了該網路的預測項
    return np.argmax(probs, axis=1)


# This function learns parameters for the neural network and returns the model.
# - nn_hdim: Number of nodes in the hidden layer
# - num_passes: Number of passes through the training data for gradient descent
# - print_loss: If True, print the loss every 1000 iterations
def build_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
    # Initialize the parameters to random values. We need to learn these.
    num_examples = len(X)
    np.random.seed(0)
    W1 = np.random.randn(Config.nn_input_dim, nn_hdim) / np.sqrt(Config.nn_input_dim)
    b1 = np.zeros((1, nn_hdim))
    W2 = np.random.randn(nn_hdim, Config.nn_output_dim) / np.sqrt(nn_hdim)
    b2 = np.zeros((1, Config.nn_output_dim))

    # This is what we return at the end
    model = {}

    # Gradient descent. For each batch...
    for i in range(0, num_passes):

        # Forward propagation
        z1 = X.dot(W1) + b1
        a1 = np.tanh(z1)
        z2 = a1.dot(W2) + b2
        exp_scores = np.exp(z2)
        # 計算softmax
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

        # Backpropagation
        delta3 = probs
        delta3[range(num_examples), y] -= 1
        dW2 = (a1.T).dot(delta3)
        db2 = np.sum(delta3, axis=0, keepdims=True)
        delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
        dW1 = np.dot(X.T, delta2)
        db1 = np.sum(delta2, axis=0)

        # Add regularization terms (b1 and b2 don't have regularization terms)
        dW2 += Config.reg_lambda * W2
        dW1 += Config.reg_lambda * W1

        # Gradient descent parameter update
        W1 += -Config.epsilon * dW1
        b1 += -Config.epsilon * db1
        W2 += -Config.epsilon * dW2
        b2 += -Config.epsilon * db2

        # Assign new parameters to the model
        model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

        # Optionally print the loss.
        # This is expensive because it uses the whole dataset, so we don't want to do it too often.
        if print_loss and i % 1000 == 0:
            print("Loss after iteration %i: %f" % (i, calculate_loss(model, X, y)))

    return model


def classify(X, y):
    # clf = linear_model.LogisticRegressionCV()
    # clf.fit(X, y)
    # return clf

    pass


def main():
    X, y = generate_data()
    model = build_model(X, y, 3, print_loss=True)
    visualize(X, y, model)


if __name__ == "__main__":
    main()

Relevant Link:

http://www.wildml.com/2015/09/implementing-a-neural-network-from-scratch/
https://github.com/dennybritz/nn-from-scratch

Copyright (c) 2017 LittleHann All rights reserved

相關文章