RMSProp算法
在AdaGrad算法中,因为调整学习率时分母上的变量st\boldsymbol{s}_tst一直在累加按元素平方的小批量随机梯度,所以目标函数自变量每个元素的学习率在迭代过程中一直在降低(或不变)。因此,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad算法在迭代后期由于学习率过小,可能较难找到一个有用的解。为了解决这一问题,RMSProp算法对AdaGrad算法做了一点小小的修改。
算法内容
之前说过指数加权移动平均。不同于AdaGrad算法里状态变量st\boldsymbol{s}_tst是截至时间步ttt所有小批量随机梯度gt\boldsymbol{g}_tgt按元素平方和,RMSProp算法将这些梯度按元素平方做指数加权移动平均。
具体来说,给定超参数0≤γ<10 \leq \gamma < 10≤γ<1,RMSProp算法在时间步t>0t>0t>0计算
st←γst−1+(1−γ)gt⊙gt.\boldsymbol{s}_t \leftarrow \gamma \boldsymbol{s}_{t-1} + (1 - \gamma) \boldsymbol{g}_t \odot \boldsymbol{g}_t. st←γst−1+(1−γ)gt⊙gt.
RMSProp算法将目标函数自变量中每个元素的学习率通过按元素运算重新调整,然后更新自变量,这是和AdaGrad算法一样的梯度下降公式:
xt←xt−1−ηst+ϵ⊙gt,\boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \frac{\eta}{\sqrt{\boldsymbol{s}_t + \epsilon}} \odot \boldsymbol{g}_t, xt←xt−1−st+ϵη⊙gt,
其中
- η\etaη是学习率
- ϵ\epsilonϵ是为了维持数值稳定性而添加的常数,如10−610^{-6}10−6。
因为RMSProp算法的状态变量st\boldsymbol{s}_tst是对平方项gt⊙gt\boldsymbol{g}_t \odot \boldsymbol{g}_tgt⊙gt的指数加权移动平均,所以可以看作是最近1/(1−γ)1/(1-\gamma)1/(1−γ)个时间步的小批量随机梯度平方项的加权平均。如此一来,自变量每个元素的学习率在迭代过程中就不再一直降低(或不变)。
还是使用相同的例子
- 目标函数f(x)=0.1x12+2x22f(\boldsymbol{x})=0.1x_1^2+2x_2^2f(x)=0.1x12+2x22
- 学习率为0.4
在AdaGrad算法中,自变量在迭代后期的移动幅度较小。但在同样的学习率下,RMSProp算法可以更快逼近最优解。
from matplotlib import pyplot as pltdef show_trace_2d(f, results): plt.plot(*zip(*results), '-o', color='#ff7f0e')x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')plt.xlabel('x1')plt.ylabel('x2')def train_2d(trainer): x1, x2, s1, s2 = -5, -2, 0, 0 # s1和s2是自变量状态,本章后续几节会使用results = [(x1, x2)]for i in range(20):x1, x2, s1, s2 = trainer(x1, x2, s1, s2)results.append((x1, x2))print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))return results
%matplotlib inline
import math
import torchdef rmsprop_2d(x1, x2, s1, s2):g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6s1 = gamma * s1 + (1 - gamma) * g1 ** 2s2 = gamma * s2 + (1 - gamma) * g2 ** 2x1 -= eta / math.sqrt(s1 + eps) * g1x2 -= eta / math.sqrt(s2 + eps) * g2return x1, x2, s1, s2def f_2d(x1, x2):return 0.1 * x1 ** 2 + 2 * x2 ** 2eta, gamma = 0.4, 0.9
show_trace_2d(f_2d, train_2d(rmsprop_2d))
实现RMSProp算法
def get_data_ch7(): data = np.genfromtxt('data/airfoil_self_noise.dat', delimiter='\t')data = (data - data.mean(axis=0)) / data.std(axis=0)return torch.tensor(data[:1500, :-1], dtype=torch.float32), \torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)
features, labels = get_data_ch7()def init_rmsprop_states():s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)s_b = torch.zeros(1, dtype=torch.float32)return (s_w, s_b)def rmsprop(params, states, hyperparams):gamma, eps = hyperparams['gamma'], 1e-6for p, s in zip(params, states):s.data = gamma * s.data + (1 - gamma) * (p.grad.data)**2p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
将初始学习率设为0.01,并将超参数γ\gammaγ设为0.9。此时,变量st\boldsymbol{s}_tst可看作是最近1/(1−0.9)=101/(1-0.9) = 101/(1−0.9)=10个时间步的平方项gt⊙gt\boldsymbol{g}_t \odot \boldsymbol{g}_tgt⊙gt的加权平均。
def train_ch7(optimizer_fn, states, hyperparams, features, labels,batch_size=10, num_epochs=2):# 初始化模型net, loss = linreg, squared_lossw = torch.nn.Parameter(torch.tensor(np.random.normal(0, 0.01, size=(features.shape[1], 1)), dtype=torch.float32),requires_grad=True)b = torch.nn.Parameter(torch.zeros(1, dtype=torch.float32), requires_grad=True)def eval_loss():return loss(net(features, w, b), labels).mean().item()ls = [eval_loss()]data_iter = torch.utils.data.DataLoader(torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)for _ in range(num_epochs):start = time.time()for batch_i, (X, y) in enumerate(data_iter):l = loss(net(X, w, b), y).mean() # 使用平均损失# 梯度清零if w.grad is not None:w.grad.data.zero_()b.grad.data.zero_()l.backward()optimizer_fn([w, b], states, hyperparams) # 迭代模型参数if (batch_i + 1) * batch_size % 100 == 0:ls.append(eval_loss()) # 每100个样本记录下当前训练误差# 打印结果和作图print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))set_figsize()plt.plot(np.linspace(0, num_epochs, len(ls)), ls)plt.xlabel('epoch')plt.ylabel('loss')
train_ch7(rmsprop, init_rmsprop_states(), {'lr': 0.01, 'gamma': 0.9}, features, labels)
亦可以使用pytorch内置的optim.RMSProp算法来实现:
train_pytorch_ch7(torch.optim.RMSprop, {'lr': 0.01, 'alpha': 0.9},features, labels)