【干货】机器学习应用中的一种优化算法(二)

作者|云多君

来源 | 数据工作室

明天和你一起学习一个非常常见的优化算法:梯度提升,它通常用于机器学习应用程序中,以找到与预测和实际输出之间的最佳拟合相对应的模型参数。这是一种不精确但功能强大的技术。让我们一起学习!本文较长,信息量大,建议收藏!

随机梯度增长广泛用于机器学习应用。与反向传播相结合,它主导了神经网络训练应用。

在本文中迭代尺度算法中的w收敛,我们将一起学习:

基本梯度增长算法

梯度增长算法是一种近似迭代的物理优化方式。我们可以用它来逼近任何可微函数的最小值。

尽管梯度增长有时会卡在局部最小值或鞍点而不是找到全局最小值,但它在实践中被广泛使用。梯度增长通常应用于数据科学和机器学习方法中以优化模型参数。例如,神经网络使用梯度增长来查找权重和错误。

损失函数:优化的目标

损失函数或成本函数是通过改变决策变量来最小化(或最大化)的函数。它通常被许多机器学习方法在内部用于解决优化问题。它们通常通过调整模型参数(例如神经网络权重和误差、随机森林的决策规则或梯度提升等)来最小化实际输出和预测输出之间的差异。

在回归问题中,通常存在输入变量和实际输出的向量。我们希望找到一个模型,该模型将映射到尽可能接近的预测响应。例如,我们可能想要预测一个输出,例如一个人的薪水,给定公司的年数或这个人的教育水平。

在分类问题中,输出是分类的,通常为 0 或 1。例如,我们尝试预测电子邮件是否为垃圾邮件。在二进制补码输出的情况下,方便的是最小化交叉熵函数,这也取决于实际输出和相应的预测。

在常用于解决分类问题的逻辑回归中,函数和定义如下。

同样,我们需要找到权重,但这次它们应该最小化交叉熵函数。

函数梯度:微积分

在微积分中,函数的派生表示在更改其参数(或参数)时值更改了多少。行列式对于优化很重要,尤其是零行列式可以表示最小值、最大值或鞍点。

用几个自变量的函数的梯度来表示,定义为偏行列式关于每个自变量的向量函数: ,符号称为nabla

在定义最快下降方向和速率的点处的函数的非零位。使用梯度增长时,我们对成本函数增长最快的方向感兴趣。这个方向由负梯度决定,即。

梯度增长背后的想法

为了理解梯度增长算法,想象一滴水从碗的侧面滑落,或者一个球从山上滚下来。液滴和球倾向于以最快的增长方向进行交流,直到它们到达顶部。随着时间的推移,由于重力加速度,它们下落得更快。

梯度增长的思路类似:从任意选择的点或向量位置开始,向代价函数增长最快的方向迭代。如前所述,这是负梯度向量的方向,即。

一旦我们有一个随机起点,更新它,或者将它移动到负梯度方向的新位置: ,其中(发音为“ee-tah”)是一个小的负值,称为学习率。

学习率决定了更新或互连步骤的大小。这是一个非常重要的参数。如果它太小,算法可能会收敛得很慢。较大的值也会导致收敛问题或使算法发散。

基本梯度增长的实现

现在我们知道了基本梯度增长的工作原理,我们可以在 Python 中实现它。我们将只使用数据科学估计包 NumPy 来执行此操作,这有助于我们能够在几行代码中处理字段(或向量),并且具有出色的性能。

这是梯度增长算法的基本实现:它从任意点开始,迭代到最小值,并返回一个预期处于或接近最小值的点。

def gradient_descent(gradient, start, learn_rate, n_iter):
    vector = start
    for _ in range(n_iter):
        diff = -learn_rate * gradient(vector)
        vector += diff
    return vector

gradient_descent() 有四个参数。

gradient 是一个函数或任何 Python 可调用函数,它接受一个向量并返回使目标最小化的函数的梯度。

start 是算法开始搜索的点,以序列(元组、列表、NumPy 链表等)或标量(在一维问题中)给出。

learn_rate 是控制向量更新幅度的学习率。

n_iter 是迭代次数。

这个函数做的和之前描述的完全一样:它取一个起点(第 2 行),根据学习率和梯度值迭代更新(第 3 到 5 行),最后返回最后找到的位置。

在我们应用 gradient_descent() 之前,我们可以添加另一个中止标准。

import numpy as np
def gradient_descent(
    gradient, start, learn_rate, n_iter=50, tolerance=1e-06 
    ):
    vector = start
    for _ in range(n_iter):
        diff = -learn_rate * gradient(vector)
        if np.all(np.abs(diff) <= tolerance):
            break
        vector += diff
    return vector

我们现在有一个额外的参数容差(第 4 行),它指定了每次迭代中允许的最小移动量。我们还为tolerance和n_iter定义了默认值,那么为什么每次调用gradient_descent()时都需要指定它们。

第 9 行和第 10 行的 gradient_descent() 停止迭代并在到达 n_iter 之前返回结果;如果当前迭代中的向量更新大于或等于容差,它也会停止迭代并返回结果,这通常发生在最小值附近,这里的梯度通常非常小。有时它也可能发生在局部最小值或鞍点附近,在那里很容易陷入局部最小值。

第 9 行使用便利的 NumPy 函数 numpy.all()[1] 和 numpy.abs()[2] 来比较单个句子中 diff 和容差的绝对值。

现在第一个版本的 gradient_descent() 已经可用,让我们测试一下这个函数:从一个小的反例开始,找到函数²的最小值。

该函数只有一个参数 ( ),其梯度是行列式。它是一个微调的凸函数 [3],从解剖学上找到它的最小值很简单。然而,在实践中,很难甚至不可能剖析函数的微分,这些微分通常在数值上近似 [4]。

只需要一句话来测试梯度提升。

>>> gradient_descent(
...     gradient=lambda v: 2 * v, start=10.0, learn_rate=0.2
... )
2.210739197207331e-06

使用 lambda 函数 lambdav:2*v 提供 ² 的梯度。将初始值设置为 10.0,将学习率设置为 0.2。得到的结果非常接近于零,这被认为是正确的最小值。

右图显示了迭代中解的运动轨迹。

我们从最左边的绿点 ( ) 开始,朝着最小值 ( ) 努力。一开始,梯度(和斜率)的值很高,更新量很大。当接近最小值时,梯度显得更低,接近 0。

学习率的影响

学习率是算法的一个特别重要的参数。不同的学习率值可以显着影响梯度增长的行为。继续使用以下示例,将学习率设置为 0.2 而不是 0.8。

>>> gradient_descent(
...     gradient=lambda v: 2 * v, start=10.0, learn_rate=0.2
... )
2.210739197207331e-06

结果将是另一个非常接近零的值,但算法的内部行为不同。如右图所示, 的值是迭代中发生的情况。

在这些情况下,相同的初始值被设置为 ,但是由于学习率很高,我们得到了很大的变化,它被传递到最优状态的另一侧,这使得它成为 -6。在接近零之前,它又经过了几次。

小的学习率会导致特别平滑的收敛。如果迭代次数有限,算法可能会在找到最小值之前返回。否则,整个过程可能需要很长时间。为了说明这一点,让我们以更小的学习率 0.005 再次运行 gradient_descent()。

>>> gradient_descent(
...     gradient=lambda v: 2 * v, start=10.0, learn_rate=0.005
... )
6.050060671375367

当前的结果是6.05,与真正的最小值0相差甚远。由于学习率小,向量变化很小。

搜索过程和以前一样开始,但不可能在五十次迭代中达到零。然而迭代尺度算法中的w收敛,一百次迭代后,偏差会小得多,而且在一千次迭代后才非常接近于零。

>>> gradient_descent(
...     gradient=lambda v: 2 * v, start=10.0, learn_rate=0.005,
...     n_iter=100
... )
3.660323412732294
>>> gradient_descent(
...     gradient=lambda v: 2 * v, start=10.0, learn_rate=0.005,
...     n_iter=1000
... )
0.0004317124741065828
>>> gradient_descent(
...     gradient=lambda v: 2 * v, start=10.0, learn_rate=0.005,
...     n_iter=2000
... )
9.952518849647663e-05

非凸函数可能有局部最小值或鞍点,算法可能会陷入局部最小值并被压死。在这些情况下,我们对学习率或起点的选择可以在找到局部最小值和找到全局最小值之间产生差异。

考虑功能。其全局最小值为 at,其局部最小值为 at。这个函数的梯度是 。让我们看看 gradient_descent() 在这里是如何工作的。

>>> gradient_descent(
...     gradient=lambda v: 4 * v**3 - 10 * v - 3, start=0,
...     learn_rate=0.2
... )
-1.4207567437458342

这次我们从零开始,算法在局部最小值附近结束。以下是引擎盖下发生的事情。

在前两次迭代中,我们的向量连接到全局最小值,但随后它越过并仍被困在局部最小值中。我们可以用小的学习率来避免这些情况。

>>> gradient_descent(
...     gradient=lambda v: 4 * v**3 - 10 * v - 3, start=0,
...     learn_rate=0.1
... )
1.285401330315467

当我们将学习率从 0.2 降低到 0.1 时,我们得到一个非常接近全局最小值的解。这一次,避免跳到另一边。虽然,梯度增长是一个近似值。

较低的学习率避免了向量中的大跳跃,在这种情况下,向量始终接近全局最优值。

调整学习率很棘手,不可能提前知道最优值。有许多技术和启发式方法试图帮助解决这个问题。机器学习工程师经常在模型选择和评估过程中调整学习率。

不仅学习率,而且初始值通常对解决方案有很大影响,尤其是对于非凸函数。

梯度增长算法的应用

在本节中,一起学习两个使用梯度增长的反例。我们都会了解到它可以用于现实生活中的机器学习问题,例如线性回归。在第二种情况下,我们需要更改 gradient_descent() 的代码,因为我们需要观察到的数据来估计梯度。

示例 1

首先,我们将 gradient_descent() 应用于另一个一维问题。以函数为例。这个函数的梯度是 。根据这些信息,我们可以找到它的最小值。

>>> gradient_descent(
...     gradient=lambda v: 1 - 1 / v, start=2.5, learn_rate=0.5
... )
1.0000011077232125

通过传入一组适当的参数,gradient_descent() 正确估计此函数的最小值为 。我们可以用其他学习率和起点的值来试试。

我们还可以对多个变量的函数使用 gradient_descent()。应用程序是一样的,我们需要提供梯度和起点作为向量或链表。例如,我们可以使用梯度 vector() 找到函数的最小值。

>>> gradient_descent(
...     gradient=lambda v: np.array([2 * v[0], 4 * v[1]**3]),
...     start=np.array([1.0, 1.0]), learn_rate=0.2, tolerance=1e-08
... )
array([8.08281277e-12, 9.75207120e-02])

在这些情况下,我们的梯度函数的起始值是一个链表,并且返回一个链表结果。结果值几乎等于零,因此我们可以说 gradient_descent() 正确地找到了该函数的最小值。

普通最小二乘加法

相信大家都学过线性回归和普通最小二乘加法[5],从输入₁, , ᵣ到输出,定义了一个线性函数₀₁ᵣᵣ,让它尽可能的接近。

这似乎是一个优化问题,以找到使平方方差之和或均方偏差最小化的权重 ₀₁ᵣ。这是观察的总数,

虽然我们也可以使用成本函数,它在物理上比 SSR 或 MSE 更方便。

线性回归的最基本形式是简单线性回归。它只有一组输入和两个权重₀和₁,回归线的多项式是₀₁。虽然 ₀ 和 ₁ 的最优值可以通过 profiling [6] 来估计,但我们这里使用梯度增长来确定结果。

首先,需要微积分来找到成本函数的梯度ᵢᵢ₀₁ᵢ²。由于有两个决策变量 ₀ 和 ₁,梯度是一个包含两个分量的向量。

1.₀ᵢ₀₁ᵢᵢ₀₁ᵢᵢ

2.₁ᵢ₀ᵢᵢᵢ₀ᵢᵢᵢ

我们需要总和的值来估计这个成本函数的梯度,除了 ₀ 和 ₁ 之外,它还有一个和作为输入。

def ssr_gradient(x, y, b):
    res = b[0] + b[1] * x - y
    return res.mean(), (res * x).mean()  
    # .mean() is a method of np.ndarray

ssr_gradient() 接收包含观察输入和输出的字段的总和,以及保存决策变量 ₀ 和 ₁ 的当前值的字段。该函数首先估计每个观测值的方差场 (res),然后返回一对 ₀ 和 ₁。

这里 NumPy 方法 ndarray.mean() 用于对传递的 NumPy 字段参数进行平均。

gradient_descent() 需要两个小的调整:

1.在第 4 行将 和 作为参数添加到 gradient_descent()。

2. 将总和提供给梯度函数,并确保我们在第 8 行将梯度元组转换为 NumPy 字段。

这是更改后的 gradient_descent() 的样子。

import numpy as np
def gradient_descent(
    gradient, x, y, start, learn_rate=0.1, n_iter=50, tolerance=1e-06
):
    vector = start
    for _ in range(n_iter):
        diff = -learn_rate * np.array(gradient(x, y, vector))
        if np.all(np.abs(diff) <= tolerance):
            break
        vector += diff
    return vector

gradient_descent() 现在接受输入和输出,并估计梯度。将 gradient(x,y,vector) 的输出转换为 NumPy 链表,以便将梯度元素添加到学习率中,对于单变量函数可以省略。

现在获取新的 gradient_descent() 以找到一些任意 x 和 y 值的回归线。

>>> x = np.array([5, 15, 25, 35, 45, 55])
>>> y = np.array([5, 20, 14, 32, 22, 38])
>>> gradient_descent(
...     ssr_gradient, x, y, start=[0.5, 0.5], learn_rate=0.0008,
...     n_iter=100_000
... )
array([5.62822349, 0.54012867])

结果是一个具有两个数值的字段,对应于决策变量: 和 。最好的回归线是 。和上面的例子一样,这个结果很大程度上取决于学习率。如果学习率太低或太高,你都可能得不到这么好的结果。

实际上这种情况并不是完全随机的,但我们得到的结果几乎与 scikit-learn 的线性回归器相同。我们可视化这条回归线的数据和回归结果。

代码改进

我们可以在不改变其核心功能的情况下让 gradient_descent() 更强大、更全面、更美观。

import numpy as np
def gradient_descent(
    gradient, x, y, start, learn_rate=0.1, n_iter=50, tolerance=1e-06,
    dtype="float64"
):
    # 检查梯度是否可调用
    if not callable(gradient):
        raise TypeError("'gradient' must be callable")
    # 设置NumPy数组的数据类型
    dtype_ = np.dtype(dtype)
    # 将x和y转换为NumPy数组
    x, y = np.array(x, dtype=dtype_), np.array(y, dtype=dtype_)
    if x.shape[0] != y.shape[0]:
        raise ValueError("'x' and 'y' lengths do not match")
    # 设置变量初始值
    vector = np.array(start, dtype=dtype_)
    # 设置并检查学习率
    learn_rate = np.array(learn_rate, dtype=dtype_)
    if np.any(learn_rate <= 0):
        raise ValueError("'learn_rate' must be greater than zero")
    # 设置并检查最大的迭代次数
    n_iter = int(n_iter)
    if n_iter <= 0:
        raise ValueError("'n_iter' must be greater than zero")
    # 设置并检查公差
    tolerance = np.array(tolerance, dtype=dtype_)
    if np.any(tolerance <= 0):
        raise ValueError("'tolerance' must be greater than zero")
    # 执行梯度下降循环
    for _ in range(n_iter):
        # 重新计算差异
        diff = -learn_rate * np.array(gradient(x, y, vector), dtype_)
        # 检查绝对差异是否足够小
        if np.all(np.abs(diff) <= tolerance):
            break
        # 更新变量的值
        vector += diff
    return vector if vector.shape else vector.item()

gradient_descent() 现在接受一个附加的 dtype 参数,该参数定义函数内部 NumPy 链表的数据类型。

在大多数应用程序中,我们不会注意到 32 位和 64 位浮点数之间的区别,但是当我们处理大型数据集时,这会显着影响内存使用,甚至可能影响处理速率。例如,虽然 NumPy 默认使用 64 位浮点数,但 TensorFlow 通常使用 32 位浮点数。

除了考虑数据类型之外,里面的代码还引入了一些与类型检测相关的变化,确保能够使用 NumPy。

至此,我们的 gradient_descent() 就完成了。然后我们学习随机梯度增长算法的具体实现。

随机梯度增长算法

随机梯度增长算法是梯度增长的一种变体。在随机梯度增长中,我们仅使用观察值的随机部分而不是所有观察值来估计梯度。在个别情况下,这些方法可以减少估计时间。

Online Stochastic Gradient Growth 是 Stochastic Gradient Growth 的一种变体,它采用每个观察的成本函数的梯度并相应地更新决策变量。这有助于找到全局最小值,尤其是当目标函数是凸函数时。

Batch 随机梯度增长法介于普通梯度增长法和在线法之间。梯度的估计和决策变量的更新是在所有观察的子集上完成的,称为小批量。这些变体在训练神经网络时很受欢迎。

我们可以将 Online 算法视为一种特殊的批处理算法,其中每个 minibatch 只有一个观察值。经典梯度增长是另一种特殊情况,其中只有一个批次包含所有观察值。

随机梯度增长中的最小批次

与普通梯度增长一样,随机梯度增长从决策变量的初始向量开始,并在多次迭代中更新。两者的区别在于迭代过程中发生的事情。

该算法随机选择小批量观察,我们可以使用随机数生成器模拟这些随机(或伪随机)行为。Python 有一个外部 ramdom 模块,而 NumPy 有自己的随机生成器。前者在使用链表时更方便。

创建一个名为 sgd() 的新函数,它与 gradient_descent() 非常相似,但使用随机选择的小批量在搜索空间中进行通信。

上下滑动查看更多源码


import numpy as np
def sgd(
    gradient, x, y, start, learn_rate=0.1, batch_size=1, n_iter=50,
    tolerance=1e-06, dtype="float64", random_state=None
):
    # 检查梯度是否可调用
    if not callable(gradient):
        raise TypeError("'gradient' must be callable")
    # 设置NumPy数组的数据类型
    dtype_ = np.dtype(dtype)
    # 将x和y转换为NumPy数组
    x, y = np.array(x, dtype=dtype_), np.array(y, dtype=dtype_)
    n_obs = x.shape[0]
    if n_obs != y.shape[0]:
        raise ValueError("'x' and 'y' lengths do not match")
    xy = np.c_[x.reshape(n_obs, -1), y.reshape(n_obs, 1)]
    # 初始化随机数发生器
    seed = None if random_state is None else int(random_state)
    rng = np.random.default_rng(seed=seed)
    # 初始化变量的值
    vector = np.array(start, dtype=dtype_)
    # 设置并检查学习率
    learn_rate = np.array(learn_rate, dtype=dtype_)
    if np.any(learn_rate <= 0):
        raise ValueError("'learn_rate' must be greater than zero")
    # 设置并检查迷我们批的大小
    batch_size = int(batch_size)
    if not 0 < batch_size <= n_obs:
        raise ValueError(
            "'batch_size' must be greater than zero and less than "
            "or equal to the number of observations"
        )
    # 设置并检查最大的迭代次数
    n_iter = int(n_iter)
    if n_iter <= 0:
        raise ValueError("'n_iter' must be greater than zero")
    # 设置并检查公差
    tolerance = np.array(tolerance, dtype=dtype_)
    if np.any(tolerance <= 0):
        raise ValueError("'tolerance' must be greater than zero")
    # 执行梯度下降循环
    for _ in range(n_iter):
        # 打乱 x 和 y
        rng.shuffle(xy)
        # 执行小批量移动
        for start in range(0, n_obs, batch_size):
            stop = start + batch_size
            x_batch, y_batch = xy[start:stop, :-1], xy[start:stop, -1:]
            # 重新计算差异
            grad = np.array(gradient(x_batch, y_batch, vector), dtype_)
            diff = -learn_rate * grad
            # 检查绝对差异是否足够小
            if np.all(np.abs(diff) <= tolerance):
                break
            # 更新变量的值
            vector += diff
    return vector if vector.shape else vector.item()

这里我们有一个新参数batch_size,它指定每个小批量中的观察数。这是随机梯度增长法的一个重要参数,可以显着影响性能,并保证batch_size为正整数且不小于观察总数。

另一个新参数是 random_state。它定义了随机数生成器的种子。种子之前被用作 default_rng() 的参数,它创建了一个 Generator 的实例。

如果为 random_state 传递 None,则随机数生成器每次实例化时都会返回不同的数字。如果我们希望生成器的每个实例的行为完全相同,那么我们需要指定随机数种子 – 最简单的方法是提供任意整数。

使用 x.shape[0] 推断观察次数。如果它是一维字段,那么这就是它的大小。如果有两个维度,则 .shape[0] 是行数。

使用 .reshape() 确保 和 都成为具有 n_obs 行和恰好一列的 2D 字段。numpy.c_[] 方便地将 和 的列连接到链表中。这是一种使数据适合随机选择的技术。

最后,实现了一个带有随机梯度增长的for循环,这与gradient_descent()不同。我们使用随机数生成器及其方式 .shuffle() 来打乱观察结果。这是随机选择小批量的方法之一。

对于每个小批量重复外部 for 循环。与正常梯度增长方法的主要区别在于,梯度是针对小批量观察(x_batch 和 y_batch)而不是所有观察(和)估计的。

x_batch 成为 xy 的一部分,包含当前 minibatch 的行(从开始到结束)和对应的列。

现在我测试随机梯度增长的实现。

>>> sgd(
...     ssr_gradient, x, y, start=[0.5, 0.5], learn_rate=0.0008,
...     batch_size=3, n_iter=100_000, random_state=0
... )
array([5.63093736, 0.53982921])

结果与使用 gradient_descent() 获得的结果几乎相同。如果省略了 random_state 或使用了 None,那么每次运行 sgd() 时都会得到一些不同的结果,因为随机数生成器对 xy 的洗牌方式不同。

随机梯度增长的动量

我们知道学习率会对梯度增长的结果产生重大影响。我们可以在算法执行期间使用几种不同的策略来调整学习率,或者我们可以将 Momentum 应用于算法。

学习率的影响可以使用动量来校正,其想法是记住向量的先前更新并在估计下一次更新时应用它。向量不会在负梯度的方向上完全连接起来,但它也倾向于保持之前连接的方向和大小。

一个称为衰减率或衰减因子的参数定义了原始更新的贡献有多大。要包括动量和衰减率,我们可以更改 sgd() 以降低参数 decay_rate,该参数用于估计向量更新 (diff) 的方向和幅度。

上下滑动查看更多源码

import numpy as np
def sgd(
    gradient, x, y, start, learn_rate=0.1, 
    decay_rate=0.0, batch_size=1, n_iter=50,
    tolerance=1e-06, dtype="float64", random_state=None
):
    # 检查梯度是否可调用
    if not callable(gradient):
        raise TypeError("'gradient' must be callable")
    # 设置NumPy数组的数据类型
    dtype_ = np.dtype(dtype)
    # 将x和y转换为NumPy数组
    x, y = np.array(x, dtype=dtype_), np.array(y, dtype=dtype_)
    n_obs = x.shape[0]
    if n_obs != y.shape[0]:
        raise ValueError("'x' and 'y' lengths do not match")
    xy = np.c_[x.reshape(n_obs, -1), y.reshape(n_obs, 1)]
    # 初始化随机数发生器
    seed = None if random_state is None else int(random_state)
    rng = np.random.default_rng(seed=seed)
    # 初始化变量的值
    vector = np.array(start, dtype=dtype_)
    # 设置并检查学习率
    learn_rate = np.array(learn_rate, dtype=dtype_)
    if np.any(learn_rate <= 0):
        raise ValueError("'learn_rate' must be greater than zero")
    # update
    # 设置和检查衰减率
    decay_rate = np.array(decay_rate, dtype=dtype_)
    if np.any(decay_rate  1):
        raise ValueError("'decay_rate' must be between zero and one")   
     
    # 设置并检查迷我们批的大小
    batch_size = int(batch_size)
    if not 0 < batch_size <= n_obs:
        raise ValueError(
            "'batch_size' must be greater than zero and less than "
            "or equal to the number of observations"
        )
    # 设置并检查最大的迭代次数
    n_iter = int(n_iter)
    if n_iter <= 0:
        raise ValueError("'n_iter' must be greater than zero")
    # 设置并检查公差
    tolerance = np.array(tolerance, dtype=dtype_)
    if np.any(tolerance <= 0):
        raise ValueError("'tolerance' must be greater than zero")
    # 将第一次迭代的差值设置为零
    diff = 0
    # 执行梯度下降循环
    for _ in range(n_iter):
        # 打乱 x 和 y
        rng.shuffle(xy)
        # 执行小批量移动
        for start in range(0, n_obs, batch_size):
            stop = start + batch_size
            x_batch, y_batch = xy[start:stop, :-1], xy[start:stop, -1:]
            # 重新计算差异
            grad = np.array(gradient(x_batch, y_batch, vector), dtype_)
            # diff = -learn_rate * grad
            diff = decay_rate * diff - learn_rate * grad
            # 检查绝对差异是否足够小
            if np.all(np.abs(diff) <= tolerance):
                break
            # 更新变量的值
            vector += diff
    return vector if vector.shape else vector.item()

在这里,在参数行中添加了death_rate参数,在第34行转换为所需类型的NumPy链表,并在第35和36行检查0和1之间。在第57行,我们在迭代开始之前初始化差异,以确保它在第一次迭代时可用。

最重要的变化发生在第 71 行,用学习率和梯度重新估计 diff,还加上了 decay_rate 和 diff 的旧值的乘积。今天的差异有两个组成部分。

decay_rate*diff 指的是动量,或者是前一个动作的效果。

**-learn_rate*grad** 是当前梯度的效果。

衰减率和学习率作为权重来定义两者的贡献。

随机起始值

相对于普通的梯度增长,初始值对于随机梯度增长往往不太重要。还有,如果每次都需要自动设置,那可能就没有必要了,试想如果我们需要自动初始化一个有上千个偏差和权重的神经网络的值,太麻烦了!

在实践中,我们可以使用随机数生成器来获取它们,并从一些小的任意值开始。

我们更新函数 sgd()。

def sgd(
    gradient, x, y, n_vars=None, start=None, learn_rate=0.1,
    decay_rate=0.0, batch_size=1, n_iter=50, tolerance=1e-06,
    dtype="float64", random_state=None
):
    # 初始化变量值
    vector = (
        rng.normal(size=int(n_vars)).astype(dtype_)
        if start is None else
        np.array(start, dtype=dtype_)
    )
    # 其余不变

这里增加了一个新的参数n_vars,它定义了问题中决策变量的数量。参数start是可选的,其默认值为None,初始化决策变量的初始值。

试试 sgd():

>>> sgd(
...     ssr_gradient, x, y, n_vars=2, learn_rate=0.0001,
...     decay_rate=0.8, batch_size=3, n_iter=100_000, random_state=0
... )
array([5.63014443, 0.53901017])

再次获得了类似的结果。

到目前为止,我们已经学会了如何编写实现梯度增长和随机梯度增长的函数。里面的代码可以显得更强大、更精致。此外,我们可以在一些著名的机器学习库中找到这种方法的不同实现。

总结

我们现在知道什么是梯度增长和随机梯度增长算法以及它们是如何工作的,它们被广泛用于人工神经网络应用中。

现在我们学会了:

我们已经使用梯度增长和随机梯度增长来找到几个函数的最小值并拟合线性回归问题中的回归线。

参考

[1]

numpy.all():

[2]

numpy.abs():

[3]

凸函数:

[4]

数值方式:

[5]

普通最小二乘除法:

[6]

解剖估计:#Simple_linear_regression_model

[7]

numpy.dtype:

[8]

NumPy.normal():


过去的评论

“今天,99%以上的代码都是垃圾!”

马斯克称自己已将大脑上传到云端,网友热议!

这是撒贝宁第一次与数字人合作主持!

7岁女孩下棋太快被机器人截断?

分享
点收藏
点点赞
点在看

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 网站程序 【干货】机器学习应用中的一种优化算法(二) http://www.wkzy.net/game/8173.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务