0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

PyTorch教程-9.5. 從零開始的遞歸神經網絡實現(xiàn)

jf_pJlTbmA9 ? 來源:PyTorch ? 作者:PyTorch ? 2023-06-05 15:44 ? 次閱讀

我們現(xiàn)在準備好從頭開始實施 RNN。特別是,我們將訓練此 RNN 作為字符級語言模型(參見 第 9.4 節(jié)),并按照第 9.2 節(jié)中概述的數(shù)據(jù)處理步驟,在由 HG Wells 的《時間機器》的整個文本組成的語料庫上對其進行訓練. 我們首先加載數(shù)據(jù)集。

%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

%matplotlib inline
import math
from mxnet import autograd, gluon, np, npx
from d2l import mxnet as d2l

npx.set_np()

%matplotlib inline
import math
import jax
from flax import linen as nn
from jax import numpy as jnp
from d2l import jax as d2l

%matplotlib inline
import math
import tensorflow as tf
from d2l import tensorflow as d2l

9.5.1. 循環(huán)神經網絡模型

我們首先定義一個類來實現(xiàn) RNN 模型(第 9.4.2 節(jié))。請注意,隱藏單元的數(shù)量num_hiddens是一個可調的超參數(shù)。

class RNNScratch(d2l.Module): #@save
  """The RNN model implemented from scratch."""
  def __init__(self, num_inputs, num_hiddens, sigma=0.01):
    super().__init__()
    self.save_hyperparameters()
    self.W_xh = nn.Parameter(
      torch.randn(num_inputs, num_hiddens) * sigma)
    self.W_hh = nn.Parameter(
      torch.randn(num_hiddens, num_hiddens) * sigma)
    self.b_h = nn.Parameter(torch.zeros(num_hiddens))

class RNNScratch(d2l.Module): #@save
  """The RNN model implemented from scratch."""
  def __init__(self, num_inputs, num_hiddens, sigma=0.01):
    super().__init__()
    self.save_hyperparameters()
    self.W_xh = np.random.randn(num_inputs, num_hiddens) * sigma
    self.W_hh = np.random.randn(
      num_hiddens, num_hiddens) * sigma
    self.b_h = np.zeros(num_hiddens)

class RNNScratch(nn.Module): #@save
  """The RNN model implemented from scratch."""
  num_inputs: int
  num_hiddens: int
  sigma: float = 0.01

  def setup(self):
    self.W_xh = self.param('W_xh', nn.initializers.normal(self.sigma),
                (self.num_inputs, self.num_hiddens))
    self.W_hh = self.param('W_hh', nn.initializers.normal(self.sigma),
                (self.num_hiddens, self.num_hiddens))
    self.b_h = self.param('b_h', nn.initializers.zeros, (self.num_hiddens))

class RNNScratch(d2l.Module): #@save
  """The RNN model implemented from scratch."""
  def __init__(self, num_inputs, num_hiddens, sigma=0.01):
    super().__init__()
    self.save_hyperparameters()
    self.W_xh = tf.Variable(tf.random.normal(
      (num_inputs, num_hiddens)) * sigma)
    self.W_hh = tf.Variable(tf.random.normal(
      (num_hiddens, num_hiddens)) * sigma)
    self.b_h = tf.Variable(tf.zeros(num_hiddens))

下面的方法forward定義了如何計算任何時間步的輸出和隱藏狀態(tài),給定當前輸入和模型在前一個時間步的狀態(tài)。請注意,RNN 模型循環(huán)遍歷 的最外層維度inputs,一次更新隱藏狀態(tài)。這里的模型使用了tanh激活函數(shù)(第 5.1.2.3 節(jié))。

@d2l.add_to_class(RNNScratch) #@save
def forward(self, inputs, state=None):
  if state is None:
    # Initial state with shape: (batch_size, num_hiddens)
    state = torch.zeros((inputs.shape[1], self.num_hiddens),
             device=inputs.device)
  else:
    state, = state
  outputs = []
  for X in inputs: # Shape of inputs: (num_steps, batch_size, num_inputs)
    state = torch.tanh(torch.matmul(X, self.W_xh) +
             torch.matmul(state, self.W_hh) + self.b_h)
    outputs.append(state)
  return outputs, state

@d2l.add_to_class(RNNScratch) #@save
def forward(self, inputs, state=None):
  if state is None:
    # Initial state with shape: (batch_size, num_hiddens)
    state = np.zeros((inputs.shape[1], self.num_hiddens),
             ctx=inputs.ctx)
  else:
    state, = state
  outputs = []
  for X in inputs: # Shape of inputs: (num_steps, batch_size, num_inputs)
    state = np.tanh(np.dot(X, self.W_xh) +
             np.dot(state, self.W_hh) + self.b_h)
    outputs.append(state)
  return outputs, state

@d2l.add_to_class(RNNScratch) #@save
def __call__(self, inputs, state=None):
  if state is not None:
    state, = state
  outputs = []
  for X in inputs: # Shape of inputs: (num_steps, batch_size, num_inputs)
    state = jnp.tanh(jnp.matmul(X, self.W_xh) + (
      jnp.matmul(state, self.W_hh) if state is not None else 0)
             + self.b_h)
    outputs.append(state)
  return outputs, state

@d2l.add_to_class(RNNScratch) #@save
def forward(self, inputs, state=None):
  if state is None:
    # Initial state with shape: (batch_size, num_hiddens)
    state = tf.zeros((inputs.shape[1], self.num_hiddens))
  else:
    state, = state
    state = tf.reshape(state, (-1, self.num_hiddens))
  outputs = []
  for X in inputs: # Shape of inputs: (num_steps, batch_size, num_inputs)
    state = tf.tanh(tf.matmul(X, self.W_xh) +
             tf.matmul(state, self.W_hh) + self.b_h)
    outputs.append(state)
  return outputs, state

我們可以將一小批輸入序列輸入 RNN 模型,如下所示。

batch_size, num_inputs, num_hiddens, num_steps = 2, 16, 32, 100
rnn = RNNScratch(num_inputs, num_hiddens)
X = torch.ones((num_steps, batch_size, num_inputs))
outputs, state = rnn(X)

batch_size, num_inputs, num_hiddens, num_steps = 2, 16, 32, 100
rnn = RNNScratch(num_inputs, num_hiddens)
X = np.ones((num_steps, batch_size, num_inputs))
outputs, state = rnn(X)

batch_size, num_inputs, num_hiddens, num_steps = 2, 16, 32, 100
rnn = RNNScratch(num_inputs, num_hiddens)
X = jnp.ones((num_steps, batch_size, num_inputs))
(outputs, state), _ = rnn.init_with_output(d2l.get_key(), X)

batch_size, num_inputs, num_hiddens, num_steps = 2, 16, 32, 100
rnn = RNNScratch(num_inputs, num_hiddens)
X = tf.ones((num_steps, batch_size, num_inputs))
outputs, state = rnn(X)

讓我們檢查一下 RNN 模型是否產生了正確形狀的結果,以確保隱藏狀態(tài)的維數(shù)保持不變。

def check_len(a, n): #@save
  """Check the length of a list."""
  assert len(a) == n, f'list's length {len(a)} != expected length {n}'

def check_shape(a, shape): #@save
  """Check the shape of a tensor."""
  assert a.shape == shape, 
      f'tensor's shape {a.shape} != expected shape {shape}'

check_len(outputs, num_steps)
check_shape(outputs[0], (batch_size, num_hiddens))
check_shape(state, (batch_size, num_hiddens))

9.5.2. 基于循環(huán)神經網絡的語言模型

下面的類定義了一個基于 RNN 的語言模型,我們通過方法的參數(shù) RNNLMScratch傳入我們的 RNN 。在訓練語言模型時,輸入和輸出來自相同的詞匯表。因此,它們具有相同的維度,即詞匯量大小。請注意,我們使用困惑來評估模型。正如 第 9.3.2 節(jié)中所討論的,這確保了不同長度的序列是可比較的。rnn__init__

class RNNLMScratch(d2l.Classifier): #@save
  """The RNN-based language model implemented from scratch."""
  def __init__(self, rnn, vocab_size, lr=0.01):
    super().__init__()
    self.save_hyperparameters()
    self.init_params()

  def init_params(self):
    self.W_hq = nn.Parameter(
      torch.randn(
        self.rnn.num_hiddens, self.vocab_size) * self.rnn.sigma)
    self.b_q = nn.Parameter(torch.zeros(self.vocab_size))

  def training_step(self, batch):
    l = self.loss(self(*batch[:-1]), batch[-1])
    self.plot('ppl', torch.exp(l), train=True)
    return l

  def validation_step(self, batch):
    l = self.loss(self(*batch[:-1]), batch[-1])
    self.plot('ppl', torch.exp(l), train=False)

class RNNLMScratch(d2l.Classifier): #@save
  """The RNN-based language model implemented from scratch."""
  def __init__(self, rnn, vocab_size, lr=0.01):
    super().__init__()
    self.save_hyperparameters()
    self.init_params()

  def init_params(self):
    self.W_hq = np.random.randn(
      self.rnn.num_hiddens, self.vocab_size) * self.rnn.sigma
    self.b_q = np.zeros(self.vocab_size)
    for param in self.get_scratch_params():
      param.attach_grad()
  def training_step(self, batch):
    l = self.loss(self(*batch[:-1]), batch[-1])
    self.plot('ppl', np.exp(l), train=True)
    return l

  def validation_step(self, batch):
    l = self.loss(self(*batch[:-1]), batch[-1])
    self.plot('ppl', np.exp(l), train=False)

class RNNLMScratch(d2l.Classifier): #@save
  """The RNN-based language model implemented from scratch."""
  rnn: nn.Module
  vocab_size: int
  lr: float = 0.01

  def setup(self):
    self.W_hq = self.param('W_hq', nn.initializers.normal(self.rnn.sigma),
                (self.rnn.num_hiddens, self.vocab_size))
    self.b_q = self.param('b_q', nn.initializers.zeros, (self.vocab_size))

  def training_step(self, params, batch, state):
    value, grads = jax.value_and_grad(
      self.loss, has_aux=True)(params, batch[:-1], batch[-1], state)
    l, _ = value
    self.plot('ppl', jnp.exp(l), train=True)
    return value, grads

  def validation_step(self, params, batch, state):
    l, _ = self.loss(params, batch[:-1], batch[-1], state)
    self.plot('ppl', jnp.exp(l), train=False)

class RNNLMScratch(d2l.Classifier): #@save
  """The RNN-based language model implemented from scratch."""
  def __init__(self, rnn, vocab_size, lr=0.01):
    super().__init__()
    self.save_hyperparameters()
    self.init_params()

  def init_params(self):
    self.W_hq = tf.Variable(tf.random.normal(
      (self.rnn.num_hiddens, self.vocab_size)) * self.rnn.sigma)
    self.b_q = tf.Variable(tf.zeros(self.vocab_size))

  def training_step(self, batch):
    l = self.loss(self(*batch[:-1]), batch[-1])
    self.plot('ppl', tf.exp(l), train=True)
    return l

  def validation_step(self, batch):
    l = self.loss(self(*batch[:-1]), batch[-1])
    self.plot('ppl', tf.exp(l), train=False)

9.5.2.1. 一次性編碼

回想一下,每個標記都由一個數(shù)字索引表示,該數(shù)字索引指示相應單詞/字符/單詞片段在詞匯表中的位置。您可能想構建一個具有單個輸入節(jié)點(在每個時間步長)的神經網絡,其中索引可以作為標量值輸入。當我們處理價格或溫度等數(shù)值輸入時,這是有效的,其中任何兩個足夠接近的值都應該被類似地對待。但這并不完全合理。這45th和 46th我們詞匯表中的詞恰好是“他們的”和“說的”,它們的含義并不相似。

處理此類分類數(shù)據(jù)時,最常見的策略是用單熱編碼表示每個項目(回憶 4.1.1 節(jié))。one-hot 編碼是一個向量,其長度由詞匯表的大小給出N,其中所有條目都設置為0,除了與我們的令牌對應的條目,它被設置為1. 例如,如果詞匯表有 5 個元素,那么索引 0 和 2 對應的單熱向量如下。

F.one_hot(torch.tensor([0, 2]), 5)

tensor([[1, 0, 0, 0, 0],
    [0, 0, 1, 0, 0]])

npx.one_hot(np.array([0, 2]), 5)

array([[1., 0., 0., 0., 0.],
    [0., 0., 1., 0., 0.]])

jax.nn.one_hot(jnp.array([0, 2]), 5)

Array([[1., 0., 0., 0., 0.],
    [0., 0., 1., 0., 0.]], dtype=float32)

tf.one_hot(tf.constant([0, 2]), 5)


我們在每次迭代中采樣的小批量將采用形狀(批量大小、時間步數(shù))。一旦將每個輸入表示為一個單熱向量,我們就可以將每個小批量視為一個三維張量,其中沿第三軸的長度由詞匯表大小 ( ) 給出len(vocab)。我們經常轉置輸入,以便獲得形狀的輸出(時間步數(shù)、批量大小、詞匯量大?。_@將允許我們更方便地循環(huán)遍歷最外層維度以更新小批量的隱藏狀態(tài),時間步長(例如,在上述方法中forward)。

@d2l.add_to_class(RNNLMScratch) #@save
def one_hot(self, X):
  # Output shape: (num_steps, batch_size, vocab_size)
  return F.one_hot(X.T, self.vocab_size).type(torch.float32)

@d2l.add_to_class(RNNLMScratch) #@save
def one_hot(self, X):
  # Output shape: (num_steps, batch_size, vocab_size)
  return npx.one_hot(X.T, self.vocab_size)

@d2l.add_to_class(RNNLMScratch) #@save
def one_hot(self, X):
  # Output shape: (num_steps, batch_size, vocab_size)
  return jax.nn.one_hot(X.T, self.vocab_size)

@d2l.add_to_class(RNNLMScratch) #@save
def one_hot(self, X):
  # Output shape: (num_steps, batch_size, vocab_size)
  return tf.one_hot(tf.transpose(X), self.vocab_size)

9.5.2.2. 轉換 RNN 輸出

語言模型使用全連接輸出層將 RNN 輸出轉換為每個時間步的標記預測。

@d2l.add_to_class(RNNLMScratch) #@save
def output_layer(self, rnn_outputs):
  outputs = [torch.matmul(H, self.W_hq) + self.b_q for H in rnn_outputs]
  return torch.stack(outputs, 1)

@d2l.add_to_class(RNNLMScratch) #@save
def forward(self, X, state=None):
  embs = self.one_hot(X)
  rnn_outputs, _ = self.rnn(embs, state)
  return self.output_layer(rnn_outputs)

@d2l.add_to_class(RNNLMScratch) #@save
def output_layer(self, rnn_outputs):
  outputs = [np.dot(H, self.W_hq) + self.b_q for H in rnn_outputs]
  return np.stack(outputs, 1)

@d2l.add_to_class(RNNLMScratch) #@save
def forward(self, X, state=None):
  embs = self.one_hot(X)
  rnn_outputs, _ = self.rnn(embs, state)
  return self.output_layer(rnn_outputs)

@d2l.add_to_class(RNNLMScratch) #@save
def output_layer(self, rnn_outputs):
  outputs = [jnp.matmul(H, self.W_hq) + self.b_q for H in rnn_outputs]
  return jnp.stack(outputs, 1)

@d2l.add_to_class(RNNLMScratch) #@save
def forward(self, X, state=None):
  embs = self.one_hot(X)
  rnn_outputs, _ = self.rnn(embs, state)
  return self.output_layer(rnn_outputs)

@d2l.add_to_class(RNNLMScratch) #@save
def output_layer(self, rnn_outputs):
  outputs = [tf.matmul(H, self.W_hq) + self.b_q for H in rnn_outputs]
  return tf.stack(outputs, 1)

@d2l.add_to_class(RNNLMScratch) #@save
def forward(self, X, state=None):
  embs = self.one_hot(X)
  rnn_outputs, _ = self.rnn(embs, state)
  return self.output_layer(rnn_outputs)

讓我們檢查前向計算是否產生具有正確形狀的輸出。

model = RNNLMScratch(rnn, num_inputs)
outputs = model(torch.ones((batch_size, num_steps), dtype=torch.int64))
check_shape(outputs, (batch_size, num_steps, num_inputs))

model = RNNLMScratch(rnn, num_inputs)
outputs = model(np.ones((batch_size, num_steps), dtype=np.int64))
check_shape(outputs, (batch_size, num_steps, num_inputs))

model = RNNLMScratch(rnn, num_inputs)
outputs, _ = model.init_with_output(d2l.get_key(),
                  jnp.ones((batch_size, num_steps),
                       dtype=jnp.int32))
check_shape(outputs, (batch_size, num_steps, num_inputs))

model = RNNLMScratch(rnn, num_inputs)
outputs = model(tf.ones((batch_size, num_steps), dtype=tf.int64))
check_shape(outputs, (batch_size, num_steps, num_inputs))

9.5.3. 漸變剪裁

雖然您已經習慣于將神經網絡視為“深度”網絡,即許多層甚至在單個時間步內將輸入和輸出分開,但序列的長度引入了新的深度概念。除了在輸入到輸出方向上通過網絡之外,第一個時間步的輸入必須通過一系列T沿著時間步長分層,以影響模型在最后時間步長的輸出。從后向的角度來看,在每次迭代中,我們通過時間反向傳播梯度,從而產生一系列具有長度的矩陣積 O(T). 如第 5.4 節(jié)所述 ,這會導致數(shù)值不穩(wěn)定,導致梯度根據(jù)權重矩陣的屬性爆炸或消失。

處理梯度消失和爆炸是設計 RNN 時的一個基本問題,并激發(fā)了現(xiàn)代神經網絡架構中一些最大的進步。在下一章中,我們將討論旨在緩解梯度消失問題的專門架構。然而,即使是現(xiàn)代 RNN 仍然經常遭受梯度爆炸的困擾。一種不優(yōu)雅但普遍存在的解決方案是簡單地裁剪梯度,強制生成的“裁剪”梯度采用較小的值。

一般來說,當通過梯度下降優(yōu)化一些目標時,我們迭代地更新感興趣的參數(shù),比如一個向量 x, 但將它推向負梯度方向g(在隨機梯度下降中,我們在隨機采樣的小批量上計算這個梯度)。例如,學習率η>0, 每次更新都采用以下形式 x←x?ηg. 讓我們進一步假設目標函數(shù)f足夠光滑。形式上,我們說目標是Lipschitz 連續(xù)的L,意味著對于任何x和 y, 我們有

(9.5.1)|f(x)?f(y)|≤L‖x?y‖.

如您所見,當我們通過減去更新參數(shù)向量時 ηg,目標值的變化取決于學習率,梯度的范數(shù)和L如下:

(9.5.2)|f(x)?f(x?ηg)|≤Lη‖g‖.

換句話說,目標的變化不能超過 Lη‖g‖. 此上限值較小可能被視為好事或壞事。不利的一面是,我們限制了降低目標價值的速度。從好的方面來說,這限制了我們在任何一個梯度步驟中可能出錯的程度。

當我們說梯度爆炸時,我們的意思是‖g‖ 變得過大。在這種最壞的情況下,我們可能會在單個梯度步驟中造成如此大的破壞,以至于我們可以撤消在數(shù)千次訓練迭代過程中取得的所有進展。當梯度如此之大時,神經網絡訓練通常會發(fā)散,無法降低目標值。在其他時候,訓練最終會收斂,但由于損失的巨大峰值而變得不穩(wěn)定。

一種限制大小的方法Lη‖g‖是縮小學習率η到微小的值。這里的一個優(yōu)勢是我們不會對更新產生偏見。但是,如果我們很少獲得大梯度怎么辦?這種激烈的舉動減慢了我們在所有步驟中的進度,只是為了應對罕見的梯度爆炸事件。一種流行的替代方法是采用梯度裁剪啟發(fā)式投影梯度 g到某個給定半徑的球上θ如下:

(9.5.3)g←min(1,θ‖g‖)g.

這確保梯度范數(shù)永遠不會超過θ并且更新后的梯度完全與原始方向對齊g. 它還具有理想的副作用,即限制任何給定的小批量(以及其中任何給定的樣本)對參數(shù)向量施加的影響。這賦予了模型一定程度的魯棒性。需要明確的是,這是一個 hack。梯度裁剪意味著我們并不總是遵循真正的梯度,并且很難對可能的副作用進行分析推理。然而,它是一個非常有用的 hack,并且在大多數(shù)深度學習框架的 RNN 實現(xiàn)中被廣泛采用。

fit_epoch下面我們定義了一個方法來裁剪漸變,該方法由類的方法調用 d2l.Trainer(參見 第 3.4 節(jié))。請注意,在計算梯度范數(shù)時,我們將所有模型參數(shù)連接起來,將它們視為一個巨大的參數(shù)向量。

@d2l.add_to_class(d2l.Trainer) #@save
def clip_gradients(self, grad_clip_val, model):
  params = [p for p in model.parameters() if p.requires_grad]
  norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
  if norm > grad_clip_val:
    for param in params:
      param.grad[:] *= grad_clip_val / norm

@d2l.add_to_class(d2l.Trainer) #@save
def clip_gradients(self, grad_clip_val, model):
  params = model.parameters()
  if not isinstance(params, list):
    params = [p.data() for p in params.values()]
  norm = math.sqrt(sum((p.grad ** 2).sum() for p in params))
  if norm > grad_clip_val:
    for param in params:
      param.grad[:] *= grad_clip_val / norm

@d2l.add_to_class(d2l.Trainer) #@save
def clip_gradients(self, grad_clip_val, grads):
  grad_leaves, _ = jax.tree_util.tree_flatten(grads)
  norm = jnp.sqrt(sum(jnp.vdot(x, x) for x in grad_leaves))
  clip = lambda grad: jnp.where(norm < grad_clip_val,
                 grad, grad * (grad_clip_val / norm))
  return jax.tree_util.tree_map(clip, grads)

@d2l.add_to_class(d2l.Trainer) #@save
def clip_gradients(self, grad_clip_val, grads):
  grad_clip_val = tf.constant(grad_clip_val, dtype=tf.float32)
  new_grads = [tf.convert_to_tensor(grad) if isinstance(
    grad, tf.IndexedSlices) else grad for grad in grads]
  norm = tf.math.sqrt(sum((tf.reduce_sum(grad ** 2)) for grad in new_grads))
  if tf.greater(norm, grad_clip_val):
    for i, grad in enumerate(new_grads):
      new_grads[i] = grad * grad_clip_val / norm
    return new_grads
  return grads

9.5.4. 訓練

使用時間機器數(shù)據(jù)集 ( ),我們基于從頭開始實施的 RNN ()data訓練字符級語言模型 ( )。請注意,我們首先計算梯度,然后裁剪它們,最后使用裁剪的梯度更新模型參數(shù)。modelrnn

data = d2l.TimeMachine(batch_size=1024, num_steps=32)
rnn = RNNScratch(num_inputs=len(data.vocab), num_hiddens=32)
model = RNNLMScratch(rnn, vocab_size=len(data.vocab), lr=1)
trainer = d2l.Trainer(max_epochs=100, gradient_clip_val=1, num_gpus=1)
trainer.fit(model, data)

pYYBAGR9NoiALI-bABFzwQfkpjk208.svg

data = d2l.TimeMachine(batch_size=1024, num_steps=32)
rnn = RNNScratch(num_inputs=len(data.vocab), num_hiddens=32)
model = RNNLMScratch(rnn, vocab_size=len(data.vocab), lr=1)
trainer = d2l.Trainer(max_epochs=100, gradient_clip_val=1, num_gpus=1)
trainer.fit(model, data)

poYBAGR9No-AMPvcABELR2NiCVM073.svg

data = d2l.TimeMachine(batch_size=1024, num_steps=32)
rnn = RNNScratch(num_inputs=len(data.vocab), num_hiddens=32)
model = RNNLMScratch(rnn, vocab_size=len(data.vocab), lr=1)
trainer = d2l.Trainer(max_epochs=100, gradient_clip_val=1, num_gpus=1)
trainer.fit(model, data)

poYBAGR9Np6APOEFABE1J_Oeyvk737.svg

data = d2l.TimeMachine(batch_size=1024, num_steps=32)
with d2l.try_gpu():
  rnn = RNNScratch(num_inputs=len(data.vocab), num_hiddens=32)
  model = RNNLMScratch(rnn, vocab_size=len(data.vocab), lr=1)
trainer = d2l.Trainer(max_epochs=100, gradient_clip_val=1)
trainer.fit(model, data)

pYYBAGR9NqeAaG6uABFMS9jcCR4776.svg

9.5.5. 解碼

一旦學習了語言模型,我們不僅可以使用它來預測下一個標記,還可以繼續(xù)預測每個后續(xù)標記,將先前預測的標記視為輸入中的下一個標記。有時我們只想生成文本,就好像我們從文檔的開頭開始一樣。但是,根據(jù)用戶提供的前綴來調節(jié)語言模型通常很有用。例如,如果我們正在為搜索引擎開發(fā)自動完成功能或幫助用戶編寫電子郵件,我們會希望輸入他們到目前為止所寫的內容(前綴),然后生成可能的延續(xù)。

以下predict方法生成一個延續(xù),一次一個字符,在攝取用戶提供的字符后,prefix循環(huán)遍歷 中的字符時prefix,我們不斷將隱藏狀態(tài)傳遞到下一個時間步,但不生成任何輸出。這稱為 預熱期。攝取前綴后,我們現(xiàn)在準備開始發(fā)出后續(xù)字符,每個字符都將作為后續(xù)時間步的輸入反饋回模型。

@d2l.add_to_class(RNNLMScratch) #@save
def predict(self, prefix, num_preds, vocab, device=None):
  state, outputs = None, [vocab[prefix[0]]]
  for i in range(len(prefix) + num_preds - 1):
    X = torch.tensor([[outputs[-1]]], device=device)
    embs = self.one_hot(X)
    rnn_outputs, state = self.rnn(embs, state)
    if i < len(prefix) - 1: # Warm-up period
      outputs.append(vocab[prefix[i + 1]])
    else: # Predict num_preds steps
      Y = self.output_layer(rnn_outputs)
      outputs.append(int(Y.argmax(axis=2).reshape(1)))
  return ''.join([vocab.idx_to_token[i] for i in outputs])

@d2l.add_to_class(RNNLMScratch) #@save
def predict(self, prefix, num_preds, vocab, device=None):
  state, outputs = None, [vocab[prefix[0]]]
  for i in range(len(prefix) + num_preds - 1):
    X = np.array([[outputs[-1]]], ctx=device)
    embs = self.one_hot(X)
    rnn_outputs, state = self.rnn(embs, state)
    if i < len(prefix) - 1: # Warm-up period
      outputs.append(vocab[prefix[i + 1]])
    else: # Predict num_preds steps
      Y = self.output_layer(rnn_outputs)
      outputs.append(int(Y.argmax(axis=2).reshape(1)))
  return ''.join([vocab.idx_to_token[i] for i in outputs])

@d2l.add_to_class(RNNLMScratch) #@save
def predict(self, prefix, num_preds, vocab, params):
  state, outputs = None, [vocab[prefix[0]]]
  for i in range(len(prefix) + num_preds - 1):
    X = jnp.array([[outputs[-1]]])
    embs = self.one_hot(X)
    rnn_outputs, state = self.rnn.apply({'params': params['rnn']},
                      embs, state)
    if i < len(prefix) - 1: # Warm-up period
      outputs.append(vocab[prefix[i + 1]])
    else: # Predict num_preds steps
      Y = self.apply({'params': params}, rnn_outputs,
              method=self.output_layer)
      outputs.append(int(Y.argmax(axis=2).reshape(1)))
  return ''.join([vocab.idx_to_token[i] for i in outputs])

@d2l.add_to_class(RNNLMScratch) #@save
def predict(self, prefix, num_preds, vocab, device=None):
  state, outputs = None, [vocab[prefix[0]]]
  for i in range(len(prefix) + num_preds - 1):
    X = tf.constant([[outputs[-1]]])
    embs = self.one_hot(X)
    rnn_outputs, state = self.rnn(embs, state)
    if i < len(prefix) - 1: # Warm-up period
      outputs.append(vocab[prefix[i + 1]])
    else: # Predict num_preds steps
      Y = self.output_layer(rnn_outputs)
      outputs.append(int(tf.reshape(tf.argmax(Y, axis=2), 1)))
  return ''.join([vocab.idx_to_token[i] for i in outputs])

在下文中,我們指定前綴并讓它生成 20 個額外的字符。

model.predict('it has', 20, data.vocab, d2l.try_gpu())

'it has of the the the the '

model.predict('it has', 20, data.vocab, d2l.try_gpu())

'it has in the the the the '

model.predict('it has', 20, data.vocab, trainer.state.params)

'it has in the time tree th'

model.predict('it has', 20, data.vocab)

'it has it the the prount o'

雖然從頭開始實施上述 RNN 模型具有指導意義,但并不方便。在下一節(jié)中,我們將了解如何利用深度學習框架來使用標準架構啟動 RNN,并通過依賴高度優(yōu)化的庫函數(shù)來獲得性能提升。

9.5.6. 概括

我們可以訓練基于 RNN 的語言模型來生成遵循用戶提供的文本前綴的文本。一個簡單的 RNN 語言模型由輸入編碼、RNN 建模和輸出生成組成。在訓練過程中,梯度裁剪可以減輕梯度爆炸的問題,但不能解決梯度消失的問題。在實驗中,我們實現(xiàn)了一個簡單的 RNN 語言模型,并在文本序列上使用梯度裁剪對其進行訓練,并在字符級別進行標記化。通過以前綴為條件,我們可以使用語言模型來生成可能的延續(xù),這在許多應用程序中被證明是有用的,例如,自動完成功能。

9.5.7. 練習

實施的語言模型是否根據(jù)時間機器中的第一個標記之前的所有過去標記預測下一個標記?

哪個超參數(shù)控制用于預測的歷史長度?

證明 one-hot 編碼等同于為每個對象選擇不同的嵌入。

調整超參數(shù)(例如,epoch 數(shù)、隱藏單元數(shù)、minibatch 中的時間步數(shù)和學習率)以提高困惑度。堅持使用這個簡單的架構,你能做到多低?

用可學習的嵌入替換單熱編碼。這會帶來更好的性能嗎?

進行實驗以確定在時間機器上訓練的這種語言模型在 HG Wells 的其他書籍(例如世界大戰(zhàn))中的效果如何。

進行另一項實驗以評估此模型對其他作者所寫書籍的困惑度。

修改預測方法,例如使用采樣而不是選擇最有可能的下一個字符。

會發(fā)生什么?

將模型偏向更可能的輸出,例如,通過從 q(xt∣xt?1,…,x1)∝P(xt∣xt?1,…,x1)α 為了α>1.

在不剪切漸變的情況下運行本節(jié)中的代碼。會發(fā)生什么?

將本節(jié)中使用的激活函數(shù)替換為 ReLU,并重復本節(jié)中的實驗。我們還需要梯度裁剪嗎?為什么?

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 神經網絡
    +關注

    關注

    42

    文章

    4717

    瀏覽量

    100018
  • pytorch
    +關注

    關注

    2

    文章

    794

    瀏覽量

    13011
收藏 人收藏

    評論

    相關推薦

    遞歸神經網絡(RNN)

    遞歸神經網絡(RNN)RNN是最強大的模型之一,它使我們能夠開發(fā)如分類、序列數(shù)據(jù)標注、生成文本序列(例如預測下一輸入詞的SwiftKey keyboard應用程序),以及將一個序列轉換為另一個序列
    發(fā)表于 07-20 09:27

    PyTorch教程之從零開始遞歸神經網絡實現(xiàn)

    電子發(fā)燒友網站提供《PyTorch教程之從零開始遞歸神經網絡實現(xiàn).pdf》資料免費下載
    發(fā)表于 06-05 09:55 ?0次下載
    <b class='flag-5'>PyTorch</b>教程之<b class='flag-5'>從零開始</b>的<b class='flag-5'>遞歸</b><b class='flag-5'>神經網絡</b><b class='flag-5'>實現(xiàn)</b>

    PyTorch教程9.6之遞歸神經網絡的簡潔實現(xiàn)

    電子發(fā)燒友網站提供《PyTorch教程9.6之遞歸神經網絡的簡潔實現(xiàn).pdf》資料免費下載
    發(fā)表于 06-05 09:56 ?0次下載
    <b class='flag-5'>PyTorch</b>教程9.6之<b class='flag-5'>遞歸</b><b class='flag-5'>神經網絡</b>的簡潔<b class='flag-5'>實現(xiàn)</b>

    PyTorch教程10.3之深度遞歸神經網絡

    電子發(fā)燒友網站提供《PyTorch教程10.3之深度遞歸神經網絡.pdf》資料免費下載
    發(fā)表于 06-05 15:12 ?0次下載
    <b class='flag-5'>PyTorch</b>教程10.3之深度<b class='flag-5'>遞歸</b><b class='flag-5'>神經網絡</b>

    PyTorch教程10.4之雙向遞歸神經網絡

    電子發(fā)燒友網站提供《PyTorch教程10.4之雙向遞歸神經網絡.pdf》資料免費下載
    發(fā)表于 06-05 15:13 ?0次下載
    <b class='flag-5'>PyTorch</b>教程10.4之雙向<b class='flag-5'>遞歸</b><b class='flag-5'>神經網絡</b>

    PyTorch教程16.2之情感分析:使用遞歸神經網絡

    電子發(fā)燒友網站提供《PyTorch教程16.2之情感分析:使用遞歸神經網絡.pdf》資料免費下載
    發(fā)表于 06-05 10:55 ?0次下載
    <b class='flag-5'>PyTorch</b>教程16.2之情感分析:使用<b class='flag-5'>遞歸</b><b class='flag-5'>神經網絡</b>

    使用PyTorch構建神經網絡

    PyTorch是一個流行的深度學習框架,它以其簡潔的API和強大的靈活性在學術界和工業(yè)界得到了廣泛應用。在本文中,我們將深入探討如何使用PyTorch構建神經網絡,包括從基礎概念到高級特性的全面解析。本文旨在為讀者提供一個完整的
    的頭像 發(fā)表于 07-02 11:31 ?434次閱讀

    遞歸神經網絡是循環(huán)神經網絡

    遞歸神經網絡(Recurrent Neural Network,簡稱RNN)和循環(huán)神經網絡(Recurrent Neural Network,簡稱RNN)實際上是同一個概念,只是不同的翻譯方式
    的頭像 發(fā)表于 07-04 14:54 ?449次閱讀

    遞歸神經網絡與循環(huán)神經網絡一樣嗎

    遞歸神經網絡(Recursive Neural Network,RvNN)和循環(huán)神經網絡(Recurrent Neural Network,RNN)是兩種不同類型的神經網絡結構,它們在
    的頭像 發(fā)表于 07-05 09:28 ?425次閱讀

    遞歸神經網絡結構形式主要分為

    結構形式。 Elman網絡 Elman網絡是一種基本的遞歸神經網絡結構,由Elman于1990年提出。其結構主要包括輸入層、隱藏層和輸出層,其中隱藏層具有時間延遲單元,可以存儲前一時刻
    的頭像 發(fā)表于 07-05 09:32 ?304次閱讀

    rnn是遞歸神經網絡還是循環(huán)神經網絡

    RNN(Recurrent Neural Network)是循環(huán)神經網絡,而非遞歸神經網絡。循環(huán)神經網絡是一種具有時間序列特性的神經網絡,能
    的頭像 發(fā)表于 07-05 09:52 ?385次閱讀

    PyTorch神經網絡模型構建過程

    PyTorch,作為一個廣泛使用的開源深度學習庫,提供了豐富的工具和模塊,幫助開發(fā)者構建、訓練和部署神經網絡模型。在神經網絡模型中,輸出層是尤為關鍵的部分,它負責將模型的預測結果以合適的形式輸出。以下將詳細解析
    的頭像 發(fā)表于 07-10 14:57 ?304次閱讀

    遞歸神經網絡實現(xiàn)方法

    (Recurrent Neural Network,通常也簡稱為RNN,但在此處為區(qū)分,我們將循環(huán)神經網絡稱為Recurrent RNN)不同,遞歸神經網絡更側重于處理樹狀或圖結構的數(shù)據(jù),如句法分析樹、自然語言的語法結構等。以下
    的頭像 發(fā)表于 07-10 17:02 ?197次閱讀

    遞歸神經網絡和循環(huán)神經網絡的模型結構

    遞歸神經網絡是一種旨在處理分層結構的神經網絡,使其特別適合涉及樹狀或嵌套數(shù)據(jù)的任務。這些網絡明確地模擬了層次結構中的關系和依賴關系,例如語言中的句法結構或圖像中的層次表示。它使用
    的頭像 發(fā)表于 07-10 17:21 ?311次閱讀
    <b class='flag-5'>遞歸</b><b class='flag-5'>神經網絡</b>和循環(huán)<b class='flag-5'>神經網絡</b>的模型結構

    pytorch中有神經網絡模型嗎

    當然,PyTorch是一個廣泛使用的深度學習框架,它提供了許多預訓練的神經網絡模型。 PyTorch中的神經網絡模型 1. 引言 深度學習是一種基于人工
    的頭像 發(fā)表于 07-11 09:59 ?527次閱讀