1.17. 神經網路模型 (監督式)#

警告

此實作不適用於大規模應用。特別是,scikit-learn 不提供 GPU 支援。對於速度更快、基於 GPU 的實作,以及提供更多彈性來建構深度學習架構的框架,請參閱相關專案

1.17.1. 多層感知器#

多層感知器 (MLP) 是一種監督式學習演算法,透過在資料集上訓練來學習函數 \(f: R^m \rightarrow R^o\),其中 \(m\) 是輸入的維度數,而 \(o\) 是輸出的維度數。給定一組特徵 \(X = {x_1, x_2, ..., x_m}\) 和目標 \(y\),它可以學習用於分類或迴歸的非線性函數逼近器。它與邏輯迴歸不同,在輸入和輸出層之間,可以有一層或多層非線性層,稱為隱藏層。圖 1 顯示具有純量輸出的單個隱藏層 MLP。

../_images/multilayerperceptron_network.png

圖 1:單個隱藏層 MLP。#

最左邊的層,稱為輸入層,由一組神經元 \(\{x_i | x_1, x_2, ..., x_m\}\) 組成,表示輸入特徵。隱藏層中的每個神經元都會以前一層的值進行加權線性加總 \(w_1x_1 + w_2x_2 + ... + w_mx_m\),然後再進行非線性啟動函數 \(g(\cdot):R \rightarrow R\) (例如雙曲正切函數)。輸出層接收來自最後一個隱藏層的值,並將它們轉換為輸出值。

該模組包含公開屬性 coefs_intercepts_coefs_ 是權重矩陣的列表,其中索引 \(i\) 處的權重矩陣表示層 \(i\) 和層 \(i+1\) 之間的權重。intercepts_ 是偏差向量的列表,其中索引 \(i\) 處的向量表示新增至層 \(i+1\) 的偏差值。

多層感知器的優點和缺點#

多層感知器的優點是

  • 學習非線性模型的能力。

  • 使用 partial_fit 即時 (線上學習) 學習模型的能力。

多層感知器 (MLP) 的缺點包括

  • 具有隱藏層的 MLP 具有非凸損失函數,其中存在多個局部最小值。因此,不同的隨機權重初始化可能會導致不同的驗證準確度。

  • MLP 需要調整許多超參數,例如隱藏神經元的數量、層數和迭代次數。

  • MLP 對特徵縮放很敏感。

請參閱實用技巧章節,其中解決了其中一些缺點。

1.17.2. 分類#

類別 MLPClassifier 實作使用反向傳播訓練的多層感知器 (MLP) 演算法。

MLP 在兩個陣列上進行訓練:大小為 (n_samples, n_features) 的陣列 X,其中包含表示為浮點特徵向量的訓練樣本;以及大小為 (n_samples,) 的陣列 y,其中包含訓練樣本的目標值 (類別標籤)

>>> from sklearn.neural_network import MLPClassifier
>>> X = [[0., 0.], [1., 1.]]
>>> y = [0, 1]
>>> clf = MLPClassifier(solver='lbfgs', alpha=1e-5,
...                     hidden_layer_sizes=(5, 2), random_state=1)
...
>>> clf.fit(X, y)
MLPClassifier(alpha=1e-05, hidden_layer_sizes=(5, 2), random_state=1,
              solver='lbfgs')

在擬合 (訓練) 後,模型可以預測新樣本的標籤

>>> clf.predict([[2., 2.], [-1., -2.]])
array([1, 0])

MLP 可以將非線性模型擬合到訓練資料。clf.coefs_ 包含構成模型參數的權重矩陣

>>> [coef.shape for coef in clf.coefs_]
[(2, 5), (5, 2), (2, 1)]

目前,MLPClassifier 僅支援交叉熵損失函數,該函數允許透過執行 predict_proba 方法來估計機率。

MLP 使用反向傳播進行訓練。更準確地說,它使用某種形式的梯度下降進行訓練,並使用反向傳播來計算梯度。對於分類,它會最小化交叉熵損失函數,從而為每個樣本 \(x\) 提供機率估計的向量 \(P(y|x)\)

>>> clf.predict_proba([[2., 2.], [1., 2.]])
array([[1.967...e-04, 9.998...-01],
       [1.967...e-04, 9.998...-01]])

MLPClassifier 透過套用Softmax 作為輸出函數來支援多類別分類。

此外,該模型支援多標籤分類,其中一個樣本可以屬於多個類別。對於每個類別,原始輸出會通過邏輯函數。大於或等於 0.5 的值會四捨五入為 1,否則四捨五入為 0。對於樣本的預測輸出,值為 1 的索引代表該樣本的指定類別

>>> X = [[0., 0.], [1., 1.]]
>>> y = [[0, 1], [1, 1]]
>>> clf = MLPClassifier(solver='lbfgs', alpha=1e-5,
...                     hidden_layer_sizes=(15,), random_state=1)
...
>>> clf.fit(X, y)
MLPClassifier(alpha=1e-05, hidden_layer_sizes=(15,), random_state=1,
              solver='lbfgs')
>>> clf.predict([[1., 2.]])
array([[1, 1]])
>>> clf.predict([[0., 0.]])
array([[0, 1]])

請參閱下方的範例和 MLPClassifier.fit 的文件字串,以取得更多資訊。

範例

1.17.3. 迴歸#

類別 MLPRegressor 實作多層感知器 (MLP),該感知器使用反向傳播進行訓練,並且在輸出層中沒有啟動函數,這也可以看作是使用識別函數作為啟動函數。因此,它使用平方誤差作為損失函數,並且輸出是一組連續的值。

MLPRegressor 也支援多輸出迴歸,其中一個樣本可以有多個目標。

1.17.4. 正規化#

MLPRegressorMLPClassifier 都使用參數 alpha 進行正規化 (L2 正規化) 項,該項透過懲罰具有較大值的權重來協助避免過度擬合。以下繪圖顯示隨著 alpha 值的變化而變化的決策函數。

../_images/sphx_glr_plot_mlp_alpha_001.png

請參閱下方的範例以取得更多資訊。

範例

1.17.5. 演算法#

MLP 使用隨機梯度下降法 (Stochastic Gradient Descent)AdamL-BFGS 進行訓練。隨機梯度下降法 (SGD) 使用損失函數相對於需要調整的參數的梯度來更新參數,即:

\[w \leftarrow w - \eta (\alpha \frac{\partial R(w)}{\partial w} + \frac{\partial Loss}{\partial w})\]

其中 \(\eta\) 是學習率,它控制參數空間搜尋中的步長。\(Loss\) 是網路使用的損失函數。

更多詳細資訊請參考 SGD 的文件。

Adam 在某種意義上類似於 SGD,它是一種隨機最佳化器,但它可以基於較低階動量的自適應估計自動調整更新參數的量。

使用 SGD 或 Adam 時,訓練支援線上學習和迷你批次學習。

L-BFGS 是一種求解器,它近似黑塞矩陣,該矩陣表示函數的二階偏導數。此外,它會近似黑塞矩陣的逆矩陣以執行參數更新。該實作使用 Scipy 版本的 L-BFGS

如果選擇的求解器是 ‘L-BFGS’,則訓練不支援線上學習或迷你批次學習。

1.17.6. 複雜度#

假設有 \(n\) 個訓練樣本、\(m\) 個特徵、\(k\) 個隱藏層,每個隱藏層包含 \(h\) 個神經元(為了簡化),以及 \(o\) 個輸出神經元。反向傳播的時間複雜度為 \(O(i \cdot n \cdot (m \cdot h + (k - 1) \cdot h \cdot h + h \cdot o))\),其中 \(i\) 是迭代次數。由於反向傳播的時間複雜度很高,建議從較少數量的隱藏神經元和較少的隱藏層開始進行訓練。

數學公式#

給定一組訓練範例 \((x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n)\),其中 \(x_i \in \mathbf{R}^n\)\(y_i \in \{0, 1\}\),一個隱藏層和一個隱藏神經元的 MLP 學習函數 \(f(x) = W_2 g(W_1^T x + b_1) + b_2\),其中 \(W_1 \in \mathbf{R}^m\)\(W_2, b_1, b_2 \in \mathbf{R}\) 是模型參數。\(W_1, W_2\) 分別表示輸入層和隱藏層的權重;而 \(b_1, b_2\) 分別表示加到隱藏層和輸出層的偏差。\(g(\cdot) : R \rightarrow R\) 是激活函數,預設設定為雙曲正切函數。其表示式如下:

\[g(z)= \frac{e^z-e^{-z}}{e^z+e^{-z}}\]

對於二元分類,\(f(x)\) 會通過邏輯函數 \(g(z)=1/(1+e^{-z})\) 以獲得介於 0 和 1 之間的輸出值。閾值設定為 0.5,將輸出大於或等於 0.5 的樣本分配到正類,其餘分配到負類。

如果有兩個以上的類別,\(f(x)\) 本身會是一個大小為 (n_classes,) 的向量。它不會通過邏輯函數,而是通過 softmax 函數,其寫法如下:

\[\text{softmax}(z)_i = \frac{\exp(z_i)}{\sum_{l=1}^k\exp(z_l)}\]

其中 \(z_i\) 表示 softmax 輸入的第 \(i\) 個元素,對應於類別 \(i\),而 \(K\) 是類別的數量。結果是一個向量,其中包含樣本 \(x\) 屬於每個類別的機率。輸出是具有最高機率的類別。

在迴歸中,輸出仍為 \(f(x)\);因此,輸出激活函數只是單位函數。

MLP 根據問題類型使用不同的損失函數。分類的損失函數是平均交叉熵,在二元情況下,其表示式如下:

\[Loss(\hat{y},y,W) = -\dfrac{1}{n}\sum_{i=0}^n(y_i \ln {\hat{y_i}} + (1-y_i) \ln{(1-\hat{y_i})}) + \dfrac{\alpha}{2n} ||W||_2^2\]

其中 \(\alpha ||W||_2^2\) 是 L2 正規化項(又稱懲罰項),用於懲罰複雜模型;而 \(\alpha > 0\) 是一個非負超參數,控制懲罰的大小。

對於迴歸,MLP 使用均方誤差損失函數;其表示式如下:

\[Loss(\hat{y},y,W) = \frac{1}{2n}\sum_{i=0}^n||\hat{y}_i - y_i ||_2^2 + \frac{\alpha}{2n} ||W||_2^2\]

從初始隨機權重開始,多層感知器 (MLP) 通過重複更新這些權重來最小化損失函數。計算損失後,反向傳遞會將損失從輸出層傳播到先前的層,為每個權重參數提供一個更新值,旨在減少損失。

在梯度下降中,計算損失相對於權重的梯度 \(\nabla Loss_{W}\),並從 \(W\) 中減去。更正式地說,可以表示為:

\[W^{i+1} = W^i - \epsilon \nabla {Loss}_{W}^{i}\]

其中 \(i\) 是迭代步驟,而 \(\epsilon\) 是學習率,其值大於 0。

當演算法達到預設的最大迭代次數,或者當損失的改善低於某個很小的數值時,演算法就會停止。

1.17.7. 實際使用上的建議#

  • 多層感知器對特徵縮放很敏感,因此強烈建議縮放您的資料。例如,將輸入向量 X 上的每個屬性縮放到 [0, 1] 或 [-1, +1],或者將其標準化為平均值為 0,變異數為 1。請注意,您必須對測試集應用 *相同* 的縮放比例,才能獲得有意義的結果。您可以使用 StandardScaler 進行標準化。

    >>> from sklearn.preprocessing import StandardScaler  
    >>> scaler = StandardScaler()  
    >>> # Don't cheat - fit only on training data
    >>> scaler.fit(X_train)  
    >>> X_train = scaler.transform(X_train)  
    >>> # apply same transformation to test data
    >>> X_test = scaler.transform(X_test)  
    

    另一種替代且推薦的方法是在 Pipeline 中使用 StandardScaler

  • 尋找合理的正規化參數 \(\alpha\) 的最佳方法是使用 GridSearchCV,通常在 10.0 ** -np.arange(1, 7) 的範圍內。

  • 根據經驗,我們觀察到 L-BFGS 在小型資料集上收斂得更快,並獲得更好的解決方案。然而,對於相對較大的資料集,Adam 非常穩健。它通常會快速收斂並提供相當不錯的效能。另一方面,如果正確調整學習率,具有動量或 Nesterov 動量的 SGD 可以表現得比這兩種演算法更好。

1.17.8. 使用 warm_start 進行更多控制#

如果您想要對 SGD 中的停止條件或學習率進行更多控制,或想要進行其他監控,則使用 warm_start=Truemax_iter=1 並自行迭代可能會有所幫助。

>>> X = [[0., 0.], [1., 1.]]
>>> y = [0, 1]
>>> clf = MLPClassifier(hidden_layer_sizes=(15,), random_state=1, max_iter=1, warm_start=True)
>>> for i in range(10):
...     clf.fit(X, y)
...     # additional monitoring / inspection
MLPClassifier(...
參考資料#