随机梯度下降法(SGD)
SGD算法的核心就是梯度下降法。梯度下降法是深度学习优化算法重的核心思想。其基本含义,简明扼要的写一下:
是一个连续可导的函数,根据一阶泰勒公式开展,得到以下近似:
参数更新为:
更广义的情况下,输入是一个维的向量,损失函数的关于向量的梯度如下:
向量的更新为:
以上,是一个小量的正数,称为学习率。如果学习率过大,则会导致一阶泰勒展开式不再成立,无法保证正常运作。一个好的学习率需要通过反复的实验最终得到。
- SGD算法的缺点:
在每次迭代中,梯度下降根据自变量当前位置,沿着当前位置的梯度更新自变量。然而,如果自变量的迭代方向仅仅取决于自变量当前位置,这可能会带来一些问题,比如强烈的震荡。
/*
用梯度下降法解a,b
x = [0.1, 0.2, 0.3]
y = [1, 2, 3]
y = ax + b
损失函数默认MSE
*/
void gradientDescent()
{
float x[3] = { 0.1, 0.2, 0.3 };
float y[3] = { 1, 2, 3 };
float a = 10; // a初始值
float b = 10; // b初始值
float lr = 0.1;
float gda, gdb;
int max_iters = 10000; // 迭代次数
int k = 0;
while (k++ < max_iters)
{
gda = gdb = 0.f;
for (int i = 0; i < 3; i++)
{
float tmp = (a * x[i] + b - y[i]);
gda += (2 * tmp) * x[i]; // a的梯度
gdb += (2 * tmp); // b的梯度
}
gda /= 3;
gdb /= 3;
a -= (gda * lr);
b -= (gdb * lr);
}
printf("a: %.f b: %.f", a, b);
}
动量法(momentum)
动量法的提出是为了解决梯度下降的上述问题。所做的改进就是引入历史梯度信息。这里梯度需要有一个衰减值 ,推荐取0.9。这样的做法可以让早期的梯度对当前梯度的影响越来越小。表示t时刻的梯度,表示t时刻梯度的权重;表示之前所有步骤所累积的动量和。
从物理含义上理解,vt表示t时刻的速度,gt表示t时刻的加速度。t时刻的速度大小受0-t时刻之间历史加速度的影响,即对过去时刻的加速度做了加权,且权重按指数衰减(时间越久,衰减越厉害)。
与SGD统一下写法,可见就相当于在SGD的基础上增加了历史梯度信息,即动量。
- 动量法缺点:
- 参数的每一个元素都用相同的学习率来更新,统一的学习率难以适应所有维度的问题。
- 动量法有助于改善梯度下降法引起的剧烈震荡,但仍然会有震荡。
/*
用动量-梯度下降法解a,b
x = [0.1, 0.2, 0.3]
y = [1, 2, 3]
y = ax + b
损失函数默认MSE
*/
void momentum()
{
float x[3] = { 0.1, 0.2, 0.3 };
float y[3] = { 1, 2, 3 };
float a = 10;
float b = 10;
float gda, gdb, va, vb;
float lr = 0.1;
float gamma = 0.9;
int max_iters = 1000;
va = vb = 0.f; // a, b动量初始化
int k = 0;
while (k++ < max_iters)
{
gda = gdb = 0.f;
for (size_t i = 0; i < 3; i++)
{
float tmp = (a * x[i] + b - y[i]);
gda += (2 * tmp) * x[i];
gdb += (2 * tmp);
}
gda /= 3;
gdb /= 3;
va = gamma * va + lr * gda;
vb = gamma * vb + lr * gdb;
a -= va;
b -= vb;
}
printf("a: %.f b: %.f", a, b);
}
Adagrad
SGD、Momentum均是以相同的学习率去更新各个参数。减少学习率,相当于全体参数学习率一起降低。而Adagrad为每个参数适当地调整学习率,为参数中的每个元素分别拥有一个学习率。对于更新不频繁的参数,我们希望单次步长更大,多学习一些知识;对于更新频繁的参数,我们则希望步长较小,使得学习到的参数更稳定,不至于被单个样本影响太多。
在上面的Momentum优化算法中,虽然初步解决了优化中摆动幅度大的问题,但震荡仍然在。如下图所示,蓝色的为Momentum优化算法所走的路线,绿色的为Adagrad优化算法所走的路线。可见,Adagrad自适应地限制了y轴方向上的参数更新,增大了x轴方向上的更新幅度。
Adagrad算法参数更新:
为了使得每个参数元素能各自独立更新,引入二阶动量
可以看到其学习率的调节策略是,由于二阶动量的叠加而不断变小,且每个元素的学习率都不一样。
- Adagrad缺点:
- 它的缺点是分母会不断积累,这样学习率就会收缩并最终会变得非常小,收敛速度很慢。
- 当学习率在迭代早期降得较快且当前解依然不佳时,迭代后期由于学习率过小,可能较难找到一个有用的解。
RMSProp算法
为了解决Adagrad算法后期学习率一直减小的问题,RMSProp算法做了一点小改进。不同于AdaGrad算法里状态变量是截至时间步 所有小批量随机梯度 按元素平方和,RMSProp算法将这些梯度按元素平方做指数加权移动平均。
RMSProp算法的参数更新如下:
RMSProp算法和AdaGrad算法的不同在于,RMSProp算法使用了小批量随机梯度按元素平方的指数加权移动平均来调整学习率,因此其后期的学习率可能会保持不变,而非一直减少。
AdaDelta
除了RMSProp算法以外,另一个常用优化算法AdaDelta算法也针对AdaGrad算法在迭代后期可能较难找到有用解的问题做了改进。有意思的是,AdaDelta算法没有学习率这一超参数。
AdaDelta算法没有学习率超参数,它通过使用有关自变量更新量平方的指数加权移动平均的项来替代RMSProp算法中的学习率。
Adam
这个算法是另一种计算每个参数的自适应学习率的方法。有人说这相当于 Momentum + RMSprop。因为,Adam引入了动量法一阶动量的概念,使得其能引入历史梯度的信息,又引入RMSProp算法二阶动量概念,能为每个参数自适应制定学习率。
一阶动量:
二阶动量:
偏差纠正,使过去各时刻随机梯度权值之和为1:
利用自适应调整学习率,学习率相当于动量的权值:
参数更新:
这几种优化算法各有各的特点,都有各自擅长的问题和缺点;目前不存在放之四海而皆准的优化方法,要视不同问题选择合适的方法。
例:求函数的最小值。
下面四幅图,感受一下各种方法在该例子中的特点。可以看到,SGD在y轴方向上震荡幅度较大,Momentum改善了y轴方向上的震荡,但震荡仍在;Adagrad算法刚开始收敛很快,后期学习率不断减小;Adam相当于结合了Momentum和Adagrad的特点。可以看出在本例中,AdaGrad算法效果最好。