参考:
https://crazyang.blog.csdn.net/article/details/84618536
随机梯度下降算法的出现是因为,BGD的迭代速度在大数据量下会变得很慢,因为它每次迭代都是用的是所有的数据样本。而SGD一次迭代只需要使用一个样本。可以根据这一个样本来计算梯度。
# 随机梯度下降SGD
# 拟合函数为:y = theta * x
# 代价函数为:J = 1 / (2 * m) * ((theta * x) - y) * ((theta * x) - y).T;
# 梯度迭代为: theta = theta - alpha / m * (x * (theta * x - y).T);
# 以 y=2*x1+9*x2为例
import numpy as np
import math
def sgd():
# 训练集,每个样本有2个分量
x = np.array([(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2), (-1, -2), (-2, -1)])
y = np.array([11, 20, 13, 22, 15, 24, -20, -13])
# 初始化
m, dim = x.shape
theta = np.zeros(dim) # 参数
alpha = 0.01 # 学习率
threshold = 0.0001 # 停止迭代的错误阈值
iterations = 1500 # 迭代次数
error = 0 # 初始错误为0
# 迭代开始
for i in range(iterations):
error = 1 / (2 * m) * np.dot((np.dot(x, theta) - y).T, (np.dot(x, theta) - y))
# 迭代停止
if abs(error) <= threshold:
break
j = np.random.randint(0, m)
theta -= alpha * (x[j] * (np.dot(x[j], theta) - y[j]))
print('迭代次数:%d' % (i + 1), 'theta:', theta, 'error:%f' % error)
if __name__ == '__main__':
sgd()
迭代次数:1278 theta: [2.01294926 8.98333428] error:0.000100
SGD在随机挑选某一分量的梯度方向进行收敛,加一个“动量”的话,相当于有了一个惯性在里面。当我们人在运动时,向前跑由于有惯性,故后期跑起来会比刚开始起步时轻松,另外当你想要往反方向时,会降低你的返回速度。故,在SGD的基础上加一个动量,如果当前收敛效果好,就可以加速收敛,如果不好,则减慢它的步伐。
用更科学的话来讲是:如果本次和上次的梯度符号是相同的,那么就能够加速下降(幅度变大),就能够解决原先下降太慢的问题;如果本次和上次的梯度符号是相反的,那么这次就和上次相互抑制,减缓震荡。由于有动量的作用,在局部最优点时,它可以借助动量跳出来,不易陷入局部最优点。
带动量的随机梯度下降SGD
def momentum_sgd():
# 训练集,每个样本有三个分量
x = np.array([(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2), (-1, -2), (-2, -1)])
y = np.array([11, 20, 13, 22, 15, 24, -20, -13])
# 初始化
m, dim = x.shape
theta = np.zeros(dim) # 参数
alpha = 0.01 # 学习率
momentum = 0.1 # 动量
threshold = 0.0001 # 停止迭代的错误阈值
iterations = 1500 # 迭代次数
error = 0 # 初始错误为0
gradient = 0 # 初始梯度为0
# 迭代开始
for i in range(iterations):
j = i % m
error = 1 / (2 * m) * np.dot((np.dot(x, theta) - y).T,
(np.dot(x, theta) - y))
# 迭代停止
if abs(error) <= threshold:
break
gradient = momentum * gradient + alpha * (x[j] *
(np.dot(x[j], theta) - y[j]))
theta -= gradient
print('迭代次数:%d' % (i + 1), 'theta:', theta, 'error:%f' % error)
if __name__ == '__main__':
momentum_sgd()
迭代次数:1104 theta: [2.01263177 8.98305501] error:0.000099
相比于SGD的1278的迭代次数,增加momentum=0.1后的SGD的迭代速度加快了。预测的准确度二者是差不多的。
将momentum=0.1改为0.3后,我们发现,函数的迭代速度又增加了,且准确率依然能够保持。继续调整参数:
故,我得到:在momentum=0.9时得到的结果是最好的。此时迭代速度提高了快10倍,并且theta的准确率更高。
对于没有momentum的SGD,计算梯度并更新参数的公式如下: g = 1 m ∇ θ ∑ i = 1 m L ( f ( x i ; θ ) , y i ) g = \frac 1m\nabla\theta\sum_{i=1}^m L\bigl(f(x_i;\theta),y_i\bigr) g=m1∇θi=1∑mL(f(xi;θ),yi)
θ = θ − δ g \theta=\theta-\delta g θ=θ−δg
每次梯度更新的时候都会更新 − δ g -\delta g −δg,这个值是恒定的。对于SGD,更新值的方差较大,收敛过程会产生波动,可能落入极小值(卡在鞍点)。
加入动量后:
计算梯度并更新参数的公式如下:
g = 1 m ∇ θ ∑ i = 1 m L ( f ( x i ; θ ) , y i ) g = \frac 1m\nabla\theta\sum_{i=1}^m L\bigl(f(x_i;\theta),y_i\bigr) g=m1∇θi=1∑mL(f(xi;θ),yi)
v = α v − δ g v=\alpha v-\delta g v=αv−δg
θ = θ + v \theta = \theta + v θ=θ+v
故第一次迭代公式如下, v 1 = − δ g 1 v_1=- \delta g_1 v1=−δg1:
θ = θ + v 1 = θ − δ g 1 \theta = \theta + v_1 = \theta - \delta g_1 θ=θ+v1=θ−δg1
第二次迭代公式如下:
θ = θ + v 2 = θ + α v 1 − δ g 2 = θ − δ ( α g 1 + g 2 ) \theta = \theta + v_2 = \theta + \alpha v_1- \delta g_2 = \theta - \delta(\alpha g_1 + g_2) θ=θ+v2=θ+αv1−δg2=θ−δ(αg1+g2)
由上述推断可知:如果和上次的梯度符号是相同的,那么就能够加速下降(解决BGD的迭代速度慢);如果本次和上次的梯度符号是相反的,那么这次就和上次相互抑制,减缓震荡(解决SGD波动大,可能落入极小值)。