梯度下降法

我们要学习一个机器学习的重要方法——梯度下降法(Gradient Descent)梯度下降法并不是一个机器学习的算法,它既不能解决回归问题,也不能解决分类问题。

那么它是什么呢?梯度下降是一种基于搜索的最优化的方法,它的目标是用于优化一个目标函数。

在机器学习中,梯度下降法的作用就是:最小化一个损失函数。

梯度下降法的模拟与可视化

梯度下降法_第1张图片

 

上图描述的是一个损失函数函数不同参数取值的情况下(x轴),对应变化值(y轴)。

对于损失函数,应该有一个最小值。对于最小化损失函数这样一个目标,实际上就是在上图所示的坐标系中,寻找合适的参数,使得损失函数的取值最小。

在这个例子是在2维平面空间上的演示,所以参数只有1个(x轴的取值)。意味着每个参数,都对应着一个损失函数值。实际上模型的参数往往不止一个,所以这个图像描述的只是损失函数优化过程的一种简单表达。

我们用代码来绘制一张这样的图

import numpy as np
import matplotlib.pyplot as plt
# 创建等差数列x,代表模型的参数取值,共150个
plot_x = np.linspace(-1., 6., 150)
print(plot_x)

[-1. -0.95302013 -0.90604027 -0.8590604 -0.81208054 -0.76510067 -0.71812081 -0.67114094 -0.62416107 -0.57718121 -0.53020134 -0.48322148 -0.43624161 -0.38926174 -0.34228188 -0.29530201 -0.24832215 -0.20134228 …… 略 …… 5.20134228 5.24832215 5.29530201 5.34228188 5.38926174 5.43624161 5.48322148 5.53020134 5.57718121 5.62416107 5.67114094 5.71812081 5.76510067 5.81208054 5.8590604 5.90604027 5.95302013 6. ]

# 通过二次方程来模拟一个损失函数的计算,plot_y值就是图形上弧线所对应的点
plot_y = (plot_x-2.5)**2 - 1

绘制图形

plt.plot(plot_x, plot_y)
plt.show()

梯度下降法_第2张图片 

下面,我们来尝试一下实现我们的梯度下降法。

首先定义一个函数,来计算损失函数对应的导数。目标就是计算参数theat的导数

def derivative(theta):
	return 2*(theta-2.5)

还需要定义一个函数来计算theat值对应的损失函数

def loss(theta):
	return (theta-2.5)**2 - 1

计算梯度值

# 以0作为theta的初始点
theta = 0.
eta = 0.1
epsilon = 1e-8
while True:
	# 计算当前theta对应点的梯度(导数)
    gradient = derivative(theta)

	# 更新theta前,先积累下上一次的theta值
    last_theta = theta

		# 更新theta,向导数的负方向移动一步,步长使用eta(学习率)来控制
    theta = theta - eta * gradient

	# 理论上theta最小值应当为0是最佳。但实际情况下,theta很难达到刚好等于0的情况
	# 所以我们可以设置一个最小值spsilon来表示我们的需要theta达到的最小值目标
    # 判断theta每次更新后,和上一个theta的差值是否已满足小于epsilon(最小值)的条件
	# 满足的话就终止运算
    if(abs(loss(theta) - loss(last_theta)) < epsilon):
        break

print(theta)
print(loss(theta))

2.499891109642585 -0.99999998814289

通过结果可以看出,当theta取2.5时候,损失函数的最小值正好对应着截距-1

学习率对梯度的影响

为了能够计算不同学习率下,theta的每一步变更值。我们对代码进行一些补充。

theta = 0.0
eta = 0.1
epsilon = 1e-8

# 添加一个记录每一步theta变更的list
theta_history = [theta]
while True:
    gradient = derivative(theta)
    last_theta = theta
    theta = theta - eta * gradient
	# 更新theta后记录它们的值
    theta_history.append(theta)
    
    if(abs(loss(theta) - loss(last_theta)) < epsilon):
        break

绘制theta每一步的变更

plt.plot(plot_x, loss(plot_x))
plt.plot(np.array(theta_history), loss(np.array(theta_history)), color="r", marker='+')
plt.show()

梯度下降法_第3张图片

 

可以看到theta在下降过程中,每一步都在逐渐变小(逐渐逼近),直到满足小于epsilon的条件。

查看一下theta_history的长度

len(theta_history)

46

我们通过梯度下降法,经过了45次查找,最终找到了theta的最小值。

为了更方便的调试eta(学习率)的值,我们对代码进行进一步的封装。把梯度下降方法和绘图分别封装为两个函数。

theta_history = []

def gradient_descent(initial_theta, eta, epsilon=1e-8):
    theta = initial_theta
    theta_history.append(initial_theta)

    while True:
        gradient = derivative(theta)
        last_theta = theta
        theta = theta - eta * gradient
        theta_history.append(theta)
    
        if(abs(loss(theta) - loss(last_theta)) < epsilon):
            break
            
def plot_theta_history():
    plt.plot(plot_x, loss(plot_x))
    plt.plot(np.array(theta_history),loss(np.array(theta_history)), color="r", marker='+')
    plt.show()

尝试更小的eta值

eta = 0.01
theta_history = []
gradient_descent(0, eta)
plot_theta_history()

梯度下降法_第4张图片

 

theta下降的路径中,每一步的长度更短了,步数也更多了。

len(theta_history)

424

这次经过了424步才得到theta的最小值。

eta就是梯度下降中我们常常谈到的学习率learn rate(lr)。学习率的大小直接影响到梯度更新的步数。很明显,学习率越小,theta下降的步长越小。所需要的步数(计算次数)也就越多。

试试更小的学习率

eta = 0.001
theta_history = []
gradient_descent(0, eta)
plot_theta_history()

梯度下降法_第5张图片

 

步长已经短到几乎连成一条粗线

len(theta_history)

3682

共经过了3682步

那么学习率调大一些是不是更好呢?

eta = 0.8
theta_history = []
gradient_descent(0, eta)
plot_theta_history()

梯度下降法_第6张图片

 

theta变成了从曲线的左侧跳到了右侧,由于eta还是够小。最终我们还是计算得到了theta的最小值。

如果更大一些的学习率,会是什么效果呢?

eta = 1.1
theta_history = []
gradient_descent(0, eta)

梯度下降法_第7张图片

直接报错!

错误描述信息是:结果值太大了。


为什么会产生这样的原因呢?

原因是eta太大所导致的,theta在每次更新也会变得越来越大。梯度非但没有收敛,反而在向着反方向狂奔。直到结果值最终超出了计算机所能容纳的最大值,错误类型OverflowError(计算溢出错误)。


为避免计算值的溢出,我们改造损失值计算方法

def loss(theta):
    try:
        return (theta-2.5)**2 - 1.
    except:
        return float('inf') # 计算溢出时,直接返回一个float的最大值

给梯度更新方法添加一个最大循环次数

def gradient_descent(initial_theta, eta, n_iters = 1e4, epsilon=1e-8):
    
    theta = initial_theta
    i_iter = 0 # 初始循环次数
    theta_history.append(initial_theta)

    while i_iter < n_iters: # 小于最大循环次数
        gradient = derivative(theta)
        last_theta = theta
        theta = theta - eta * gradient
        theta_history.append(theta)
    
        if(abs(loss(theta) - loss(last_theta)) < epsilon):
            break
            
        i_iter += 1  # 循环次数+1

重新执行刚刚会报错的

eta = 1.1
theta_history = []
gradient_descent(0, eta)

没有错误了~

len(theta_history)

10001

达到最大循环次数后,梯度计算就停止了。

为了观察eta=1.1时,theta到底是如何更新的。我们指定一个有限的梯度更新次数(10次)

eta = 1.1
theta_history = []
gradient_descent(0, eta, n_iters=10)
plot_theta_history()

梯度下降法_第8张图片

 

theta的值从曲线出发,逐渐向外。最终越变越大……

学习率的最佳取值

学习率eta的取值,是不是1就是极限值呢?

实际上eta的取值是和损失函数相关的,或者说时和theta的导数相关的。所以没有一个固定标准。

所以,学习率对于梯度下降法来说也是一个超参数。也需要网格搜索来寻找。

保险的方法,是把eta先设置为0.01,然后逐渐寻找它的最佳取值。大多数函数,都是可以胜任的。

如果出现了参数值过大,也可以用上面的方法绘制图形来查看哦~

你可能感兴趣的:(机器学习,python,人工智能)