损失函数、梯度和学习率的理解及用python实现梯度下降法

机器学习的最终目标就是获得泛化能力,而什么是泛化能力呢,就是指处理未被观察过的数据(非训练数据)的能力。

一、为何要引入损失函数

损失函数就是评估一个学习器“恶劣程度”的指标——即描述了当前学习器对训练数据在多大程度上不拟合、不一致。所谓让机器通过“学习”数据信息从而产生一个学习器的过程,就是去寻找最优参数,使得损失函数达到极小甚至最小的过程。
而这一过程如何实现?——答案就是求导,而导数就是所谓的“梯度”。

举个例子:假设 y k y_k yk 表示一个学习器的预测结果, t k t_k tk 表示该数据的实际值,假设以均方误差为损失函数,那么损失函数可以表示为: E = 1 2 ∑ k = 1 n ( y k − t k ) 2 E=\frac{1}{2}\sum^{n}_{k=1}(y_k-t_k)^2 E=21k=1n(yktk)2
y k y_k yk是由训练数据和模型参数 β , α \beta,\alpha β,α共同决定的,可以表示为 y k = f ( x ; β , α ) y_k = f(x;\beta,\alpha) yk=f(x;β,α)
因此,损失函数可以表示为
L ( x ; β , α ) = 1 2 ∑ k = 1 n ( f ( x ; β , α ) − t k ) 2 L(x;\beta,\alpha)=\frac{1}{2}\sum^{n}_{k=1}( f(x;\beta,\alpha)-t_k)^2 L(x;β,α)=21k=1n(f(x;β,α)tk)2
训练的任务就是要找到最优的 β , α \beta,\alpha β,α的值,使得 L ( x ; β , α ) L(x;\beta,\alpha) L(x;β,α)达到极小或最小,数学上如何求极小值/最小值?答案就是求导。
那么求导得到的“导数”(即梯度)是什么意义呢?它表示“如果稍稍改变权重参数的值,损失函数的值会如何变化”。如果导数的值为负,那么通过使模型参数正向变化,可以减小损失函数的值;如果导数的值为正,那么通过使模型参数负向变化,可以减小损失函数的值。而当导数为0时,无论模型的参数往什么方向变化,损失函数的值都不会改变。

那么问题来了,机器学习的最终目的是获得泛化性,也就是要提高预测或识别的精度,为什么不以精度为学习的目标,反而整一个损失函数为目标函数呢?
试想这样一个场景,假设有100条训练数据(测试数据也行~),如果有目前的预测精度为0.32,那么表示当前有32条数据预测正确。这时,如果我们微调模型参数,会有什么结果呢?
(1)调节的幅度过于微小,预测精度没有任何改善,还是0.32.
(2)预测精度有所改善,本来能预测对32个,现在预测对了33个,精度变为了0.33.
显然,精度对微小的参数变化基本上没有什么反应,即便有反应,它的值也是不连续的、突然的变化。就好比,当前精度是0.32,我们没法通过调参使其达到0.325;而如果当前损失函数是0.32,我们可以让其达到值域内的任意值,0.325, 0.3256,0.32567…
总结一下就是——精度是不连续的函数,而损失函数是连续型的函数;如果用精度最为目标,绝大多数地方的导数都将为0,参数将无法更新。

二、梯度和学习率

上文已经提及,梯度就是对损失函数求导的导数值,它表示“如果稍稍改变权重参数的值,损失函数的值会如何变化”。我们想要损失函数往最小的方向走,那么梯度的方向就是各点处函数值减小最多的方向。注意,梯度的方向并不一定指向最小值,但在每一点处,沿着梯度可以最大限度地减小损失函数的值。
像这样,通过不断沿着梯度方向前进,逐渐减小函数值的过程,就是梯度法。梯度法是解决机器学习最优化问题的常见方法。我们通过公式来描述梯度法,参数的迭代过程可以描述为
β = β − η ∂ f ∂ β \beta=\beta-\eta\frac{\partial{f}}{\partial{\beta}} β=βηβf α = α − η ∂ f ∂ α \alpha=\alpha-\eta\frac{\partial{f}}{\partial{\alpha}} α=αηαf
其中, η \eta η表示每次迭代的更新量,被称为学习率。它决定在一次学习中应该学习多少,以及在多大程度上更新参数。学习率是一个超参数(不能通过数据训练得到,而需要人工设定的参数),一般这个值过大或过小都无法抵达一个“好的位置”。

三、用Python简单实现一个梯度下降算法

【例】使用梯度法求解 f ( x 0 , x 1 ) = x 0 2 + x 1 2 f(x_0, x_1)=x_0^2+x_1^2 f(x0,x1)=x02+x12的最小值。
【求解】
step 1、首先定义一个求梯度的函数

import numpy as np
def gradient(f,x):
    h = 1e-4 #h是求导时自变量的差值,设为很小的常数即可
    grad = np.zeros_like(x) #
    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = tmp_val + h 
        fxh1 = f(x) #计算f(x+h)
        x[idx] = tmp_val - h 
        fxh2 = f(x) #计算f(x-h)
        grad[idx] = (fxh1 - fxh2)/(2*h)
        x[idx] = tmp_val #还原
    return grad

step 2、定义一个函数用于实现梯度下降的过程

def descent(f, init_x, lr = 0.01, step_num = 100): 
    x = init_x
    for i in range(step_num): #step_num为迭代次数
        grad = gradient(f, x) #求梯度
        x -= lr*grad #每次迭代用学习率乘以梯度
    return x

step 3、定义待求解函数并初始化输出 x 0 x_0 x0 x 1 x_1 x1的最小值

def func(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0]) 
descent(func, init_x = init_x, lr = 0.1, step_num = 100)

最终的输出结果为

array([-6.11110793e-10,  8.14814391e-10])

最终结果非常接近于0,这是正确的。这里可以做一个实验,如果学习率过大或过小,都不会得到接近于0的结果:

print(descent(func, init_x = init_x, lr = 10, step_num = 100))
print(descent(func, init_x = init_x, lr = 1e-10, step_num = 100))

输出结果如下,可见学习率过大的话,最终会发散成一个很大的值;而学习率过小的话,基本上没怎么更新就结束了。

[-2.58983747e+13   -1.29524862e+12]
[-2.99999994   3.99999992]

本文参考了《深度学习——基于python的理论与实践》
欢迎指正!

  • 16
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值