机器学习(九):学习率与学习率调度

全文共7000余字,预计阅读时间约15~25分钟 | 满满干货,建议收藏!

在这里插入图片描述

一、引言

在机器学习领域,尤其是深度学习领域,梯度下降算法是最基本也是最重要的优化技术之一。然而,为了确保算法的效率和有效性,通常需要对其进行一些优化操作。在这些优化策略中,数据的标准化和学习率调度是两个最关键的方面。在之前的博客文章:机器学习(八):梯度下降优化_数据归一化,已经详细介绍了数据标准化如何通过消除特征的量纲差异,帮助梯度下降算法更快地找到最优解。在这篇文章中,补充另一种同样重要的优化策略——学习率调度。

二、什么是学习率

梯度下降优化的核心目标就是希望“更快更好”的找到最小值点,上篇文章中讲的归一化是通过修改损失函数来达成这个目标,而学习率调度,则是通过调整学习率来达到这个目标。

它是一个控制模型参数更新速度的超参数。在进行梯度下降优化时,学习率决定了每次参数更新的步长。

可以通过以下的公式来更好地理解学习率的作用:

新的参数值 = 原来的参数值 − 学习率 ∗ 梯度 (1) 新的参数值 = 原来的参数值 - 学习率 * 梯度 \tag{1} 新的参数值=原来的参数值学习率梯度(1)

在这个公式中,"梯度"是损失函数相对于模型参数的偏导数,代表了损失函数在当前参数值下的变化趋势。我们的目标是找到一组参数值,使得损失函数的值最小。

学习率控制了我们朝着梯度方向下降的速度或者说步长。如果学习率太大,那么可能会“跳过”最小值,导致模型无法收敛;反之,如果学习率太小,那么优化过程会非常慢,甚至可能停留在一个不理想的局部最小值点。因此,选择合适的学习率对于模型的优化是非常重要的。

看下代码:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 设置随机数种子
np.random.seed(42)

def convex_function(x):
    return x**2

def gradient_descent(initial_x, learning_rate, num_iterations):
    x_values = [initial_x]  # 用于存储每次迭代的x值
    y_values = [convex_function(initial_x)]  # 用于存储每次迭代的函数值
    
    x = initial_x
    
    for i in range(num_iterations):
        gradient = 2 * x  # 函数 f(x) = x^2 的导数为 f'(x) = 2x
        x -= learning_rate * gradient
        
        x_values.append(x)
        y_values.append(convex_function(x))
    
    return x_values, y_values

# 参数设置
initial_x = 4  # 初始值
learning_rates = [0.01, 0.05]  # 学习率
num_iterations = 20

# 生成凸函数的数据
x = np.linspace(-5, 5, 100)  # 生成 x 值
y = convex_function(x)  # 凸函数 f(x) = x^2

# 绘制两个子图
fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# 针对每个学习率进行绘制
for i, learning_rate in enumerate(learning_rates):
    # 运行梯度下降算法
    x_values, y_values = gradient_descent(initial_x, learning_rate, num_iterations)
    
    # 绘制凸函数的曲线
    axs[i].plot(x, y, label="Convex Function")
    
    # 绘制梯度下降轨迹
    axs[i].plot(x_values, y_values, '-o', label=f"Learning Rate: {learning_rate}")
    
    # 设置子图标题、坐标轴标签和图例
    axs[i].set_title(f"Gradient Descent (Learning Rate: {learning_rate})")
    axs[i].set_xlabel("x")
    axs[i].set_ylabel("f(x)")
    axs[i].legend()
    axs[i].grid(True)

# 调整子图之间的间距
plt.tight_layout()

# 显示图形
plt.show()

当初始参数为学习率 0.01 和 0.05 时,该代码通过绘制两个子图来比较两种学习率下的梯度下降轨迹。每个子图显示了一个凸函数的曲线以及使用相应学习率进行梯度下降时的轨迹。

image-20230626093604200

从图像上看,学习率较小(0.01)时,梯度下降的收敛速度较慢;而学习率较大(0.05)时,梯度下降的收敛速度较快。这表明选择适当的学习率对梯度下降算法的性能至关重要,过小的学习率可能导致收敛速度缓慢,而过大的学习率可能导致无法收敛或发散。因此,根据具体问题和数据集的特点,选择合适的学习率是优化算法中的重要考虑因素。

三、学习率与学习率调度

上一小节中理解了学习率及不同的学习率会导致不同的收敛效果,现在需要理解什么是学习率调度。

学习率和学习率调度是密切相关的两个概念,它们都是机器学习和深度学习中优化算法的重要组成部分。

学习率调度是一种策略或者方法,它的目的是在训练过程中动态地调整学习率。初始阶段,可能需要较大的学习率以快速逼近全局最优;随着训练的进行,为了避免在全局最优附近震荡而无法收敛,逐渐降低学习率。通过学习率调度,可以在训练过程中根据需要调整学习率,从而提高模型的训练效率和最终性能。

总的来说,学习率定义了模型学习的速度,而学习率调度则是管理和调整学习率的策略,两者结合使用,可以大大提高模型优化的效果。

四、常见的学习率调度策略

学习率调度方法有很多种,目前流行的也达数十种之多,而其中一种最为通用的学习率调度方法是学习率衰减法,指的是在迭代开始时设置较大学习率,而伴随着迭代进行不断减小学习率。通过这样的学习率设置,能够让梯度下降收敛速度更快、效果更好。

4.1 Step Decay

Step Decay(阶梯衰减)是一种常见的学习率调度策略。顾名思义,它会在训练的特定步骤或者周期(Epoch)降低学习率。通常情况下,会设定一个初始的学习率以及一个衰减因子,然后在特定的训练周期进行学习率的衰减。

具体的衰减公式如下:

d e c a y e d _ l e a r n i n g _ r a t e = l e a r n i n g _ r a t e ∗ d e c a y _ r a t e ( g l o b a l _ s t e p / d e c a y _ s t e p s ) (2) decayed\_learning\_rate = learning\_rate * decay\_rate ^ {(global\_step / decay\_steps)} \tag{2} decayed_learning_rate=learning_ratedecay_rate(global_step/decay_steps)(2)

其中,learning_rate 是初始学习率,decay_rate 是衰减率,global_step 是当前迭代轮数,decay_steps 是衰减速度,即每迭代多少轮就衰减一次。衰减因子通常设定为一个0到1之间的常数,例如0.5或0.1。衰减步长通常设置为一个固定的周期数,例如每10个周期衰减一次。"floor"函数表示向下取整。

这种学习率调度策略的优点是实现简单,并且能够在一定程度上动态地调整学习率。但是,它的缺点是需要手动设定衰减步长和衰减因子,如果设定不合适,可能无法达到最佳的优化效果。

上代码看一下:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 设置随机数种子
np.random.seed(42)

def convex_function(x):
    return x**2

def gradient_descent(initial_x, learning_rate, num_iterations, decay_rate):
    x_values = [initial_x]  # 用于存储每次迭代的x值
    y_values = [convex_function(initial_x)]  # 用于存储每次迭代的函数值
    
    x = initial_x
    
    for i in range(num_iterations):
        gradient = 2 * x  # 函数 f(x) = x^2 的导数为 f'(x) = 2x
        learning_rate *= decay_rate  # 学习率衰减
        x -= learning_rate * gradient
        
        x_values.append(x)
        y_values.append(convex_function(x))
    
    return x_values, y_values

# 参数设置
initial_x = 4  # 初始值
learning_rate = 0.05  # 初始学习率
num_iterations = 20
decay_rate = 0.8  # 衰减率

# 生成凸函数的数据
x = np.linspace(-5, 5, 100)  # 生成 x 值
y = convex_function(x)  # 凸函数 f(x) = x^2

# 创建两个子图
fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# 运行常数学习率的梯度下降算法
x_values_const, y_values_const = gradient_descent(initial_x, learning_rate, num_iterations, decay_rate=1.0)

# 绘制凸函数的曲线和常数学习率的梯度下降轨迹
axs[0].plot(x, y, label="Convex Function")
axs[0].plot(x_values_const, y_values_const, '-o', label="Constant Learning Rate")
axs[0].set_xlabel("x")
axs[0].set_ylabel("f(x)")
axs[0].set_title("Constant Learning Rate")
axs[0].legend()
axs[0].grid(True)

# 运行步长衰减的梯度下降算法
x_values_decay, y_values_decay = gradient_descent(initial_x, learning_rate, num_iterations, decay_rate)

# 绘制凸函数的曲线和步长衰减的梯度下降轨迹
axs[1].plot(x, y, label="Convex Function")
axs[1].plot(x_values_decay, y_values_decay, '-o', label="Step Decay Learning Rate")
axs[1].set_xlabel("x")
axs[1].set_ylabel("f(x)")
axs[1].set_title("Step Decay Learning Rate")
axs[1].legend()
axs[1].grid(True)

# 调整子图之间的间距
plt.tight_layout()

# 显示图形
plt.show()
print(x_values_const[-1])
print(x_values_decay[-1])

image-20230626102946204

很多人在实践中遇到的困惑,包括我刚开始也是,就是:不使用学习率衰减,反而在相同的迭代次数下更接近全局最优值,所以为什么要使用学习率调度?

首先需要明白,学习率调度并不是在所有情况下都会优于常数学习率。它的效果取决于许多因素,包括初始化的参数值、初始学习率、衰减率、衰减步长等等。

从代码和结果来看,可以看出以下几点:

  1. 目标函数是一个凸函数, f ( x ) = x 2 f(x) = x^2 f(x)=x2。这种情况下,如果初始学习率选取得合适,梯度下降算法本身就可以很好地找到全局最优解,因此学习率调度的效果可能不明显。
  2. 初始学习率设置为0.05,这是一个相对较小的值,衰减率设置为0.8,这意味着每次迭代后,学习率都会减小20%。在这种情况下,学习率可能在早期的迭代就已经减小得很小,导致后续的迭代进展得较慢。
  3. 只运行了10次迭代,这是一个相对较少的迭代次数。如果试试更多的迭代次数,会发现,虽然常数学习率的梯度下降在早期的迭代中可能更快地接近最优解,但在后期的迭代中,可能会在最优解附近震荡,无法精确地收敛到最优解。相比之下,学习率调度的梯度下降虽然在早期的迭代中进展较慢,但在后期的迭代中,由于学习率逐渐减小,可能能更精确地收敛到最优解。

以上这些因素都可能影响到学习率调度的效果。大家也可以自行尝试一下修改目标函数,找到使用Step Decay能得到更好结果的实验。

4.2 其他策略

上面探讨了一种最基础的学习率调度策略:Step Decay。实际上还存在许多其他的学习率调度策略,如Exponential Decay、AdaGrad、RMSprop、Adam等,主要应用在深度学习和神经网络的优化中,每一种策略都有其独特的优点,例如,AdaGrad可以根据每个参数的历史梯度信息进行自适应调整,RMSprop则能够防止学习率过早衰减,以适应深度网络的训练,而Adam则结合了RMSprop和Momentum的优点,既可以自适应调整学习率,又可以利用历史梯度进行加速。

考虑到现在写这些内容对读者还不是很友好,在后面神经网络系列中,会详细解析它们。

五、如何在实践中选择和使用学习率调度策略

学习率调度通常与小批量(mini-batch)或者随机梯度下降(Stochastic Gradient Descent, SGD)结合使用,这样做有两个主要原因:

  1. 更好地逼近全局最优:随机梯度下降和小批量梯度下降都会引入一些噪声到梯度更新中,这些噪声可以帮助模型跳出局部最优点,更好地逼近全局最优。然而,这也使得训练过程存在更多的波动。学习率调度策略可以在初期采用较大的学习率以加快训练速度并跳出不良的局部最优,随着训练的进行,逐渐减小学习率,减少波动,使得模型更稳定地接近最优解。
  2. 加速收敛:在训练初期,模型参数通常离最优解很远,此时需要较大的学习率来加快学习速度。然而,当模型接近最优解时,如果仍然使用较大的学习率,可能会导致模型在最优解附近来回震荡,无法收敛。此时如果能逐渐减小学习率,就可以使模型更精细地接近并最终停留在最优解,从而加速收敛。

因此,学习率调度策略与随机或小批量梯度下降的结合使用,可以在保证模型收敛的同时,提高训练的效率和最终模型的性能。

六、总结

在这篇文章中,探讨了学习率以及学习率调度策略的基本概念,以及其对于梯度下降优化算法的重要性。学习率调度策略与随机或小批量梯度下降的结合使用,可以在保证模型收敛的同时,提高训练的效率和最终模型的性能。

最后,感谢您阅读这篇文章!如果您觉得有所收获,别忘了点赞、收藏并关注我,这是我持续创作的动力。您有任何问题或建议,都可以在评论区留言,我会尽力回答并接受您的反馈。如果您希望了解某个特定主题,也欢迎告诉我,我会乐于创作与之相关的文章。谢谢您的支持,期待与您共同成长!

期待与您在未来的学习中共同成长。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法小陈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值