PyTorch教程-12.5。小批量随机梯度下降

电子说

1.3w人已加入

描述

到目前为止,我们在基于梯度的学习方法中遇到了两个极端:第 12.3 节使用完整数据集来计算梯度和更新参数,一次一个传递。相反, 第 12.4 节一次处理一个训练示例以取得进展。它们中的任何一个都有其自身的缺点。当数据非常相似时,梯度下降并不是特别有效。随机梯度下降在计算上不是特别有效,因为 CPU 和 GPU 无法利用矢量化的全部功能。这表明可能存在介于两者之间的东西,事实上,这就是我们迄今为止在讨论的示例中一直使用的东西。

12.5.1。矢量化和缓存

决定使用小批量的核心是计算效率。在考虑并行化到多个 GPU 和多个服务器时,这一点最容易理解。在这种情况下,我们需要向每个 GPU 发送至少一张图像。每台服务器 8 个 GPU 和 16 个服务器,我们已经达到了不小于 128 的小批量大小。

当涉及到单个 GPU 甚至 CPU 时,事情就有点微妙了。这些设备有多种类型的内存,通常有多种类型的计算单元和它们之间不同的带宽限制。例如,CPU 有少量寄存器,然后是 L1、L2,在某些情况下甚至是 L3 缓存(在不同处理器内核之间共享)。这些缓存的大小和延迟都在增加(同时它们的带宽在减少)。可以说,处理器能够执行的操作比主内存接口能够提供的要多得多。

首先,具有 16 个内核和 AVX-512 矢量化的 2GHz CPU 最多可以处理2⋅109⋅16⋅32=1012每秒字节数。GPU 的能力很容易超过这个数字的 100 倍。另一方面,中端服务器处理器的带宽可能不会超过 100 GB/s,即不到保持处理器所需带宽的十分之一喂。更糟糕的是,并非所有内存访问都是平等的:内存接口通常为 64 位宽或更宽(例如,在 GPU 上高达 384 位),因此读取单个字节会产生更宽访问的成本。

其次,第一次访问的开销很大,而顺序访问相对便宜(这通常称为突发读取)。还有很多事情要记住,比如当我们有多个套接字、小芯片和其他结构时的缓存。 有关更深入的讨论,请参阅此 维基百科文章。

缓解这些限制的方法是使用 CPU 高速缓存的层次结构,这些高速缓存的速度实际上足以为处理器提供数据。这是深度学习中批处理背后的驱动力。为了简单起见,考虑矩阵-矩阵乘法,比如 A=BC. 我们有多种计算方法A. 例如,我们可以尝试以下操作:

我们可以计算 Aij=Bi,:C:,j,即,我们可以通过点积的方式逐元素计算它。

我们可以计算 A:,j=BC:,j,也就是说,我们可以一次计算一列。同样我们可以计算 A一排Ai,:一次。

我们可以简单地计算A=BC.

我们可以打破B和C分成更小的块矩阵并计算A一次一个块。

如果我们遵循第一个选项,每次我们想要计算一个元素时,我们都需要将一行和一列向量复制到 CPU 中 Aij. 更糟糕的是,由于矩阵元素是顺序对齐的,因此当我们从内存中读取两个向量之一时,我们需要访问许多不相交的位置。第二种选择要有利得多。在其中,我们能够保留列向量C:,j在 CPU 缓存中,同时我们继续遍历B. 这将内存带宽要求减半,访问速度也相应加快。当然,选项 3 是最可取的。不幸的是,大多数矩阵可能无法完全放入缓存(毕竟这是我们正在讨论的内容)。然而,选项 4 提供了一个实用的替代方法:我们可以将矩阵的块移动到缓存中并在本地将它们相乘。优化的库会为我们解决这个问题。让我们看看这些操作在实践中的效率如何。

除了计算效率之外,Python 和深度学习框架本身引入的开销也相当可观。回想一下,每次我们执行命令时,Python 解释器都会向 MXNet 引擎发送命令,而 MXNet 引擎需要将其插入计算图中并在调度期间对其进行处理。这种开销可能非常有害。简而言之,强烈建议尽可能使用矢量化(和矩阵)。

 

%matplotlib inline
import time
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l

A = torch.zeros(256, 256)
B = torch.randn(256, 256)
C = torch.randn(256, 256)

 

 

%matplotlib inline
import time
from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

A = np.zeros((256, 256))
B = np.random.normal(0, 1, (256, 256))
C = np.random.normal(0, 1, (256, 256))

 

 

%matplotlib inline
import time
import numpy as np
import tensorflow as tf
from d2l import tensorflow as d2l

A = tf.Variable(tf.zeros((256, 256)))
B = tf.Variable(tf.random.normal([256, 256], 0, 1))
C = tf.Variable(tf.random.normal([256, 256], 0, 1))

 

由于我们将在本书的其余部分频繁地对运行时间进行基准测试,因此让我们定义一个计时器。

 

class Timer: #@save
  """Record multiple running times."""
  def __init__(self):
    self.times = []
    self.start()

  def start(self):
    """Start the timer."""
    self.tik = time.time()

  def stop(self):
    """Stop the timer and record the time in a list."""
    self.times.append(time.time() - self.tik)
    return self.times[-1]

  def avg(self):
    """Return the average time."""
    return sum(self.times) / len(self.times)

  def sum(self):
    """Return the sum of time."""
    return sum(self.times)

  def cumsum(self):
    """Return the accumulated time."""
    return np.array(self.times).cumsum().tolist()

timer = Timer()

 

 

class Timer: #@save
  """Record multiple running times."""
  def __init__(self):
    self.times = []
    self.start()

  def start(self):
    """Start the timer."""
    self.tik = time.time()

  def stop(self):
    """Stop the timer and record the time in a list."""
    self.times.append(time.time() - self.tik)
    return self.times[-1]

  def avg(self):
    """Return the average time."""
    return sum(self.times) / len(self.times)

  def sum(self):
    """Return the sum of time."""
    return sum(self.times)

  def cumsum(self):
    """Return the accumulated time."""
    return np.array(self.times).cumsum().tolist()

timer = Timer()

 

 

class Timer: #@save
  """Record multiple running times."""
  def __init__(self):
    self.times = []
    self.start()

  def start(self):
    """Start the timer."""
    self.tik = time.time()

  def stop(self):
    """Stop the timer and record the time in a list."""
    self.times.append(time.time() - self.tik)
    return self.times[-1]

  def avg(self):
    """Return the average time."""
    return sum(self.times) / len(self.times)

  def sum(self):
    """Return the sum of time."""
    return sum(self.times)

  def cumsum(self):
    """Return the accumulated time."""
    return np.array(self.times).cumsum().tolist()

timer = Timer()

 

逐元素赋值简单地遍历所有行和列 B和C分别赋值给A.

 

# Compute A = BC one element at a time
timer.start()
for i in range(256):
  for j in range(256):
    A[i, j] = torch.dot(B[i, :], C[:, j])
timer.stop()

 

 

1.5775339603424072

 

 

# Compute A = BC one element at a time
timer.start()
for i in range(256):
  for j in range(256):
    A[i, j] = np.dot(B[i, :], C[:, j])
A.wait_to_read()
timer.stop()

 

 

2547.9816353321075

 

 

# Compute A = BC one element at a time
timer.start()
for i in range(256):
  for j in range(256):
    A[i, j].assign(tf.tensordot(B[i, :], C[:, j], axes=1))
timer.stop()

 

 

164.52903413772583

 

更快的策略是按列分配。

 

# Compute A = BC one column at a time
timer.start()
for j in range(256):
  A[:, j] = torch.mv(B, C[:, j])
timer.stop()

 

 

1.0594699382781982

 

 

# Compute A = BC one column at a time
timer.start()
for j in range(256):
  A[:, j] = np.dot(B, C[:, j])
A.wait_to_read()
timer.stop()

 

 

6.320310592651367

 

 

timer.start()
for j in range(256):
  A[:, j].assign(tf.tensordot(B, C[:, j], axes=1))
timer.stop()

 

 

0.5073747634887695

 

最后,最有效的方式是在一个块中执行整个操作。请注意,将任意两个矩阵相乘 B∈Rm×n和 C∈Rn×p大约需要 2mnp浮点运算,当标量乘法和加法被视为单独的运算时(实际上是融合的)。因此,乘以两个256×256矩阵需要0.03 亿个浮点运算。让我们看看各自的操作速度是多少。

 

# Compute A = BC in one go
timer.start()
A = torch.mm(B, C)
timer.stop()

gigaflops = [0.03 / i for i in timer.times]
print(f'performance in Gigaflops: element {gigaflops[0]:.3f}, '
   f'column {gigaflops[1]:.3f}, full {gigaflops[2]:.3f}')

 

 

performance in Gigaflops: element 0.019, column 0.028, full 2.167

 

 

# Compute A = BC in one go
timer.start()
A = np.dot(B, C)
A.wait_to_read()
timer.stop()

gigaflops = [0.03 / i for i in timer.times]
print(f'performance in Gigaflops: element {gigaflops[0]:.3f}, '
   f'column {gigaflops[1]:.3f}, full {gigaflops[2]:.3f}')

 

 

performance in Gigaflops: element 0.000, column 0.005, full 1.746

 

 

timer.start()
A.assign(tf.tensordot(B, C, axes=1))
timer.stop()

gigaflops = [0.03 / i for i in timer.times]
print(f'performance in Gigaflops: element {gigaflops[0]:.3f}, '
   f'column {gigaflops[1]:.3f}, full {gigaflops[2]:.3f}')

 

 

performance in Gigaflops: element 0.000, column 0.059, full 1.155

 

12.5.2。小批量

在过去,我们理所当然地认为我们会读取小批量数据而不是单个观察来更新参数。我们现在给出一个简短的理由。处理单个观测值需要我们执行许多单个矩阵-向量(甚至向量-向量)乘法,这是非常昂贵的,并且代表底层深度学习框架会产生大量开销。这既适用于在应用于数据时评估网络(通常称为推理),也适用于计算梯度以更新参数时。也就是说,这适用于我们执行的任何时候 w←w−ηtgt在哪里

(12.5.1)gt=∂wf(xt,w)

我们可以通过一次将其应用于一小批观察来提高此操作的计算效率。也就是我们替换梯度gt一个人对一小批人的一次观察

(12.5.2)gt=∂w1|Bt|∑i∈Btf(xi,w)

让我们看看这对 gt: 因为两者xt以及小批量的所有元素Bt从训练集中均匀地随机抽取,梯度的期望保持不变。另一方面,方差显着减少。由于小批量梯度由 b=def|Bt|被平均的独立梯度,它的标准偏差减少了一个因素b−12. 这本身就是一件好事,因为这意味着更新更可靠地与完整梯度对齐。

天真地说,这表明选择一个大的 minibatch Bt将是普遍可取的。las,在某个时间点之后,与计算成本的线性增加相比,标准偏差的额外减少是最小的。在实践中,我们选择一个足够大的小批量来提供良好的计算效率,同时仍然适合 GPU 的内存。为了说明节省的成本,让我们看一些代码。我们在其中执行相同的矩阵乘法,但这次分解为一次 64 列的“小批量”。

 

timer.start()
for j in range(0, 256, 64):
  A[:, j:j+64] = torch.mm(B, C[:, j:j+64])
timer.stop()
print(f'performance in Gigaflops: block {0.03 / timer.times[3]:.3f}')

 

 

performance in Gigaflops: block 0.655

 

 

timer.start()
for j in range(0, 256, 64):
  A[:, j:j+64] = np.dot(B, C[:, j:j+64])
timer.stop()
print(f'performance in Gigaflops: block {0.03 / timer.times[3]:.3f}')

 

 

performance in Gigaflops: block 1.983

 

 

timer.start()
for j in range(0, 256, 64):
  A[:, j:j+64].assign(tf.tensordot(B, C[:, j:j+64], axes=1))
timer.stop()
print(f'performance in Gigaflops: block {0.03 / timer.times[3]:.3f}')

 

 

performance in Gigaflops: block 2.796

 

正如我们所见,小批量的计算基本上与全矩阵一样有效。需要注意的是。在 第 8.5 节中,我们使用了一种正则化,这种正则化在很大程度上取决于小批量中的方差量。当我们增加后者时,方差会减少,并且由于批量归一化而带来的噪声注入的好处也会随之减少。有关如何重新缩放和计算适当项的详细信息,请参见例如 Ioffe ( 2017 ) 。

12.5.3。读取数据集

让我们看看如何从数据中有效地生成小批量。下面我们使用 NASA 开发的数据集测试不同飞机的机翼噪声 来比较这些优化算法。为了方便我们只使用第一个1,500例子。数据被白化以进行预处理,即我们去除均值并将方差重新调整为 1每个坐标。

 

#@save
d2l.DATA_HUB['airfoil'] = (d2l.DATA_URL + 'airfoil_self_noise.dat',
              '76e5be1548fd8222e5074cf0faae75edff8cf93f')

#@save
def get_data_ch11(batch_size=10, n=1500):
  data = np.genfromtxt(d2l.download('airfoil'),
             dtype=np.float32, delimiter='t')
  data = torch.from_numpy((data - data.mean(axis=0)) / data.std(axis=0))
  data_iter = d2l.load_array((data[:n, :-1], data[:n, -1]),
                batch_size, is_train=True)
  return data_iter, data.shape[1]-1

 

 

#@save
d2l.DATA_HUB['airfoil'] = (d2l.DATA_URL + 'airfoil_self_noise.dat',
              '76e5be1548fd8222e5074cf0faae75edff8cf93f')

#@save
def get_data_ch11(batch_size=10, n=1500):
  data = np.genfromtxt(d2l.download('airfoil'),
             dtype=np.float32, delimiter='t')
  data = (data - data.mean(axis=0)) / data.std(axis=0)
  data_iter = d2l.load_array(
    (data[:n, :-1], data[:n, -1]), batch_size, is_train=True)
  return data_iter, data.shape[1]-1

 

 

#@save
d2l.DATA_HUB['airfoil'] = (d2l.DATA_URL + 'airfoil_self_noise.dat',
              '76e5be1548fd8222e5074cf0faae75edff8cf93f')

#@save
def get_data_ch11(batch_size=10, n=1500):
  data = np.genfromtxt(d2l.download('airfoil'),
             dtype=np.float32, delimiter='t')
  data = (data - data.mean(axis=0)) / data.std(axis=0)
  data_iter = d2l.load_array((data[:n, :-1], data[:n, -1]),
                batch_size, is_train=True)
  return data_iter, data.shape[1]-1

 

12.5.4。从零开始实施

回忆一下3.4 节中的小批量随机梯度下降实现 。在下文中,我们提供了一个稍微更通用的实现。为方便起见,它与本章稍后介绍的其他优化算法具有相同的调用签名。具体来说,我们添加状态输入states并将超参数放入字典中hyperparams。另外,我们会在训练函数中平均每个minibatch样本的损失,因此优化算法中的梯度不需要除以batch size。

 

def sgd(params, states, hyperparams):
  for p in params:
    p.data.sub_(hyperparams['lr'] * p.grad)
    p.grad.data.zero_()

 

 

def sgd(params, states, hyperparams):
  for p in params:
    p[:] -= hyperparams['lr'] * p.grad

 

 

def sgd(params, grads, states, hyperparams):
  for param, grad in zip(params, grads):
    param.assign_sub(hyperparams['lr']*grad)

 

接下来,我们实现一个通用的训练函数,以方便使用本章后面介绍的其他优化算法。它初始化了一个线性回归模型,可以用来用小批量随机梯度下降和随后介绍的其他算法来训练模型。

 

#@save
def train_ch11(trainer_fn, states, hyperparams, data_iter,
        feature_dim, num_epochs=2):
  # Initialization
  w = torch.normal(mean=0.0, std=0.01, size=(feature_dim, 1),
           requires_grad=True)
  b = torch.zeros((1), requires_grad=True)
  net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
  # Train
  animator = d2l.Animator(xlabel='epoch', ylabel='loss',
              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(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')
  return timer.cumsum(), animator.Y[0]

 

 

#@save
def train_ch11(trainer_fn, states, hyperparams, data_iter,
        feature_dim, num_epochs=2):
  # Initialization
  w = np.random.normal(scale=0.01, size=(feature_dim, 1))
  b = np.zeros(1)
  w.attach_grad()
  b.attach_grad()
  net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
  # Train
  animator = d2l.Animator(xlabel='epoch', ylabel='loss',
              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:
      with autograd.record():
        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(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')
  return timer.cumsum(), animator.Y[0]

 

 

#@save
def train_ch11(trainer_fn, states, hyperparams, data_iter,
        feature_dim, num_epochs=2):
  # Initialization
  w = tf.Variable(tf.random.normal(shape=(feature_dim, 1),
                  mean=0, stddev=0.01),trainable=True)
  b = tf.Variable(tf.zeros(1), trainable=True)

  # Train
  net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
  animator = d2l.Animator(xlabel='epoch', ylabel='loss',
              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:
     with tf.GradientTape() as g:
      l = tf.math.reduce_mean(loss(net(X), y))

     dw, db = g.gradient(l, [w, b])
     trainer_fn([w, b], [dw, db], states, hyperparams)
     n += X.shape[0]
     if n % 200 == 0:
       timer.stop()
       p = n/X.shape[0]
       q = p/tf.data.experimental.cardinality(data_iter).numpy()
       r = (d2l.evaluate_loss(net, data_iter, loss),)
       animator.add(q, r)
       timer.start()
  print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')
  return timer.cumsum(), animator.Y[0]

 

让我们看看优化是如何进行批量梯度下降的。这可以通过将小批量大小设置为 1500(即示例总数)来实现。因此,模型参数每个时期仅更新一次。进展甚微。事实上,在 6 个步骤之后,进度停滞了。

 

def train_sgd(lr, batch_size, num_epochs=2):
  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)

 

 

loss: 0.249, 0.036 sec/epoch

 

pytorch

 

def train_sgd(lr, batch_size, num_epochs=2):
  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)

 

 

loss: 0.254, 5.565 sec/epoch

 

pytorch

 

def train_sgd(lr, batch_size, num_epochs=2):
  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)

 

 

loss: 0.244, 0.027 sec/epoch

 

pytorch

当批量大小等于 1 时,我们使用随机梯度下降进行优化。为了简化实施,我们选择了一个恒定(尽管很小)的学习率。在随机梯度下降中,每当处理一个示例时,模型参数都会更新。在我们的例子中,这相当于每个时期 1500 次更新。正如我们所看到的,目标函数值的下降在一个 epoch 之后变慢了。尽管这两个过程在一个时期内处理了 1500 个示例,但在我们的实验中,随机梯度下降比梯度下降消耗更多的时间。这是因为随机梯度下降更频繁地更新参数,并且一次处理单个观测值的效率较低。

 

sgd_res = train_sgd(0.005, 1)

 

 

loss: 0.242, 0.767 sec/epoch

 

pytorch

 

sgd_res = train_sgd(0.005, 1)

 

 

loss: 0.243, 65.299 sec/epoch

 

pytorch

 

sgd_res = train_sgd(0.005, 1)

 

 

loss: 0.244, 6.689 sec/epoch

 

pytorch

最后,当 batch size 等于 100 时,我们使用 minibatch 随机梯度下降进行优化。每个时期所需的时间比随机梯度下降所需的时间和批量梯度下降所需的时间短。

 

mini1_res = train_sgd(.4, 100)

 

 

loss: 0.242, 0.028 sec/epoch

 

pytorch

 

mini1_res = train_sgd(.4, 100)

 

 

loss: 0.251, 20.161 sec/epoch

 

pytorch

 

mini1_res = train_sgd(.4, 100)

 

 

loss: 0.246, 0.085 sec/epoch

 

pytorch

将批处理大小减少到 10,每个时期的时间都会增加,因为每个批处理的工作负载执行效率较低。

 

mini2_res = train_sgd(.05, 10)

 

 

loss: 0.247, 0.107 sec/epoch

 

pytorch

 

mini2_res = train_sgd(.05, 10)

 

 

loss: 0.243, 20.888 sec/epoch

 

pytorch

 

mini2_res = train_sgd(.05, 10)

 

 

loss: 0.243, 0.698 sec/epoch

 

pytorch

现在我们可以比较前四个实验的时间与损失。可以看出,尽管随机梯度下降在处理的示例数量方面比 GD 收敛得更快,但它比 GD 使用更多的时间来达到相同的损失,因为逐个示例计算梯度示例效率不高。Minibatch 随机梯度下降能够权衡收敛速度和计算效率。小批量大小为 10 比随机梯度下降更有效;就运行时间而言,100 的小批量甚至优于 GD。

 

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 size=100', 'batch size=10'])
d2l.plt.gca().set_xscale('log')

 

pytorch

 

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 size=100', 'batch size=10'])
d2l.plt.gca().set_xscale('log')

 

pytorch

 

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 size=100', 'batch size=10'])
d2l.plt.gca().set_xscale('log')

 

pytorch

12.5.5。简洁的实现

在 Gluon 中,我们可以使用Trainer类来调用优化算法。这用于实现通用训练功能。我们将在当前章节中使用它。

 

#@save
def train_concise_ch11(trainer_fn, hyperparams, data_iter, num_epochs=4):
  # Initialization
  net = nn.Sequential(nn.Linear(5, 1))
  def init_weights(module):
    if type(module) == nn.Linear:
      torch.nn.init.normal_(module.weight, std=0.01)
  net.apply(init_weights)

  optimizer = trainer_fn(net.parameters(), **hyperparams)
  loss = nn.MSELoss(reduction='none')
  animator = d2l.Animator(xlabel='epoch', ylabel='loss',
              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` computes squared error without the 1/2 factor
        animator.add(n/X.shape[0]/len(data_iter),
               (d2l.evaluate_loss(net, data_iter, loss) / 2,))
        timer.start()
  print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')

 

 

#@save
def train_concise_ch11(tr_name, hyperparams, data_iter, num_epochs=2):
  # Initialization
  net = nn.Sequential()
  net.add(nn.Dense(1))
  net.initialize(init.Normal(sigma=0.01))
  trainer = gluon.Trainer(net.collect_params(), tr_name, hyperparams)
  loss = gluon.loss.L2Loss()
  animator = d2l.Animator(xlabel='epoch', ylabel='loss',
              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:
      with autograd.record():
        l = loss(net(X), y)
      l.backward()
      trainer.step(X.shape[0])
      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(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')

 

 

#@save
def train_concise_ch11(trainer_fn, hyperparams, data_iter, num_epochs=2):
  # Initialization
  net = tf.keras.Sequential()
  net.add(tf.keras.layers.Dense(1,
      kernel_initializer=tf.random_normal_initializer(stddev=0.01)))
  optimizer = trainer_fn(**hyperparams)
  loss = tf.keras.losses.MeanSquaredError()
  animator = d2l.Animator(xlabel='epoch', ylabel='loss',
              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:
      with tf.GradientTape() as g:
        out = net(X)
        l = loss(y, out)
        params = net.trainable_variables
        grads = g.gradient(l, params)
      optimizer.apply_gradients(zip(grads, params))
      n += X.shape[0]
      if n % 200 == 0:
        timer.stop()
        p = n/X.shape[0]
        q = p/tf.data.experimental.cardinality(data_iter).numpy()
        # `MeanSquaredError` computes squared error without the 1/2
        # factor
        r = (d2l.evaluate_loss(net, data_iter, loss) / 2,)
        animator.add(q, r)
        timer.start()
  print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')

 

使用 Gluon 重复上一个实验显示相同的行为。

 

data_iter, _ = get_data_ch11(10)
trainer = torch.optim.SGD
train_concise_ch11(trainer, {'lr': 0.01}, data_iter)

 

 

loss: 0.242, 0.111 sec/epoch

 

pytorch

 

data_iter, _ = get_data_ch11(10)
train_concise_ch11('sgd', {'learning_rate': 0.05}, data_iter)

 

 

loss: 0.244, 23.568 sec/epoch

 

pytorch

 

data_iter, _ = get_data_ch11(10)
trainer = tf.keras.optimizers.SGD
train_concise_ch11(trainer, {'learning_rate': 0.05}, data_iter)

 

 

loss: 0.253, 1.290 sec/epoch

 

pytorch

12.5.6。概括

由于减少了深度学习框架产生的开销以及更好的内存局部性和 CPU 和 GPU 上的缓存,矢量化使代码更高效。

随机梯度下降产生的统计效率与一次处理大批量数据产生的计算效率之间存在权衡。

小批量随机梯度下降提供了两全其美的方法:计算和统计效率。

在小批量随机梯度下降中,我们处理通过训练数据的随机排列获得的批量数据(即,每个观察每个时期只处理一次,尽管是随机顺序)。

建议在训练期间降低学习率。

一般来说,当以时钟时间衡量时,minibatch 随机梯度下降比随机梯度下降和梯度下降更快收敛到更小的风险。

12.5.7。练习

修改 batch size 和 learning rate,观察目标函数值的下降率和每个 epoch 消耗的时间。

阅读 MXNet 文档并使用Trainer类 set_learning_rate函数将小批量随机梯度下降的学习率在每个时期后降低到其先前值的 1/10。

将小批量随机梯度下降与实际从训练集中进行替换采样的变体进行比较。会发生什么?

一个邪恶的精灵在不告诉你的情况下复制你的数据集(即,每次观察发生两次,你的数据集增长到原来大小的两倍,但没有人告诉你)。随机梯度下降、小批量随机梯度下降和梯度下降的行为如何变化?

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分