神經網絡的數學基礎:張量運算

回顧上一篇 神經網絡的數學基礎 :張量和梯度
通過上一篇的內容,我們知道了張量表示神經網絡中的數據,那麼數據在網絡中流動必然要經過各種運算或者叫做處理,這一系列的處理就是達到最終結果的過程。可以形象把中間的變換稱爲神經網絡的“齒輪”,或者叫做張量運算。就像二進制運算有邏輯與(AND),或(OR),異或(NOR)一樣,張量運算有以下幾種:

  1. 逐元素運算
  2. 張量點積
  3. 廣播
  4. 張量變形
1. 逐元素運算

relu運算和加法(減法)都是逐元素(element-wise)的運算,即該運算獨立地應用於張量中的每個元素。如果你想對逐元素運算編寫簡單的Python 實現,那麼可以用for循環,但是一般都使用Numpy封裝好的函數,這也是Numpy功能強大的一個原因。示例:

>>> import numpy as np
>>> x = np.array([[1,2,3],[4,5,6]])
>>> x.shape
(2, 3)
>>> y = np.array([[1,1,1],[2,2,2]])
>>> y.shape
(2, 3)
>>> z = x + y
>>> z
array([[2, 3, 4],
       [6, 7, 8]])
>>> z.shape
(2, 3)
>>> z = x - y
>>> z
array([[0, 1, 2],
       [2, 3, 4]])
>>> z.shape
(2, 3)
>>> z = y - x
>>> z
array([[ 0, -1, -2],
       [-2, -3, -4]])
>>> z.shape
(2, 3)
2. 張量點積

點積運算,也叫張量積(tensor product,不要與逐元素的乘積弄混),是最常見也最有用的張量運算。與逐元素的運算不同,它將輸入張量的元素合併在一起。
在Numpy、Keras、Theano 和TensorFlow 中,都是用* 實現逐元素乘積。TensorFlow 中的點積使用了不同的語法,但在Numpy 和Keras 中,都是用標準的dot 運算符來實現點積。

import numpy as np
z = np.dot(x, y)

數學符號中的點(.)表示點積運算。
z=x.y

示例如下:

>>> import numpy as np
>>> x = np.array([[1,2,3],[4,5,6]])
>>> x.shape
(2, 3)
>>> y = np.array([[1,1,1],[2,2,2],[3,3,3]])
>>> y.shape
(3, 3)
>>> z = np.dot(x,y)
>>> z
array([[14, 14, 14],
       [32, 32, 32]])
>>> z.shape
(2, 3)
3. 廣播

上一節所說的加減法僅支持兩個形狀相同的張量相加。如果將兩個形狀不同的張量相加,會發生什麼?如果沒有歧義的話,較小的張量會被廣播(broadcast),以匹配較大張量的形狀。廣播包含以下兩步。
(1) 向較小的張量添加軸(叫作廣播軸),使其ndim 與較大的張量相同。
(2) 將較小的張量沿着新軸重複,使其形狀與較大的張量相同。

舉例,下圖可以形象說明廣播是怎麼進行的。
圖3-1

>>> x = np.array([[1, 2], [3, 4]])
>>> y = np.array([10])
>>> x * y
array([[10,20],
     [30,40]])

再舉一例:
圖3-2

>>> x = np.array([[1, 2], [3, 4]])
>>> y = np.array([10,20])
>>> x * y
array([[10,40],
     [30,80]])
4. 張量變形

第四個重要的張量運算是張量變形(tensor reshaping)。張量變形是指改變張量的行和列,以得到想要的形狀。變形後的張量的元素總個數與初始
張量相同。簡單的例子可以幫助我們理解張量變形。

>>> x = np.array([[0., 1.],
                [2., 3.],
                [4., 5.]])
>>> print(x.shape)
(3, 2)
>>> x = x.reshape((6, 1))
>>> x
array([[ 0.],
    [ 1.],
    [ 2.],
    [ 3.],
    [ 4.],
    [ 5.]])
>>> x = x.reshape((2, 3))
>>> x
    array([[ 0., 1., 2.],
    [ 3., 4., 5.]])

經常遇到的一種特殊的張量變形是轉置(transposition)。對矩陣做轉置是指將行和列互換,比如:使x[i, :] 變爲x[:, i]。

>>> x = np.zeros((300, 20))
>>> x = np.transpose(x)
>>> print(x.shape)
(20, 300)

小結一下,張量運算:

運算 Numpy對應操作
逐元素運算 +,-,np.maximum
張量點積 np.dot
廣播 操作自帶支持
張量變形 reshape, np.transpose

介紹上面的張量基本運算是爲了使學習神經網絡容易一些,至此,介紹完了基本運算。

5、神經網絡的內積

簡單來說,輸入與權重的點積就是神經網絡的內積,沒有考慮激活函數。
先看一個小例子,我們來計算一下,在神經網絡學習中可以看到如下圖所示的表達:
圖5-1

x代表輸入用一個1維數組表示,y代表輸出,也是一個1維數組,直線上的數字代表權重W,用2乘以3的矩陣來表示,這是一個最簡單的神經網絡,可以用下述代碼來表示表示內積

>>> X = np.array([1, 2])
>>> X.shape
(2,)
>>> W = np.array([[1, 3, 5], [2, 4, 6]])
>>> print(W)
[[1 3 5]
[2 4 6]]
>>> W.shape
(2, 3)
>>> Y = np.dot(X, W)
>>> print(Y)
[ 5 11 17]

我們把上面的例子引伸一下,並做一些符號上的約定,,以便公式推導。
圖5-2

這是一個3層神經網絡結構,輸入X爲第0層,有兩個隱藏層,最後輸出算一層,共3層。
權重和隱藏層的神經元的右上角有一個“(1)”,它表示權重和神經元的層號(即第1層的權重、第1層的神經元)。此外,權重的右下角有兩個數字,它們是後一層的神經元和前一層的神經元的索引號。比如,表示前一層的第2個神經元x2 到後一層的第1個神經元的權重。權重右下角按照“後一層的索引號、前一層的索引號”的順序排列,即後前順序,如下圖所示:
圖5-3

上面的神經網絡還增加了表示偏置的神經元“1”。請注意,偏置的右下角的索引號只有一個。這是因爲前一層的偏置神經元(神經元“1”)只有一個。爲了確認前面的內容,現在用數學式表示。通過加權信號和偏置的和按如下方式進行計算。

a1(1)=w111x1+w121x2+b11a_1^{(1)} = w{_1}{_1}{^1}x_1 + w{_1}{_2}{^1}x_2 + b_1^{1}

此外,如果使用矩陣的乘法運算,則可以將第1 層的加權和表示成下面的式。

A1=XW1+B1A^{1} = XW^{1} + B^{1}
其中,A(1)、X、B(1)、W(1)如下所示。

A1=(a1(1)  a2(1)  a3(1)),   X=(x1,  x2),   B(1)=(b1(1)  b2(1)  b3(1))A^{1} = (a_1^{(1)} ~~ a_2^{(1)} ~~ a_3^{(1)}), ~~~ X=(x1,~~ x2), ~~~ B^{(1)} = (b_1^{(1)} ~~ b_2^{(1)} ~~ b_3^{(1)})

W(1)=(w11(1)w21(1)w31(1)w12(1)w22(1)w32(1))W^{(1)} = \begin{pmatrix}{w_{11}^{(1)}}&w_{21}^{(1)}&{w_{31}^{(1)}}\\ {w_{12}^{(1)}}&w_{22}^{(1)}&{w_{32}^{(1)}}\\ \end{pmatrix}
下面我們用NumPy多維數組來實現,這裏將輸入信號、權重、偏置設置成任意值。

X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X, W1) + B1

這個運算和上一節進行的運算是一樣的。W1是2 × 3 的數組,X是元素個
數爲2 的一維數組。這裏,W1和X的對應維度的元素個數也保持了一致。

至此,我們已經把神經網絡的一層的計算公式作了簡單推導,大家看到還少了激活函數,下一篇繼續在講解 激活函數 後,繼續完善上面的推導。

繼續下一篇對閱讀 激活函數的理解和實現-最新整理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章