- 深度學習進階:自然語言處理
- (日)齋藤康毅
- 3897字
- 2021-02-07 09:25:56
1.2 神經網絡的推理
現在我們開始復習神經網絡。神經網絡中進行的處理可以分為學習和推理兩部分。本節將圍繞神經網絡的推理展開說明,而神經網絡的學習會在下一節進行討論。
1.2.1 神經網絡的推理的全貌圖
簡單地說,神經網絡就是一個函數。函數是將某些輸入變換為某些輸出的變換器,與此相同,神經網絡也將輸入變換為輸出。
舉個例子,我們來考慮輸入二維數據、輸出三維數據的函數。為了使用神經網絡進行實現,需要在輸入層準備2個神經元,在輸出層準備3個神經元。然后,在隱藏層(中間層)放置若干神經元,這里我們放置4個神經元。
這樣一來,我們的神經網絡就可以畫成圖1-7。

圖1-7 神經網絡的例子
在圖1-7中,用〇表示神經元,用箭頭表示它們的連接。此時,在箭頭上有權重,這個權重和對應的神經元的值分別相乘,其和(嚴格地講,是經過激活函數變換后的值)作為下一個神經元的輸入。另外,此時還要加上一個不受前一層的神經元影響的常數,這個常數稱為偏置。因為所有相鄰的神經元之間都存在由箭頭表示的連接,所以圖1-7的神經網絡稱為全連接網絡。
圖1-7的網絡一共包含3層,但有權重的層實際上是2層,本書中將這樣的神經網絡稱為2層神經網絡。因為圖1-7的網絡由3層組成,所以有的文獻也稱之為3層神經網絡。
下面用數學式來表示圖1-7的神經網絡進行的計算。這里用(x1, x2)表示輸入層的數據,用w11和w12表示權重,用b1表示偏置。這樣一來,圖1-7中的隱藏層的第1個神經元就可以如下進行計算:

如式(1.2)所示,隱藏層的神經元是基于加權和計算出來的。之后,改變權重和偏置的值,根據神經元的個數,重復進行相應次數的式(1.2)的計算,這樣就可以求出所有隱藏層神經元的值。
權重和偏置都有下標,這個下標的規則(為何將下標設為11或12等)并不是很重要,重要的是神經元是通過加權和計算的,并且可以通過矩陣乘積整體計算。實際上,基于全連接層的變換可以通過矩陣乘積如下進行整理:

這里,隱藏層的神經元被整理為(h1, h2, h3, h4),它可以看作1×4的矩陣(或者行向量)。另外,輸入是(x1, x2),這是一個1×2的矩陣。再者,權重是2×4的矩陣,偏置是1×4的矩陣。這樣一來,式(1.3)可以如下進行簡化:

這里,輸入是x,隱藏層的神經元是h,權重是W,偏置是b,這些都是矩陣。此時,留意式(1.4)的矩陣形狀,可知進行了如圖1-8所示的變換。

圖1-8 形狀檢查:確認對應維度的元素個數一致(省略偏置)
如圖1-8所示,在矩陣乘積中,要使對應維度的元素個數一致。通過像這樣觀察矩陣的形狀,可以確認變換是否正確。
在矩陣乘積的計算中,形狀檢查非常重要。據此,可以判斷計算是否正確(至少可以判斷計算是否成立)。
這樣一來,我們就可以利用矩陣來整體計算全連接層的變換。不過,這里進行的變換只針對單筆樣本數據(輸入數據)。在神經網絡領域,我們會同時對多筆樣本數據(稱為mini-batch,小批量)進行推理和學習。因此,我們將單獨的樣本數據保存在矩陣x的各行中。假設要將N筆樣本數據作為mini-batch整體處理,關注矩陣的形狀,其變換如圖1-9所示。

圖1-9 形狀檢查:mini-batch版的矩陣乘積(省略偏置)
如圖1-9所示,根據形狀檢查,可知各mini-batch被正確地進行了變換。此時,N筆樣本數據整體由全連接層進行變換,隱藏層的N個神經元被整體計算出來。現在,我們用Python寫出mini-batch版的全連接層變換。
>>> import numpy as np >>> W1 = np.random.randn(2 , 4) # 權重 >>> b1 = np.random.randn(4) # 偏置 >>> x = np.random.randn(10 , 2) # 輸入 >>> h = np.dot(x , W1) + b1
在這個例子中,10筆樣本數據分別由全連接層進行變換。此時,x的第1個維度對應于各筆樣本數據。比如,x[0]是第0筆輸入數據,x[1]是第1筆輸入數據……類似地,h[0]是第0筆數據的隱藏層的神經元,h[1]是第1筆數據的隱藏層的神經元,以此類推。
在上面的代碼中,偏置b1的加法運算會觸發廣播功能。b1的形狀是(4,),它會被自動復制,變成(10, 4)的形狀。
全連接層的變換是線性變換。激活函數賦予它“非線性”的效果。嚴格地講,使用非線性的激活函數,可以增強神經網絡的表現力。激活函數有很多種,這里我們使用式(1.5)的sigmoid函數(sigmoid function):

如圖1-10所示,sigmoid函數呈S形曲線。

圖1-10 sigmoid函數的圖像
sigmoid函數接收任意大小的實數,輸出0~1的實數。現在我們用Python來實現這個sigmoid函數。
def sigmoid(x): return 1 / (1 + np.exp(-x))
這是式(1.5)的直接實現,應該沒有特別難的地方。現在,我們使用這個sigmoid函數,來變換剛才的隱藏層的神經元。
>>> a = sigmoid(h)
基于sigmoid函數,可以進行非線性變換。然后,再用另一個全連接層來變換這個激活函數的輸出a(也稱為activation)。這里,因為隱藏層有4個神經元,輸出層有3個神經元,所以全連接層使用的權重矩陣的形狀必須設置為4×3,這樣就可以獲得輸出層的神經元。以上就是神經網絡的推理。現在我們用Python將這一段內容總結如下。
import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) x = np.random.randn(10, 2) W1 = np.random.randn(2, 4) b1 = np.random.randn(4) W2 = np.random.randn(4, 3) b2 = np.random.randn(3) h = np.dot(x, W1) + b1 a = sigmoid(h) s = np.dot(a, W2) + b2
這里,x的形狀是(10, 2),表示10筆二維數據組織為了1個mini-batch。最終輸出的s的形狀是(10, 3)。同樣,這意味著10筆數據一起被處理了,每筆數據都被變換為了三維數據。
上面的神經網絡輸出了三維數據。因此,使用各個維度的值,可以分為3個類別。在這種情況下,輸出的三維向量的各個維度對應于各個類的“得分”(第1個神經元是第1個類別,第2個神經元是第2個類別……)。在實際進行分類時,尋找輸出層神經元的最大值,將與該神經元對應的類別作為結果。
得分是計算概率之前的值。得分越高,這個神經元對應的類別的概率也越高。后面我們會看到,通過把得分輸入Softmax函數,可以獲得概率。
以上就是神經網絡的推理部分的實現。接下來,我們使用Python的類,將這些處理實現為層。
1.2.2 層的類化及正向傳播的實現
現在,我們將神經網絡進行的處理實現為層。這里將全連接層的變換實現為Affine層,將sigmoid函數的變換實現為Sigmoid層。因為全連接層的變換相當于幾何學領域的仿射變換,所以稱為Affine層。另外,將各個層實現為Python的類,將主要的變換實現為類的forward()方法。
神經網絡的推理所進行的處理相當于神經網絡的正向傳播。顧名思義,正向傳播是從輸入層到輸出層的傳播。此時,構成神經網絡的各層從輸入向輸出方向按順序傳播處理結果。之后我們會進行神經網絡的學習,那時會按與正向傳播相反的順序傳播數據(梯度),所以稱為反向傳播。
神經網絡中有各種各樣的層,我們將其實現為Python的類。通過這種模塊化,可以像搭建樂高積木一樣構建網絡。本書在實現這些層時,制定以下“代碼規范”。
·所有的層都有forward()方法和backward()方法
·所有的層都有params和grads實例變量
簡單說明一下這個代碼規范。首先,forward()方法和backward()方法分別對應正向傳播和反向傳播。其次,params使用列表保存權重和偏置等參數(參數可能有多個,所以用列表保存)。grads以與params中的參數對應的形式,使用列表保存各個參數的梯度(后述)。這就是本書的代碼規范。
遵循上述代碼規范,代碼看上去會更清晰。我們后面會說明為什么要遵循這樣的規范,以及它的有效性。
因為這里只考慮正向傳播,所以我們僅關注代碼規范中的以下兩點:一是在層中實現forward()方法;二是將參數整理到實例變量params中。我們基于這樣的代碼規范來實現層,首先實現Sigmoid層,如下所示(ch01/forward_net.py)。
import numpy as np class Sigmoid: def__init__(self): self.params = [] def forward(self, x): return 1 / (1 + np.exp(-x))
如上所示,sigmoid函數被實現為一個類,主變換處理被實現為forward(x)方法。這里,因為Sigmoid層沒有需要學習的參數,所以使用空列表來初始化實例變量params。下面,我們接著來看一下全連接層Affine層的實現,如下所示(ch01/forward_net.py)。
class Affine: def__init__(self, W, b): self.params = [W, b] def forward(self, x): W, b = self.params out = np.dot(x, W) + b return out
Affine層在初始化時接收權重和偏置。此時,Affine層的參數是權重和偏置(在神經網絡的學習時,這兩個參數隨時被更新)。因此,我們使用列表將這兩個參數保存在實例變量params中。然后,實現基于forward(x)的正向傳播的處理。
根據本書的代碼規范,所有的層都需要在實例變量params中保存要學習的參數。因此,可以很方便地將神經網絡的全部參數整理在一起,參數的更新操作、在文件中保存參數的操作都會變得更容易。
現在,我們使用上面實現的層來實現神經網絡的推理處理。這里實現如圖1-11所示的層結構的神經網絡。

圖1-11 要實現的神經網絡的層結構
如圖1-11所示,輸入X經由Affine層、Sigmoid層和Affine層后輸出得分S。我們將這個神經網絡實現為名為TwoLayerNet的類,將主推理處理實現為predict(x)方法。
之前,我們在用圖表示神經網絡時,使用的是像圖1-7那樣的“神經元視角”的圖。與此相對,圖1-11是“層視角”的圖。
TwoLayerNet的代碼如下所示(ch01/forward_net.py)。
class TwoLayerNet: def__init__(self, input_size, hidden_size, output_size): I, H, O = input_size, hidden_size, output_size # 初始化權重和偏置 W1 = np.random.randn(I, H) b1 = np.random.randn(H) W2 = np.random.randn(H, O) b2 = np.random.randn(O) # 生成層 self.layers = [ Affine(W1, b1), Sigmoid(), Affine(W2, b2) ] # 將所有的權重整理到列表中 self.params = [] for layer in self.layers: self.params += layer.params def predict(self, x): for layer in self.layers: x = layer.forward(x) return x
在這個類的初始化方法中,首先對權重進行初始化,生成3個層。然后,將要學習的權重參數一起保存在params列表中。這里,因為各個層的實例變量params中都保存了學習參數,所以只需要將它們拼接起來即可。這樣一來,TwoLayerNet的params變量中就保存了所有的學習參數。像這樣,通過將參數整理到一個列表中,可以很輕松地進行參數的更新和保存。
此外,Python中可以使用+運算符進行列表之間的拼接。下面是一個簡單的例子。
>>> a = ['A' , 'B'] >>> a += ['C' , 'D'] >>> a ['A', 'B', 'C', 'D']
如上所示,通過列表之間的加法將列表拼接了起來。在上面的TwoLayerNet的實現中,通過將各個層的params列表加起來,從而將全部學習參數整理到了一個列表中。現在,我們使用TwoLayerNet類進行神經網絡的推理。
x = np.random.randn(10, 2) model = TwoLayerNet(2, 4, 3) s = model.predict(x)
這樣就可以求出輸入數據x的得分s了。像這樣,通過將層實現為類,可以輕松實現神經網絡。另外,因為要學習的參數被匯總在model.params中,所以之后進行神經網絡的學習會更加容易。