网站建设步骤大全,做网站还有用,wordpress豆瓣主题,购物网站产品做促销能赚钱吗一 梯度下降
尽管梯度下降#xff08;gradient descent#xff09;很少直接用于深度学习#xff0c;但了解它是理解下一节 随机梯度下降算法 的关键。例如#xff0c;由于学习率过大#xff0c;优化问题可能会发散#xff0c;这种现象早已在梯度下降中出现。同样地…一 梯度下降
尽管梯度下降gradient descent很少直接用于深度学习但了解它是理解下一节 随机梯度下降算法 的关键。例如由于学习率过大优化问题可能会发散这种现象早已在梯度下降中出现。同样地预处理 preconditioning是梯度下降中的一种常用技术还被沿用到更高级的算法中。让我们从简单的一维梯度 下降开始。
1.1 一维梯度下降
即在一阶近似中f(x ϵ)可通过x处的函数值f(x)和一阶导数f ′ (x)得出。我们可以假设在负梯度方向上移动 的ϵ会减少f。为了简单起见我们选择固定步长η 0然后取ϵ −ηf′ (x)。将其代入泰勒展开式我们可以得到。 因此在梯度下降中我们首先 选择初始值x和常数η 0然后使用它们 连续迭代x直到停止条件达成。例如 当梯度|f ′ (x)|的幅度足够小 或 迭代次数达到某个值 时。
下面我们来展示如何实现梯度下降。为了简单起见我们选用目标函数f(x) x^2。尽管我们知道x 0时f(x)能 取得最小值但我们仍然使用这个简单的函数来观察x的变化。
%matplotlib inline
import numpy as np
import torch
from d2l import torch as d2ldef f(x):return x ** 2def f_grad(x):return 2 * x
接下来我们使用 x 10作为初始值并假设η 0.2。使用梯度下降法 迭代x共10次我们可以看到x的值 最终将接近最优解。
def gd(eta, f_grad):x 10.0result [x]for i in range(10):x - eta * f_grad(x)result.append(float(x))print(fepoch 10, x: {x:f})return result
results gd(0.2, f_grad) 对进行x优化的过程可以绘制如下。
def show_trace(results, f):n max(abs(min(results)), abs(max(results)))f_line torch.arange(-n, n, 0.01)d2l.set_figsize()d2l.plot([f_line, results], [[f(x) for x in f_line], [f(x) for x in results]], x, f(x), fmts [-, -o])show_trace(results, f) 1.1.1 学习率
学习率learning rate决定目标函数能否收敛到局部最小值以及何时收敛到最小值。学习率η可由算法设 计者设置。请注意如果我们使用的学习率太小将导致 x的更新非常缓慢需要更多的迭代。例如考虑同 一优化问题中η 0.05的进度。如下所示尽管经过了10个步骤我们仍然离最优解很远。
show_trace(gd(0.05, f_grad), f) 相反如果我们使用过高的学习率|ηf′ (x)|对于一阶泰勒展开式可能太大。也就是说(11.3.1)中 的O(η 2f ′2 (x))可能变得显著了。在这种情况下x的迭代不能保证降低f(x)的值。例如当学习率为η 1.1时 x超出了最优解x 0并逐渐发散。
show_trace(gd(1.1, f_grad), f)1.1.2 局部最小值
为了演示非凸函数的梯度下降考虑函数f(x) x · cos(cx)其中c为某常数。这个函数有无穷多个局部最小 值。根据我们选择的学习率我们最终可能只会得到许多解的一个。下面的例子说明了不切实际的高学习率如何导致较差的局部最小值。
c torch.tensor(0.15 * np.pi)def f(x):return x * torch.cos(c * x)def f_grad(x):return torch.cos(c * x) - c * x * torch.sin(c * x)show_trace(gd(2, f_grad), f) 1.2 多元梯度下降
现在我们对单变量的情况有了更好的理解让我们考虑一下x [x1, x2, . . . , xd] ⊤的情况。即目标函数f : R d → R将向量映射成标量。相应地它的梯度也是多元的它是一个由d个偏导数组成的向量。
换句话说在ϵ的二阶项中最陡下降的方向由负梯度−∇f(x)得出。选择合适的学习率η 0来生成典型的梯 度下降算法 我们还需要两个辅助函数第一个是update函数并将其应用于初始值20次第二个函数会显示x的轨迹。
def train_2d(trainer, steps 20, f_grad None): #savex1, x2, s1, s2 -5, -2, 0, 0results [(x1, x2)]for i in range(steps):if f_grad:x1, x2, s1, s2 trainer(x1, x2, s1, s2, f_grad)else:x1, x2, s1, s2 trainer(x1, x2, s1, s2)results.append((x1, x2))print(fepoch {i 1}, x1: {float(x1): f}, x2: {float(x2): f})return results
def show_trace_2d(f, results): #saved2l.set_figsize()d2l.plt.plot(*zip(*results), -o, color #ff7f0e)x1, x2 torch.meshgrid(torch.arange(-5.5, 1.0, 0.1),torch.arange(-3.0, 1.0, 0.1), indexing ij)d2l.plt.contour(x1, x2, f(x1, x2), colors #1f77b4)d2l.plt.xlabel(x1)d2l.plt.ylabel(x2)
接下来我们观察 学习率η 0.1时优化变量x的轨迹。可以看到经过20步之后x的值接近其位于[0, 0]的最小值。虽然进展相当顺利但相当缓慢。
def f_2d(x1, x2):return x1 ** 2 2 * x2 ** 2def f_2d_grad(x1, x2):return (2 * x1, 4 * x2)def gd_2d(x1, x2, s1, s2, f_grad):g1, g2 f_grad(x1, x2)return (x1 - eta * g1, x2 - eta * g2, 0, 0)eta 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad f_2d_grad))
1.2.1 自适应方法
选择“恰到好处”的学习率η是很棘手的。如果我们把它选得太小就没有什么进展如果太大得到的解就会振荡甚至可能发散。如果我们可以自动确定η或者完全不必选择学习 率会怎么样除了考虑目标函数的值和梯度、还考虑它的曲率的二阶方法可以帮我们解决这个问题。虽然 由于计算代价的原因这些方法不能直接应用于深度学习但它们为如何设计高级优化算法提供了有用的思维直觉这些算法可以模拟下面概述的算法的许多理想特性。
也就是说作为优化问题的一部分我们 需要将Hessian矩阵H求逆。
举一个简单的例子对于f(x) 1 / 2 x ^ 2我们有∇f(x) x和H 1。因此对于任何x我们可以获得ϵ −x。 换言之单单一步就足以完美地收敛而无须任何调整。我们在这里比较幸运泰勒展开式是确切的因 为f(x ϵ) 1/2 x^2 ϵx 1/2 ϵ^2。
让我们看看其他问题。给定一个凸双曲余弦函数c其中c为某些常数我们可以看到经过几次迭代后得到 了x 0处的全局最小值。
c torch.tensor(0.5)def f(x): # O目标函数return torch.cosh(c * x)def f_grad(x): # 目标函数的梯度return c * torch.sinh(c * x)def f_hess(x): # 目标函数的Hessianreturn c**2 * torch.cosh(c * x)def newton(eta1):x 10.0results [x]for i in range(10):x - eta * f_grad(x) / f_hess(x)results.append(float(x))print(epoch 10, x:, x)return resultsshow_trace(newton(), f) 现在让我们考虑一个非凸函数比如f(x) x cos(cx)c为某些常数。请注意在牛顿法中我们最终将除 以Hessian。这意味着如果二阶导数是负的f的值可能会趋于增加。这是这个算法的致命缺陷让我们看看 实践中会发生什么。
c torch.tensor(0.15 * np.pi)def f(x):return x * torch.cos(c * x)def f_grad(x):return torch.cos(c * x) - c * x * torch.sin(c * x)def f_hess(x):return - 2 * c * torch.sin(c * x) - x * c ** 2 * torch.cos(c * x)show_trace(newton(), f) 这发生了惊人的错误。我们怎样才能修正它一种方法是用取Hessian的绝对值来修正另一个策略是重新引入学习率。这似乎违背了初衷但不完全是——拥有二阶信息可以使我们在曲率较大时保持谨慎而在目 标函数较平坦时则采用较大的学习率。让我们看看在学习率稍小的情况下它是如何生效的比如η 0.5。如 我们所见我们有了一个相当高效的算法。
show_trace(newton(0.5), f) 小结
学习率的大小很重要学习率太大会使模型发散学习率太小会没有进展。梯度下降会可能陷入局部极小值而得不到全局最小值。在高维模型中调整学习率是很复杂的。预处理 有助于调节比例。牛顿法在凸问题中一旦开始正常工作速度就会快得多。对于非凸问题不要不作任何调整就使用牛顿法。 1.3 随机梯度下降
在前面的章节中我们一直在训练过程中使用随机梯度下降但没有解释它为什么起作用。为了澄清这一点本节继续更详细地说明 随机梯度下降stochastic gradient descent。
%matplotlib inline
import math
import torch
from d2l import torch as d2l
在深度学习中目标函数通常是训练数据集中每个样本的损失函数的平均值。给定n个样本的训练数据集我们假设fi(x)是关于索引i的训练样本的损失函数其中x是参数向量。
def f(x1, x2): # 目标函数return x1 ** 2 2 * x2 ** 2def f_grad(x1, x2): # 目标函数的梯度return 2 * x1, 4 * x2def sgd(x1, x2, s1, s2, f_grad):g1, g2 f_grad(x1, x2)# 模拟有噪声的梯度g1 torch.normal(0.0, 1, (1,)).item()g2 torch.normal(0.0, 1, (1,)).item()eta_t eta * lr()return (x1 - eta_t * g1, x2 - eta_t * g2, 0, 0)def constant_lr():return 1eta 0.1
lr constant_lr # 常数学习速度
d2l.show_trace_2d(f, d2l.train_2d(sgd, steps50, f_gradf_grad)) 1.3.1 动态学习率
用与时间相关的学习率η(t)取代η增加了控制优化算法收敛的复杂性。特别是我们需要弄清η的衰减速度。 如果太快我们将过早停止优化。如果减少的太慢我们会在优化上浪费太多时间。
正如预期的那样参数的方差大大减少。但是这是以未能收敛到最优解x (0, 0)为代价的。即使经过1000个迭代步骤我们仍然离最优解很远。事实上该算法根本无法收敛。另一方面如果我们使用多项式衰减其中学习率随迭代次数的平方根倒数衰减那么仅在50次迭代之后收敛就会更好。
def polynomial_lr():# 在函数外部定义而在内部更新的全局变量global tt 1return (1 0.1 * t) ** (-0.5)t 1
lr polynomial_lr
d2l.show_trace_2d(f, d2l.train_2d(sgd, steps50, f_gradf_grad)) 小结
对于凸问题我们可以证明对于广泛的学习率选择随机梯度下降将收敛到最优解。对于深度学习而言情况通常并非如此。但是对凸问题的分析使我们能够深入了解如何进行优化即逐步降低学习率尽管不是太快。如果学习率太小或太大就会出现问题。实际上通常只有经过多次实验后才能找到合适的学习率。当训练数据集中有更多样本时计算梯度下降的每次迭代的代价更高因此在这些情况下首选随机梯度下降。随机梯度下降的最优性保证在非凸情况下一般不可用因为需要检查的局部最小值的数量可能是指数级的。 二 小批量随机梯度下降
到目前为止我们在基于梯度的学习方法中遇到了两个极端情况一是使用完整数据集来计算梯度并更新参数二是一次处理一个训练样本来取得进展。二者各有利弊每当数据非常相似时梯度下降并不是非常“数据高效”。而由于 CPU和GPU无法充分利用向量化随机梯度下降并不特别“计算高效”。这暗示 了两者之间可能有折中方案这便涉及到 小批量随机梯度下降minibatch gradient descent。
2.1 向量化和缓存
使用 小批量的决策的核心是计算效率。当考虑与多个GPU和多台服务器并行处理时这一点最容易被理解。 在这种情况下我们需要向每个GPU发送至少一张图像。有了每台服务器8个GPU和16台服务器我们 就能得到大小为128的小批量。
当涉及到单个GPU甚至CPU时事情会更微妙一些这些设备有多种类型的内存、通常情况下多种类型的计算单元以及在它们之间不同的带宽限制。例如一个CPU有少量寄存器registerL1和L2缓存以及L3缓存在不同的处理器内核之间共享。随着缓存的大小的增加它们的延迟也在增加同时带宽在减少。可以说处理器能够执行的操作远比主内存接口所能提供的多得多。
首先具有16个内核和AVX‐512向量化的2GHz CPU每秒可处理高达2 · 109 · 16 · 32 1012个字节。同时GPU的 性能很容易超过该数字100倍。而另一方面中端服务器处理器的带宽可能不超过100Gb/s即不到处理器满负荷所需的十分之一。更糟糕的是并非所有的内存入口都是相等的内存接口通常为64位或更宽例如在 最多384位的GPU上。因此读取单个字节会导致由于更宽的存取而产生的代价。
其次第一次存取的额外开销很大而按序存取sequential access或突发读取burst read相对开销较小。
除了计算效率之外Python和深度学习框架 本身带来的 额外开销 也是相当大的。回想一下每次我们执行代码时Python解释器都会向深度学习框架发送一个命令要求将其插入到计算图中并在调度过程中处理它。 这样的额外开销可能是非常不利的。总而言之我们最好用 向量化和矩阵。
%matplotlib inlineimport numpy as np
import torch
from torch import nn
from d2l import torch as d2ltimer d2l.Timer()
A torch.zeros(256, 256)
B torch.randn(256, 256)
C torch.randn(256, 256)
按元素分配只需遍历分别为B和C的所有行和列即可将该值分配给A。
timer.start()
for i in range(256):for j in range(256):A[i, j] torch.dot(B[i, :], C[:, j])timer.stop() # 0.6227602958679199
更快的策略是执行按列分配。
# 逐列计算ABC
timer.start()
for j in range(256):A[:, j] torch.mv(B, C[:, j])
timer.stop() # 0.01296234130859375
最有效的方法是在一个区块中执行整个操作。让我们看看它们各自的操作速度是多少。
# 一次性计算ABC
timer.start()
A torch.mm(B, C)
timer.stop()
# 乘法和加法作为单独的操作在实践中融合
gigaflops [2/i for i in timer.times]
print(fperformance in Gigaflops: element {gigaflops[0]:.3f}, fcolumn {gigaflops[1]:.3f}, full {gigaflops[2]:.3f})# performance in Gigaflops: element 3.212, column 3.670, full 154.293
2.2 读取数据集
让我们来看看如何从数据中有效地生成小批量。下面我们 使用NASA开发的测试机翼的数据集不同飞行器产 生的噪声132 来比较这些优化算法。为方便起见我们只使用前1, 500样本。数据已作预处理我们移除了均值 并将方差重新缩放到每个坐标为1。
让我们来看看如何从数据中有效地生成小批量。下面我们使用NASA开发的测试机翼的数据集不同飞行器产 生的噪声132来比较这些优化算法。为方便起见我们只使用前1, 500样本。数据已作预处理我们移除了均值 并将方差重新缩放到每个坐标为1。
#save
d2l.DATA_HUB[airfoil] (d2l.DATA_URL airfoil_self_noise.dat,76e5be1548fd8222e5074cf0faae75edff8cf93f)
#save
def get_data_ch11(batch_size10, n1500):data np.genfromtxt(d2l.download(airfoil),dtypenp.float32, delimiter\t)data torch.from_numpy((data - data.mean(axis0)) / data.std(axis0))data_iter d2l.load_array((data[:n, :-1], data[:n, -1]),batch_size, is_trainTrue)return data_iter, data.shape[1]-1 2.3 从零开始实现
已经实现过小批量随机梯度下降算法。我们在这里将它的输入参数变得更加通用主要是为了方 便本章后面介绍的其他优化算法也可以使用同样的输入。具体来说我们添加了一个状态输入states并将超 参数放在字典hyperparams中。此外我们将在训练函数里对各个小批量样本的损失求平均因此优化算法中 的梯度不需要除以批量大小。
def sgd(params, states, hyperparams):for p in params:p.data.sub_(hyperparams[lr] * p.grad)p.grad.data.zero_()
下面实现一个通用的训练函数以方便本章后面介绍的其他优化算法使用。它初始化了一个线性回归模型 然后可以使用小批量随机梯度下降以及后续小节介绍的其他算法来训练模型。
#save
def train_ch11(trainer_fn, states, hyperparams, data_iter,feature_dim, num_epochs2):# 初始化模型w torch.normal(mean0.0, std0.01, size(feature_dim, 1),requires_gradTrue)b torch.zeros((1), requires_gradTrue)net, loss lambda X: d2l.linreg(X, w, b), d2l.squared_loss# 训练模型animator d2l.Animator(xlabelepoch, ylabelloss,xlim[0, num_epochs], ylim[0.22, 0.35])n, timer 0, d2l.Timer()for _ in range(num_epochs):for X, y in data_iter:l loss(net(X), y).mean()l.backward()trainer_fn([w, b], states, hyperparams)n X.shape[0]if n % 200 0:timer.stop()animator.add(n/X.shape[0]/len(data_iter),(d2l.evaluate_loss(net, data_iter, loss),))timer.start()print(floss: {animator.Y[0][-1]:.3f}, {timer.avg():.3f} sec/epoch)return timer.cumsum(), animator.Y[0]
让我们来看看批量梯度下降的优化是如何进行的。这可以通过将小批量设置为1500即样本总数来实现。 因此模型参数每个迭代轮数只迭代一次。
def train_sgd(lr, batch_size, num_epochs2):data_iter, feature_dim get_data_ch11(batch_size)return train_ch11(sgd, None, {lr: lr}, data_iter, feature_dim, num_epochs)gd_res train_sgd(1, 1500, 10) 当批量大小为1时优化使用的是随机梯度下降。为了简化实现我们选择了很小的学习率。在随机梯度下 降的实验中每当一个样本被处理模型参数都会更新。在这个例子中这相当于每个迭代轮数有1500次更 新。可以看到目标函数值的下降在1个迭代轮数后就变得较为平缓。尽管两个例子在一个迭代轮数内都处理 了1500个样本但实验中随机梯度下降的一个迭代轮数耗时更多。这是因为随机梯度下降更频繁地更新了参 数而且一次处理单个观测值效率较低。
sgd_res train_sgd(0.005, 1) mini1_res train_sgd(.4, 100) mini2_res train_sgd(.05, 10) d2l.set_figsize([6, 3])
d2l.plot(*list(map(list, zip(gd_res, sgd_res, mini1_res, mini2_res))),time (sec), loss, xlim[1e-2, 10],legend[gd, sgd, batch size100, batch size10])d2l.plt.gca().set_xscale(log) 2.4 简洁实现
下面用深度学习框架自带算法实现一个通用的训练函数。
#save
def train_concise_ch11(trainer_fn, hyperparams, data_iter, num_epochs4):# 初始化模型net nn.Sequential(nn.Linear(5, 1))def init_weights(m):if type(m) nn.Linear:torch.nn.init.normal_(m.weight, std0.01)net.apply(init_weights)optimizer trainer_fn(net.parameters(), **hyperparams)loss nn.MSELoss(reductionnone)animator d2l.Animator(xlabelepoch, ylabelloss,xlim[0, num_epochs], ylim[0.22, 0.35])n, timer 0, d2l.Timer()for _ in range(num_epochs):for X, y in data_iter:optimizer.zero_grad()out net(X)y y.reshape(out.shape)l loss(out, y)l.mean().backward()optimizer.step()n X.shape[0]if n % 200 0:timer.stop()# MSELoss计算平方误差时不带系数1/2animator.add(n/X.shape[0]/len(data_iter),(d2l.evaluate_loss(net, data_iter, loss) / 2,))timer.start()print(floss: {animator.Y[0][-1]:.3f}, {timer.avg():.3f} sec/epoch)
下面使用这个训练函数复现之前的实验。
data_iter, _ get_data_ch11(10)
trainer torch.optim.SGD
train_concise_ch11(trainer, {lr: 0.01}, data_iter)
小结
由于减少了深度学习框架的额外开销使用更好的内存定位以及CPU和GPU上的缓存向量化使代码更加高效。随机梯度下降的“统计效率”与大批量一次处理数据的“计算效率”之间存在权衡。小批量随机梯度下 降提供了两全其美的答案计算和统计效率。在小批量随机梯度下降中我们处理通过训练数据的随机排列获得的批量数据即每个观测值只处理 一次但按随机顺序。在训练期间降低学习率有助于训练。一般来说小批量随机梯度下降比随机梯度下降和梯度下降的速度快收敛风险较小。