Skip to content

1.5. 随机梯度下降(Stochastic Gradient Descent)

随机梯度下降法(SGD)是一种简单但非常有效的方法,主要用于凸损失函数下线性分类器的判别式学习(例如(线性)支持向量机Logistic回归)。尽管SGD在机器学习社区中已经存在很长时间了,但最近在大规模学习的背景下,它已经引起了相当多的关注。

SGD已成功应用于文本分类和自然语言处理中经常遇到的大规模稀疏机器学习问题。对于数据稀疏,本模块中的分类器可以轻松处理超过10^5个训练样本和10^5个特征以上的问题。

随机梯度下降的优点:

  • 高效。
  • 易于实现 (有大量优化代码的机会)。

随机梯度下降的缺点:

  • SGD 需要设置一些超参数,例如正则化参数和迭代的次数。
  • SGD 对特征缩放(feature scaling)很敏感。

1.5.1. 分类

警告:

在进行训练之前,确保您已经重新排列(打乱)了训练数据,或者在每次迭代后用 shuffle=True 来打乱。

SGDClassifier实现了一种简单的随机梯度下降学习例程,该例程支持不同的损失函数和分类惩罚。

https://scikit-learn.org/stable/_images/sphx_glr_plot_sgd_separating_hyperplane_0011.png

与其他分类器一样,SGD必须接收两个数组作为参数:一个大小为[n_samples,n_features]的数组X存放训练数据,一个大小为[n_samples]的数组Y存放训练数据的目标值(类别标签):

>>> from sklearn.linear_model import SGDClassifier
>>> X = [[0., 0.], [1., 1.]]
>>> y = [0, 1]
>>> clf = SGDClassifier(loss="hinge", penalty="l2", max_iter=5)
>>> clf.fit(X, y)
SGDClassifier(max_iter=5)

在训练后,该模型可用于预测:

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

SGD 会在训练数据上拟合出一个线性模型。成员变量 coef_ 里面存放学习到的模型参数:

>>> clf.coef_
array([[9.9..., 9.9...]])

成员变量 intercept_ 存放学习到的模型的截距(intercept) (也叫 偏置(offset or bias)):

>>> clf.intercept_
array([-9.9...])

模型是否使用截距(intercept) (即偏置超平面(biased hyperplane)),由参数fit_intercept决定。

要获得到超平面的签名距离(signed distance),请使用SGDClassifier.decision_function

>>> clf.decision_function([[2., 2.]])
array([29.6...])

可以通过设置参数loss 的值来设置具体使用的损失函数。SGDClassifier支持以下损失函数:

  • loss="hinge":(软边距)线性支持向量机,
  • loss="modified_huber":平滑的合页损失(hinge loss),
  • loss="log":逻辑回归,
  • 以及下面所有的回归损失。

前两个损失函数是懒惰的(lazy),只有当某个/些样本点违反了边界约束(margin constraint)时,它们才会去更新模型的参数, 这使得训练非常有效率,所以即使使用了 L2 惩罚,我们仍然可以得到稀疏的模型。

使用loss="log"loss="modified_huber"激活 predict_proba方法,该方法会为每个样本$x$估计出它属于每个类的概率向量$P(y|x)$:

>>> clf = SGDClassifier(loss="log", max_iter=5).fit(X, y)
>>> clf.predict_proba([[1., 1.]])
array([[0.00..., 0.99...]])

具体的惩罚项可以通过设置 penalty参数 , SGD 支持下列的惩罚项:

  • penalty="l2": 对 coef_应用L2惩罚
  • penalty="l1": 对 coef_应用L1惩罚
  • penalty="elasticnet": L2和L1的凸组合; (1 - l1_ratio)L2 + l1_ratio * L1.

默认的设置是 penalty="l2"。 L1 惩罚导致稀疏解,使得大多数系数为零。 弹性网(Elastic Net)解决了在特征高相关时 L1惩罚的一些不足。 参数 l1_ratio 用来控制 L1 惩罚 和 L2 惩罚 的凸组合。

SGDClassifier通过以“一对多(one versus all)”(OVA)机制来组合多个二分类器。对于每个$K$类,可以训练一个二分类器来区分自身和其他$K−1$个类。在测试阶段, 我们计算每个分类器的置信度分数(confidence score)(也就是与超平面的距离),并选择置信度最高的分类。 下图阐释了基于鸢尾花数据集上的 OVA 方法。虚线表示三个 OVA 分类器; 不同背景色代表由三个分类器产生的决策面。

https://scikit-learn.org/stable/_images/sphx_glr_plot_sgd_iris_0011.png

在多类分类的情况下,coef_ 是一个 shape=[n_classes, n_features] 的二维数组, intercept_ 是一个 shape=[n_classes] 的一维数组。 coef_ 的第 i 行保存了第 i 类的 OVA 分类器的权重向量; 类以升序索引 (参照属性 classes_ )。 注意,原则上,由于它们允许创建一个概率模型,所以 loss="log"loss="modified_huber" 更适合于 一对多(one-vs-all) 分类。

SGDClassifier通过参数 class_weightsample_weight 来支持加权类(weighted classes)和加权实例(weighted instances)。 更多信息请参照下面的案例和SGDClassifier.fit文档

案例:

SGDClassifier支持平均SGD(ASGD)算法。可以通过设置 average=True 来启用平均SGD(ASGD)。ASGD 工作原理是在普通 SGD 的基础上,对每个样本的每次迭代后的系数取平均值。 当使用 ASGD 时,学习速率可以更大甚至是恒定,在一些数据集上能够加速训练过程。

对于具有逻辑损失(logistic loss)的分类,LogisticRegression中提供了另一个采取平均策略(averaging strategy)的 SGD 变体,其使用了随机平均梯度 (SAG) 算法。

1.5.2. 回归

SGDRegressor实现了一种简单的随机梯度下降学习例程,该例程支持不同的损失函数和惩罚来拟合线性回归模型。SGDRegressor非常适合具有大量训练样本(> 10.000)的回归问题,对于其他的问题,我们建议使用岭(Ridge)Lasso弹性网(ElasticNet)

可以通过设置参数loss 的值来设置具体使用的损失函数。SGDRegressor支持以下损失函数:

  • loss="squared_loss": 普通最小二乘。
  • loss="huber": Huber loss ,可以获得鲁棒的回归。
  • loss="epsilon_insensitive": 线性支持向量回归(linear Support Vector Regression)。

Huber 和 epsilon-insensitive 损失函数可用于鲁棒回归(robust regression)。 不敏感区域的宽度必须通过参数 epsilon 来设定。这个参数取决于目标变量的尺度(scale)。

SGDRegressor支持平均SGD算法,就像SGDClassifier一样,通过设置average=True可以启用平均SGD。

对于具有平方损失(squared loss)和L2惩罚的回归算法来说,在Ridge中提供了另一个采取平均策略(averaging strategy)的 SGD 变体, 它使用的是随机平均梯度 (SAG) 算法。

1.5.3. 用于稀疏数据的随机梯度下降

注意:

由于对截距(intercept)使用了收缩的学习率,因此可能导致稀疏实现(sparse implementation)与密集实现(dense implementation)的结果略有不同。

scipy.sparse支持的格式中,任意的矩阵都有对稀疏数据的内置支持方法。 但是,为了获得最高的效率,请使用scipy.sparse.csr_matrix中定义的CSR矩阵格式。

案列:

1.5.4. 复杂度

SGD的主要优点就是它的效率,对于不同规模的训练样本,处理复杂度基本上是线性的。 假如 X 是 size 为 (n, p) 的矩阵,训练成本为$O(kn\bar{p})$,其中$k$是迭代次数, $\bar{p}$是每个样本非零特征的平均数。

然而,最新的理论结果表明,随着训练集大小的增加,得到我们期望的优化精度的运行时间并不会增加。

1.5.5. 停止准则

当达到给定的收敛水平时,SGDClassifierSGDRegressor提供两种停止准则:

  • 如果设置了 early_stopping=True,输入数据会被划分成训练集和验证集。模型首先在训练集上拟合, 然后停止准则是基于在验证集上计算出的预测得分进行的。验证集的大小可以由参数 validation_fraction 来修改。
  • 如果设定了 early_stopping=False,模型将会在整个输入数据集上进行拟合,此时的停止准则是基于在输入数据上计算出的目标函数。

在上述两种情形下,停止准则会在每一个epoch中评估一次。并且当 “准则不再改进” 这一事件发生了 n_iter_no_change 次时,该算法就会停止。 “准则的改进” 使用一个容忍度参数(tolerance tol)进行评估。最后,无论何种情况下,只要达到最大迭代次数 max_iter 后,算法都会停止。

1.5.6. 实用提示

随机梯度下降法对特征缩放(feature scaling)很敏感,因此 强烈建议您缩放您的数据 。 例如,将输入向量 X 上的每个特征缩放到 [0,1] 或 [- 1,+1], 或将其标准化,使其均值为 0,方差为 1。 请注意,必须将 相同 的缩放应用于对应的测试向量中,以获得有意义的结果。使用 StandardScaler 可以轻松完成此操作:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)  # 不要作弊 - 只在训练集上进行训练
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)  # 对测试数据运用相同的数据缩放

假如你的属性(attributes)有一个固有尺度(例如词频或指标型特征(indicator features))就不需要缩放。

  • 最好使用 GridSearchCV 找到一个合理的正则化项(regularization term):α , 它的范围通常在 10.0**-np.arange(1,7)
  • 经验表明,SGD 在处理约 10^6 训练样本后基本收敛。因此,对于迭代次数第一个合理的猜想是 max_iter = np.ceil(10**6 / n),其中 n 是训练集的大小。
  • 如过将 SGD 应用于使用 PCA 做特征提取得到的数据上,我们发现通过某个常数 c 来缩放特征值是明智的,这样可以使训练数据的 L2 范数的平均值为 1。
  • 我们发现,平均SGD在具有更多特征和更高eta0的情况下效果最好

参考文献:

1.5.7. 数学表达式

给定一组训练数据$(x_1, y_1), \ldots, (x_n, y_n)$,其中$x_i \in \mathbf{R}^m$并且$y_i \in {-1,1}$, 我们的目标是学习一个线性评分函数(linear scoring function):$f(x) = w^T x + b$,其中$w \in \mathbf{R}^m$是待学习的模型参数, $b \in \mathbf{R}$是待学习的截距。在做预测的时候,我们只需要简单的判断一下$f(x)$的正负符号。 寻找模型参数的通常做法就是最小化一个带有正则化项的训练误差,如下: $$ E(w,b) = \frac{1}{n}\sum_{i=1}^{n} L(y_i, f(x_i)) + \alpha R(w) $$ 其中$L$是衡量模型拟合程度的损失函数,$R$ 是惩罚模型复杂度的正则化项(也叫作惩罚),$α>0$是一个非负超参数。

$L$的不同的选择会产生不同的分类器,例如

  • Hinge:(软边距)支持向量机。
  • Log: 逻辑回归。
  • Least-Squares: 岭回归(Ridge Regression).
  • Epsilon-Insensitive: (软边距)支持向量回归。

上述所有损失函数可以看作是错误分类误差(Zero-one loss即0-1损失)的上限,如下图所示:

https://scikit-learn.org/stable/_images/sphx_glr_plot_sgd_loss_functions_0011.png

比较流行的正则化项$R$包括:

  • L2 范数:$R(w) := \frac{1}{2} \sum_{i=1}^{n} w_i^2$
  • L1 范数: $R(w) := \sum_{i=1}^{n} |w_i|$,导致解稀疏。
  • 弹性网(Elastic Net):$R(w) := \frac{\rho}{2} \sum_{i=1}^{n} w_i^2 + (1-\rho) \sum_{i=1}^{n} |w_i|$,L2 和 L1 的凸组合, 其中参数1 - l1_ratio可以设置$ρ$的值。

下图显示当 $R(w)=1$ 时参数空间中不同正则项的轮廓。

https://scikit-learn.org/stable/_images/sphx_glr_plot_sgd_penalties_0011.png

1.5.7.1. SGD

随机梯度下降是一种无约束优化问题的优化方法。与(批量)梯度下降相反,SGD 通过一次只考虑单个训练样本来近似 $E(w,b)$的真实梯度。

SGDClassifier实现了一个一阶 SGD 学习例程(first-order SGD learning routine)。 算法在训练样本上遍历,并且对每个样本根据由以下式子给出的更新规则来更新模型参数: $$ w \leftarrow w - \eta (\alpha \frac{\partial R(w)}{\partial w} +\frac{\partial L(w^T x_i + b, y_i)}{\partial w}) $$ 其中 $η$是控制参数空间步长的学习率。拦截$b$也想上面的$w$一样进行更新,但它不进行正则化。

学习率$η$可以是恒定的,也可以是逐渐衰减的。对于分类,默认学习率设置方案(learning_rate='optimal')的具体计算由下式给出:

$$ \eta^{(t)} = \frac {1}{\alpha (t_0 + t)} $$ 其中$t$是时间步长(总共有 n_samples * n_iter 个时间步长), $t_0$ 是由 Léon Bottou 提出的启发式算法决定的, 这样的话,预期的初始更新与预期的权重大小可以保持相当(comparable)(这里假定了训练样本的范数近似为1)。 在 BaseSGD 中的 _init_t 中可以找到确切的定义。

为了进行回归,默认的学习率设置方案(learning_rate='invscaling')的具体计算由下式给出:

$$ \eta^{(t)} = \frac{eta_0}{t^{power_t}} $$ 其中$eta0$ 和$power$_$t$ 是用户通过 eta0power_t 分别选择的超参数。

如果使用固定的学习速率则设置 learning_rate='constant' 和使用 eta0 来设置学习率。

如果要使用自适应下降的学习率, 则设置 learning_rate='adaptive',并在 eta0 指定一个起始学习率。 当停止准则达到以后,算法不会立刻停止并且学习率会被除以5。自适应情况下,算法只有在学习率降低到1e-6时才会停止。

模型参数可以通过访问成员变量 coef_intercept_ 来获得:

  • 成员变量 coef_ 存放$w$
  • 成员变量 intercept_ 存放$b$

参考文献:

1.5.8. 实现细节

SGD的实现受到莱昂·布托(LéonBottou)的随机梯度SVM 影响。跟SvmSGD类似,SGD权重向量表示为标量和向量的乘积,在L2正则化的情况下,该向量允许有效地更新权重。在稀疏特征向量的情况下,以较小的学习率(乘以0.01)更新截距,这导致了它的更新频率更频繁。训练样本按顺序选取并且每处理一个样本就要降低学习速率。我们采用了 Shalev-Shwartz 等人2007年提出的的学习速率设定方案。对于多类分类,使用了“一对多(one versus all)”的方法。我们在 L1 正则化(和 弹性网(Elastic Net))中使用 Tsuruoka 等人2009年提出的截断梯度算法(truncated gradient algorithm)。该代码是用Cython编写的。

参考文献:

©2007-2019,scikit-learn 开发人员(BSD许可证)。 显示此页面源码