zoukankan      html  css  js  c++  java
  • Python实现梯度下降法

    梯度下降法是机器学习算法更新模型参数的常用的方法之一。

    【一些基本概念】

    • 梯度 : 表示某一函数在一点处变化率最快的方向向量(可理解为这点的导数/偏导数)
    • 样本 : 实际观测到的数据集,包括输入和输出(本文的样本数量用 m 表述,元素下标 i 表示)
    • 特征 : 样本的输入(本文的特征数量用 n 表示,元素下标 j 表示)
    • 假设函数 : 用来拟合样本的函数,记为 (θ 为参数向量, X 为特征向量)
    • 损失函数 : 用于评估模型拟合的程度,训练的目标是最小化损失函数,记为 J(θ)
    • 线性假设函数 :      其中 X 为特征向量,为模型参数,是特征向量的第 j 个元素(令x0=1)。

    •  经典的平方差损失函数如下:

      

    其中 m 为样本个数,为样本特征集合的第 i 个元素(是一个向量),是样本输出的第 i 个元素,是假设函数。

    【梯度下降法】

    Goal:通过合理的方法更新假设函数  的参数 θ 使得损失函数 J(θ) 对于所有样本最小化。

    步骤:

    • 根据经验设计假设函数和损失函数,以及假设函数所有 θ  的初始值
    • 对损失函数求所有 θ 的偏导(梯度): 
    • 使用样本数据更新假设函数的 θ ,更新公式为:

      其中 α  为更新步长(调整参数的灵敏度,灵敏度太高容易振荡,灵敏度过低收敛缓慢)

     <批量梯度下降算法>

     由之前所述,求ΘT的问题演变成了求J(Θ)的极小值问题,这里使用梯度下降法。而梯度下降法中的梯度方向由J(Θ)对Θ的偏导数确定,由于求的是极小值,因此梯度方向是偏导数的反方向。

     公式(5)中α为学习速率,当α过大时,有可能越过最小值,而α当过小时,容易造成迭代次数较多,收敛速度较慢。

    假如数据集中只有一条样本,那么样本数量,所以公式(5)中的求导为:

         

      于是公式(5)就演变成:

           

    当样本数量m不为1时,将公式(5)中由公式(4)带入求偏导,那么每个参数沿梯度方向的变化值由公式(7)求得。

          

    初始时ΘT可设为,然后迭代使用公式(7)计算ΘT中的每个参数,直至收敛为止。由于每次迭代计算ΘT时,都使用了整个样本集,因此我们称该梯度下降算法为批量梯度下降算法(batch gradient descent)。

     <随机梯度下降算法>

     当样本集数据量m很大时,批量梯度下降算法每迭代一次的复杂度为O(mn),复杂度很高。因此,为了减少复杂度,当m很大时,我们更多时候使用随机梯度下降算法(stochastic gradient descent),算法如下所示:

    即每读取一条样本,就迭代对ΘT进行更新,然后判断其是否收敛,若没收敛,则继续读取样本进行处理,如果所有样本都读取完毕了,则循环重新从头开始读取样本进行处理。

    这样迭代一次的算法复杂度为O(n)。对于大数据集,很有可能只需读取一小部分数据,函数J(Θ)就收敛了。比如样本集数据量为100万,有可能读取几千条或几万条时,函数就达到了收敛值。所以当数据量很大时,更倾向于选择随机梯度下降算法。

    但是,相较于批量梯度下降算法而言,随机梯度下降算法使得J(Θ)趋近于最小值的速度更快,但是有可能造成永远不可能收敛于最小值,有可能一直会在最小值周围震荡,但是实践中,大部分值都能够接近于最小值,效果也都还不错。

     <算法收敛判断方法>

    • 参数ΘT的变化距离为0,或者说变化距离小于某一阈值(ΘT中每个参数的变化绝对值都小于一个阈值)。为减少计算复杂度,该方法更为推荐使用。
    • J(Θ)不再变化,或者说变化程度小于某一阈值。计算复杂度较高,但是如果为了精确程度,那么该方法更为推荐使用。

    <梯度下降算法的优缺点>

    第一种,遍历全部数据集算一次损失函数,然后算函数对各个参数的梯度,更新梯度。这种方法每更新一次参数都要把数据集里的所有样本都看一遍,计算量开销大,计算速度慢,不支持在线学习,这称为Batch gradient descent,批梯度下降。慢但准确度好

    另一种,每看一个数据就算一下损失函数,然后求梯度更新参数,这个称为随机梯度下降,stochastic gradient descent。这个方法速度比较快,但是收敛性能不太好,可能在最优点附近晃来晃去,hit不到最优点。两次参数的更新也有可能互相抵消掉,造成目标函数震荡的比较剧烈。快但准确度欠缺

    为了克服两种方法的缺点,现在一般采用的是一种折中手段,mini-batch gradient decent,小批的梯度下降,这种方法把数据分为若干个批,按批来更新参数,这样,一个批中的一组数据共同决定了本次梯度的方向,下降起来就不容易跑偏,减少了随机性。另一方面因为批的样本数与整个数据集相比小了很多,计算量也不是很大。

     【Python代码实现如下】(基于Python 3.X )

      1 # !/usr/bin/env python
      2 # encoding: utf-8
      3 __author__ = 'Administrator'
      4 
      5 #梯度下降法的目标是通过合理的方法更新假设函数 hθ 的参数 θ 使得损失函数 J(θ) 对于所有样本最小化。
      6 # 这个合理的方法的步骤如下:
      7 #
      8 # 根据经验设计假设函数和损失函数,以及假设函数所有 θ 的初始值
      9 # 对损失函数求所有 θ 的偏导(梯度): ∂J(θ)/∂θj
     10 # 使用样本数据更新假设函数的 θ,更新公式为: θj=θj−α⋅∂J/∂θj (其中 α 为更新步长(调整参数的灵敏度,灵敏度太高容易振荡,灵敏度过低收敛缓慢)
     11 
     12 import numpy as np
     13 import matplotlib.pyplot as plt
     14 
     15 def GD1():
     16     spaces = [45, 73, 89, 120, 140, 163]
     17     prices = [80, 150, 198, 230, 280, 360]
     18     spaces, prices = np.array(spaces), np.array(prices)  # numpy.array:使用列表生成一维数组
     19 
     20     # #-------画出房屋面积与房屋价格的散点图
     21     # plt.scatter(spaces,prices,c="g")
     22     # plt.xlabel("house space")
     23     # plt.ylabel("house price")
     24     # plt.show()
     25 
     26     # 设置theta的初始值
     27     theta0 = 0
     28     theta1 = 0
     29 
     30     # 选择步长alpha:如果步长选择不对,则theta参数更新结果可能会不对
     31     alpha = 0.00005
     32     # 当α过大时,有可能越过最小值,而α当过小时,容易造成迭代次数较多,收敛速度较慢。
     33 
     34     x_i0 = np.ones(len(spaces))  # 返回一个用1 填充的数组,一个参数表示这行的元素个数np.ones(5):一行5列。np.ones((2, 1)):两行一列,都是1
     35 
     36     # 所以是返回一个有6个元素的一组数组
     37     # 假设函数h(x):
     38     def h(x):
     39         return theta0 + theta1 * x  # 一个x为一个特征
     40 
     41     # 损失函数
     42     def calc_error():
     43         return np.sum(np.power((h(spaces) - prices), 2)) / 2
     44 
     45     # 损失函数偏导数 (theta 0 和theta 1)
     46     def calc_delta0():
     47         return alpha * np.sum(h(spaces) - prices) * x_i0
     48 
     49     def calc_delta1():
     50         return alpha * np.sum(h(spaces) - prices) * spaces
     51 
     52     # 循环更新 theta 值并计算误差,停止条件为:
     53     #  1.误差小于某个值
     54     #  2.循环次数控制
     55     k = 0
     56     while True:
     57         delta0 = calc_delta0()
     58         delta1 = calc_delta1()
     59         theta0 = theta0 - delta0
     60         theta1 = theta1 - delta1
     61         error = calc_error()
     62         # print("delta [%f,%f],theta [%f,%f], error %f" % (delta0,delta1,theta0,theta1,error))
     63         print()
     64 
     65         k = k + 1
     66         if (k > 10 or error < 200):
     67             break
     68     # print("h(x)=%f + %f * x"%(theta0,theta1))
     69 
     70     # print('h(x)={}+{}*x'.format(theta0, theta1))
     71     # 使用假设函数计算出来的价格,用于画拟合曲线
     72     y_out = h(spaces)
     73 
     74     plt.scatter(spaces, prices, c="g")  # 绿色点表示房屋面积和价格数据的散点图
     75     plt.plot(spaces, y_out, c="b")  # 蓝色点表示使用梯度下降法拟合出来的曲线
     76     plt.xlabel("house space")
     77     plt.ylabel("house price")
     78     plt.show()
     79 # GD1()
     80 
     81 
     82 def GD2():
     83     # Training data set
     84     # each element in x represents (x1)
     85     x = [1, 2, 3, 4, 5, 6]
     86     # y[i] is the output of y =theta0 + theta1*x[1]
     87     y = [13, 14, 20, 21, 25, 30]
     88     # 设置允许误差值
     89     epsilon = 1
     90     # 学习率
     91     alpha = 0.01
     92 
     93     diff = [0, 0]
     94     max_itor = 20
     95 
     96     error0 = 0
     97     error1 = 0
     98 
     99     count = 1
    100     m = len(x)
    101 
    102     # init the parameters to zero
    103     theta0 = 0
    104     theta1 = 0
    105     error_list = []
    106     while 1:
    107 
    108         count = count + 1
    109         diff = [0, 0]
    110         for i in range(m):
    111             diff[0] += theta0 + theta1 * x[i] - y[i]
    112             diff[1] += (theta0 + theta1 * x[i] - y[i]) * x[i]
    113         theta0 = theta0 - alpha / m * diff[0]
    114         theta1 = theta0 - alpha / m * diff[1]
    115         error1 = 0
    116 
    117         for i in range(m):
    118             error1 += (theta0 + theta1 * x[i] - y[i]) ** 2
    119         error_list.append(error1)
    120         if abs(error1 - error0) < epsilon:
    121             break
    122 
    123         print("iterator :%d theta0 :%f,theta1 :%f,error:%f" % (count-1,theta0, theta1, error1))
    124         if count > 100:
    125             print("count>100...")
    126             break
    127     print("theta0 :%f,theta1 :%f,error:%f" % (theta0, theta1, error1))
    128     print("error_list为:",error_list)
    129     #-----画收敛散点图--
    130     # plt.scatter(x,y,c="g")
    131     n=len(error_list)
    132     ite=range(n)
    133     plt.plot(ite,error_list,c="r",linewidth=3)
    134     plt.title("Convergence curve")
    135     plt.xlabel("iteration")
    136     plt.ylabel("error")
    137     plt.show()
    138 
    139 GD2()

     GD2()运行结果为:

     

    损失函数的收敛曲线图:

     

    【Reference】

    1. http://blog.lisp4fun.com/2017/11/02/gradient-desent

    2. http://www.cnblogs.com/eczhou/p/3951861.html

    3. http://blog.csdn.net/l18930738887/article/details/50670370

  • 相关阅读:
    Apache Ant 1.9.1 版发布
    Apache Subversion 1.8.0rc2 发布
    GNU Gatekeeper 3.3 发布,网关守护管理
    Jekyll 1.0 发布,Ruby 的静态网站生成器
    R语言 3.0.1 源码已经提交到 Github
    SymmetricDS 3.4.0 发布,数据同步和复制
    beego 0.6.0 版本发布,Go 应用框架
    Doxygen 1.8.4 发布,文档生成工具
    SunshineCRM 20130518发布,附带更新说明
    Semplice Linux 4 发布,轻量级发行版
  • 原文地址:https://www.cnblogs.com/shenxiaolin/p/8648804.html
Copyright © 2011-2022 走看看