首页 > 技术文章 > 机器学习:梯度下降法

volcao 2018-05-30 15:49 原文

一、梯度下降法基础

  • 定义:梯度下降法不是一个机器学习算法,是一种基于搜索的最优化方法
  • 功能:最优化一个损失函数;
  • 梯度上升法:最大化一个效用函数;
  • 机器学习中,熟练的使用梯度法(下降法、上升法)求取目标函数的最优解,非常重要;
  • 线性回归算法模型的本质就是最小化一个损失函数,求出损失函数的参数的数学解;
  • 很多机器学习的模型是无法求出目标函数的参数的最优解;
  • 梯度下降法是在机器学习领域中最小化损失函数的最为常用的方法;

 

 1)梯度下降法的逻辑思路

  • 每次改变一点参数theta,目标函数 J 跟着改变,不断的递进改变参数值,得到目标函数的极值;经过多次运行,每次随机选取初始化的点,得出不同的局部最优解(极值),比较所有最优解,最小/最大的合格值就是目标函数的最值;
  • theta:模型中的参数,而不是模型中的变量;(以线性回归模型为例)
  • 模型中的每一个 X 表示一个样本,每一个 y 表示该样本对应的值;
  • y = X.dot(θ):结果为一个数值;

 

  • 变量 theta 的变化量  =  学习率  X  梯度/导数
  • new_theta  =  last_theta  -  theta的变化量
  • 损失函数 J 应该有一个最小值,对于最小化一个损失函数来说,相当于在此坐标系中,寻找一个点参数theta使得 J 取得最小值

 

 

  • 导数可以代表函数变化的方向,对应 J 增大的方向,因为公式前加了符号 “ - ” ;

 

  • η:学习率(Learning rate)
  1. η 的取值影响获得最优解的速度;
  2. η 取值不合适,甚至得不到最优解;
  3. η 是梯度下降法的一个超参数;一般需要调参找到最适合的 η;
  4. η 太小,减慢收敛学习速度
  5. η 太大,导致不收敛
  • 如果出现 J 的变化有减有曾,可能是 η 的取值太大;
  • 收敛:得到极值的过程

 

 2)梯度下降法的问题

  • 问题:并不是所有的函数都有唯一的极值点,优化的目标是找到最小值点;
  • 方案:多次运行,随机化初始点,比较后取最优解;
  • 方案弊端:也不一定能找到全局最优解;

 

 3)其它

  • 直线方程中导数代表斜率;
  • 曲线方程中导数代表在这一点的切线的斜率;
  • 为什么叫梯度:在多维函数中,要对各个方向的分量分别求导,最终得到的方向就是梯度;
  • 多维函数中,梯度代表函数变化的方向,对应 就 J 增大/减小的方向;
  • 梯度下降法的初始点也是一个超参数,起始点对于一个算法是非常重要的;

 

二、程序模拟梯度下降法原理

  1)具体实现

    # 模拟损失函数:y = (x - 2.5)**2 - 1

    # 数据集特征值:plot_x = np.linspace(-1, 6, 141)

  • 代码
    import numpy as np
    import matplotlib.pyplot as plt
    
    
    # np.linspace(-1, 6, 141):将区间[-1, 6]等分成141份,包含-1和6
    plot_x = np.linspace(-1, 6, 141)
    
    # 记录搜索过程中的theta值
    theta_history = []
    
    # 1)计算当前theta值对应的损失函数的导数值
    def dJ(theta):
        return 2*(theta-2.5)
    
    # 2)计算当前theta值对应的损失函数值
    # 在计算损失函数时添加异常检测功能
    # 设置异常检测原因:当 eta 过大时,使得损失函数是不断增大的,也就得不到满足精度的损失函数值,就会报错
    # 异常检测:没有异常时执行try,有异常时执行except
    # 此处执行except时返回浮点数的最大值
    def J(theta):
        try:
            return (theta-2.5)**2 - 1.
        except:
            return float('inf')
    
    # 3) 梯度下降,循环搜索,获取局部最优解
    # 一般判断函数的极值点位置:导数 == 0
    # 如何判断theta是否来到的极值点?
    # 问题:编程具体实现的时候,有可能由于eta设置的不合适,或者求导时有浮点精度,使得求取的损失函数最小值所对应的theta点,不是导数刚好等于 0 的点
    # 循环结束:当前的损失函数值 - 上一次的损失函数值之间的差 < 精度,此时停止循环,以为当前的损失函数值为局部最优解
    # initial_theta:theta的初始值
    # eta:学习率
    # n_iters:循环次数,默认10000次;(如果不设定循环次数,程序出现死循环时会一直执行)
    # espsilon:精度,默认10**-8
    def gradient_descent(initial_theta, eta, n_iters = 10**4, espsilon=10**-8):
        theta = initial_theta
        theta_history.append(initial_theta)
        i_iter = 0
        
        while i_iter < n_iters:
            # 循环开始时,先求取当前theta所对应的梯度
            gradient = dJ(theta)
    
            # abs(x):求x的绝对值
            last_theta = theta
            theta = theta - eta * gradient
            theta_history.append(theta)
        
            if(abs(J(theta) - J(last_theta)) < epsilon):
                break
             
            # 每进行一次循环,得不到结果时,记录一次循环次数
            # 如果得到了结果,break直接终端循环
            i_iter += 1 
    
    # 4)绘制参数与损失函数的关系图形、绘制循环搜索过程中的theta值与损失函数的关系图
    def plot_theta_history():
        plt.plot(plot_x, J(plot_x))
        plt.plot(np.array(theta_history), J(np.array(theta_history)), color='r', marker='+')
        plt.show()

     

 

  • 其它
  1. 计算当前theta值对应的损失函数 J 的值时,要进行异常检测
    原因:当 eta 过大时,使得损失函数是不断增大的,也就得不到满足精度的损失函数值,就会报错;
  2. 如何判断theta是否来到了极值点?
    方案设定精度,当前的损失函数值 - 上一次的损失函数值之间的差 < 精度,此时停止循环,以为当前的损失函数值为局部最优解;
  3. 问题:
    ①、一般判断函数的极值点位置:导数 == 0
    ②、编程具体实现的时候,有可能由于eta设置的不合适,或者求导时有浮点精度,使得求取的损失函数最小值所对应的theta点,不是导数刚好等于 0 的点;
  4. 梯度下降,循环搜索时,设定循环次数;
    原因:如果不设定循环次数,程序出现死循环时会一直执行;
  5. np.linspace(-1, 6, 141):将区间 [-1, 6] 等分成141个点,包含 -1 和 6;
  6. abs(x):返回x的绝对值;

 

 

 2)给定不同的学习率、初始值,查看优化情况

  1. eta = 0.1
    theta_history = []
    gradient_descent(0., eta)
    plot_theta_history()
    # len(theta_history) == 46

  2. eta = 0.01
    theta_history = []
    gradient_descent(0., eta)
    plot_theta_history()
    # len(theta_history) == 424

  3. eta = 0.8
    theta_history = []
    gradient_descent(0., eta)
    plot_theta_history()
    # len(theta_history) == 22

  4. eta = 1.1
    theta_history = []
    gradient_descent(0., eta, n_iters=10)
    plot_theta_history()
    # len(theta_history) == 10001

 

  • 分析
  1. 现象:搜索开始时 J 和 theta 变化都比较大,最后变化较小;(搜索点的分布:由疏到密)
    # 原因:theta的每次变化量 == eta * 2 * (theta-2.5),随着theta的不断减小,每次的变化量也会减小,因此水平方向上点的分布越来越密
           另外,由J == (theta - 2.5) ** 2 - 1看出,每次的 J 的变化量也会减小,因此垂直方向上点的分布也会越来越密
    # 变化量 == 学习率 X 导数,导数 == 2*(theta - 2.5),new_theta == last_theta  —  last_变化量

 

推荐阅读