什么是优化算法
优化算法要求解的,是一个问题的最优解或者近似最优解。在机器学习中,有很多问题都是优化问题,即我们要求出输入特征和标签之间的映射关系,并且我们一定是期望,通过这个映射关系得到的标签结果,和实际标签之间的误差最小。
机器学习中,优化算法有很多种,最基本的,也是应用非常广泛的,就是梯度上升/下降,此外还有遗传算法等。
什么是梯度下降
当我们得到一个目标函数之后,如何求解它的参数?直接求解?并不一定可解,线性回归算是一个特例(使用最小二乘法求解)。
梯度上升/下降,是机器学习中求解无约束优化问题时,最常用的方法之一。
它的套路是,喂给机器一堆数据,告诉它什么样的方式是对的(目标函数),然后让它自己朝着这个方向去做。
那么怎么知道【方向】呢?这个方向,就是我们说的梯度。在微积分里边,对多元函数的参数求∂偏导数,把求得的偏导数用向量表示出来,就是梯度。沿着梯度的方向,函数值的变化最快。
所以,如果我们要求损失函数的最大值,就采用梯度上升的方法,沿着梯度方向一步步迭代,得到最大值;反之,如果我们要求损失函数的最小值,就采用梯度下降,沿着梯度的反方向,一步步迭代,得到最小值。
所以,梯度下降法,就是一个用于【寻找最小化损失函数的参数值】的【最优化算法】。
梯度下降的视觉直观解释
我们把下面的图形,想象成两座山,假设我们站在【Initial Condition】这个山顶点,我们想下到山的最底部,但是不知道往哪个方向走最快。于是我们每走一步,都计算一下当前所在位置的梯度,即最陡峭、下山最快的方向,然后沿着这个方向走。这样走一步算一步,最终就走到了山底。这就是梯度下降法,它的要点是:【梯度】和【迭代】,即沿着函数值变化最快的方向,每走一步,计算一次,不断迭代,从而找到最优解。
当然按照梯度下降法走下去,我们不一定走到了两座山的最低点(图中的Global Minimum),而是走到了一个局部最低点(图中次低点)。所以梯度下降法得到的不一定是全局最优解,而是局部最优解。
不过机器学习中我们遇到的很多损失函数,都是凸函数,如线性回归对应的就是凸函数。如果只有一个特征变量,凸函数的形状类似于下面的图形,分别为下凸和上凸:
如果有两个特征变量,则类似于下面这个碗的造型。如果是凸函数,采用梯度上升/下降法,得到的一定是全局最优解。
最小二乘法和梯度下降法的关系
最小二乘法的目标是,求解最小误差平方和,有两种形式:线性和非线性;线性的解可以直接用数学方法推导出来,也可以用迭代的方法求解;非线性的解,需要用迭代的方法求解。
梯度下降法是一种迭代求解的方法,可以用来求解最小二乘问题(线性和非线性的都可以)。
梯度下降相关概念
1. 步长:在神经网络中也叫学习速率(Learning Rate),步长决定了在在梯度下降迭代过程中,每一步沿着梯度负方向前进的长度。
2. 损失函数:也叫成本函数;用来度量拟合的程度,损失函数极小化,意味着拟合程度最好,对应的模型参数即为最优解。损失函数的定义如下:
其中m代表样本数量,1/m是为了平均化,因为如果m=100,右边的平方和所得结果,肯定比m=1000的时候小很多,但这并不能代表m=100的时候计算所得参数值要优于m=1000的时候的参数值。所以这里取平均值,消除这个影响。
特征数据归一化
样本的特征取值范围可能都不一样,这可能导致迭代速度很慢。但是不能因为想要加速下降速度,就把下降步伐调整过大,否则找到函数最小值的速度反而容易变慢,出现overshoot the minimum的现象,即如下图所示:
为了加快迭代速度,我们可以采用Feature Scaling的方法,对特征数据进行归一化处理,来加速下降的执行速度。即将各个特征值标准化,使他们的取值范围都在(-1,1)之间。这样迭代速度会大大加快。
常用的方法是均值归一化(Mean Normalization),即:
或者求标准分(Standard Score),即:
算法流程
初始化:初始化θ参数,步长α,终止距离ε
-
循环操作:
2.1 计算当前位置的损失函数对应的梯度,通过求损失函数的偏导获得:
2.2 步长 * 损失函数的梯度 = 当前位置下降的距离;
2.3 判断是否所有的θ参数,梯度下降的距离都小于ε,如果是,则算法终止;否则进入下一步;
2.4 更新所有θ参数,更新表达式如下:
输出最优解
梯度下降的三种方式
批量梯度下降法 BGD(Batch Gradient Descent)
批量梯度下降法,即在计算梯度时,使用所有的样本数据来计算。如果我们有m个样本,求梯度的时候,就用到m个样本的梯度数据。
这种方法容易得到最优解。但是每次都要考虑所有的样本数据,迭代速度非常慢。尤其是在样本数据量巨大的时候。
随机梯度下降法 SGD(Stochastic Gradient Descent)
和批量梯度下降法的区别,仅仅在于,在求梯度时,没有用到所有样本的数据,而是随机选取其中一个样本来计算梯度。
这种方法的优点是迭代速度很快。但是每次只选择一个样本,所以不一定每次都是朝着全局最优的方向,不过大方向是朝着全局最优的。
小批量梯度下降法 MBGD(Mini-Batch Gradient Descent)
这是最实用的一种方法,综合了批量和随机的特点,每次选择一小批样本数据(32,64,128比较常见)来计算平均梯度的更新方向。
三种算法的Python实现和说明
假设我们有这样的样本数据:
x1 | x2 | y |
---|---|---|
1 | 4 | 19 |
2 | 5 | 26 |
5 | 1 | 19 |
4 | 2 | 29 |
3 | 5 | 29 |
8 | 1 | 28 |
7 | 2 | 29 |
1 | 2 | 11 |
6 | 2 | 24 |
9 | 3 | 39 |
我们的目标是求出θ1和θ2,让h(θ)尽量逼近实际值y。
我们结合Python和实际案例,来说明一下三种算法。参考自:批量梯度下降(BGD)、随机梯度下降(SGD)、小批量随机梯度下降(MSGD)实现过程详解
批量梯度下降法
#BGD
input_x = [[1,4], [2,5], [5,1], [4,2],[3,5],[8,1],[7,2],[1,2],[6,2],[9,3]] #输入
y = [19,26,19,20,28,25,27,13,28,37] #输出
theta = [1,1] #θ参数初始化
loss = 10 #loss先定义一个数,为了进入循环迭代
step_size = 0.01 #步长
eps =0.0001 #精度要求
max_iters = 10000 #最大迭代次数
error =0 #损失值
iter_count = 0 #当前迭代次数
err1=[0,0,0,0,0,0,0,0,0,0] #求Θ1梯度的中间变量1
err2=[0,0,0,0,0,0,0,0,0,0] #求Θ2梯度的中间变量2
while(loss > eps and iter_count < max_iters):
loss = 0
err1num = 0
err2num = 0
for i in range(10): #我们共有10个样本,每次迭代所有样本计算梯度
pred_y = theta[0]*input_x[i][0] + theta[1]*input_x[i][1] #第i个样本对应的预测值
err1[i] = (pred_y - y[i]) * input_x[i][0]
err2[i] = (pred_y - y[i]) * input_x[i][1]
err1num = err1num + err1[i] #Θ1对应的梯度
err2num = err2num + err2[i] #Θ2对应的梯度
theta[0] = theta[0] - step_size * err1num/10 #更新Θ1对应的梯度
theta[1] = theta[1] - step_size * err2num/10 #更新Θ2对应的梯度
for i in range(10):
pred_y = theta[0]*input_x[i][0] + theta[1]*input_x[i][1] #更新Θ1和Θ2之后重新计算预测值
error = (1/(2*10))*(pred_y - y[i])**2 #计算损失值
loss = loss + error
iter_count += 1
print('theta:', theta)
print('final loss:', loss)
print('iter counts:', iter_count)
得到结果为:
theta: [2.798690040454637, 4.119758556475943]
final loss: 0.9069318692609009
iter counts: 10000
随机梯度下降法
#SGD
input_x = [[1,4], [2,5], [5,1], [4,2],[3,5],[8,1],[7,2],[1,2],[6,2],[9,3]] #输入
y = [19,26,19,20,28,25,27,13,28,37] #输出
theta = [1,1] #θ参数初始化
loss = 10 #loss先定义一个数,为了进入循环迭代
step_size = 0.01 #步长
eps =0.0001 #精度要求
max_iters = 1000000 #最大迭代次数
error =0 #损失值
iter_count = 0 #当前迭代次数
err1=[0,0,0,0,0,0,0,0,0,0] #求Θ1梯度的中间变量1
err2=[0,0,0,0,0,0,0,0,0,0] #求Θ2梯度的中间变量2
while(loss > eps and iter_count < max_iters):
loss = 0
err1num = 0
err2num = 0
i = random.randint(0,3) #随机选取10个样本中的一个,由于样本数增加,导致迭代次数增多,这里缩减为从前4个样本中取数
pred_y = theta[0]*input_x[i][0] + theta[1]*input_x[i][1] #计算选取的样本对应的预测值
err1num = (pred_y - y[i]) * input_x[i][0] #Θ1对应的梯度
err2num = (pred_y - y[i]) * input_x[i][1] #Θ2对应的梯度
theta[0] = theta[0] - step_size * err1num #更新Θ1对应的梯度
theta[1] = theta[1] - step_size * err2num #更新Θ2对应的梯度
for i in range(4):
pred_y = theta[0]*input_x[i][0] + theta[1]*input_x[i][1]#更新Θ1和Θ2之后重新计算预测值
error = (1/2)*(pred_y - y[i])**2 #计算损失值
loss = loss + error
iter_count += 1
print('theta:', theta)
print('final loss:', loss)
print('iter counts:', iter_count)
计算所得结果:
theta: [3.001936311335262, 3.9976736608405257]
final loss: 8.908461998310991e-05
iter counts: 122
小批量梯度下降法
#MBGD
input_x = [[1,4], [2,5], [5,1], [4,2],[3,5],[8,1],[7,2],[1,2],[6,2],[9,3]] #输入
y = [19,26,19,20,28,25,27,13,28,37] #输出
theta = [1,1] #θ参数初始化
loss = 10 #loss先定义一个数,为了进入循环迭代
step_size = 0.01 #步长
eps =0.0001 #精度要求
max_iters = 100000 #最大迭代次数
error =0 #损失值
iter_count = 0 #当前迭代次数
err1=[0,0,0,0,0,0,0,0,0,0] #求Θ1梯度的中间变量1
err2=[0,0,0,0,0,0,0,0,0,0] #求Θ2梯度的中间变量2
while(loss > eps and iter_count < max_iters):
loss = 0
err1num = 0
err2num = 0
#这里总体样本取前4个,每批次选取其中两个样本
i = random.randint(0,3) #缩减为从前4个样本中随机抽取一个样本
j = (i+1)%4 #选取与i相邻的样本为第二个样本
pred_yi = theta[0]*input_x[i][0] + theta[1]*input_x[i][1] #计算选取的样本i对应的预测值
pred_yj = theta[0]*input_x[j][0] + theta[1]*input_x[j][1] #计算选取的样本j对应的预测值
err1num = (pred_yi - y[i]) * input_x[i][0] + (pred_yj - y[j]) * input_x[j][0] #Θ1对应的梯度
err2num = (pred_yi - y[i]) * input_x[i][1] + (pred_yj - y[j]) * input_x[j][1] #Θ1对应的梯度
theta[0] = theta[0] - step_size * (1/2) * err1num #更新Θ1对应的梯度
theta[1] = theta[1] - step_size * (1/2) * err2num #更新Θ2对应的梯度
for i in range(4):
pred_y = theta[0]*input_x[i][0] + theta[1]*input_x[i][1]#更新Θ1和Θ2之后重新计算预测值
error = (1/2*2)*(pred_y - y[i])**2 #计算损失值
loss = loss + error
iter_count += 1
print('theta:', theta)
print('final loss:', loss)
print('iter counts:', iter_count)
计算所得结果:
theta: [3.0015170025160156, 3.9983740403913126]
final loss: 9.42763188681316e-05
iter counts: 116