梯度下降法是机器学习算法更新模型参数的常用的方法之一。
【一些基本概念】
- 梯度 : 表示某一函数在一点处变化率最快的方向向量(可理解为这点的导数/偏导数)
- 样本 : 实际观测到的数据集,包括输入和输出(本文的样本数量用 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