在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。这里就对梯度下降法做一个完整的总结。
在微积分里面,对多元函数的参数求∂偏导数,把求得的各个参数的偏导数以向量的形式写出来,就是梯度。比如函数f(x,y), 分别对x,y求偏导数,求得的梯度向量就是(∂f/∂x, ∂f/∂y)T,简称grad f(x,y)或者▽f(x,y)。对于在点(x0,y0)的具体梯度向量就是(∂f/∂x0, ∂f/∂y0)T.或者▽f(x0,y0),如果是3个参数的向量梯度,就是(∂f/∂x, ∂f/∂y,∂f/∂z)T,以此类推。
那么这个梯度向量求出来有什么意义呢?他的意义从几何意义上讲,就是函数变化增加最快的地方。具体来说,对于函数f(x,y),在点(x0,y0),沿着梯度向量的方向就是(∂f/∂x0, ∂f/∂y0)T的方向是f(x,y)增加最快的地方。或者说,沿着梯度向量的方向,更加容易找到函数的最大值。反过来说,沿着梯度向量相反的方向,也就是 -(∂f/∂x0, ∂f/∂y0)T的方向,梯度减少最快,也就是更加容易找到函数的最小值。
在机器学习算法中,在最小化损失函数时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数,和模型参数值。反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了。
梯度下降法和梯度上升法是可以互相转化的。比如我们需要求解损失函数f(θ)的最小值,这时我们需要用梯度下降法来迭代求解。但是实际上,我们可以反过来求解损失函数 -f(θ)的最大值,这时梯度上升法就派上用场了。
下面来详细总结下梯度下降法。
首先来看看梯度下降的一个直观的解释。比如我们在一座大山上的某处位置,由于我们不知道怎么下山,于是决定走一步算一步,也就是在每走到一个位置的时候,求解当前位置的梯度,沿着梯度的负方向,也就是当前最陡峭的位置向下走一步,然后继续求解当前位置梯度,向这一步所在位置沿着最陡峭最易下山的位置走一步。这样一步步的走下去,一直走到觉得我们已经到了山脚。当然这样走下去,有可能我们不能走到山脚,而是到了某一个局部的山峰低处。
从上面的解释可以看出,梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。
在详细了解梯度下降的算法之前,我们先看看相关的一些概念。
1. 步长(Learning rate):步长决定了在梯度下降迭代的过程中,每一步沿梯度负方向前进的长度。用上面下山的例子,步长就是在当前这一步所在位置沿着最陡峭最易下山的位置走的那一步的长度。
2.特征(feature):指的是样本中输入部分,比如2个单特征的样本(x(0),y(0)),(x(1),y(1))(x(0),y(0)),(x(1),y(1)),则第一个样本特征为x(0)x(0),第一个样本输出为y(0)y(0)。
3. 假设函数(hypothesis function):在监督学习中,为了拟合输入样本,而使用的假设函数,记为hθ(x)hθ(x)。比如对于单个特征的m个样本(x(i),y(i))(i=1,2,...m)(x(i),y(i))(i=1,2,...m),可以采用拟合函数如下: hθ(x)=θ0+θ1xhθ(x)=θ0+θ1x。
4. 损失函数(loss function):为了评估模型拟合的好坏,通常用损失函数来度量拟合的程度。损失函数极小化,意味着拟合程度最好,对应的模型参数即为最优参数。在线性回归中,损失函数通常为样本输出和假设函数的差取平方。比如对于m个样本(xi,yi)(i=1,2,...m)(xi,yi)(i=1,2,...m),采用线性回归,损失函数为:
J(θ0,θ1)=∑i=1m(hθ(xi)−yi)2J(θ0,θ1)=∑i=1m(hθ(xi)−yi)2
其中xixi表示第i个样本特征,yiyi表示第i个样本对应的输出,hθ(xi)hθ(xi)为假设函数。
梯度下降法的算法可以有代数法和矩阵法(也称向量法)两种表示,如果对矩阵分析不熟悉,则代数法更加容易理解。不过矩阵法更加的简洁,且由于使用了矩阵,实现逻辑更加的一目了然。这里先介绍代数法,后介绍矩阵法。
1. 先决条件: 确认优化模型的假设函数和损失函数。
比如对于线性回归,假设函数表示为 hθ(x1,x2,...xn)=θ0+θ1x1+...+θnxnhθ(x1,x2,...xn)=θ0+θ1x1+...+θnxn, 其中θiθi (i = 0,1,2... n)为模型参数,xixi (i = 0,1,2... n)为每个样本的n个特征值。这个表示可以简化,我们增加一个特征x0=1x0=1 ,这样hθ(x0,x1,...xn)=∑i=0nθixihθ(x0,x1,...xn)=∑i=0nθixi。
同样是线性回归,对应于上面的假设函数,损失函数为:
J(θ0,θ1...,θn)=12m∑j=0m(hθ(x(j)0,x(j)1,...x(j)n)−yj)2J(θ0,θ1...,θn)=12m∑j=0m(hθ(x0(j),x1(j),...xn(j))−yj)2
2. 算法相关参数初始化:主要是初始化θ0,θ1...,θnθ0,θ1...,θn,算法终止距离εε以及步长αα。在没有任何先验知识的时候,我喜欢将所有的θθ初始化为0, 将步长初始化为1。在调优的时候再 优化。
3. 算法过程:
1)确定当前位置的损失函数的梯度,对于θiθi,其梯度表达式如下:
∂∂θiJ(θ0,θ1...,θn)∂∂θiJ(θ0,θ1...,θn)
2)用步长乘以损失函数的梯度,得到当前位置下降的距离,即α∂∂θiJ(θ0,θ1...,θn)α∂∂θiJ(θ0,θ1...,θn)对应于前面登山例子中的某一步。
3)确定是否所有的θiθi,梯度下降的距离都小于εε,如果小于εε则算法终止,当前所有的θiθi(i=0,1,...n)即为最终结果。否则进入步骤4.
4)更新所有的θθ,对于θiθi,其更新表达式如下。更新完毕后继续转入步骤1.
θi=θi−α∂∂θiJ(θ0,θ1...,θn)θi=θi−α∂∂θiJ(θ0,θ1...,θn)
下面用线性回归的例子来具体描述梯度下降。假设我们的样本是(x(0)1,x(0)2,...x(0)n,y0),(x(1)1,x(1)2,...x(1)n,y1),...(x(m)1,x(m)2,...x(m)n,ym)(x1(0),x2(0),...xn(0),y0),(x1(1),x2(1),...xn(1),y1),...(x1(m),x2(m),...xn(m),ym),损失函数如前面先决条件所述:
J(θ0,θ1...,θn)=12m∑j=0m(hθ(x(j)0,x(j)1,...x(j)n)−yj)2J(θ0,θ1...,θn)=12m∑j=0m(hθ(x0(j),x1(j),...xn(j))−yj)2。
则在算法过程步骤1中对于θiθi 的偏导数计算如下:
∂∂θiJ(θ0,θ1...,θn)=1m∑j=0m(hθ(x(j)0,x(j)1,...x(j)n)−yj)x(j)i∂∂θiJ(θ0,θ1...,θn)=1m∑j=0m(hθ(x0(j),x1(j),...xn(j))−yj)xi(j)
由于样本中没有x0x0上式中令所有的xj0x0j为1.
步骤4中θiθi的更新表达式如下:
θi=θi−α1m∑j=0m(hθ(x(j)0,x(j)1,...xjn)−yj)x(j)iθi=θi−α1m∑j=0m(hθ(x0(j),x1(j),...xnj)−yj)xi(j)
从这个例子可以看出当前点的梯度方向是由所有的样本决定的,加1m1m 是为了好理解。由于步长也为常数,他们的乘机也为常数,所以这里α1mα1m可以用一个常数表示。
在下面第4节会详细讲到的梯度下降法的变种,他们主要的区别就是对样本的采用方法不同。这里我们采用的是用所有样本。
这一部分主要讲解梯度下降法的矩阵方式表述,相对于3.3.1的代数法,要求有一定的矩阵分析的基础知识,尤其是矩阵求导的知识。
1. 先决条件: 和3.3.1类似, 需要确认优化模型的假设函数和损失函数。对于线性回归,假设函数hθ(x1,x2,...xn)=θ0+θ1x1+...+θnxnhθ(x1,x2,...xn)=θ0+θ1x1+...+θnxn的矩阵表达方式为:
hθ(x)=Xθhθ(x)=Xθ ,其中, 假设函数hθ(X)hθ(X)为mx1的向量,θθ为(n+1)x1的向量,里面有n个代数法的模型参数。XX为mx(n+1)维的矩阵。m代表样本的个数,n+1代表样本的特征数。
损失函数的表达式为:J(θ)=12(Xθ−Y)T(Xθ−Y)J(θ)=12(Xθ−Y)T(Xθ−Y), 其中YY是样本的输出向量,维度为mx1.
2. 算法相关参数初始化: θθ向量可以初始化为默认值,或者调优后的值。算法终止距离εε,步长αα和3.3.1比没有变化。
3. 算法过程:
1)确定当前位置的损失函数的梯度,对于θθ向量,其梯度表达式如下:
∂∂θJ(θ)∂∂θJ(θ)
2)用步长乘以损失函数的梯度,得到当前位置下降的距离,即α∂∂θJ(θ)α∂∂θJ(θ)对应于前面登山例子中的某一步。
3)确定θθ向量里面的每个值,梯度下降的距离都小于εε,如果小于εε则算法终止,当前θθ向量即为最终结果。否则进入步骤4.
4)更新θθ向量,其更新表达式如下。更新完毕后继续转入步骤1.
θ=θ−α∂∂θJ(θ)θ=θ−α∂∂θJ(θ)
还是用线性回归的例子来描述具体的算法过程。
损失函数对于θθ向量的偏导数计算如下:
∂∂θJ(θ)=XT(Xθ−Y)∂∂θJ(θ)=XT(Xθ−Y)
步骤4中θθ向量的更新表达式如下:θ=θ−αXT(Xθ−Y)θ=θ−αXT(Xθ−Y)
对于3.3.1的代数法,可以看到矩阵法要简洁很多。这里面用到了矩阵求导链式法则,和两个矩阵求导的公式。
公式1:∂∂X(XXT)=2X∂∂X(XXT)=2X
公式2:∂∂θ(Xθ)=XT∂∂θ(Xθ)=XT
如果需要熟悉矩阵求导建议参考张贤达的《矩阵分析与应用》一书。
在使用梯度下降时,需要进行调优。哪些地方需要调优呢?
1. 算法的步长选择。在前面的算法描述中,我提到取步长为1,但是实际上取值取决于数据样本,可以多取一些值,从大到小,分别运行算法,看看迭代效果,如果损失函数在变小,说明取值有效,否则要增大步长。前面说了。步长太大,会导致迭代过快,甚至有可能错过最优解。步长太小,迭代速度太慢,很长时间算法都不能结束。所以算法的步长需要多次运行后才能得到一个较为优的值。
2. 算法参数的初始值选择。 初始值不同,获得的最小值也有可能不同,因此梯度下降求得的只是局部最小值;当然如果损失函数是凸函数则一定是最优解。由于有局部最优解的风险,需要多次用不同初始值运行算法,关键损失函数的最小值,选择损失函数最小化的初值。
3.归一化。由于样本不同特征的取值范围不一样,可能导致迭代很慢,为了减少特征取值的影响,可以对特征数据归一化,也就是对于每个特征x,求出它的期望x¯¯¯x¯和标准差std(x),然后转化为:
x−x¯¯¯std(x)x−x¯std(x)
这样特征的新期望为0,新方差为1,迭代次数可以大大加快。
批量梯度下降法,是梯度下降法最常用的形式,具体做法也就是在更新参数时使用所有的样本来进行更新,这个方法对应于前面3.3.1的线性回归的梯度下降算法,也就是说3.3.1的梯度下降算法就是批量梯度下降法。
θi=θi−α∑j=0m(hθ(x(j)0,x(j)1,...x(j)n)−yj)x(j)iθi=θi−α∑j=0m(hθ(x0(j),x1(j),...xn(j))−yj)xi(j)
由于我们有m个样本,这里求梯度的时候就用了所有m个样本的梯度数据。
随机梯度下降法,其实和批量梯度下降法原理类似,区别在与求梯度时没有用所有的m个样本的数据,而是仅仅选取一个样本j来求梯度。对应的更新公式是:
θi=θi−α(hθ(x(j)0,x(j)1,...x(j)n)−yj)x(j)iθi=θi−α(hθ(x0(j),x1(j),...xn(j))−yj)xi(j)
随机梯度下降法,和4.1的批量梯度下降法是两个极端,一个采用所有数据来梯度下降,一个用一个样本来梯度下降。自然各自的优缺点都非常突出。对于训练速度来说,随机梯度下降法由于每次仅仅采用一个样本来迭代,训练速度很快,而批量梯度下降法在样本量很大的时候,训练速度不能让人满意。对于准确度来说,随机梯度下降法用于仅仅用一个样本决定梯度方向,导致解很有可能不是最优。对于收敛速度来说,由于随机梯度下降法一次迭代一个样本,导致迭代方向变化很大,不能很快的收敛到局部最优解。
那么,有没有一个中庸的办法能够结合两种方法的优点呢?有!这就是4.3的小批量梯度下降法。
小批量梯度下降法是批量梯度下降法和随机梯度下降法的折衷,也就是对于m个样本,我们采用x个样子来迭代,1 θi=θi−α∑j=tt+x−1(hθ(x(j)0,x(j)1,...x(j)n)−yj)x(j)iθi=θi−α∑j=tt+x−1(hθ(x0(j),x1(j),...xn(j))−yj)xi(j) 在机器学习中的无约束优化算法,除了梯度下降以外,还有前面提到的最小二乘法,此外还有牛顿法和拟牛顿法。 梯度下降法和最小二乘法相比,梯度下降法需要选择步长,而最小二乘法不需要。梯度下降法是迭代求解,最小二乘法是计算解析解。如果样本量不算很大,且存在解析解,最小二乘法比起梯度下降法要有优势,计算速度很快。但是如果样本量很大,用最小二乘法由于需要求一个超级大的逆矩阵,这时就很难或者很慢才能求解解析解了,使用迭代的梯度下降法比较有优势。 梯度下降法和牛顿法/拟牛顿法相比,两者都是迭代求解,不过梯度下降法是梯度求解,而牛顿法/拟牛顿法是用二阶的海森矩阵的逆矩阵或伪逆矩阵求解。相对而言,使用牛顿法/拟牛顿法收敛更快。但是每次迭代的时间比梯度下降法长。 Dataset https://raw.githubusercontent.com/huangtaosdt/Gradient-Descent-Algorithm/master/data/pga.csv 包含两个属性:accuracy 和 distance。accuracy 精确度描述了命中球道( fairways hit)的比例,Distances 描述的是发球的平均距离。我们的目的是用距离来预测命中率。在高尔夫中,一个人发球越远,那么精度会越低。对于很多机器学习算法来说,输入数据前会先进行一些预处理,比如规范化,因为当计算两个样本的距离时,当一个属性的取值很大,那么这个距离会偏向取值较大的那个属性。由于此处的精度是百分比,而距离是码数。用到的规范化方法是每个元素减去均值然后除以标准方差。 上述代码生成了一组系数,并且将误差与系数一起画了一个3D图。图中最低的地方就是最优解。 1.本质相同:两种方法都是在给定已知数据(independent& dependent variables)的前提下对dependent variables算出出一个一般性的估值函数。然后对给定新数据的dependent variables进行估算。 当然, 其实梯度下降法还有别的其他用处, 比如其他找极值问题. 另外, 牛顿法也是一种不错的方法, 迭代收敛速度快于梯度下降法, 只是计算代价也比较高. 梯度下降法的步骤:初始化模型参数为w0,然后求误差关于每个参数的偏导,偏导的反方向就是下降最快的方向,因此将参数减去偏导的值,也可以加上学习率(下降的速度): 以上是对数据集pga.csv操作的最终结果5. 梯度下降法和其他无约束优化算法的比较
实例
本文的数据集pga.csv包含了职业高尔夫球手的发球统计信息,import pandas
import matplotlib.pyplot as plt
pga = pandas.read_csv("pga.csv")
# Normalize the data DataFrame可以用{.属性名}来调用一列数据的方式,返回ndarray对象
pga.distance = (pga.distance - pga.distance.mean()) / pga.distance.std()
pga.accuracy = (pga.accuracy - pga.accuracy.mean()) / pga.accuracy.std()
print(pga.head())
plt.scatter(pga.distance, pga.accuracy)
plt.xlabel('normalized distance')
plt.ylabel('normalized accuracy')
plt.show()
#结果:
distance accuracy
0 0.314379 -0.707727
1 1.693777 -1.586669
2 -0.059695 -0.176699
3 -0.574047 0.372640
4 1.343083 -1.934584
Linear Model
观察数据散点图,发现距离和精度呈现负相关。首先用基本的线性回归LinearRegression 来拟合数据:from sklearn.linear_model import LinearRegression
import numpy as np
# We can add a dimension to an array by using np.newaxis
print("Shape of the series:", pga.distance.shape) #这是一个ndarray对象,但是列数未知,因此需要人为指定一个
print("Shape with newaxis:", pga.distance[:, np.newaxis].shape)
'''
Shape of the series: (197,)
Shape with newaxis: (197, 1)
更简单的方法:# 前面之所以要添加一个维度,是因为此时属性只有一列,但是X要求的是矩阵形(DataFrame或者二维ndarray)式,因此只要pga[['distance']]将distance放在列表中,取出来的就是一个DataFrame对象而不是Series对象。
from sklearn.linear_model import LinearRegression
import numpy as np
lm = LinearRegression()
lm.fit(pga[['distance']],pga.accuracy)
theta1 = lm.coef_[0]
Cost Function, Introduction
上述线性回归利用了sklearn中的包去估计模型的参数,用的是最小二乘法。最小二乘法通过矩阵计算可以很有效的拟合线性模型,并且提供确切的参数值。但是当矩阵的太大,直接用矩阵运算是不现实的,这时候就需要用一些迭代的方法来求解参数的估计值。梯度下降就是常见的一种迭代算法。# The cost function of a single variable linear model
def cost(theta0, theta1, x, y):
# Initialize cost
J = 0
# The number of observations
m = len(x)
# Loop through each observation
for i in range(m):
# Compute the hypothesis
h = theta1 * x[i] + theta0
# Add to cost
J += (h - y[i])**2
# Average and normalize cost
J /= (2*m)
return J
# The cost for theta0=0 and theta1=1
print(cost(0, 1, pga.distance, pga.accuracy))
theta0 = 100
theta1s = np.linspace(-3,2,100)
costs = []
for theta1 in theta1s:
costs.append(cost(theta0, theta1, pga.distance, pga.accuracy))
plt.plot(theta1s, costs)
上面这段代码所做的工作是:将截距设置为100,系数设置为-3到2之间的100个等距离的值。然后计算每个系数所对应的模型的误差,误差公式如下,画出系数与误差的曲线图。发现在-0.7左右,模型的误差最小。
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
theta0s = np.linspace(-2,2,100)
theta1s = np.linspace(-2,2, 100)
COST = np.empty(shape=(100,100))
# T0S:为100theta0s(theta1s的长度)行theta0s,T1S:为100列(theta0s的长度)theta1s
T0S, T1S = np.meshgrid(theta0s, theta1s)
# for each parameter combination compute the cost
for i in range(100):
for j in range(100):
COST[i,j] = cost(T0S[0,i], T1S[j,0], pga.distance, pga.accuracy)
# make 3d plot
fig2 = plt.figure()
ax = fig2.gca(projection='3d')
ax.plot_surface(X=T0S,Y=T1S,Z=COST)
plt.show()
上面这段代码首先用了一个新的函数meshgrid,参数为两个数组,第一个长度为m,第二个长度为n。因此返回的是第一个数组的n行复制,以及第二个数组的m列复制。举个例子:
x = [1,2,3],y=[5,6]————X=[[1,2,3],[1,2,3]],Y=[[5,5,5],[6,6,6]].然后去(X[i,j],Y[i,j])就可以得到x元素和y元素的任意组合。Cost Function, Slopes
最小二乘法和梯度下降法有哪些区别?
2.目标相同:都是在已知数据的框架内,使得估算值与实际值的总平方差尽量更小(事实上未必一定要使用平方)。
3.实现方法和结果不同:最小二乘法是直接求导找出全局最小,是非迭代法。而梯度下降法是一种迭代法,先给定一个参数向量,然后向误差值下降最快的方向调整参数,在若干次迭代之后找到局部最小。梯度下降法的缺点是到最小点的时候收敛速度变慢,并且对初始点的选择极为敏感,其改进大多是在这两方面下功夫。# Partial derivative of cost in terms of theta0
def partial_cost_theta0(theta0, theta1, x, y):
# Hypothesis
h = theta0 + theta1*x
# Difference between hypothesis and observation
diff = (h - y)
# Compute partial derivative
partial = diff.sum() / (x.shape[0])
return partial
partial0 = partial_cost_theta0(1, 1, pga.distance, pga.accuracy)
Gradient Descent Algorithm
从之前的可视化图中,可以从视觉上感受到,不同的斜率和截距将带来不同的误差,为了减少模型的误差,我们必须找到误差函数中参数的最优值。下面这段代码就是计算梯度下降的详细代码,一气呵成,结合了前面的函数,所以在完成一个大的工程时,预先写好一些小功能,然后在最后,拼接在一起就可以完成一个很好的功能:此处用到前面计算误差的函数cost(),以及计算偏导的函数partial_cost_theta0(),学会这种编程方式。
# x is our feature vector -- distance
# y is our target variable -- accuracy
# alpha is the learning rate
# theta0 is the intial theta0
# theta1 is the intial theta1
def gradient_descent(x, y, alpha=0.1, theta0=0, theta1=0):
max_epochs = 1000 # Maximum number of iterations
counter = 0 # Intialize a counter
c = cost(theta1, theta0, pga.distance, pga.accuracy) ## Initial cost
costs = [c] # Lets store each update
# Set a convergence threshold to find where the cost function in minimized
# When the difference between the previous cost and current cost
# is less than this value we will say the parameters converged
convergence_thres = 0.000001
cprev = c + 10
theta0s = [theta0]
theta1s = [theta1]
# When the costs converge or we hit a large number of iterations will we stop updating
while (np.abs(cprev - c) > convergence_thres) and (counter < max_epochs):
cprev = c
# Alpha times the partial deriviative is our updated
update0 = alpha * partial_cost_theta0(theta0, theta1, x, y)
update1 = alpha * partial_cost_theta1(theta0, theta1, x, y)
# Update theta0 and theta1 at the same time
# We want to compute the slopes at the same set of hypothesised parameters
# so we update after finding the partial derivatives
theta0 -= update0
theta1 -= update1
# Store thetas
theta0s.append(theta0)
theta1s.append(theta1)
# Compute the new cost
c = cost(theta0, theta1, pga.distance, pga.accuracy)
# Store updates
costs.append(c)
counter += 1 # Count
return {'theta0': theta0, 'theta1': theta1, "costs": costs}
print("Theta1 =", gradient_descent(pga.distance, pga.accuracy)['theta1'])
descend = gradient_descent(pga.distance, pga.accuracy, alpha=.01)
plt.scatter(range(len(descend["costs"])), descend["costs"])
plt.show()