[C1W3] Neural Networks and Deep Learning - Shallow neural networks
第三周:淺層神經網絡(Shallow neural networks)
神經網絡概述(Neural Network Overview)
神經網絡的表示(Neural Network Representation)
神經網絡中,我們將使用上標方括號的形式表示這些值來自于哪一層,有趣的是在約定俗成的符號傳統中,在這里你所看到的這個例子,只能叫做一個兩層的神經網絡。原因是輸入層是不算入總層數內,所以隱藏層是第一層,輸出層是第二層。第二個慣例是我們將輸入層稱為第零層,所以在技術上,這仍然是一個三層的神經網絡,因為這里有輸入層、隱藏層,還有輸出層。但是在傳統的符號使用中,如果你閱讀研究論文或者在這門課中,你會看到人們將這個神經網絡稱為一個兩層的神經網絡,因為我們不將輸入層看作一個標準的層。
計算一個神經網絡的輸出(Computing a Neural Network's output)
單個樣本時,向量化圖示
單個樣本時,運用四行代碼計算出一個簡單神經網絡的輸出(預測)。
多樣本向量化(Vectorizing across multiple examples)
上一節使用的是單個樣本,如果把單個樣本的向量橫向堆疊成矩陣,就可以計算全部樣本的神經網絡輸出(預測)。
向量化實現的解釋(Justification for vectorized implementation)
激活函數(Activation functions)
Pros and cons of activation functions
結果表明,如果在隱藏層上使用 tanh (雙曲正切)函數效果總是優于 sigmoid 函數。因為函數值域在 -1 和 +1 的激活函數,其均值是更接近 0 均值的,而不是0.5,這會使下一層學習簡單一點。
在討論優化算法時,有一點要說明:我基本已經不用 sigmoid 激活函數了,tanh 函數在所有場合都優于 sigmoid 函數。
但有一個例外:在二分類的問題中,對于輸出層,想讓的數值介于 0 和 1 之間,而不是在 -1 和 +1 之間,所以需要使用 sigmoid 激活函數。
sigmoid 函數和 tanh 函數兩者共同的缺點是,在 \(Z\) 特別大或者特別小的情況下,導數的梯度或者函數的斜率會變得特別小,最后就會接近于0,導致降低梯度下降的速度。
在機器學習另一個很流行的函數是:修正線性單元的函數(ReLu)。只要 \(Z\) 是正值的情況下,導數恒等于1,當 \(Z\) 是負值的時候,導數恒等于 0。從實際上來說,當使用 \(Z\) 的導數時,\(Z=0\) 的導數是沒有定義的。但是當編程實現的時候,\(Z\) 的取值剛好是 0.0000000000 的概率很低,所以不用擔心這個。你也可以在 \(Z=0\) 時 ,給它的導數賦值 0 或 1 都可以。
這有一些選擇激活函數的經驗法則:
如果輸出是0、1值(二分類問題),則輸出層選擇 sigmoid 函數,然后其它的所有單元都選擇 Relu 函數。這是很多激活函數的默認選擇,如果在隱藏層上不確定使用哪個激活函數,那么通常會使用 Relu 激活函數。有時,也會使用 tanh 激活函數,Relu 的一個缺點是:當 \(Z\) 是負值的時候,導數等于0,在實踐中這沒什么問題。
但 Relu 還有另外一個版本叫 Leaky Relu。當 \(Z\) 是負值時,這個函數的導數不是為 0,而是有一個平緩的斜率。這個函數通常比 Relu 激活函數效果要好,盡管在實際中 Leaky ReLu 使用的并不多。這些選一個就好了,我通常只用 Relu。
Relu 和 Leaky Relu 的好處在于,for a lot of the space of \(Z\),激活函數的導數和 0 差很遠,所以在實踐中使用 Relu 激活函數,你的神經網絡學習速度通常會快很多。原因是 Relu 沒有這種函數斜率接近 0 時,減慢學習速度的效應。我知道對于 \(Z\) 的一半范圍來說,Relu 的斜率為 0,但在實踐中,有足夠多的隱藏單元令 \(Z\) 大于 0,所以對于大多數訓練樣本來說還是很快的。
在選擇自己神經網絡的激活函數時,有一定的直觀感受,在深度學習中的經常遇到一個問題:在編寫神經網絡的時候,會有很多選擇:隱藏層單元的個數、激活函數的選擇、初始化權值……這些選擇想得到一個對比較好的指導原則是挺困難的。
鑒于以上三個原因,以及在工業界的見聞,提供一種直觀的感受,哪一種工業界用的多,哪一種用的少。但是,自己的神經網絡的應用,以及其特殊性,是很難提前知道選擇哪些效果更好。所以通常的建議是:如果不確定哪一個激活函數效果更好,可以把它們都試試,然后在交叉驗證集或者在我們稍后會講到的開發集上跑跑,看看哪一個表現好,就用哪個。在你的應用中自己多嘗試下不同的選擇,很可能你會搭建出具有前瞻性的神經網絡架構,可以對你的問題的特質更有針對性,讓你的算法迭代更流暢。這里不會告訴你一定要使用 Relu 激活函數,而不用其他的,這對你現在或未來要處理的問題而言,可能管用,也可能不管用。
為什么需要非線性激活函數?(why need a nonlinear activation function?)
如果你使用線性激活函數或者叫恒等激勵函數,那么神經網絡只是把輸入線性組合再輸出。
如果你使用線性激活函數或者沒有使用一個激活函數,那么無論你的神經網絡有多少層一直在做的只是計算線性函數,所以不如直接去掉全部隱藏層。
事實證明如果你在隱藏層用線性激活函數,在輸出層用 sigmoid 函數,那么這個模型的復雜度和沒有任何隱藏層的標準Logistic回歸是一樣的。
除非你引入非線性,否則你無法計算更有趣的函數,即使你的網絡層數再多也不行;只有一個地方可以使用線性激活函數,就是你在做機器學習中的回歸問題。舉個例子,\(y\) 是一個實數,比如你想預測房地產價格, 就不是二分類任務 0 或 1,而是一個實數,從 0 到正無窮。如果 \(y\) 是個實數,那么在輸出層用線性激活函數也許可行,你的輸出 \(y\) 也是一個實數,從負無窮到正無窮。而且,此時隱藏層也不能用線性激活函數,可以用 ReLU 或者 tanh 或者 leaky ReLU 或者其他的非線性激活函數,唯一可以用線性激活函數的通常就是輸出層;除了這種情況,會在隱層用線性函數的,還有一些特殊情況,比如與壓縮有關的,不在這里深入討論。另外,因為房價都是非負數,所以我們也可以在輸出層使用 ReLU 函數,這樣你的 \(\hat{y}\) 都大于等于 0。
激活函數的導數(Derivatives of activation functions)
sigmoid 導數為 \(a(1-a)\),當 a 已知的時候 ,可以很方便計算出其導數。
tanh 導數為 \(1-a^2\),當 a 已知的時候 ,可以很方便計算出其導數。
注:
- ReLu 導數為 0 或 1,通常在 \(Z = 0\) 的時候導數 undefined,直接將導數賦值 0 或 1;當然 \(Z = 0.000000000\) 的概率很低。
- Leaky ReLu 導數為 0.01 或 1,通常在 \(Z = 0\) 的時候導數 undefined,直接將導數賦值 0.01 或 1;當然 \(Z = 0.000000000\) 的概率很低。
神經網絡的梯度下降(Gradient descent for neural networks)
這里 np.sum 是 python 的 numpy 命令,axis=1 表示水平相加求和,keepdims = True 是防止 python 輸出秩為 1 的數組,如果不使用 keepdims,可以可以使用 reshape 顯性指明數組的維度。
圖中唯一的一個乘號是元素相乘。
以上就是正向傳播的 4 個方程和反向傳播的 6 個方程,這里我是直接給出的,在下一節,我會講如何導出反向傳播的這 6 個式子的。如果你要實現這些算法,你必須正確執行正向和反向傳播運算,你必須能計算所有需要的導數,用梯度下降來學習神經網絡的參數;
(選修)直觀理解反向傳播(Backpropagation intuition)
\(J\) 中第一個求和,是最后一層所有激活項所有元素整體求和,最后是一個常數。第二個求和是所有層中,所有 \(W\) 中的所有元素求和,最后也是一個常數,所以 \(J\) 最后也是一個常數,shape 為 (1, 1)。
紫色為正則化項,因為 \(J\) 中正則化項包含 \(W\),所以使用鏈式求導法則求到 \(W\) 時,即需要對 \(W\) 求全導數時,還需要把 \(J\) 中的正則化項(因為包含 \(W\))帶過來。求導參考連接,其他時候該項導數為 0,可以忽略。
\(L\) 表示為神經網絡的層數,在計算神經網絡層數時,\(X\)(即:\(A^{[0]}\)),即:輸入層,不被算入在內,或者也可以把它稱作神經網絡的第 0 層。所以圖中演示的神經網絡有 2 層,所以 \(L = 2\).
\(n^{[l]}\) 表示第 \(l\) 層的激活單元數,本例中 \(n^{[0]} = 2\)(輸入層樣本有兩個特征), \(n^{[1]} = 4\)(隱藏層有 4 個激活單元), \(n^{[2]} = 1\)(最后一層,即輸出層有 1 個激活單元)。因為 \(n^{[2]}\) 是最后一層,所以 \(n^{[L]}=n^{[2]}\)。
\(m\) 表示樣本數量,圖中示例有 200 個樣本,所以 \(m = 200\),圖中涉及 \(m\) 的地方均用紅色字體高亮顯示,方便比對查看。另外,在計算 \(dW\) 和 \(db\) 時,最后需要除以一個 \(m\),這是因為 \(J\) 中也帶有一個 \(\frac{1}{m}\) 項,所以當鏈式求導求導終點的時候,需要把它帶過來。此處求導終點的意思是 \(dW\) 和 \(db\) 里面沒有復合函數存在了,所以是求導的重點。對比理解當求導求到 \(dA\) 時,它仍是一個復合函數,里面還包含其他函數,所以不是求導終點。它繼續求導,可以求出下一層的 \(dZ\),再繼續可以求出下一層的 \(dW\) 和 \(db\),到此又是一個求導的終點。
關于綠色的 \(dW\) 和 \(db\),也即梯度。\(dW\) 中存在矩陣乘法,內含求和操作,向量化的方式計算高效,代碼簡潔。但是 \(db\) 在計算時沒有矩陣乘法,也就是沒有求和操作,所以需要用其他方法來完成,圖中在計算 \(db^{[1]}\) 和 \(db^{[2]}\) 時分別記錄了兩種方式來對 \(db\) 按行求和。np.sum(\(dZ^{[1]}\), axis=1, keepdims=True) 是調用 numpy 中的 sum 方法來求和,axis = 1 表示對每行進行求和,keepdims 表示求和后保留其維度特性。另外一種求和方式是初始化一個全部為 1 的矩陣,然后讓 \(dZ\) 與其進行矩陣相乘,言外之意就是做求和的操作,異曲同工。
關于褐紅色 \(dZ\),可參考多元復合函數的鏈式求導法則,其間均為點號相連,表示相乘。式中的第二個等號是其向量化的表示,可以看到其中既有矩陣乘,也有元素乘(*),因為是向量化表示,所以需要合理的組織和選擇向量的前后位置,是否需要轉置,使用矩陣乘還是元素乘等等,使之達到正確計算第一個等號列出的鏈式求導公式的目的。可以看到圖中反向傳播求導時都是直接求到 \(dZ\),跨過了 \(dA\),因為每一層的激活單元可能不同,所以 \(dA\) 的導數都是使用 \({g^{[l]}}^{'}(Z^{[l]})\) 來代替,以不變應萬變。另外,\(dZ^{[l]}\) 可以推導出它上一層的 \(dZ^{[l-1]}\),而每一層的 \(dZ\) 又可以直接推到出該層的 \(dW\) 和 \(db\)。所以在編寫程序時,直接計算出并緩存各個層的 \(dZ^{[l]}\) 是一個非常好的設計,既可以計算本層的 \(dW\) 和 \(db\),也可以計算上一層的 \(dZ\)。它就相當于一個計算梯度的中介和橋梁。
關于圖中垂直的紅色虛線,被它貫穿的向量維度均一致。如此方便比對神經網絡在計算時各個步驟間向量呈現出的維度特性和變化規律。比如:
\(A^{[l]}.shape=(n^{[l]}, m)\)
\(Z^{[l]}.shape=dZ^{[l]}.shape=(n^{[l]}, m)\)
\(W^{[l]}.shape=dW^{[l]}.shape=(n^{[l]}, n^{[l-1]})\)
\(b^{[l]}.shape=db^{[l]}.shape=(n^{[l]}, 1)\)
\(J.shape=(1, 1)\)
如此可以更合理和高效的使用 Python 的 assert :
assert (\(A^{[l]}\).shape == (\(n^{[l]}\), m));
assert (\(Z^{[l]}\).shape == \(dZ^{[l]}\).shape == (\(n^{[l]}\), m));
assert (\(W^{[l]}\).shape == \(dW^{[l]}\).shape == (\(n^{[l]}\), \(n^{[l-1]}\)));
assert (\(b^{[l]}\).shape == \(db^{[l]}\).shape == (\(n^{[l]}\), 1));
assert (J.shape == (1, 1));
隨機初始化(Random+Initialization)
如果把 \(W\) 全部初始化為 0,意味著每個隱藏層中所有激活單元的權重全部相同,輸出也全部相同,當梯度下降算法計算出更新 \(W\) 的梯度之后,梯度也完全相同。如此的隱藏層毫無意義,無法自動的捕捉到一些有意思的特征,所以在初始化 \(W\) 的時候需要打破這種對稱性(symmetry breaking problem),令外 \(b\) 在初始化時可以全部初始化為 0,因為 \(W\) 已經打破對稱,整個激活單元數出也就打破對稱了。
如何初始化?
在吳恩達老師機器學習課程中,初始化神經網絡參數的方法是讓 \(W\) 為一個在 \([-\epsilon, \epsilon]\) 之間的隨機數。
epsilon_init = 0.12;
np.random.rand(\(s_l\), \(s_{l-1}\)) * 2 * epsilon_init - epsilon_init
在吳恩達老師本門課程(深度學習)中,使用如下方法:
\(W^{[1]}\)=np.random.randn(\(s_l\), \(s_{l-1})\) * 0.01 (生成高斯分布)
\(b^{[1]}\) = np.zeros((\(s_l\), 1))
之所以 * 0.01,而不是 100 或 1000 ...是因為通常我們傾向于將 \(W\) 初始化為一個很小的隨機數。試想,如果 \(W\) 很大, 導致 \(Z\) 過大或過小都會令 tanh 或者 sigmoid 激活函數的梯度處于平坦接近 0 的地方,從而導致龜速梯度下降。如果沒有 sigmoid / tanh 激活函數在整個的神經網絡里,就不成問題。但如果你做二分類并且你的輸出單元是 Sigmoid 函數,那么你是不會希望初始參數太大的,所以乘上 0.01 或者其他一些很小的數都是合理的嘗試。事實上有時候也有比 0.01 更好的常數,當你訓練一個只有一層隱藏層的網絡時(這是相對淺的神經網絡,沒有太多的隱藏層),設為 0.01 或許還可以。但當你訓練一個非常非常深的神經網絡時,可能需要試試 0.01 以外的常數。下面課程中我們會討論 how & when 去選擇一個不同于 0.01 的常數,但是無論如何它通常都會是個相對很小的數。
轉載于:https://www.cnblogs.com/keyshaw/p/11160359.html
總結
以上是生活随笔為你收集整理的[C1W3] Neural Networks and Deep Learning - Shallow neural networks的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 整数中1出现的次数(从1到n整数中1出现
- 下一篇: EF 如何更新多对多关系的实体