随着深度学习的兴起,其算法的核心:梯度下降算法正不断发展,本文将简要介绍几种主流的optimizer:SGD(Stochastic Gradient Descent),Momentum,AdaGrad(Adaptive Gradient Algorithm),RMSProp(Root Mean Square prop )和Adam(Adaptive Moment Estimation)。对于每一种optimizer,将从四个方面展开:
①简要介绍:算法的基本思路,优缺点等;
②计算过程:主要以数学表达式的形式呈现;
③Python原生实现:不借助任何外部库,实现各种optimizer
④Pytorch对应介绍:每一种optimizer在深度学习框架pytorch中都有与之对应的命令,可以直接调用,将对之进行详细介绍
在开始之前,首先要讲清楚的一件事情是:无论是任何optimizer,其核心都是对神经网络中参数(需要进行学习的参数,例如全连接神经网络中的权重参数和偏置参数)的梯度进行不同方式的使用,从而更新参数。这里不同方式的使用就是不同optimizer的区别所在。因此,所有的opitmizer都是在已知参数梯度信息(grads)的前提下展开的。
再啰嗦一嘴梯度的意义:从数学上的直观理解是,梯度的正负指明了在当前参数下,优化模型应该前进的方向,梯度的大小反映了在当前参数下,模型优化的快慢。
接下来进入正文:
1.SGD(Stochastic Gradient Descent)
①基本介绍
SGD(Stochastic Gradient Descent)全称:随机梯度下降法。实际上,SGD对梯度(grads,后文全部用grads代替梯度)的使用非常简单,仅仅是使用梯度本身,再乘以learning rate直接来更新参数。(这里我不想过分的去探究SGD优化器背后的数学意义,而仅仅描述优化器本身对梯度的使用情况)
②计算过程
假设需要更新的参数为W,则SGD的做法如下所示:
③Python原生实现
源代码:
class SGD:
def __init__(self, lr=0.01): # 默认的learning rate为0.01
self.lr = lr
def update(self, params, grads): # 需要更新的参数为Params,params的梯度信息保存在grads中
for keys in params.keys():
params[keys] -= self.lr * grads[keys] # 更新参数
将SGD以类的方式实现,默认的learning rate为0.01,更新参数时的调用方式为:
SGD.updata(params, grads)
④Pytorch对应介绍
在Pytorch中调用SGD的方式为:
torch.optim.SGD(params, lr=0.01, momentum=0, dampening=0, weight_dacay=0, nesterov=False)
这里与SGD有关的仅仅是前两个参数,后边的保持默认就与③中的源代码作用相同了。
参数介绍:
params:所搭建的神经网络模型中需要更新的参数。
lr:学习率
对于参数:momentum,dampening,weight_dacay,nesterov这里不做介绍,原因是:在Pytorch框架中,SGD优化器不仅仅是我们上述介绍的简单的SGD,它将优化其momentum,nesterov都融合进了一个类,只是命名时,将这个大类命名为了SGD,在后续的介绍中,再介绍momentum。
2.Momentum
①基本介绍
Momentum的翻译是动量,算法momentum的思想就是模拟物理学中的动量,让参数的更新不仅仅与当前的梯度更新方向有关,同时与上一步梯度更新方向有关。虽然这里使用了动量,但仅仅是使用了动量的思想,仔细观察算法的核心计算过程,会发现其与物理学上的动量P=Mv没有任何关系。
②计算过程
第一步,获取参数需要更新的大小,实际上是获取更新参数的方向和大小,为上一次更新方向和梯度向量的向量和,的大小衡量上一步更新在此次更新中的重要程度。
第二步:更新神经网络模型中的参数
③Python原生实现
源代码:
class Momentum:
def __init__(self, lr=0.1):
self.lr = lr
self.alpha = 0.9 # 默认α为0.9
self.v = None # 用来记录上一步更新的方向和大小
def update(self, params, grads):
if self.v is None:
self.v = {}
for keys, values in params.items():
self.v[keys] = np.zeros_like(values) # 根据网络中参数的形状,对V进行初始化
for keys in params.keys(): # 更新网络模型中需要学习的参数
self.v[keys] = self.alpha * self.v[keys] - self.lr * grads[keys]
params[keys] += self.v[keys]
将momentum以python中类(class)的形式实现。
超参数:learning_rate=0.1,=0.9。
调用momentum进行模型参数更新时的调用格式:
Momentum.updata(params, grads)
④Pytorch对应介绍
在Pytorch框架中没有专门的momentum优化器,而是将其写入到了SGD优化器内。
torch.optim.SGD(params, lr=0.01, momentum=0, dampening=0, weight_dacay=0, nesterov=False)
这里与momentum有关的是前三个参数,之后的参数保持默认。
参数介绍:
params:所搭建的神经网络模型中需要更新的参数。
lr:学习率
momentum:相当于上述源代码中的,表示上一步梯度在此次参数更新过程的重要程度。在momentum=0时,退化为SGD。
3.AdaGrad(Adaptive Gradient Algorithm)
①基本介绍
之前介绍的两种optimizer:SGD和momentum,在整个神经网络的训练过程中,learning rate始终保持不变。这样带来的问题是,当梯度值非常小时,采用小的学习率,会使网络的训练非常的低效。AdaGrad从learning rate的角度来考虑问题,基本的思路是:当梯度的值较大时,采用较小的学习率,当梯度的值较小时,采用较大的学习率,从而使得神经网络的训练过程变得高效。AdaGrad在Adaptive Subgradient Methods for Online Learning and Stochastic Optimization中提出,有兴趣的读者可以研究原文。
②计算过程
第一步,计算h,对于神经网络中的每一个参数,h为整个训练过程此参数梯度的平方和。
第二步:更新神经网络模型中的参数
③Python原生实现
源代码:
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.r = None
def update(self, params, grads):
eps = 1e-7 # 引入一个极小值,防止被除数为零报错
if self.r is None:
self.r = {} # 将r保存为字典型变量
for keys, values in params.items():
self.r[keys] = np.zeros_like(values) # 根据神经网络中需要被更新的参数的形状初始化r
for keys in params.keys(): # 更新参数
self.r[keys] = self.r[keys] + grads[keys]**2
params[keys] -= self.lr * grads[keys] / (np.sqrt(self.r[keys]) + eps)
AdaGrad中的超参数只有learning rate,这里默认为0.01。使用AdaGrad更新参数时,调用格式与前述的SGD与Momentum没有任何区别,这里不再赘述。
④Pytorch对应介绍
在Pytorch中调用AdaGrad的方式为:
torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0, eps=1e-10)
参数介绍:
params:神经网络中需要更新的参数。
lr:学习率,需给定
lr_decay(可选):有关学习率衰减的参数。
weight_decay(可选):权值衰减系数。
initial_accumulator_value(可选):初始的累加h(②中的h)值。
eps:一个极小值,为了防止分母为0而引入。
4.RMSProp(Root Mean Square prop )
①基本介绍
RMSProp的提出有意思的是,这种优化器的设计并不是发表在某一篇论文或者杂志上的。他是由Hinton教授在自己的课堂上提出的。算法的每一步都对之前的梯度进行一种累计的加权平均。正式版首次出现在: Adaptive Subgradient Methods for Online Learning and Stochastic Optimization
②计算过程
第一步,计算r,对神经网络中的每一个参数计算累计加权平均。
第二步:更新神经网络模型中的参数:
③Python原生实现
源代码:
class RMSProp:
def __init__(self, lr=0.01): # 默认学习率为0.01
self.lr = lr
self.r = None
self.alpha = 0.9 # 超参数,设为0.9
def update(self, params, grads):
eps = 1e-7 # 为防止分母为零对分母加上一个极小值
if self.r is None: # 根据神经网络中参数的形状初始化r
self.r = {}
for keys, values in params.items():
self.r[keys] = np.zeros_like(values)
for keys in params.keys(): # 更新参数
self.r[keys] = self.alpha * self.r[keys] + (1 - self.alpha) * grads[keys]**2
params[keys] -= self.lr * grads[keys] / (np.sqrt(self.r[keys]) + eps)
这里的调用方式与之前介绍的优化器没有任何区别,这里不再赘述。
④Pytorch对应介绍
在Pytorch中调用RMSProp的方式为:
torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
参数介绍:
params:神经网络中需要学习的参数;
lr:学习率;
alpha:平滑参数;
eps:极小值
weight_decay:权值衰减参数
momentum,centered与此处的RMSProp没有任何关系,这里不做介绍。
5.Adam(Adaptive Moment Estimation)
①基本介绍
Adam在RMSProp的基础上进行了速度滑动平均和偏差修正。具体的内容可以参考原始的论文:《Adam:A Method for Stochastic Optimization》
②计算过程
第一步:计算动量平均和速度平均:
第二步:修正过程
第三步:更新参数
③Python原生实现
源代码:
class Adam:
def __init__(self, lr=0.001):
self.lr = lr
self.beta1 = 0.9
self.beta2 = 0.999
self.eps = 1e-7
self.m = None
self.v = None
self.step = 0
def update(self, params, grads):
if self.m is None:
self.m = {}
self.v = {}
for keys, values in params.items():
self.m[keys] = np.zeros_like(values)
self.v[keys] = np.zeros_like(values)
self.step += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.step) / (1.0 - self.beta1**self.step)
for keys in params.keys():
self.m[keys] = (self.beta1 * self.m[keys] + (1 - self.beta1) * grads[keys])
self.v[keys] = (self.beta2 * self.v[keys] + (1 - self.beta2) * grads[keys]**2)
params[keys] -= lr_t * self.m[keys] / (np.sqrt(self.v[keys]) + self.eps)
这里的调用方式与之前介绍的优化器没有任何区别,这里不再赘述。
④Pytorch对应介绍
在Pytorch中调用Adam的方式为:
torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
参数介绍:
params:神经网络中需要学习的参数;
lr:学习率
betas:平滑常数和;
eps:极小值,防止分母为零;
weight_decay:权值衰减
amsgrad:与Adam没有关系,不做解释。