电子说
让我们首先考虑具有不常出现的特征的学习问题。
12.7.1。稀疏特征和学习率
想象一下,我们正在训练一个语言模型。为了获得良好的准确性,我们通常希望在继续训练时降低学习率,通常为O(t−12)或更慢。现在考虑在稀疏特征(即不常出现的特征)上进行模型训练。这在自然语言中很常见,例如,我们看到preconditioning一词的可能性要小于learning 。然而,它在计算广告和个性化协同过滤等其他领域也很常见。毕竟,有很多东西只是少数人感兴趣的。
与不常见特征关联的参数只有在这些特征出现时才会收到有意义的更新。如果学习率下降,我们可能会遇到这样一种情况,即常见特征的参数会很快收敛到它们的最优值,而对于不常见的特征,在确定它们的最优值之前,我们仍然没有足够频繁地观察它们。换句话说,学习率要么对于频繁出现的特征下降得太慢,要么对于不频繁出现的特征下降得太快。
解决此问题的一种可能的破解方法是计算我们看到特定功能的次数,并将其用作调整学习率的时钟。也就是说,而不是选择形式的学习率η=η0t+c我们可以使用 ηi=η0s(i,t)+c. 这里s(i,t) 计算特征的非零数i我们观察到时间t. 这实际上很容易实现,而且没有任何有意义的开销。然而,每当我们不太具有稀疏性而只是梯度通常非常小且很少大的数据时,它就会失败。毕竟,尚不清楚人们会在哪里划清是否符合观察到的特征的界限。
Duchi等人的 Adagrad 。( 2011 )通过更换相当粗糙的计数器来解决这个问题s(i,t)通过先前观察到的梯度的平方的集合。特别是,它使用 s(i,t+1)=s(i,t)+(∂if(x))2作为调整学习率的手段。这有两个好处:首先,我们不再需要决定梯度何时足够大。其次,它会随着梯度的大小自动缩放。通常对应于大梯度的坐标会显着缩小,而其他具有小梯度的坐标会得到更温和的处理。在实践中,这导致了计算广告和相关问题的非常有效的优化过程。但这隐藏了 Adagrad 固有的一些额外好处,这些好处最好在预处理的背景下理解。
12.7.2。预处理
凸优化问题有利于分析算法的特性。毕竟,对于大多数非凸问题来说,很难得出有意义的理论保证,但直觉和洞察力 往往会起作用。让我们看看最小化问题 f(x)=12x⊤Qx+c⊤x+b.
正如我们在12.6 节中看到的,可以根据其特征分解来重写这个问题 Q=U⊤ΛU得出一个大大简化的问题,其中每个坐标都可以单独求解:
(12.7.1)f(x)=f¯(x¯)=12x¯⊤Λx¯+c¯⊤x¯+b.
这里我们使用了x¯=Ux因此c¯=Uc. 修改后的问题具有最小值 x¯=−Λ−1c¯ 和最小值 −12c¯⊤Λ−1c¯+b. 这更容易计算,因为Λ是包含特征值的对角矩阵Q.
如果我们扰乱cslightly 我们希望在最小化器中找到微小的变化f. 不幸的是,这种情况并非如此。虽然略有变化c导致同样轻微的变化c¯,这不是最小化的情况f(和的f¯分别)。每当特征值Λi很大,我们只会看到很小的变化x¯i并且至少 f¯. 反之,对于小Λi 改变在x¯i可以是戏剧性的。最大和最小特征值之比称为优化问题的条件数。
(12.7.2)κ=Λ1Λd.
如果条件数κ大,难以准确求解优化问题。我们需要确保我们谨慎地获得正确的大动态值范围。我们的分析引出了一个明显但有些幼稚的问题:难道我们不能简单地通过扭曲空间来“解决”问题,使得所有特征值都是 1. 理论上这很容易:我们只需要的特征值和特征向量Q从重新调整问题 x到一个 z=defΛ12Ux. 在新的坐标系中 x⊤Qx可以简化为 ‖z‖2. las,这是一个相当不切实际的建议。计算特征值和特征向量通常 比解决实际问题要昂贵得多。
虽然精确计算特征值可能很昂贵,但猜测它们并对其进行近似计算可能已经比什么都不做要好得多。特别是,我们可以使用对角线项Q并相应地重新缩放它。这比计算特征值便宜得多。
(12.7.3)Q~=diag−12(Q)Qdiag−12(Q).
在这种情况下,我们有 Q~ij=Qij/QiiQjj 特别是Q~ii=1对全部i. 在大多数情况下,这会大大简化条件数。例如,我们之前讨论的情况,这将完全消除手头的问题,因为问题是轴对齐的。
不幸的是,我们面临另一个问题:在深度学习中,我们通常甚至无法访问目标函数的二阶导数:对于x∈Rd即使是小批量的二阶导数也可能需要O(d2)空间和计算工作,因此实际上是不可行的。Adagrad 的巧妙想法是使用一个代理来表示 Hessian 的难以捉摸的对角线,它既计算成本相对低又有效——梯度本身的大小。
为了了解为什么会这样,让我们看一下 f¯(x¯). 我们有那个
(12.7.4)∂x¯f¯(x¯)=Λx¯+c¯=Λ(x¯−x¯0),
在哪里x¯0是的最小值f¯. 因此,梯度的大小取决于 Λ和最优的距离。如果 x¯−x¯0没有改变,这将是所有需要的。毕竟,在这种情况下,梯度的大小∂x¯f¯(x¯) 足够了。由于 AdaGrad 是一种随机梯度下降算法,即使在最优状态下,我们也会看到具有非零方差的梯度。因此,我们可以安全地使用梯度的方差作为 Hessian 尺度的廉价代理。彻底的分析超出了本节的范围(可能需要好几页)。详情请读者参考 ( Duchi et al. , 2011 ) 。
12.7.3。算法
让我们将上面的讨论正式化。我们使用变量 st如下累积过去的梯度方差。
(12.7.5)gt=∂wl(yt,f(xt,w)),st=st−1+gt2,wt=wt−1−ηst+ϵ⋅gt.
这里的操作是按坐标应用的。那是, v2有条目vi2. 同样地 1v有条目1vi和 u⋅v有条目uivi. 像之前一样η是学习率和ϵ是一个附加常数,确保我们不除以0. 最后,我们初始化s0=0.
就像在动量的情况下一样,我们需要跟踪一个辅助变量,在这种情况下允许每个坐标的单独学习率。相对于 SGD,这不会显着增加 Adagrad 的成本,因为主要成本通常是计算 l(yt,f(xt,w))及其衍生物。
请注意,在中累积平方梯度st意思是st基本上以线性速率增长(实际上比线性速率慢一些,因为梯度最初会减小)。这导致O(t−12) 学习率,尽管在每个坐标的基础上进行了调整。对于凸问题,这已经足够了。但是,在深度学习中,我们可能希望更慢地降低学习率。这导致了我们将在后续章节中讨论的许多 Adagrad 变体。现在让我们看看它在二次凸问题中的表现。我们使用与之前相同的问题:
(12.7.6)f(x)=0.1x12+2x22.
我们将使用之前相同的学习率来实现 Adagrad,即η=0.4. 可以看出,自变量的迭代轨迹更加平滑。然而,由于累积效应st,学习率不断衰减,因此自变量在迭代的后期阶段不会移动太多。
%matplotlib inline import math import torch from d2l import torch as d2l def adagrad_2d(x1, x2, s1, s2): eps = 1e-6 g1, g2 = 0.2 * x1, 4 * x2 s1 += g1 ** 2 s2 += g2 ** 2 x1 -= eta / math.sqrt(s1 + eps) * g1 x2 -= eta / math.sqrt(s2 + eps) * g2 return x1, x2, s1, s2 def f_2d(x1, x2): return 0.1 * x1 ** 2 + 2 * x2 ** 2 eta = 0.4 d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1: -2.382563, x2: -0.158591
%matplotlib inline import math from mxnet import np, npx from d2l import mxnet as d2l npx.set_np() def adagrad_2d(x1, x2, s1, s2): eps = 1e-6 g1, g2 = 0.2 * x1, 4 * x2 s1 += g1 ** 2 s2 += g2 ** 2 x1 -= eta / math.sqrt(s1 + eps) * g1 x2 -= eta / math.sqrt(s2 + eps) * g2 return x1, x2, s1, s2 def f_2d(x1, x2): return 0.1 * x1 ** 2 + 2 * x2 ** 2 eta = 0.4 d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1: -2.382563, x2: -0.158591
%matplotlib inline import math import tensorflow as tf from d2l import tensorflow as d2l def adagrad_2d(x1, x2, s1, s2): eps = 1e-6 g1, g2 = 0.2 * x1, 4 * x2 s1 += g1 ** 2 s2 += g2 ** 2 x1 -= eta / math.sqrt(s1 + eps) * g1 x2 -= eta / math.sqrt(s2 + eps) * g2 return x1, x2, s1, s2 def f_2d(x1, x2): return 0.1 * x1 ** 2 + 2 * x2 ** 2 eta = 0.4 d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1: -2.382563, x2: -0.158591
当我们将学习率提高到2我们看到了更好的行为。这已经表明学习率的降低可能相当激进,即使在无噪声的情况下也是如此,我们需要确保参数适当收敛。
eta = 2 d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1: -0.002295, x2: -0.000000
eta = 2 d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1: -0.002295, x2: -0.000000
eta = 2 d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1: -0.002295, x2: -0.000000
12.7.4。从零开始实施
就像动量法一样,Adagrad 需要维护一个与参数形状相同的状态变量。
def init_adagrad_states(feature_dim): s_w = torch.zeros((feature_dim, 1)) s_b = torch.zeros(1) return (s_w, s_b) def adagrad(params, states, hyperparams): eps = 1e-6 for p, s in zip(params, states): with torch.no_grad(): s[:] += torch.square(p.grad) p[:] -= hyperparams['lr'] * p.grad / torch.sqrt(s + eps) p.grad.data.zero_()
def init_adagrad_states(feature_dim): s_w = np.zeros((feature_dim, 1)) s_b = np.zeros(1) return (s_w, s_b) def adagrad(params, states, hyperparams): eps = 1e-6 for p, s in zip(params, states): s[:] += np.square(p.grad) p[:] -= hyperparams['lr'] * p.grad / np.sqrt(s + eps)
def init_adagrad_states(feature_dim): s_w = tf.Variable(tf.zeros((feature_dim, 1))) s_b = tf.Variable(tf.zeros(1)) return (s_w, s_b) def adagrad(params, grads, states, hyperparams): eps = 1e-6 for p, s, g in zip(params, states, grads): s[:].assign(s + tf.math.square(g)) p[:].assign(p - hyperparams['lr'] * g / tf.math.sqrt(s + eps))
与12.5 节的实验相比,我们使用更大的学习率来训练模型。
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10) d2l.train_ch11(adagrad, init_adagrad_states(feature_dim), {'lr': 0.1}, data_iter, feature_dim);
loss: 0.242, 0.143 sec/epoch
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10) d2l.train_ch11(adagrad, init_adagrad_states(feature_dim), {'lr': 0.1}, data_iter, feature_dim);
loss: 0.242, 23.515 sec/epoch
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10) d2l.train_ch11(adagrad, init_adagrad_states(feature_dim), {'lr': 0.1}, data_iter, feature_dim);
loss: 0.242, 1.183 sec/epoch
12.7.5。简洁的实现
使用Trainer算法的实例adagrad,我们可以在 Gluon 中调用 Adagrad 算法。
trainer = torch.optim.Adagrad d2l.train_concise_ch11(trainer, {'lr': 0.1}, data_iter)
loss: 0.242, 0.143 sec/epoch
d2l.train_concise_ch11('adagrad', {'learning_rate': 0.1}, data_iter)
loss: 0.242, 23.234 sec/epoch
trainer = tf.keras.optimizers.Adagrad d2l.train_concise_ch11(trainer, {'learning_rate' : 0.1}, data_iter)
loss: 0.243, 1.159 sec/epoch
12.7.6。概括
Adagrad 在每个坐标的基础上动态降低学习率。
它使用梯度的大小作为调整取得进展的速度的一种手段——具有大梯度的坐标用较小的学习率进行补偿。
由于内存和计算限制,在深度学习问题中计算精确的二阶导数通常是不可行的。梯度可以是一个有用的代理。
如果优化问题的结构相当不均匀,Adagrad 可以帮助减轻失真。
Adagrad 对于稀疏特征特别有效,在这些稀疏特征中,对于不常出现的术语,学习率需要更慢地降低。
在深度学习问题上,Adagrad 有时会过于激进地降低学习率。我们将在第 12.10 节的上下文中讨论缓解这种情况的策略。
12.7.7。练习
证明对于正交矩阵U和一个向量 c以下内容成立: ‖c−δ‖2=‖Uc−Uδ‖2. 为什么这意味着在变量正交变化后扰动的大小不会改变?
试用 Adagradf(x)=0.1x12+2x22并且目标函数也旋转了 45 度,即 f(x)=0.1(x1+x2)2+2(x1−x2)2. 它的行为是否不同?
证明Gerschgorin 的圆定理 ,该定理指出特征值λi矩阵的 M满足 |λi−Mjj|≤∑k≠j|Mjk| 至少一个选择j.
Gerschgorin 定理告诉我们关于对角线预处理矩阵的特征值的什么信息 diag−12(M)Mdiag−12(M)?
尝试 Adagrad 以获得适当的深度网络,例如 应用于 Fashion-MNIST 时的第 7.6 节。
您需要如何修改 Adagrad 才能使学习率下降得不那么激进?
全部0条评论
快来发表一下你的评论吧 !