Lesson 4.5 梯度下降优化基础:数据归一化与学习率调度-2

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: Lesson 4.5 梯度下降优化基础:数据归一化与学习率调度-2

二、梯度下降算法优化初阶

  • 归一化和学习率调度,是梯度下降算法优化的基本方法。

1. 数据归一化与梯度下降算法优化


  • 数据准备
  • 此处我们选取 Lesson 1 中的鲍鱼数据集,并且采用其中相关性比较强的几列进行建模分析。数据集读取过程如下:
aba_data = pd.read_csv("abalone.csv")
aba_data


6a51a69bb1844206aacbaeeae6070337.png



  • 其中,我们选取鲍鱼数据集中的 Length(身体长度)和 Diameter(身体宽度/直径)作为特征,
    Whole weight(体重)作为标签进行线性回归建模分析。
aba_value = aba_data.values
aba_value
#array([[ 1.    ,  0.455 ,  0.365 , ...,  0.101 ,  0.15  , 15.    ],
#       [ 1.    ,  0.35  ,  0.265 , ...,  0.0485,  0.07  ,  7.    ],
#       [-1.    ,  0.53  ,  0.42  , ...,  0.1415,  0.21  ,  9.    ],
#       ...,
#       [ 1.    ,  0.6   ,  0.475 , ...,  0.2875,  0.308 ,  9.    ],
#       [-1.    ,  0.625 ,  0.485 , ...,  0.261 ,  0.296 , 10.    ],
#       [ 1.    ,  0.71  ,  0.555 , ...,  0.3765,  0.495 , 12.    ]])
features = aba_value[:, 1: 3]
features
#array([[0.455, 0.365],
#       [0.35 , 0.265],
#       [0.53 , 0.42 ],
#       ...,
#       [0.6  , 0.475],
#       [0.625, 0.485],
#       [0.71 , 0.555]])
labels = aba_value[:, 4:5]
labels
#array([[0.514 ],
#       [0.2255],
#       [0.677 ],
#       ...,
#       [1.176 ],
#       [1.0945],
#       [1.9485]])



  • 然后分别准备一份原始数据与归一化后的数据。
features = np.concatenate((features, np.ones_like(labels)), axis=1)
# 深拷贝features用于归一化
features_norm = np.copy(features)
# 归一化处理
features_norm[:, :-1] = z_score(features_norm[:, :-1])


features
#array([[0.455, 0.365, 1.   ],
#       [0.35 , 0.265, 1.   ],
#       [0.53 , 0.42 , 1.   ],
#       ...,
#       [0.6  , 0.475, 1.   ],
#       [0.625, 0.485, 1.   ],
#       [0.71 , 0.555, 1.   ]])
features_norm
#array([[-0.57455813, -0.43214879,  1.        ],
#       [-1.44898585, -1.439929  ,  1.        ],
#       [ 0.05003309,  0.12213032,  1.        ],
#       ...,
#       [ 0.6329849 ,  0.67640943,  1.        ],
#       [ 0.84118198,  0.77718745,  1.        ],
#       [ 1.54905203,  1.48263359,  1.        ]])
features.shape
#(4177, 3)


  • 建模过程
  • 首先是参数初始化与定义核心参数。
# 设置初始参数
np.random.seed(24) 
n = features.shape[1]
w = np.random.randn(n, 1)
w_norm = np.copy(w)
# 记录迭代过程损失函数取值变化
Loss_l = []
Loss_norm_l = []
# 迭代次数/遍历数据集次数
epoch = 100
w
#array([[ 1.32921217],
#       [-0.77003345],
#       [-0.31628036]])


  • 接下来,首先进行梯度下降算法尝试。
for i in range(epoch):
    w = w_cal(features, w, labels, lr_gd, lr = 0.02, itera_times = 1)
    Loss_l.append(MSELoss(features, w, labels))
    w_norm = w_cal(features_norm, w_norm, labels, lr_gd, lr = 0.02, itera_times = 1)
    Loss_norm_l.append(MSELoss(features_norm, w_norm, labels))



  • 观察结果
  • 我们可以通过损失函数变化曲线来观察梯度下降执行情况。
plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l')
plt.plot(list(range(epoch)), np.array(Loss_norm_l).flatten(), label='Loss_norm_l')
plt.xlabel('epochs')
plt.ylabel('MSE')
plt.legend(loc = 1)


2178f1c7c96f4e8db092981df554e5e5.png



  • 经过归一化后的数据集,从损失函数变化图像上来看,收敛速度更快(损失函数下降速度更快),且最终收敛到一个更优的结果。
Loss_l[-1]
#array([[0.12121076]])
Loss_norm_l[-1]
#array([[0.05988496]])
w
#array([[ 1.71641833],
#       [-0.46009876],
#       [ 0.13633803]])
w_norm
#array([[ 1.22387436],
#       [-0.76710614],
#       [ 0.80942526]])


这里需要注意,由于我们没有对标签进行归优化,因此两个迭代过程可以直接进行绝对数值的大小关系比较。

对比全域最小值点

当然,由于上述损失函数是凸函数,因此我们可以用最小二乘法直接求解全域最优解。

这里需要注意,由于归一化只对数据进行平移和放缩而不改变数据分布规律,因此即使最小值点位置不同,但最终对应的对标签的预测结果应该保持一致,并且全域最小值点对应 MSE 数值也应该一致。

w1 = np.linalg.lstsq(features, labels, rcond=-1)[0]
w1
#array([[ 1.87229017],
#       [ 2.33724788],
#       [-1.1056427 ]])
w2 = np.linalg.lstsq(features_norm, labels, rcond=-1)[0]
w2
#array([[0.22482186],
#       [0.2319204 ],
#       [0.82874216]])
features.dot(w1)
#array([[0.59934481],
#       [0.16902955],
#       [0.8683152 ],
#       ...,
#       [1.12792415],
#       [1.19810388],
#       [1.5208559 ]])
features_norm.dot(w2)
#array([[0.59934481],
#       [0.16902955],
#       [0.8683152 ],
#       ...,
#       [1.12792415],
#       [1.19810388],
#       [1.5208559 ]])
MSELoss(features_norm, w2, labels)
#array([[0.03318563]])
MSELoss(features, w1, labels)
#array([[0.03318563]])
  • 结论分析
  • 通过上述计算结果,我们不难分析,其实在进行梯度下降计算过程中,在以 0.02 作为学习率进行迭代的过程中,两组模型都没有收敛到全域最小值点,也就是出现了类似如下情况:
plt.title('lr=0.001')
show_trace(gd(lr=0.001))


a395df2cc45b402ea0c5e4bbf21c0c85.png


但有趣的是,为何在相同学习率下,在归一化之后的数据集上进行梯度下降,却更加接近全域最小值点,这又是什么原因呢?

回顾此前我们所讨论的归一化对损失函数的影响,从等高线图上来看是等高线变得更加均匀,但实际上是整个损失函数在不同区域对应梯度都更加均匀,从而在靠近最小值点附近的梯度也比归一化之前的损失函数梯度要大,也就是说,虽然学习率相同,但由于归一化之后最小值点附近梯度要更大,

因此同样的迭代次,在归一化之后的损失函数上参数点将移动至更加靠近最小值地附近的点。也就类似如下情况:


def gd1(lr = 0.02, itera_times = 20, w = 10):
    """
    梯度下降计算函数
    :param lr: 学习率
    :param itera_times:迭代次数
    :param w:参数初始取值
    :return results:每一轮迭代的参数计算结果列表
    """                              
    results = [w]
    for i in range(itera_times):
        w -= lr * 28 * 2 * (w - 2)            # gd函数系数是28
        results.append(w)
    return results
def show_trace1(res):
    """
    梯度下降轨迹绘制函数
    """
    f_line = np.arange(-6, 10, 0.1)
    plt.plot(f_line, [28 * np.power(x-2, 2) for x in f_line])
    plt.plot(res, [28 * np.power(x-2, 2) for x in res], '-o')
    plt.xlabel('x')
    plt.ylabel('Loss(x)')


plt.subplot(121)
plt.title('28(x-2)**2')
show_trace(gd(lr=0.001))
plt.subplot(122)
plt.title('56(x-2)**2')
show_trace1(gd1(lr=0.001))

f9e17e2c68da434fbe0564f019ddab7a.png

  • 学习率相同,迭代次数相同,但经过归一化的损失函数更加陡峭,靠近最小值点附近梯度更大,因此最终收敛到一个更加靠近最小值点附近的点。
  • 而这个问题要如何解决,我们首先想到的是增加学习率,但问题是学习率增加多少才合适呢?试着增加 10 倍看下结果:
# 设置初始参数
np.random.seed(24) 
n = features.shape[1]
w = np.random.randn(n, 1)
w_norm = np.copy(w)
# 记录迭代过程损失函数取值变化
Loss_l = []
Loss_norm_l = []
# 迭代次数/遍历数据集次数
epoch = 100


for i in range(epoch):
    w = w_cal(features, w, labels, lr_gd, lr = 0.2, itera_times = 1)
    Loss_l.append(MSELoss(features, w, labels))
    w_norm = w_cal(features_norm, w_norm, labels, lr_gd, lr = 0.2, itera_times = 1)
    Loss_norm_l.append(MSELoss(features_norm, w_norm, labels))
plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l')
plt.plot(list(range(epoch)), np.array(Loss_norm_l).flatten(), label='Loss_norm_l')
plt.xlabel('epochs')
plt.ylabel('MSE')
plt.legend(loc = 1)

d2e6ae065e254d4e8f8f5ebb8a171c01.png

Loss_l[-1]
#array([[0.06004797]])
Loss_norm_l[-1]
#array([[0.04334334]])

我们发现,在提高学习率之后,梯度下降效果略有提升,能够收敛到一个更加趋近于全域最小值的点,并且归一化之后的损失函数收敛速度明显更快。

但以当前学习率,还是无法收敛止最小值点,此时我们可以不断尝试,直到“测出”最佳学习率为止。

当然,在 Scikit-Learn 中其实也提供了这种类似枚举去找出最佳超参数取值的方法,但如果是面对超大规模数据集的建模,受到计算资源的限制,我们其实是无法反复建模来找到最优学习率的,此时就需要采用一种更加先进的计算流程来解决这个问题。

伴随数据集复杂程度提升,寻找最小值点过程将越来越复杂。并且哪怕是凸函数,很多情况也无法用最小二乘法一步到位求出最优解,仍然需要依靠梯度下降来求解,此时能够使用梯度下降的优化算法来帮助进行顺利求解,就变得至关重要。


2. 学习率调度

  • 基本概念


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

值得注意的是,此时找到一个确定的最优学习率并不是目标,更快更好找到最小值点才是目标,因此我们完全可以考虑在迭代过程动态调整学习率。而所谓学习率调度,也并不是一个寻找最佳学习率的方法,而是一种伴随迭代进行、不断调整学习率的策略。

学习率调度方法有很多种,目前流行的也达数十种之多,而其中一种最为通用的学习率调度方法是学习率衰减法,指的是在迭代开始时设置较大学习率,而伴随着迭代进行不断减小学习率。

通过这样的学习率设置,能够让梯度下降收敛速度更快、效果更好。

实践过程

例如在上述例子中,我们不妨设置这样的减速衰减的一个学习调度策略,衰减过程比例由如下函数计算得出:


lr_lambda = lambda epoch: 0.95 ** epoch
lr_lambda(0)
#1.0
lr_lambda(2)
#0.9025
lr_l = []
for i in range(10):
    lr_l.append(lr_lambda(i))
lr_l
#[1.0,
# 0.95,
# 0.9025,
# 0.8573749999999999,
# 0.8145062499999999,
# 0.7737809374999998,
# 0.7350918906249998,
# 0.6983372960937497,
# 0.6634204312890623,
# 0.6302494097246091]


  • 即假设初始学习率为 0.5,则第一次迭代时实际学习率为 0.5×1,第二轮迭代时学习率为 0.5×0.95,以此类推。
  • 据此,我们可以优化梯度下降迭代过程,此时我们对比恒定学习率和学习率衰减的两个梯度下降过程,并且都采用归一化后的数据集进行计算:
# 设置初始参数
np.random.seed(24) 
n = features.shape[1]
w = np.random.randn(n, 1)
w_lr = np.copy(w)
# 记录迭代过程损失函数取值变化
Loss_l = []
Loss_lr_l = []
# 迭代次数/遍历数据集次数
epoch = 20
for i in range(epoch):
    w = w_cal(features_norm, w, labels, lr_gd, lr = 0.2, itera_times = 10)
    Loss_l.append(MSELoss(features_norm, w, labels))
    w_lr = w_cal(features_norm, w_lr, labels, lr_gd, lr = 0.5*lr_lambda(i), itera_times = 10)
    Loss_lr_l.append(MSELoss(features_norm, w_lr, labels))
plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l')
plt.plot(list(range(epoch)), np.array(Loss_lr_l).flatten(), label='Loss_lr_l')
plt.xlabel('epochs')
plt.ylabel('MSE')
plt.legend(loc = 1)

27b41ab35c294d658c4dfbe5d538e47f.png


Loss_lr_l[-1]
#array([[0.03416214]])
Loss_l[-1]
#array([[0.03671235]])

这里有一点进行了微调,那就是我们实际上是令梯度下降计算过程中每迭代 10 次更新一次学习率,总共更新了 20 次学习率,即总共迭代了 200 次,最后10次更新时学习率为

0.5*lr_lambda(20)
#0.17924296120427094


整体来看学习率变化区间横跨 0.18-0.5 之间,而最终上述学习率调度也确实起到了更好的效果。

算法评价

接下来,简单总结学习率调度的使用场景和注意事项。

首先,在很多海量数据处理场景下,学习率调度的重大价值在于能够提供对学习率超参数设置更大的容错空间。

在很多情况下,搜索出一个最佳学习率取值进而设置恒定学习率进行梯度下降,难度会远高于设置一组学习率衰减的参数。

有的时候,刚开始学习率设置过大其实也可以通过多轮迭代进行调整,其所消耗的算力也远低于反复训练模型寻找最佳恒定学习率。

其次,尽管上述例子我们是在梯度下降中使用学习率衰减这一调度策略,但实际上更为一般的情况是学习率调度和小批量梯度下降或者随机梯度下降来配合使用。

一般来说梯度下降的使用场景在于小规模数据集且损失函数较为简单的情况,此时可利用梯度下降 + 枚举找到最佳学习率的策略进行模型训练,其相关操作的技术门槛相对较低(枚举法可借助 Scikit-Learn 的网格搜索)。

对于更大规模的数据集且损失函数情况更加复杂时,则需要考虑小批量梯度下降+学习率调度方法来进行梯度下降求解损失函数。


3. 小批量梯度下降与迭代收敛速度


  • 小批量梯度下降不仅可以帮助损失函数跨越局部最小值点,同时也能加快梯度下降的收敛速度。
  • 例如以 0.02 作为学习率、batch_size 为 50 的情况下,测试梯度下降收敛过程:
# 设置初始参数
np.random.seed(24) 
n = features.shape[1]
w = np.random.randn(n, 1)
w_norm = np.copy(w)
# 记录迭代过程损失函数取值变化
Loss_l = []
Loss_norm_l = []
# 迭代次数/遍历数据集次数
epoch = 50
np.random.seed(24)  
w = np.random.randn(3, 1)
sgd_cal(Xtrain, w, ytrain, lr_gd, batch_size=1, epoch=3000, lr=0.02)
#array([[ 0.76959334],
#       [-0.29175077],
#       [-0.17624047]])
# 执行迭代计算
for i in range(epoch):
    w = sgd_cal(features, w, labels, lr_gd, batch_size=50, epoch=1, lr=0.02)
    Loss_l.append(MSELoss(features, w, labels))
    w_norm = sgd_cal(features_norm, w_norm, labels, lr_gd, batch_size=50, epoch=1, lr=0.02)
    Loss_norm_l.append(MSELoss(features_norm, w_norm, labels))
# 观察计算结果
plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l')
plt.plot(list(range(epoch)), np.array(Loss_norm_l).flatten(), label='Loss_norm_l')
plt.xlabel('epochs')
plt.ylabel('MSE')
plt.legend(loc = 1)

df18cff42fe6498c840576a06f3e9ebd.png

Loss_l[-1]
#array([[0.03418411]])

4. 梯度下降组合优化策略

  • 当然,无论是数据归一化、学习率调度还是采用小批量梯度下降,这些方法并不互斥,我们完全可以组合进行使用。
# 设置初始参数
np.random.seed(24) 
n = features.shape[1]
w = np.random.randn(n, 1)
w_opt = np.copy(w)
# 记录迭代过程损失函数取值变化
Loss_l = []
Loss_opt_l = []
# 迭代次数/遍历数据集次数
epoch = 100
w
#array([[ 1.32921217],
#       [-0.77003345],
#       [-0.31628036]])
# 执行迭代计算
for i in range(epoch):
    w = w_cal(features, w, labels, lr_gd, lr = 0.2, itera_times = 1)
    Loss_l.append(MSELoss(features, w, labels))
    w_opt = sgd_cal(features_norm, w_opt, labels, lr_gd, batch_size=50, epoch=1, lr=0.5*lr_lambda(i))
    Loss_opt_l.append(MSELoss(features_norm, w_opt, labels))
# 观察计算结果
plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l')
plt.plot(list(range(epoch)), np.array(Loss_opt_l).flatten(), label='Loss_norm_l')
plt.xlabel('epochs')
plt.ylabel('MSE')
plt.legend(loc = 1)

7eb0bee014ec4c1e899efe2862935c95.png



Loss_opt_l[-1]
#array([[0.03318614]])
Loss_l[-1]
#array([[0.06004797]])















相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
机器学习/深度学习 缓存 监控
Pytorch学习笔记(7):优化器、学习率及调整策略、动量
Pytorch学习笔记(7):优化器、学习率及调整策略、动量
934 0
Pytorch学习笔记(7):优化器、学习率及调整策略、动量
|
23天前
|
机器学习/深度学习 调度 计算机视觉
深度学习中的学习率调度:循环学习率、SGDR、1cycle 等方法介绍及实践策略研究
本文探讨了多种学习率调度策略在神经网络训练中的应用,强调了选择合适学习率的重要性。文章介绍了阶梯式衰减、余弦退火、循环学习率等策略,并分析了它们在不同实验设置下的表现。研究表明,循环学习率和SGDR等策略在提高模型性能和加快训练速度方面表现出色,而REX调度则在不同预算条件下表现稳定。这些策略为深度学习实践者提供了实用的指导。
30 2
深度学习中的学习率调度:循环学习率、SGDR、1cycle 等方法介绍及实践策略研究
|
机器学习/深度学习 资源调度 监控
深度学习基础入门篇[六]:模型调优,学习率设置(Warm Up、loss自适应衰减等),batch size调优技巧,基于方差放缩初始化方法。
深度学习基础入门篇[六]:模型调优,学习率设置(Warm Up、loss自适应衰减等),batch size调优技巧,基于方差放缩初始化方法。
|
3月前
|
TensorFlow 算法框架/工具
【Tensorflow+Keras】学习率指数、分段、逆时间、多项式衰减及自定义学习率衰减的完整实例
使用Tensorflow和Keras实现学习率衰减的完整实例,包括指数衰减、分段常数衰减、多项式衰减、逆时间衰减以及如何通过callbacks自定义学习率衰减策略。
62 0
|
4月前
|
机器学习/深度学习 数据采集 监控
算法金 | DL 骚操作扫盲,神经网络设计与选择、参数初始化与优化、学习率调整与正则化、Loss Function、Bad Gradient
**神经网络与AI学习概览** - 探讨神经网络设计,包括MLP、RNN、CNN,激活函数如ReLU,以及隐藏层设计,强调网络结构与任务匹配。 - 参数初始化与优化涉及Xavier/He初始化,权重和偏置初始化,优化算法如SGD、Adam,针对不同场景选择。 - 学习率调整与正则化,如动态学习率、L1/L2正则化、早停法和Dropout,以改善训练和泛化。
44 0
算法金 | DL 骚操作扫盲,神经网络设计与选择、参数初始化与优化、学习率调整与正则化、Loss Function、Bad Gradient
|
4月前
|
机器学习/深度学习 索引 Python
。这不仅可以减少过拟合的风险,还可以提高模型的准确性、降低计算成本,并帮助理解数据背后的真正含义。`sklearn.feature_selection`模块提供了多种特征选择方法,其中`SelectKBest`是一个元变换器,可以与任何评分函数一起使用来选择数据集中K个最好的特征。
。这不仅可以减少过拟合的风险,还可以提高模型的准确性、降低计算成本,并帮助理解数据背后的真正含义。`sklearn.feature_selection`模块提供了多种特征选择方法,其中`SelectKBest`是一个元变换器,可以与任何评分函数一起使用来选择数据集中K个最好的特征。
|
6月前
|
机器学习/深度学习 人工智能 数据可视化
【视频】R语言支持向量回归SVR预测水位实例讲解|附代码数据
【视频】R语言支持向量回归SVR预测水位实例讲解|附代码数据
|
6月前
|
资源调度 数据可视化 Python
Python随机波动模型Stochastic volatility,SV随机变分推断SVI分析标普500指数时间数据波动性可视化
Python随机波动模型Stochastic volatility,SV随机变分推断SVI分析标普500指数时间数据波动性可视化
|
6月前
|
机器学习/深度学习 TensorFlow 定位技术
神经网络学习率指数衰减ExponentialDecay策略的参数含义与使用方法详解
神经网络学习率指数衰减ExponentialDecay策略的参数含义与使用方法详解
123 1
|
机器学习/深度学习
采用附加动量法和自适应学习率设计来改进bp神经网络的迭代速度,如果不迭代学习率会提高精度;迭代学习率(自适应)会加快收敛,但精度降低(Matlab代码实现)
采用附加动量法和自适应学习率设计来改进bp神经网络的迭代速度,如果不迭代学习率会提高精度;迭代学习率(自适应)会加快收敛,但精度降低(Matlab代码实现)
126 0