梯度下降算法
算法简介
梯度下降(Gradient Descent)是一种求局部最优解的优化算法。在求解机器学习算法的模型参数即无约束优化问题,梯度下降是常用方法之一,主要用来递归性地逼近最小误差模型。
方向导数与梯度
在学习梯度下降算法之前,我们需要先了解梯度(Gradient)的概念。在此之前,我们先来回顾一下什么方向导数及其几何意义。
图形解释:
对于导数以及偏导数的定义,均为沿坐标轴正方向函数的变化率。当我们讨论函数沿任意方向的变化率,就引出了方向导数的定义,即某一点在某一方向上的导数值。
梯度下降
梯度下降,又名最速下降(Steepest Descent),是求解无约束最优化问题最常用的方法。它是一种迭代方法,每一步主要的操作是求解目标函数的梯度向量。既然在向量空间的某一点处,函数沿梯度正方向具有最大的变化率,那么在优化目标函数的时候,自然是沿着梯度负方向去减少函数值,以此达到我们的优化目标。因为在梯度负方向上目标函数下降最快,这也是最速下降名称的由来。梯度下降法特点为越接近目标值步长越小,下降速度越慢。如图,每一个圈代表一个函数梯度,其中心位置表示函数极值点。每次迭代根据当前位置求得的梯度(用于确定搜索方向以及与步长共同决定前进速度)和步长找到一个新位置,这样不断迭代最终到达目标函数局部最优点(如果目标函数是凸函数,则到达全局最优点)。
上述梯度下降过程可描述为一个函数自变量的迭代过程,用一个数学公式描述如下:
β=β-α·▽J(o)
其中,J为关于o的函数,β为当前所处位置,从该位置沿着下降最快的方向,即为梯度负方向-▽j(o),移动前进至β(i+1),α为每次的移动步长。重复该步骤直至抵达函数J的极值点。梯度下降中的α在机器学习中也被称为学习率(Learning Rate)或步长,通过α来控制每一步的距离,既要保证不让步长大大错过最低点,也要保证让步长太小而导致学习速度过慢而影响整体效率。
基于梯度下降算法的线性回归
在统计学中,线性回归(Linear Regression)是利用线性回归方程对一个或多个自变量与因变量之间的关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。在回归分析中,只包含一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析包含两个或两个以上的自变量,且因变量和自变量之间存在线性关系,则称为多元线性回归分析。
一元线性回归分析,简而言之,就是通过给定的一系列数据点,求出符合这些点的最佳直线方程。假设有如图的一组数据点,我们要找到一条合适的直线来拟合这些数据。为此,我们将使用标准的y=mx+b直线方程,其中m为直线的斜率(Slope),b为直线的y轴截距(Intercept)。想要找到最佳的数据拟合直线,只需找到m与b最佳的值即可。
解决这类问题的标准方法是,首先定义一个误差函数,亦可称为代价函数或成本函数(Cost Function),用于评估函数与数据点之间的拟合程度。误差函数的值越小,代表模型拟合程度越好。该函数以(m,b)为输入,并根据模拟数据点与直线的匹配程度返回一个误差值。为了计算给定直线的误差,我们将遍历给定模拟数据集中的每个数据点(x,y),并求出每个点的y值与候选直线y值之间的平方距离(Square Distance)之和。误差函数可定义如下:
示例代码:
def error_function(b,m,points): totalError=0 for i in range(0,len(points)): x=points[i][0] y=points[i][1] totalError+=(y-(m*x+b))**2 return totalError/float(len(points))
现在我们就可以进行接下来执行梯度算法,梯度下降算法实现的示例代码如下:
def step_gradient(m_current,b_current,points,learningRate): ''' 梯度下降算法核心方法 参数说明 m_current:当前斜率值m b_current:当前截距值b points:模拟数据点集合 learningRate:学习率,也是每次移动的步长 ''' b_gradient=0 #erro函数关于b的偏导数 m_gradient=0 #erro函数关于m的偏导数 N=float(len(points)) #数据集长度 #通过梯度下降计算更新后的m与b值 for i in range(0,len(points)): x=points[i,0] y=points[i,1] #erro函数对b求偏导数 b_gradient+=-(2/N)*x*(y-((m_current*x)+b_current))
学习率变量控制在每次迭代中“走下坡路”的幅度.为确保梯度下降正常工作的最好方式是确保每次迭代的误差持续递减。
基于上述定义的误差函数和梯度计算方法,就可以通过多次梯度下降算法来获取最佳拟合直线的斜率m和截距b。示例代码如下:
from numpy import * def gradient_descent_runner(points,starting_b,starting_m,learning_rate,num_iterations): ''' 定义梯度下降运行方法 points:模拟数据点集合 starting_b:初始化b值 starting_m:初始化m值 learningRate:学习率,也是每次移动的步长 num_iterations:迭代次数 ''' b=starting_b #初始化b值 m=starting_m b_m_sets=[] #用于存放所有拟合直线的m,b值 #梯度下降算法迭代 for i in range(num_iterations): b,m=step_gradient(b,m,array()) b_m_sets.append([b,m]) #返回所有拟合直线的m,b值 return b_m_sets def run(): ''' 定义主程序 读取本地文件,设置本地曲线 通过多次梯度下降算法迭代来获取最佳拟合直线的斜率m与截距b ''' points=genfromtxt('data.csv',delimiter=',') learning_rate=0.0001 initial_b=0 #初始化b值 initial_m=0 #初始化m值 num_iterations=100 #迭代次数 print("Starting gradient descent at b={0},m={1},error={2}".format(initial_b,initial_m,error_function(initial_b,initial_m,points))) #通过梯度下降算法获取拟合直线的m,b值 parameters=gradient_descent_runner(points,initial_b,initial_m,learning_rate,num_iterations) [b,m]=parameters[-1] print('After {0} iterations b={1},m={2},error={3}'.format(num_iterations,b,m,error_function(b,m,points))) # 可视化输出数据点,最佳拟合直线以及误差梯度下降曲线 gd_visualization(points,parameters,num_iterations) def gd_visualization(points,parameters,iter_num): xx=[] yy=[] for i in range(len(points)): xx.append(points[i][0]) yy.append(points[i][1]) plt.plot(xx,yy,'bo',label='模拟数据点') plt.title('一元线性回归分析示例') plt.xlabel('x') plt.ylabel('y') plt.grid(False) [b,m]=parameters[-1] x=np.linspace(0,100,100) y=m*x+b plt.plot(x,y,'r-',label='最佳拟合直线') plt.legend() plt.show() erro=[] for j in range(len(parameters)): [b,m]=parameters[j] erro.append(error_function(b,m,points)) iteration=range(iter_num) plt.plot(iteration,erro,'b--',label='误差函数梯度下降函数') plt.xlabel('迭代次数') plt.ylabel('误差') plt.legend() plt.show() if __name__=='__main__': run()
算法总结
在线性回归问题中,一般只有一个极小值。我们定义的误差函数为凸曲线。因此无论从哪里开始,最终都会到达绝对最小值。一般来说,并非所有情况皆如此,有些函数可能存在局部极小值,普通的梯度下降搜索则有可能会陷入其中,而通过随机梯度下降(Stochastic Gradient Descent,SGD)算法,在某种程度上可缓解这种情况。除了设定明确的循环次数之外,我们也可通过其他方式(例如设定收敛条件等)来终止循环。当梯度小于某个设定值时,表明迭代已经接近函数极值,则退出迭代循环。