S'S ALGORITHM

神经网络:优化算法和它的进化


优化算法

优化算法的目的,是不断更新神经网络的权重,使损失函数不断最小化。

如果说损失函数关于权重的函数是 C(w),那么优化算法的过程就是,不断计算该函数的梯度,然后乘以学习率,用以更新现有的权重矩阵,最终使得权重矩阵在函数 C(w) 中得到的结果是全局最小,也就意味着损失最小了。使用 Python 对这一个过程的表达如下:

for t in range(steps):
    dw = gradient(loss, data, w)
    w = w - learning_rate * dw

在上述代码中看到有 step 这个变量,他是在整个优化过程中的步长,那么如何定义每一步多长,至关重要,因为它关系到算法的效率,结果,以及算力等要素。

主要有三种梯度下降等优化算法:批量梯度下降(Batch Gradient Descent),随机梯度下降(Stochastic Gradient Descent),以及小批量随机梯度下降(Mini-batch Stochastic Gradient Descent)。

批量梯度下降每次更新参数时都使用整个训练数据集的数据进行计算。它的主要优点是在更新参数时使用了整个训练数据集,因此可以更准确地估计梯度,尤其是当训练数据集相对较小且能够全部加载到内存中时。此外,批量梯度下降通常会更稳定,更容易收敛到全局最优解。因为每次更新都是基于整个数据集的信息。然而,批量梯度下降也具有一些缺点,主要是在处理大型数据集时,需要消耗大量的内存和计算资源,并且收敛速度可能较慢。

随机梯度下降在每次更新参数时只使用一个样本(或一个小批量样本)的数据进行计算。与批量梯度下降相比,随机梯度下降的更新更为频繁,但每次更新的方向并不是完全基于整个数据集的信息,而是基于单个样本或小批量样本的梯度信息。它的优点在于,适合更大的数据集,更新速度更快,还不容易陷入局部最优。但同时也可以想像,由于每次都是随机的小样本更新,所以存在不稳定性,收敛速度很慢,有时候可能不利于更新参数,错过全局最优解的位置。

附上随机梯度下降算法的代码:

for t in range(steps):
    for example in data:
        dw = gradient(loss, example, w)
        w = w - learning_rate * dw

因此小批量的随机梯度下降算法,就是考虑了上述两种算法的一种融合后的小进化。在每次更新参数时,小批量梯度下降会使用一小部分(小批量)样本的数据进行计算梯度,而不是全部样本或单个样本。这样既可以利用数据的并行性,又可以减少参数更新的方差,使得优化过程更稳定且更快速。但是在实际应用中要考虑好批次的大小,以及内存消耗的平衡。一种说法是 32 的大小批次有利于你的计算机健康,这是当下的一种不错的经验之谈,当然根据你的算力,贷款,开销,你也可以看情况增加。

附上小批量随机梯度下降的代码:

for t in range(steps):
    for mini_batch in get_batches(data, batch_size):
        dw = gradient(loss, mini_batch, w)
        w = w - learning_rate * dw

鞍点问题

优化算法的鞍点问题是指在优化过程中遇到的一种特殊情况,即算法陷入了局部最小值或局部最大值附近的某个点,该点既不是全局最优解也不是局部最优解,而是一个相对平坦的区域。(想象一匹马的鞍部,中间平坦,两边是瀑布,前后是翘起,但是中间是平的,也就是梯度为零,但是这个点不是最大也不是最小,非常尴尬)在这种情况下,优化算法可能会停滞不前或收敛速度变得非常缓慢,导致算法无法找到满意的解。

鞍点问题的主要是:优化算法的步长和方向选择不当。

因此想要解决问题就要在学习率,等方面找策略,于是进化出很多新的要素。比如:

增加动量(momentum)

动量的基本思想是在梯度下降的过程中不仅考虑当前梯度指向的方向,还要考虑之前梯度变化的趋势。具体来说,动量方法引入了一个参数(通常称为动量参数或动量系数),用来表示之前梯度更新的累积效果。在每次参数更新时,动量项将根据当前梯度和之前动量的方向进行加权平均,并用于更新参数。

直观的代码表达:

for t in range(steps):
    dw = gradient(loss, w)
    v = rho * v + dw
    w = w - learning_rate * v

其中 v 为速度,rho 为摩擦力或者叫动量系数。

动量优化算法中的动量系数的概念来源于物理学中的动量和摩擦力。在物理学中,动量是物体运动的量度,它等于物体的质量乘以其速度。而摩擦力则是一种阻碍物体运动的力,它的大小与物体的速度成正比。这两个概念对动量优化算法的理解提供了直观的解释。

在动量优化算法中,动量项 v 可以被理解为参数更新的速度。具体来说,它是历史梯度的加权和,表示了参数更新的方向和速度。动量系数控制了历史动量对当前速度的影响程度。如果动量系数较大,那么历史动量对当前速度的影响较大,参数更新的速度会更快;反之,如果动量系数较小,那么历史动量对当前速度的影响较小,参数更新的速度会更慢。

在物理学中,动量系数可以被类比为摩擦系数。摩擦系数越大,物体的运动速度受到的摩擦力越大,物体的运动速度就会更快地减小。类似地,在动量优化算法中,动量系数越大,历史动量对当前速度的影响越大,参数更新的速度就会更快地减小,从而使参数更新更加平滑和稳定。

因此,可以将动量系数视为控制参数更新速度和稳定性的重要超参数,其选择类比于在物理学中选择摩擦系数以控制物体运动的速度和稳定性。

对物理的一点补充说明:在物理学中,摩擦力与速度之间的关系是复杂的,并不是简单的正比或反比关系。摩擦力通常被分为静摩擦力和动摩擦力两种。静摩擦力是在物体尚未开始滑动之前阻止物体运动的力,通常与外力的大小相等,但方向相反,因此静摩擦力与外力成正比。而动摩擦力是在物体已经开始滑动之后阻止物体继续滑动的力,其大小与物体的速度有关,通常近似与速度成正比。也就是说,动摩擦力随着速度的增加而增加,但增加的速率是非线性的,并不是严格的正比关系。因此,摩擦力与速度之间的关系不能简单地描述为正比或反比关系,而是一个复杂的非线性关系。在动量优化算法中,动量系数的选择与物理中的摩擦系数类比,并不是严格的物理模型,而是一种启发式的方法,用来平滑参数更新的过程,提高算法的收敛速度和稳定性。

优化算法 Adagrad

Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,旨在解决在训练深度神经网络时学习率设置困难的问题。它通过对每个参数应用不同的学习率,根据其历史梯度的大小自适应地调整学习率,从而在训练过程中更好地适应不同参数的变化情况。

Adagrad 的核心思想是根据每个参数的历史梯度来调整学习率。具体来说,对于每个参数 Wi,Adagrad 维护一个累积的梯度平方的指数加权移动平均(exponentially weighted moving average),并将其作为学习率的分母。这样可以使得对于频繁出现的参数更新,学习率变小,而对于不经常出现的参数更新,学习率变大,从而在梯度较小的方向上前进得更快,而在梯度较大的方向上前进得更慢。

直观代码表达:

for t in range(steps):
    dw = gradient(loss, w)
    squared_gradients += dw * dw
    w = w - learning_rate * dw / (squared_gradients.sqrt() + e) 

但是这个算法,适用于稀疏数据。由于使用了梯度的平方来调整学习率,因此对于稀疏数据中的梯度较大的参数,学习率会自动变小,从而减少了参数更新的幅度,防止其占据主导地位。然而,Adagrad 也有一些缺点,主要是随着训练的进行,累积的梯度平方会不断增大,导致学习率逐渐减小,可能会导致学习率过早衰减,使得模型收敛速度变慢。为了解决这个问题,后续出现了一些改进的算法,如 RMSProp 和 Adam 等。

优化算法 RMSprop (Leaky Adagrad)

从它的别名就可以看出它是 Adagrad 的进化算法。本质上,就是通过衰减先前的梯度平方和再次添加摩擦的概念。

RMSprop 的核心思想是对历史梯度的平方进行指数加权移动平均,并将其作为学习率的分母,从而减缓学习率的衰减速度。与 Adagrad 不同的是,RMSprop 使用一个衰减系数来控制历史梯度的衰减速度,使得学习率的衰减更加平缓。

直观的代码表达:

for t in range(steps):
    dw = gradient(loss, w)
    # 这里和 Adagrad 不同
    squared_gradients = decay_rate * squared_gradients + (1 - decay_rate) * dw * dw
    w = w - learning_rate * dw / (squared_gradients.sqrt() + e)

分母是梯度的均方根误差 (RMS),这也是该算法的名称。

优化算法 Adam

Adam 是现在最流行的优化算法。结合了动量法(momentum)和 RMSprop (自适应学习率)算法的优点,旨在解决梯度下降优化算法中学习率难以设置的问题,并提高了优化算法的稳定性和收敛速度。

Adam 算法的核心思想是利用一阶矩估计和二阶矩估计来自适应地调整参数的学习率。它维护了两个动量项,一个是对梯度的一阶矩估计(即梯度的指数加权移动平均),另一个是对梯度的二阶矩估计(即梯度的平方的指数加权移动平均)。这两个动量项分别用来调整参数的方向和步长,从而使得参数更新更加平滑和稳定。

直观代码和解释:

for t in range(steps):
    dw = gradient(loss, w)
    moment1 = delta1 * moment1  + (1 - delta1) * dw
    moment2 = delta2* moment2 + (1 - delta2) * dw * dw
    moment1_unbiased = moment1  / (1 - delta1**t)
    moment2_unbiased = moment2  / (1 - delta2**t)
    w = w - learning_rate * moment1_unbiased / (moment2_unbiased.sqrt() + e)

总结一下 Adam 算法的优点:

什么是矩,当我们谈论随机变量的矩时,可以将其比喻为描述随机变量数据的一种方式,类似于我们在描述一组数据时使用的平均值、方差等统计量。想象一组数据,比如一群学生的身高。我们可以用各种统计量来描述这组数据的特征,比如平均身高、身高的波动程度等。类似地,随机变量的矩也是一种描述数据特征的工具,只不过这里的“数据”是随机变量的取值。举个例子,假设我们有一个随机变量表示投掷一个骰子得到的点数。这个随机变量的一阶矩(或称期望)就是所有可能点数的平均值,二阶矩(或称方差)表示了点数偏离平均值的程度。在这个例子中,一阶矩告诉我们投掷骰子的平均点数是多少,二阶矩告诉我们点数偏离平均值的程度。这些矩提供了关于随机变量分布特征的信息,帮助我们理解随机变量的性质。总的来说,随机变量的矩就是一种描述随机变量特征的统计量,它们帮助我们理解随机变量的分布、中心位置和离散程度。