如何更好地理解神經網路的正向傳播?我們需要從「矩陣乘法」入手
GIF/1.7M
圖:pixabay
原文來源:medium
作者:Matt Ross
「機器人圈」編譯:嗯~阿童木呀、多啦A亮
介紹
我為什麼要寫這篇文章呢?主要是因為我在構建神經網路的過程中遇到了一個令人沮喪的bug,最終迫使我進入該系統,並且真正了解了神經網路的核心的線性代數。我發現我已經做得很好了,而這隻需要確保兩個相乘矩陣的內部維度相匹配,而當發生bug時,我只是將各自將矩陣轉置於不同的位置,直到事情解決。但是其中有一個隱藏的事實,那就是我並沒有真正了解矩陣乘法運算的每一步。
我們將通過遍歷正向傳播的每一個步驟來計算一個相當簡單的神經網路的成本函數。當然,如果你想知道我的矩陣乘法那些由於疏忽所引起的錯誤,那就是我把偏差單元(1的向量)加在一個列上,而它的正確位置應該是在一個行上。我這樣做是因為在這一步驟之前,我並沒有真正了解矩陣乘法的全部輸出,所以沒有意識到我必須做出改變。首先,我將介紹一個在神經網路正向傳播中所發生事情的高級背景原因,然後我們將仔細研究一個特定的範例,並使用指數和代碼讓事情更清晰。
因此,神經網路對複雜關係進行建模是不可思議的。我們即將談論的只是網路的前饋傳播部分。現在,神經網路的輸入單元可以是任何東西。例如,它們可以是表示一堆手寫數字的20像素×20像素圖像的灰度強度(介於0和1之間)。在這種情況下,你將擁有400個輸入單元。現在我們有2個輸入單元,加上我們的+1偏差單元(為什麼要有偏差單元?答案在這裡https://www.quora.com/What-is-bias-in-artificial-neural-network)。正向傳播本質上是從一個例子(例如手寫數字的那些圖像中的一個)中獲取每個輸入,然後將輸入值乘以單元/節點之間的每個連接的權重(見圖5),然後將所有連接的所有乘積相加到你正在計算激活的節點中,然後將獲取總和(z),這一點可通過sigmoid函數實現(見下文)。
圖1 Sigmoid函數
因此你就會獲得隱藏層的每一個單元的激活。然後,你可以使用相同的方法來計算下一層,但是這次你將使用隱藏層的激活作為「輸入」值。你將所有a^2激活(即隱藏層)單元乘以第二組權重Theta2,將連接到單個最終輸出單元的每個乘積相加,並將該乘積通過Sigmoid函數加以計算,以獲得最終的輸出激活a^3。g(z)是Sigmoid函數,z是x輸入(或隱藏層中的激活)和權重θ(由圖5中的正常神經網路圖中的單個箭頭表示)的乘積。
圖2 應用Sigmoid函數的假設函數
一旦你擁有這一切,你想計算網路的成本(圖4)。你的成本函數本質上將計算出給出範例的輸出假設h(x)和實際y值之間的成本/差異。所以,在我繼續使用的範例中,y是由輸入所表示的實際數字的值。如果在網路中有一個「4」饋送圖像,則y就是值「4」。由於存在多個輸出單元,所以成本函數將h(x)與輸出相比較,相對於列向量,其中第4行為1,其餘的都為0。意思是表示「4」輸出的輸出單元為真,其餘為假。對於1,2或n的輸出,請參見下文。
圖3 我們的樣本數據y值表示為邏輯真/假列向量
圖4 多級Logistic回歸成本函數
上述成本函數J(theta)中的兩個Sigma將總結出你通過網路(m)和每個單個輸出級(K)提供的每個示例的成本。現在,你可以通過單獨進行每個計算來實現這一點,但是事實證明,人類已經定義的矩陣乘法的方法使得它能夠完美地同時執行所有這些正向傳播計算。我們那些專攻數值計算的朋友已經優化了矩陣乘法函數,使得神經網路可以極大地輸出假設。要編寫我們的代碼,以便我們能夠同時進行所有的計算,而不是說在所有輸入示例中的for循環中運行所有內容,這是一個稱為向量化代碼的過程。這在神經網路中是非常重要的,因為它們的計算成本已經足夠高了,我們不需要任何循環來減慢我們的運算速度。
我們的網路範例
在我們的網路中,我們將有四級,即1,2,3,4,並將遍歷這個計算的每個步驟。我們假設擁有的是一個已訓練的網路,並已通過反向傳播訓練了Theta參數/權重。它將是一個3層網路(2個輸入單元,2個隱藏層單元和4個輸出單元)。網路和參數(又名權重)可以表示如下:
圖5 具有權重的神經網路
在進一步深入之前,如果你不知道矩陣乘法是如何工作的,那就花費7分鐘來看看可汗學院(Khan Academy)(https://www.khanacademy.org/math/precalculus/precalc-matrices/multiplying-matrices-by-matrices/v/matrix-multiplication-intro),然後再看一兩個範例,確保你對它的工作原理有一個直觀的認識。再次強調,在進一步深入研究之前了解這一點很重要。
那我們就從所有的數據開始。我們的3個示例數據和相應的y輸出值。這些數據不代表任何東西,它們只是數字,用來顯示我們將要做的計算:
圖6 數據
當然,如上所述,由於有4個輸出單元,我們的數據必須表示為一個邏輯向量矩陣,三個示例輸出中的每一個都要如此。我使用的是MATLAB,以便將我們的y向量轉換成一個邏輯向量矩陣:
yv=[1:4] == y; %creating logical vectors of y values
圖7 樣本矩陣輸出y數據變為邏輯向量
另外,請注意,我們的X數據沒有足夠的特徵。在圖5的神經網路中,當我們計算權重/參數和輸入值的乘積時,我們就有了那個虛線偏差單元x(0)。這意味著我們需要將偏差單元添加到數據中,也就是說我們在矩陣的開頭添加一個列:
X = [ones(m,1),X];
圖8 附有偏差的數據,偏差即由圖5中的神經網路虛線單位/節點所指代
數據X被定義為第一個輸入層a^1的第一個激活值,所以如果你在代碼(第3行)中看到了一個a^1,它只是指初始輸入數據。網路中每個連接/箭頭的權值或參數如下所示:
圖9 我們的神經網路的第一組權重/參數,其指數與圖5神經網路圖的箭頭匹配。
下面是我們將用於計算邏輯成本函數的完整代碼,我們已經解決了第2行和第9行,但是我們將在本代碼的其餘部分慢慢地分解矩陣乘法和重要的矩陣操作:
1: m = size(X, 1);
2: X = [ones(m,1),X];
3: a1 = X;
4: z2 = Theta1*a1';
5: a2 = sigmoid(z2);
6: a2 = [ones(1,m);a2];
7: z3 = Theta2*a2;
8: a3 = sigmoid(z3);
9: yv=[1:4] == y;
10: J = (1/m) * (sum(-yv』 .* log(a3) — ((1 — yv』) .* log(1 — a3))));
11: J = sum(J);
首先,我們來進行正向傳播的第一步,第4行代碼。將每個範例的輸入值乘以與其相應的權重。我總是想像輸入值在圖5的網路中沿著箭頭流動,乘以權重,然後在激活單元/節點等待其他箭頭進行乘法運算。然後,特定單元的整個激活值首先由這些箭頭(輸入x權重)的總和組成,然後該和通過Sigmoid函數進行操作(參見上面的圖1)。
所以在這裡很容易犯你的第一個矩陣乘法錯誤。由於我們的附有偏差的單元被添加到X(在這裡也稱為a^1)是一個3x3矩陣,而我們的Theta1是一個2x3矩陣。由於Theta:2x3和X:3x3的兩個內部維度是相同的,因此把兩個矩陣相乘就變得很簡單了,其結果應該是正確的,且會給出我們的2x3合成矩陣?對不起,錯了!
z2 = Theta1 * a1; %WRONG! THIS DOESN'T GIVE US WHAT WE WANT
儘管運行這個計算將輸出一個我們期望和需要將其用於下一步的正確維度的矩陣,但是所有計算的值將是錯誤的,因此所有的計算都將從這裡開始表現為錯誤的。另外,由於沒有計算機錯誤,所以很難判斷為什麼網路成本函數計算出了錯誤的成本,如果你注意到了,請記住,當進行矩陣乘法時,得到的矩陣的每個元素ab是第1矩陣中的行a和第二個矩陣中的列b的點積和。如果我們使用上面的代碼來計算z^2,則得到的矩陣中的第一個元素將由我們的第一行Theta的[0.1 0.3 . 0.5]與整列偏差單元相乘得到,[1.000;1.000; 1.000],這對我們沒有用。這意味著我們需要將範例的輸入數據矩陣進行轉置,使得矩陣將每個theta與每個輸入正確相乘:
z2 = Theta1*a1';
矩陣乘法的運算如下:
圖10 矩陣乘法的指數符號表示。列中的結果元素表示單個示例,並且行是隱藏層中的不同激活單元。每個示例中 2個隱藏層導致兩個值(或行)。
然後,我們將上述z2矩陣中的6個元素中的每個單元應用於Sigmoid函數:
a2 = sigmoid(z2);
這為我們提供了三個示例中每兩個隱藏單元的隱藏層激活值的2×3矩陣:
圖11 隱藏單元的激活值
因為這是作為矩陣乘法完成的,所以我們能夠同時計算隱藏層的激活值,而不是在所有這些例子中使用for循環,當使用更大的數據集時,計算變得極其昂貴。更不用說再需要反向傳播了。
現在我們具有第二層激活單元的值,它們作為輸入到下一層和最後一層,即輸出層。該層對於第2層和第3層之間的圖5中的每個箭頭,都有一組新的權重/參數Theta2,我們繼續重複上面的步驟。將連接到每個激活節點的權重的激活值(輸入)乘以連接到每個激活節點的產品,然後通過sigmoid函數運行每個激活節點和以獲得最終輸出。我們的a2作為我們的輸入數據,我們的權重/參數如下:
圖12 帶指數的Theta2權重/參數。每行表示對每個輸出單元貢獻的權重。
我們要做以下計算:
z3 = Theta2*a2;
但是在我們這樣做之前,我們必須再次添加我們的偏差單元到我們的數據,在這種情況下,隱藏層激活a2。如果你在圖5中再次注意到,隱藏層中的虛線圓圈(0),僅在下一次計算時才添加的偏置單元。因此,我們把它添加到上面圖11所示的激活矩陣中。
介紹我犯過的錯誤就是我寫這篇文章的動機。要正向傳播激活值,我們將Theta中的一行的每個元素與a2中的每個元素相乘,並且這些乘積的總和將給出所得到的z3矩陣的單個元素。通常,數據結構的方式是將偏差單元添加為列,但是如果你這樣做(我愚蠢地做了),這將會給我們一個錯誤的結果。所以我們將偏置單位作為一行添加到a2中。
a2 = [ones(1,m);a2];
圖13 將偏移行添加到a2激活中
在我們運行矩陣乘法以計算z3之前,請注意,在z2之前,你必須轉置輸入數據a 1,使其對於矩陣乘法「正確排列」,以計算出我們想要的結果。這裡,我們的矩陣按照我們想要的方式排列,所以沒有轉置a2矩陣。這是另一個常見的錯誤,如果你不了解這個核心的計算,那麼很容易犯這個錯誤(我過去對此非常內疚)。現在我們可以在4x3和3x3矩陣上運行矩陣乘法,得到3個例子中的每一個的4×3矩陣輸出假設:
z3 = Theta2*a2;
圖14 矩陣乘法的指數符號表示。列中的合成元素代表單個示例,並且行是輸出層的不同激活單元,共有四個輸出單元。在分類問題中,這意味著四個類/類別。還需要注意的是,每個元素中的所有a的[m]上標指數是示例編號。
然後我們對z2矩陣中的12個元素中的每一個元素使用sigmoid 函數:
a3 = sigmoid(z3);
這為我們每個輸出單元/類提供了一個4x3矩陣的輸出層激活(類似於假設):
圖15 每個示例網路的每個輸出單元的激活值。如果你在所有的示例中做一個循環,這將是一個列向量,而不是一個矩陣。
從這裡,你只是計算成本函數。唯一需要注意的是,你必須轉置y向量的矩陣,以確保你在成本函數中正在進行的元素操作與每個示例和輸出單元完全對齊。
圖16 邏輯y向量矩陣的轉置
然後我們把它們放在一起來計算成本函數:
圖4 多級Logistic回歸成本函數
J = (1/m) * (sum(-yv』 .* log(a3) — ((1 — yv』) .* log(1 — a3))));
J = sum(J);
這就是我們的成本,應該注意的是以計算所有類以及所有示例的雙倍總和。這就是所有人。矩陣乘法可以使這個代碼非常整齊和高效,不需要讓循環減慢,但是你必須知道矩陣乘法中發生了什麼,以便你可以適當地調整矩陣,無論是乘法順序,必要時進行轉置,並將偏差單元添加到矩陣的正確區域。一旦你把它打破了,掌握得就更加直觀,我強烈推薦,如果你仍然不確定,慢慢地像這樣通過一個示例,它總是歸結為一些非常簡單的基本原理。
我希望這對於正向傳播所需的線性代數的掌握,同時去神秘化是非常有幫助的。
※遞歸神經網路RNN怎樣加速?看PyTorch如何進行動態批處理
※人工智慧在「奔跑」,助跑器「計算」怎可缺席
※用TensorFlow實現ResNeXt和DenseNet,超簡單!
※「深」到什麼程度才能稱得上是「深度」學習呢?
※警察必備工具!用空間融合卷積神經網路鑒別偽裝的「壞蛋」
TAG:機器人圈 |