神經網路(neural networks)

Sion258發表於2024-07-27

神經網路(neural networks)

神經元

感知機模型(Perceptron)

感知機模型

感知機是由神經元複合的線性分類器
$$
y =f(\sum_{i=1}^n w_ix_i -\theta) = f(w^Tx-\theta)
$$
其中:

  • x為樣本的特徵向量。
  • w為權重向量。
  • θ稱為偏置(閾值)

如何這裡的$f$為階躍函式$\epsilon(·)$,則感知機模型可以表示為啟用函式:
$$
(1)y =\epsilon(w^Tx-\theta) =\left{
\begin{matrix}
\ 1,w^Tx>=\theta\
\ 0,w^Tx<\theta
\end{matrix}
\right.
$$
由$n$維空間中的超平面方程:
$$
w_1x_1+w_2x_2+w_3x_3+...+w_nx_n+b=w^Tx+b=0
$$
可知$(1)$式將n維空間劃分,可以看作一個超平面,將n維空間劃分為$wTx>=\theta$和$wTx<\theta$兩個空間,落在前者的輸出值為1,後者的輸出值為0,於是實現了分類功能。

感知機的學習策略以及引數的調整規則

所以線性模型中的思考,我們需要對權重向量$w^T$和偏置$\theta$進行選擇,這就是感知機的學習

不妨考慮一種簡單的情況,現在有特徵矩陣$x_1$=[2, 3],$x_2$=[1,5],包含標籤的向量$y={1,0}$

  • 初始化權重向量$w$:假設$w=[1,1]$,偏置$\theta=2$;

  • 計算輸出:

    • 根據公式$(1)$, 有$w^Tx_1-\theta=1·2+1·3-2=3>0$, 因此被判定為1,符合真實輸出;
    • 對於$x_2$,計算得到$w^Tx_2-\theta = 6-2=4>0$,不符合輸出,因此需要調整權重向量$w$和偏置$\theta$
  • 提出以下最佳化規則:
    $$
    w' = w+η(y-y_{pred})x\
    \theta'=\theta-η(y-y_{pred})
    $$
    ​ 假設模型的學習率$η$為1,於是$w' = w + η(0-1)·[1,5] = [0,-4]$,$\theta' = 2 + 1 =3$

    • 對於$x_1$,再次計算$w^Tx_1-\theta= -15 < 0$,與真實輸出不符;
    • 對於$x_2$,易得$w^Tx_1-\theta = -23 < 0$,輸出為0符合標籤

    由於輸出仍不滿足,因此繼續根據規則調整權重和偏置驗證計算。

    多層前饋神經網路

    在前面我們提到,感知機是一種線性分類器,利用感知機模型構成的單層神經元只有輸出層神經原進行啟用函式處理,只能解決線性可分的問題

    ![image-20240726164829724](神經網路(neural networks).assets/image-20240726164829724.png)

    其中“與”“或”“非”都是線性可分的,即存在一個線性超平面能將他們分開,在感知機的學習過程中調整權重矩陣和偏置最終使得學習過程收斂(converge),而對於非線性可分的問題,例如圖上的"異或"問題,可以看到不存在有且僅有一個線性超平面將他們分開,這使得學習過程發生振盪(fluctuation),無法確定解。

    含隱藏層的多層功能神經元

    為了解決非線性可分問題,我們引入多層功能神經元的概念,這裡兩層感知機模型就能解決上“異或”問題.

    ![image-20240726165420935](神經網路(neural networks).assets/image-20240726165420935.png)

    更一般的多層功能神經元構成的神經網路如圖所示:

    ![image-20240726165606198](神經網路(neural networks).assets/image-20240726165606198.png)

每層神經元與下一層神經元全互連,同層神經元不進行連線,也不跨層連線,上一層神經元的輸出作為下一層神經元的輸入,我們定義這樣的神經網路為"多層前饋神經網路"(multi-layer feedback neural networks).神經元之間用“連線權”和閾值進行連線。

單隱層前饋網路是指只有一個隱藏層,雙隱層前饋網路有兩層隱藏層,需要注意的是,輸入層神經元沒有啟用函式進行處理,僅僅作為輸入。

誤差逆傳播演算法(BP神經網路)

由於構造了複雜的學習模型多層前饋神經網路,我們的單層感知機模型的學習方法$\Delta w = η(y-y_{pred})x$

$\Delta\theta = -η(y-y_{pred})$已經無法正常執行。因此我們需要更適用的演算法。

誤差逆傳播演算法(error BackPropagation,BP)是一種適用於多層前饋神經網路的學習演算法(BP演算法不止適用於多層前饋神經網路)。

![image-20240726171227862](神經網路(neural networks).assets/image-20240726171227862.png)

我們來解釋一下BP演算法的具體過程。

  1. 訓練集定義:

    • 設定訓練集 $(D = {(x_1, y_1), (x_2, y_2), ..., (x_m, y_m)})$,其中$ (x_i \in \mathbb{R}^d),(y_i \in \mathbb{R}^l)$,即每個示例由 $d$個屬性輸入,輸出 $l$ 維實值向量。
  2. BP網路結構:

    • 圖 5.7 展示了一個具有輸入層、隱藏層和輸出層的三層前饋神經網路:
      • 輸入層: 包含$ (d)$ 個輸入神經元。
      • 隱藏層: 包含 $(q) $個神經元。
      • 輸出層: 包含 $(l)$ 個神經元。
  3. 數學符號和公式:

    • 隱層神經元的輸入:
      • 第 $h$ 個隱層神經元接收到的輸入為:$(a_h = \sum_{i=1}^{d} v_{ih} x_i)$,其中 $w_{ih}$ 表示從輸入層第 $i$ 個神經元到隱層第 $h$ 個神經元的連線權值。
    • 輸出層神經元的輸入:
      • 第 $j$ 個輸出層神經元接收到的輸入為:$(β_j = \sum_{h=1}^{q} w_{hj} b_h)$,其中 ($b_h$) 是隱層第 ($h$) 個神經元的輸出,($w_{hj}$) 是從隱層第 ($h$) 個神經元到輸出層第 ($j$) 個神經元的連線權值。
  4. 啟用函式:

    • 假設隱層和輸出層神經元都使用 $Sigmoid $函式作為啟用函式。
  5. 輸出層的計算:

    • 對於訓練例 $((x_k, y_k))$,假設神經網路的輸出為 $$(y_{k1}^, y_{k2}^, ..., y_{kl}^)$$(表示第k個輸入的第j個神經元輸出),即 $$(y_{kj}^ = f(β_j - θ_j))$$,其中 ($f$) 為啟用函式,($θ_j$) 為第 ($j$) 個輸出神經元的閾值。

    網路中有 $(d+l+1)q+l$個引數需確定:輸入層到隱層的$d\times q$個權值,隱層到輸出層的$q\times l$個權 層神 的權值、 $q$ 個隱層神經元的偏置,$l$個輸出層神經元的閾值。

    直到上面為止還是能沿用單層感知機的理解,接下來我們也同單層模型一樣,考慮學習方法以確定權重矩陣和偏置。

    均方誤差

    為了找到一組好的引數值$(w,\theta)$,我們需要一個評估指標描述什麼是"好的"。此時我們想起線性迴歸模型的處理方法,這裡我們適用均方誤差來進行。

    對於訓練例 $(x_k, y_k)$,定義網路在 $(x_k, y_k)$ 上的均方誤差為:
    $$
    E_k = \frac{1}{2} \sum_{j=1}^{l} (y_{kj}^* - y_{kj})^2
    $$

    更新過程與梯度下降

    $v$為任意引數,$v$的更新過程表示為更新估計式:
    $$
    v \leftarrow v+\Delta v \ .
    $$
    以隱層到輸出層的連線權$w_{hj}$為例,使用梯度下降(gradient descent)策略,梯度下降線上性迴歸模型中也關鍵方法。給定學習率$η$,有:
    $$
    \Delta w_{hj} = -η\frac{∂E_k}{∂w_{hj}}
    $$

    鏈式法則

    我們可以注意到$E_k$是$w_{hj}$的複合函式,這裡使用複合函式求導的鏈式法則,有:
    $$
    (1)\frac{∂E_k}{∂w_{hj}}=\frac{∂E_k}{∂y*_{kj}}·\frac{∂y{kj}}{∂\beta_j}·\frac{∂\beta_j}{∂w{hj}}\ .
    $$
    由於$(β_j = \sum_{h=1}^{q} w_{hj} b_h)$,所以:
    $$
    (2)\frac{∂\beta_j}{∂w_{hj}} = b_h\ .
    $$
    定義$g_j$:
    $$
    (3)g_j =-\frac{∂E_k}{∂y*_{kj}}·\frac{∂y
    {kj}}{∂\beta_j}\
    =-(y^*
    -y_{kj})f'(\beta_j-\theta_j)\
    =y*_{kj}(1-y{kj})(y-y^{kj})
    $$
    最終可以得到
    $$
    \Delta w
    =ηg_jb_h\ .
    $$
    類似可得其他引數:
    $$
    \Delta \theta_j = - \eta g_j,
    $$
    $$
    \Delta w_{ih} = \eta e_h x_i,
    $$
    $$
    \Delta \gamma_h = - \eta e_h, \quad
    $$

    $$
    e_h = - \frac{\partial E_k}{\partial b_h} \cdot \frac{\partial b_h}{\partial \alpha_h}
    $$
    $$
    = - \sum_{j=1}^{l} \frac{\partial E_k}{\partial \beta_j} \cdot \frac{\partial \beta_j}{\partial b_h} f'(\alpha_h - \gamma_h)
    $$
    $$
    = \sum_{j=1}^{l} w_{hj} g_j f'(\alpha_h - \gamma_h)
    $$
    $$
    = b_h (1 - b_h) \sum_{j=1}^{l} w_{hj} g_j。 \quad (5.15)
    $$

BP演算法手動實現

對每個訓練樣例,BP演算法執行以下操作:

(1)先將輸入示例提供給輸入層神經元,然後逐層將訊號前傳,直到產生輸出層的結果;

(2)然後計算輸出層的誤差(第4-5行)

(3)再將誤差逆向傳播至隱層神經元(第6行)

(4)最後根據各層神經元的誤差來對連線權和閾值進行調整(第7行)。

該迭代過程迴圈進行,直到達到某些停止條件為止,例如訓練誤差達到一個很小的值。圖給出了在2個屬性、5個樣本的西瓜資料上,隨著訓練樣例的增加,網路引數和分類邊界的變化情況。

![image-20240726180539860](神經網路(neural networks).assets/image-20240726180539860.png)

輸入:訓練集 $D = \{(x_k, y_k)\}_{k=1}^m$
      學習率 $\eta$

過程:

1. 在 $[-1, 0, 1]$ 範圍內隨機初始化網路中所有連線權和閾值
2. repeat
3. for all $(x_k, y_k) \in D$ do
4. 根據式(5.3)計算當前樣本的輸出 $y_{kj}^*$
5. 根據式(5.10)計算輸出層神經元的梯度 $g_j$
6. 根據式(5.11)計算隱層神經元的梯度 $e_h$
7. 根據式(5.12)-(5.14)更新連線權 $w_{hj}, v_{ih}$ 和閾值 $\theta_j, \gamma_h$
8. until 達到停止條件

輸出:連線權與閾值確定的多層前饋神經網路

附上手寫的原始碼:

import numpy as np

class nn:
    def __init__(self, input_size, hidden_size, output_size, eta):
        self.weight_1 = np.random.randn(hidden_size, input_size)
        self.weight_2 = np.random.randn(output_size, hidden_size)
        self.thre_1 = np.random.randn(hidden_size)
        self.thre_2 = np.random.randn(output_size)
        self.eta = eta

    def _get_input(self, weight, X):
        return np.dot(X, weight.T)
    
    def _sigmoid(self, input, thre):
        input = np.array(input) - thre
        return 1 / (1 + np.exp(-input))
    
    def _get_output(self, input, thre):
        return self._sigmoid(input, thre)
    
    def _get_MSE(self, output, pre_output):
        return np.mean((pre_output - output) ** 2) / 2
    
    def _get_new_weight(self, grad, X):
        delta_weight = self.eta * np.outer(grad, X)
        return delta_weight
    
    def _get_new_thre(self, grad):
        return -self.eta * np.sum(grad, axis=0)
    
    def _get_output_grad(self, output, y):
        error_output = output - y
        grad_output = error_output * output * (1 - output)
        return grad_output
    
    def _get_hide_grad(self, weight, output, grad_output):
        error_hidden = np.dot(weight.T, grad_output)
        grad_hidden = error_hidden * output * (1 - output)
        return grad_hidden

    def fit(self, X, y, num_epochs=1000):
        for epoch in range(num_epochs):
            # 前向傳播
            input_1 = self._get_input(self.weight_1, X)
            output_1 = self._get_output(input_1, self.thre_1)
            
            input_2 = self._get_input(self.weight_2, output_1)
            output_2 = self._get_output(input_2, self.thre_2)
            
            # 計算損失
            loss = self._get_MSE(y, output_2)
            
            # 反向傳播
            grad_output = self._get_output_grad(output_2, y)
            grad_hidden = self._get_hide_grad(self.weight_2, output_1, grad_output)
            
            # 更新權重和偏置
            delta_weight_2 = self._get_new_weight(grad_output, output_1)
            self.weight_2 -= delta_weight_2
            self.thre_2 -= self._get_new_thre(grad_output)
            
            delta_weight_1 = self._get_new_weight(grad_hidden, X)
            self.weight_1 -= delta_weight_1
            self.thre_1 -= self._get_new_thre(grad_hidden)
            
            if epoch % 100 == 0:
                print(f"Epoch {epoch}, Loss: {loss}")

    def predict(self, X):
        input_1 = self._get_input(self.weight_1, X)
        output_1 = self._get_output(input_1, self.thre_1)
        
        input_2 = self._get_input(self.weight_2, output_1)
        output_2 = self._get_output(input_2, self.thre_2)
        
        return output_2
        
input_size = 4
hidden_size = 5
output_size = 3
eta = 0.7

model = nn(input_size, hidden_size, output_size, eta)

X = np.array([0.5, 1.0, 1.5, 2.0])
y = np.array([0.1, 0.2, 0.3])

model.fit(X, y)

prediction = model.predict(X)
print(f"Prediction: {prediction}")

'''
Epoch 0, Loss: 0.08452484439144649
Epoch 100, Loss: 0.00043097425030784627
Epoch 200, Loss: 6.173784504106113e-05
Epoch 300, Loss: 6.111382932194645e-06
Epoch 400, Loss: 5.274844786786648e-07
Epoch 500, Loss: 4.445371701622411e-08
Epoch 600, Loss: 3.7266722662723944e-09
Epoch 700, Loss: 3.1195917455011153e-10
Epoch 800, Loss: 2.6102862257159366e-11
Epoch 900, Loss: 2.1838569148871845e-12
Prediction: [0.1000008  0.19999945 0.3000004 ]
'''

累積誤差逆傳播演算法(累積BP)

前面介紹的BP演算法針對單個均方誤差$E_k$,這意味著每次引數更新都只針對一個樣例資料,導致更新頻繁以及可能出現的不同樣例導致的引數調整抵消等問題。所以為了使資料集整體達到同樣的誤差極小點,累積BP直接針對累積誤差最小化:$E = \frac{1}{m} \sum_{k=1}^{m}E_k$,讀取資料集一遍之後再進行更新,然而累積BP在累積誤差下降到一定程度時,可能出現下降緩慢的情況,這時標準BP會得到更好的解。

類似隨機梯度下降和標準梯度下降的區別。

過擬合

  • 早停:將資料集分成訓練集和驗證集,訓練集用來計算梯度,更新連線權和偏置,驗證集用來估計誤差,如果訓練集誤差降低但是驗證機誤差升高,則停止訓練(類似決策樹的後剪枝)
  • 正則化:在誤差目標函式增加一個描述網路複雜度的部分,例如連線權和偏置的平方和

$$
E=\lambda \frac{1}{m}\sum_{k=1}^m E_k + (1-\lambda)\sum_i w_i^*\ ,
$$

其中$\lambda \in (0,1)$用於經驗誤差與網路複雜度這兩項進行折中。常使用交叉驗證法來估計。

相關文章