cataloguephp
0. 引言 1. 感知器及激活函數 2. 代價函數(loss function) 3. 用梯度降低法來學習-Learning with gradient descent 4. 用反向傳播調整神經網絡中逐層全部神經元的超參數 5. 過擬合問題 6. IMPLEMENTING A NEURAL NETWORK FROM SCRATCH IN PYTHON – AN INTRODUCTION
0. 引言html
0x1: 神經網絡的分層神經元意味着什麼node
爲了解釋這個問題,咱們先從一個咱們熟悉的場景開始提及,電子電路的設計git
如上圖所示,在實踐中,在解決線路設計問題(或者大多數其餘算法問題)時,咱們一般先考慮如何解決子問題,而後逐步地集成這些子問題的解。換句話說,咱們經過多層的抽象來得到最終的解答,回到上圖的電路,咱們能夠看到,不論多麼複雜的電路功能,在最底層的底層,都是由最簡單的"與、或、非"門經過必定的邏輯關係組成github
這就很天然地讓我麼聯想到深度神經網絡的一張膾炙人口的架構圖算法
深度神經網絡中間的隱層能夠理解爲是一種逐層抽象封裝的思想,這麼說可能並無嚴格的理論依據,可是卻十分符合我本身直覺上的理解,例如,若是咱們在進行視覺模式識別網絡
1. 第一層的神經元可能學會識別邊 2. 第二層的神經元能夠在邊的基礎上學會識別更加複雜的形狀(例如三角形或者矩形) 3. 第三層可以識別更加複雜的形狀 4. 以此類推,這些多層的抽象看起來可以賦予深度網絡一種學習解決複雜模式識別問題的能力
藉着這個話題,咱們引伸出一個頗有趣的論點架構
用4個參數能夠描繪出一個大象,若是給我5個參數,我甚至可讓它卷鼻子
經過線性方程組來擬合一個數據集,本質是說世界上的全部的事物均可以用線性方程組來描繪app
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: 感知器
咱們剛纔提到決策這個概念,一個感知器接受幾個二進制輸入x一、x二、x3...,併產生一個二進制輸出
同時,對每一個二進制輸入(對應圖上左邊的3根箭頭)都定義了權重w一、w二、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 ):
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 和 L一、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 (also two-dimensional) as follows:
2. LEARNING THE PARAMETERS
Learning the parameters for our network means finding parameters () 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
training examples and
classes then the loss for our prediction
with respect to the true labels
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