一切的源头
求解深度学习网络参数===》梯度下降法===》加快收敛速度===》优化方法(momentum、RMSprop和Adam)
大纲内容:
- 指数加权平均(Exponentially weighted average)
- 带偏差修正的指数加权平均(bias correction in exponentially weighted average)
- momentum
- Nesterov Momentum
- Adagrad
- Adadelta
- RMSprop
- Adam
一、 指数加权平均(Exponentially weighted average)
指数加权平均是处理时间序列的常用工具,下面用一个例子来引入指数加权平均的概念。下图是一个180天的气温图(图片来自ng Coursera deep learning 课)



可以认为近似是
天的平均气温,所以上面公式设置了β=0.9,当 t>10时,则可以认为
近似是它前10天的平均气温。比如,按照上面的公式,我们计算
从上面的公式能够看出,实际上是个指数衰减函数。 下面再看下 β 取不同值时,曲线的拟合情况(图片来自ng deep learning课):

- 当 β较大时(β=0.98 相当于每一点前50天的平均气温),曲线波动相对较小更加平滑(绿色曲线),因为对很多天的气温做了平均处理,正因为如此,曲线还会右移。
- 同理,当 β较小时(β=0.5相当于每一点前2天的平均气温),曲线波动相对激烈,但是它可以更快的适应温度的变化。
下面直接看实现指数加权平均(Exponentially weighted average) 的伪代码:
V0 = 0
repeat
{
get next theta_t
V_theta = beta * V_theta + (1 - beta)* theta_t
}
关于指数加权平均的优缺点:
当 β=0.9 时,我们可以近似的认为当前的数值是过去10天的平均值,但是显然如果我们直接计算过去10天的平均值,要比用指数加权平均来的更加准确。但是如果直接计算过去10天的平均值,我们要存储过去10天的数值,而加权平均只要存储一个数值即可,而且只需要一行代码即可,所以在机器学习中用的很多。
二、带偏差修正的指数加权平均(bias correction in exponentially weighted average)
按照原始的指数加权平均公式,还有一个问题,就是当k比较小时,其最近的数据太少,导致估计误差比较大。例如。为了减小最初几个数据的误差,通常对于k比较小时,需要做如下修正:
是所有权重的和,这相当于对权重做了一个归一化处理。下面的图中,紫色的线就是没有做修正的结果,修正之后就是绿色曲线。二者在前面几个数据点之间相差较大,后面则基本重合了。

在机器学习中,多数的指数加权平均运算并不会使用偏差修正。因为大多数人更愿意在初始阶段,用一个捎带偏差的值进行运算。不过,如果在初试阶段就开始考虑偏差,指数加权移动均值仍处于预热阶段,偏差修正可以做出更好的估计。
三、动量(momentum)
动量的引入就是为了加快学习过程,特别是对于高曲率、小但一致的梯度,或者噪声比较大的梯度能够很好的加快学习过程。动量的主要思想是积累了之前梯度指数级衰减的移动平均(前面的指数加权平均),下面用一个图来对比下,SGD和动量的区别:

最直观的理解就是,若当前的梯度方向与累积的历史梯度方向一致,则当前的梯度会被加强,从而这一步下降的幅度更大。若当前的梯度方向与累积的梯度方向不一致,则会减弱当前下降的梯度幅度。 用一个图来形象的说明下上面这段话(图片来自李宏毅《一天搞懂深度学习》):


momentum的伪代码:
initialize VdW = 0, vdb = 0 //VdW维度与dW一致,Vdb维度与db一致
on iteration t:
compute dW,db on current mini-batch
VdW = beta*VdW + (1-beta)*dW
Vdb = beta*Vdb + (1-beta)*db
W = W - learning_rate * VdW
b = b - learning_rate * Vdb
公式也可以写为(下面代码中用到的公式,同pytorch):
θ 为当前速度(动量),α为学习率,衰减值(动量参数)γ ,推荐取0.9。
以下为利用Momentum参数优化更新方法来训练一个三层神经网络的MNIST手写体数字识别
import numpy as np
import torch
from torchvision.datasets import MNIST # 导入 pytorch 内置的 mnist 数据
from torch.utils.data import DataLoader
from torch import nn
from torch.autograd import Variable
import time
import matplotlib.pyplot as plt
# %matplotlib inline
def data_tf(x):
x = np.array(x, dtype='float32') / 255
x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到
x = x.reshape((-1,)) # 拉平
x = torch.from_numpy(x)
return x
# train_set = MNIST('./data', train=True, transform=data_tf, download=True) # 载入数据集,申明定义的数据变换
# test_set = MNIST('./data', train=False, transform=data_tf, download=True)
# 下载训练集 MNIST手写数字训练集
train_set = MNIST(root='/home/hk/Desktop/learn_pytorch/data', train=True, transform=data_tf, download=False)#data_tf auto normalization in the process of the transform
test_set = MNIST(root='/home/hk/Desktop/learn_pytorch/data', train=False, transform=data_tf, download=True)
# 定义 loss 函数
criterion = nn.CrossEntropyLoss()
def sgd_momentum(parameters, vs, lr, gamma):
for param, v in zip(parameters, vs):
v[:] = gamma * v + lr * param.grad.data
param.data = param.data - v
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(),
nn.Linear(200, 10),
)
# 将速度初始化为和参数形状相同的零张量
vs = []
for param in net.parameters():
vs.append(torch.zeros_like(param.data))
# 开始训练
losses = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
net.zero_grad()
loss.backward()
sgd_momentum(net.parameters(), vs, 1e-2, 0.9) # 使用的动量参数为 0.9,学习率 0.01
# 记录误差
train_loss += loss.data
if idx % 20 == 0:
losses.append(loss.data)
idx+=1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
x_axis = np.linspace(0, 5, len(losses), endpoint=True)
plt.semilogy(x_axis, losses, label='adagrad')
plt.legend(loc='best')
plt.show()
以下为训练的结果
epoch: 0, Train Loss: 0.367609
epoch: 1, Train Loss: 0.168976
epoch: 2, Train Loss: 0.123189
epoch: 3, Train Loss: 0.100595
epoch: 4, Train Loss: 0.083965
使用时间: 69.73666 s
当然,pytorch 内置了动量法的实现,非常简单,直接在 torch.optim.SGD(momentum=0.9) 即可,下面实现一下
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(),
nn.Linear(200, 10),
)
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2, momentum=0.9) # 加动量
# 开始训练
losses = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data[0]
if idx % 30 == 0: # 30 步记录一次
losses.append(loss.data[0])
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
我们可以对比一下不加动量的随机梯度下降法
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(),
nn.Linear(200, 10),
)
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2) # 不加动量
# 开始训练
losses1 = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data[0]
if idx % 30 == 0: # 30 步记录一次
losses1.append(loss.data[0])
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
RMSprop算法

观察上面的公式可以看到,s是对梯度的平方做了一次平滑。在更新w时,先用梯度除以,相当于对梯度做了一次归一化。如果某个方向上梯度震荡很大,应该减小其步长;而震荡大,则这个方向的s也较大,除完之后,归一化的梯度就小了;如果某个方向上梯度震荡很小,应该增大其步长;而震荡小,则这个方向的s也较小,归一化的梯度就大了。因此,通过RMSprop,我们可以调整不同维度上的步长,加快收敛速度。把上式合并后,RMSprop迭代更新公式如下:
五、AdaGrad(Adaptive Gradient)
通常,我们在每一次更新参数时,对于所有的参数使用相同的学习率。而AdaGrad算法的思想是:每一次更新参数时(一次迭代),不同的参数使用不同的学习率。AdaGrad 的公式为:
从公式中我们能够发现: 优点:对于梯度较大的参数,
相对较大,则
较小,意味着学习率会变得较小。而对于梯度较小的参数,则效果相反。这样就可以使得参数在平缓的地方下降的稍微快些,不至于徘徊不前。
缺点:由于是累积梯度的平方,到后面累积的比较大,会导致梯度
导致梯度消失。
关于AdaGrad,goodfellow和bengio的《deep learning》书中对此的描述是:在凸优化中,AdaGrad算法具有一些令人满意的理论性质。但是,在实际使用中已经发现,对于训练深度神经网络模型而言,从训练开始时累积梯度平方会导致学习率过早过量的减少。AdaGrad算法在某些深度学习模型上效果不错,但不是全部。
具体到实现,对于 iteration t,compute on current mini−batch:
六、Adam
Adam实际上是把momentum和RMSprop结合起来的一种算法,算法流程是:

def adam(parameters, vs, sqrs, lr, t, beta1=0.9, beta2=0.999):
eps = 1e-8
for param, v, sqr in zip(parameters, vs, sqrs):
v[:] = beta1 * v + (1 - beta1) * param.grad.data
sqr[:] = beta2 * sqr + (1 - beta2) * param.grad.data ** 2
v_hat = v / (1 - beta1 ** t)
s_hat = sqr / (1 - beta2 ** t)
param.data = param.data - lr * v_hat / torch.sqrt(s_hat + eps)
以下为Adam优化方法实现的有三层的网络MNIST手写体数字识别
import numpy as np
import torch
from torchvision.datasets import MNIST # 导入 pytorch 内置的 mnist 数据
from torch.utils.data import DataLoader
from torch import nn
from torch.autograd import Variable
import time
import matplotlib.pyplot as plt
%matplotlib inline
def data_tf(x):
x = np.array(x, dtype='float32') / 255
x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到
x = x.reshape((-1,)) # 拉平
x = torch.from_numpy(x)
return x
train_set = MNIST('./data', train=True, transform=data_tf, download=True) # 载入数据集,申明定义的数据变换
test_set = MNIST('./data', train=False, transform=data_tf, download=True)
# 定义 loss 函数
criterion = nn.CrossEntropyLoss()
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(),
nn.Linear(200, 10),
)
# 初始化梯度平方项和动量项
sqrs = []
vs = []
for param in net.parameters():
sqrs.append(torch.zeros_like(param.data))
vs.append(torch.zeros_like(param.data))
t = 1
# 开始训练
losses = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
net.zero_grad()
loss.backward()
adam(net.parameters(), vs, sqrs, 1e-3, t) # 学习率设为 0.001
t += 1
# 记录误差
train_loss += loss.data
if idx % 30 == 0:
losses.append(loss.data)
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
x_axis = np.linspace(0, 5, len(losses), endpoint=True)
plt.semilogy(x_axis, losses, label='adam')
plt.legend(loc='best')
epoch: 0, Train Loss: 0.372057
epoch: 1, Train Loss: 0.186132
epoch: 2, Train Loss: 0.132870
epoch: 3, Train Loss: 0.107864
epoch: 4, Train Loss: 0.091208
使用时间: 85.96051 s
当然 pytorch 中也内置了 adam 的实现,只需要调用 torch.optim.Adam(),下面是例子
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(),
nn.Linear(200, 10),
)
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
# 开始训练
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data[0]
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
在学习完以上四种参数的优化方法之后我们在这里对四种方法进行对比,观察loss函数的变化情况(loss越小并不代表最终的模型效果越好) 以下为四中算法进行比较的代码
import numpy as np
import torch
from torchvision.datasets import MNIST # 导入 pytorch 内置的 mnist 数据
from torch.utils.data import DataLoader
from torch import nn
from torch.autograd import Variable
import time
import matplotlib.pyplot as plt
# %matplotlib inline
def data_tf(x):
x = np.array(x, dtype='float32') / 255
x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到
x = x.reshape((-1,)) # 拉平
x = torch.from_numpy(x)
return x
# 下载训练集 MNIST手写数字训练集
train_set = MNIST(root='/home/hk/Desktop/learn_pytorch/data', train=True, transform=data_tf, download=False)#data_tf auto normalization in the process of the transform
test_set = MNIST(root='/home/hk/Desktop/learn_pytorch/data', train=False, transform=data_tf, download=True)
# 定义 loss 函数
criterion = nn.CrossEntropyLoss()
def sgd_momentum(parameters, vs, lr, gamma):
for param, v in zip(parameters, vs):
v[:] = gamma * v + lr * param.grad.data
param.data = param.data - v
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(),
nn.Linear(200, 10),
)
# 将速度初始化为和参数形状相同的零张量
vs = []
for param in net.parameters():
vs.append(torch.zeros_like(param.data))
# 开始训练
print("*"*10)
losses = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
net.zero_grad()
loss.backward()
sgd_momentum(net.parameters(), vs, 1e-2, 0.9) # 使用的动量参数为 0.9,学习率 0.01
# 记录误差
train_loss += loss.data
if idx % 30 == 0:
losses.append(loss.data)
idx+=1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('Momentum:使用时间: {:.5f} s'.format(end - start))
#SGD
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2) # 不加动量
# 开始训练
print("*"*10)
losses1 = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data
if idx % 30 == 0: # 30 步记录一次
losses1.append(loss.data)
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('SGD:使用时间: {:.5f} s'.format(end - start))
#Adam
print("*"*10)
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
losses2 = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
net.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data
if idx % 30 == 0:
losses2.append(loss.data)
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('Adam:使用时间: {:.5f} s'.format(end - start))
#RMSProp
print("*"*10)
optimizer = torch.optim.RMSprop(net.parameters(), lr=1e-3, alpha=0.9)
losses3 = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
net.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data
if idx % 30 == 0:
losses3.append(loss.data)
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('RMSProp:使用时间: {:.5f} s'.format(end - start))
#Adagrad
print("*"*10)
optimizer = torch.optim.Adagrad(net.parameters(), lr=1e-2)
losses4 = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播
out = net(im)
loss = criterion(out, label)
# 反向传播
net.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.data
if idx % 30 == 0:
losses4.append(loss.data)
idx += 1
print('epoch: {}, Train Loss: {:.6f}'
.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('Adagrad:使用时间: {:.5f} s'.format(end - start))
x_axis = np.linspace(0, 5, len(losses), endpoint=True)
plt.semilogy(x_axis, losses, label='Momentum:alpha=0.9')
plt.semilogy(x_axis, losses1, label='SGD')
plt.semilogy(x_axis, losses2, label='Adam')
plt.semilogy(x_axis, losses3, label='RMSProp:alpha=0.9')
plt.semilogy(x_axis, losses4, label='Adagrad')
plt.legend(loc='best')
plt.show()
**********
epoch: 0, Train Loss: 0.370089
epoch: 1, Train Loss: 0.171468
epoch: 2, Train Loss: 0.123055
epoch: 3, Train Loss: 0.098832
epoch: 4, Train Loss: 0.085154
Momentum:使用时间: 78.35162 s
**********
epoch: 0, Train Loss: 0.056292
epoch: 1, Train Loss: 0.052914
epoch: 2, Train Loss: 0.051503
epoch: 3, Train Loss: 0.050107
epoch: 4, Train Loss: 0.049181
SGD:使用时间: 55.99813 s
**********
epoch: 0, Train Loss: 0.109644
epoch: 1, Train Loss: 0.087866
epoch: 2, Train Loss: 0.080869
epoch: 3, Train Loss: 0.070733
epoch: 4, Train Loss: 0.063566
Adam:使用时间: 81.13758 s
**********
epoch: 0, Train Loss: 0.062457
epoch: 1, Train Loss: 0.057542
epoch: 2, Train Loss: 0.054834
epoch: 3, Train Loss: 0.051196
epoch: 4, Train Loss: 0.048507
RMSProp:使用时间: 64.00369 s
**********
epoch: 0, Train Loss: 0.061198
epoch: 1, Train Loss: 0.014729
epoch: 2, Train Loss: 0.011167
epoch: 3, Train Loss: 0.009214
epoch: 4, Train Loss: 0.007709
Adagrad:使用时间: 53.32201 s


引用
github.com/tz28/deep-l… (代码实现)