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\) 层的偏差值。

多层感知器 (MLP) 的优缺点#

多层感知器的优点是

  • 能够学习非线性模型。

  • 能够使用 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 使用 随机梯度下降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 是一种求解器,它近似于 Hessian 矩阵,Hessian 矩阵表示函数的二阶偏导数。此外,它近似于 Hessian 矩阵的逆来执行参数更新。该实现使用 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.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(...
参考文献#