.. _sec_rnn_scratch: Thực hiện các mạng nơ-ron tái phát từ đầu ========================================= Trong phần này, chúng tôi sẽ triển khai RNN từ đầu cho mô hình ngôn ngữ cấp ký tự, theo mô tả của chúng tôi trong :numref:`sec_rnn`. Một mô hình như vậy sẽ được đào tạo trên H Gwells' \* The Time Machine\*. Như trước đây, chúng ta bắt đầu bằng cách đọc tập dữ liệu đầu tiên, được giới thiệu trong :numref:`sec_language_model`. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python %matplotlib inline import math from mxnet import autograd, gluon, np, npx from d2l import mxnet as d2l npx.set_np() batch_size, num_steps = 32, 35 train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps) .. raw:: html
.. raw:: html
.. code:: python %matplotlib inline import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l batch_size, num_steps = 32, 35 train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps) .. raw:: html
.. raw:: html
.. code:: python %matplotlib inline import math import tensorflow as tf from d2l import tensorflow as d2l batch_size, num_steps = 32, 35 train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps) train_random_iter, vocab_random_iter = d2l.load_data_time_machine( batch_size, num_steps, use_random_iter=True) .. raw:: html
.. raw:: html
Mã hóa Một-Hot -------------- Nhớ lại rằng mỗi mã thông báo được biểu diễn dưới dạng chỉ số số trong ``train_iter``. Việc cung cấp các chỉ số này trực tiếp vào mạng thần kinh có thể khiến bạn khó học. Chúng ta thường đại diện cho mỗi token như một vector tính năng biểu cảm hơn. Đại diện dễ nhất được gọi là \* mã hóa một nóng\*, được giới thiệu trong :numref:`subsec_classification-problem`. Tóm lại, chúng ta ánh xạ mỗi chỉ mục đến một vector đơn vị khác nhau: giả sử rằng số lượng token khác nhau trong từ vựng là :math:`N` (``len(vocab)``) và các chỉ số token dao động từ :math:`0` đến :math:`N-1`. Nếu chỉ số của một mã thông báo là số nguyên :math:`i`, thì ta tạo một vectơ của tất cả 0s với độ dài :math:`N` và đặt phần tử ở vị trí :math:`i` thành 1. Vector này là vectơ một nóng của mã thông báo gốc. Các vectơ một nóng với chỉ số 0 và 2 được hiển thị bên dưới. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python npx.one_hot(np.array([0, 2]), len(vocab)) .. parsed-literal:: :class: output array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) .. raw:: html
.. raw:: html
.. code:: python F.one_hot(torch.tensor([0, 2]), len(vocab)) .. parsed-literal:: :class: output tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) .. raw:: html
.. raw:: html
.. code:: python tf.one_hot(tf.constant([0, 2]), len(vocab)) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Hình dạng của minibatch mà chúng tôi lấy mẫu mỗi lần là (kích thước lô, số bước thời gian). Hàm ``one_hot`` biến một minibatch như vậy thành một tensor ba chiều với chiều cuối cùng bằng với kích thước từ vựng (``len(vocab)``). Chúng ta thường chuyển đổi đầu vào để chúng ta sẽ có được một đầu ra của hình dạng (số bước thời gian, kích thước lô, kích thước từ vựng). Điều này sẽ cho phép chúng tôi vòng lặp thuận tiện hơn thông qua kích thước ngoài cùng để cập nhật các trạng thái ẩn của một minibatch, từng bước từng bước. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python X = np.arange(10).reshape((2, 5)) npx.one_hot(X.T, 28).shape .. parsed-literal:: :class: output (5, 2, 28) .. raw:: html
.. raw:: html
.. code:: python X = torch.arange(10).reshape((2, 5)) F.one_hot(X.T, 28).shape .. parsed-literal:: :class: output torch.Size([5, 2, 28]) .. raw:: html
.. raw:: html
.. code:: python X = tf.reshape(tf.range(10), (2, 5)) tf.one_hot(tf.transpose(X), 28).shape .. parsed-literal:: :class: output TensorShape([5, 2, 28]) .. raw:: html
.. raw:: html
Khởi tạo các tham số mô hình ---------------------------- Tiếp theo, chúng ta khởi tạo các tham số mô hình cho mô hình RNN. Số lượng các đơn vị ẩn ``num_hiddens`` là một siêu tham số có thể điều chỉnh. Khi đào tạo mô hình ngôn ngữ, các đầu vào và đầu ra là từ cùng một từ vựng. Do đó, chúng có cùng chiều, tương đương với kích thước từ vựng. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python def get_params(vocab_size, num_hiddens, device): num_inputs = num_outputs = vocab_size def normal(shape): return np.random.normal(scale=0.01, size=shape, ctx=device) # Hidden layer parameters W_xh = normal((num_inputs, num_hiddens)) W_hh = normal((num_hiddens, num_hiddens)) b_h = np.zeros(num_hiddens, ctx=device) # Output layer parameters W_hq = normal((num_hiddens, num_outputs)) b_q = np.zeros(num_outputs, ctx=device) # Attach gradients params = [W_xh, W_hh, b_h, W_hq, b_q] for param in params: param.attach_grad() return params .. raw:: html
.. raw:: html
.. code:: python def get_params(vocab_size, num_hiddens, device): num_inputs = num_outputs = vocab_size def normal(shape): return torch.randn(size=shape, device=device) * 0.01 # Hidden layer parameters W_xh = normal((num_inputs, num_hiddens)) W_hh = normal((num_hiddens, num_hiddens)) b_h = torch.zeros(num_hiddens, device=device) # Output layer parameters W_hq = normal((num_hiddens, num_outputs)) b_q = torch.zeros(num_outputs, device=device) # Attach gradients params = [W_xh, W_hh, b_h, W_hq, b_q] for param in params: param.requires_grad_(True) return params .. raw:: html
.. raw:: html
.. code:: python def get_params(vocab_size, num_hiddens): num_inputs = num_outputs = vocab_size def normal(shape): return tf.random.normal(shape=shape,stddev=0.01,mean=0,dtype=tf.float32) # Hidden layer parameters W_xh = tf.Variable(normal((num_inputs, num_hiddens)), dtype=tf.float32) W_hh = tf.Variable(normal((num_hiddens, num_hiddens)), dtype=tf.float32) b_h = tf.Variable(tf.zeros(num_hiddens), dtype=tf.float32) # Output layer parameters W_hq = tf.Variable(normal((num_hiddens, num_outputs)), dtype=tf.float32) b_q = tf.Variable(tf.zeros(num_outputs), dtype=tf.float32) params = [W_xh, W_hh, b_h, W_hq, b_q] return params .. raw:: html
.. raw:: html
Mô hình RNN ----------- Để định nghĩa một mô hình RNN, trước tiên chúng ta cần một hàm ``init_rnn_state`` để trả về trạng thái ẩn lúc khởi hóa. Nó trả về một tensor chứa đầy 0 và với một hình dạng của (batch size, số đơn vị ẩn). Sử dụng các tuples làm cho nó dễ dàng hơn để xử lý các tình huống mà trạng thái ẩn chứa nhiều biến, mà chúng ta sẽ gặp phải trong các phần sau. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python def init_rnn_state(batch_size, num_hiddens, device): return (np.zeros((batch_size, num_hiddens), ctx=device), ) .. raw:: html
.. raw:: html
.. code:: python def init_rnn_state(batch_size, num_hiddens, device): return (torch.zeros((batch_size, num_hiddens), device=device), ) .. raw:: html
.. raw:: html
.. code:: python def init_rnn_state(batch_size, num_hiddens): return (tf.zeros((batch_size, num_hiddens)), ) .. raw:: html
.. raw:: html
Hàm ``rnn`` sau định nghĩa cách tính trạng thái ẩn và đầu ra tại một bước thời gian Lưu ý rằng mô hình RNN vòng qua chiều ngoài cùng của ``inputs`` để nó cập nhật các trạng thái ẩn ``H`` của một minibatch, từng bước từng bước thời gian. Bên cạnh đó, chức năng kích hoạt ở đây sử dụng chức năng :math:`\tanh`. Như được mô tả trong :numref:`sec_mlp`, giá trị trung bình của hàm :math:`\tanh` là 0, khi các phần tử được phân bố đồng đều trên các số thực. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python def rnn(inputs, state, params): # Shape of `inputs`: (`num_steps`, `batch_size`, `vocab_size`) W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] # Shape of `X`: (`batch_size`, `vocab_size`) for X in inputs: H = np.tanh(np.dot(X, W_xh) + np.dot(H, W_hh) + b_h) Y = np.dot(H, W_hq) + b_q outputs.append(Y) return np.concatenate(outputs, axis=0), (H,) .. raw:: html
.. raw:: html
.. code:: python def rnn(inputs, state, params): # Here `inputs` shape: (`num_steps`, `batch_size`, `vocab_size`) W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] # Shape of `X`: (`batch_size`, `vocab_size`) for X in inputs: H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h) Y = torch.mm(H, W_hq) + b_q outputs.append(Y) return torch.cat(outputs, dim=0), (H,) .. raw:: html
.. raw:: html
.. code:: python def rnn(inputs, state, params): # Here `inputs` shape: (`num_steps`, `batch_size`, `vocab_size`) W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] # Shape of `X`: (`batch_size`, `vocab_size`) for X in inputs: X = tf.reshape(X,[-1,W_xh.shape[0]]) H = tf.tanh(tf.matmul(X, W_xh) + tf.matmul(H, W_hh) + b_h) Y = tf.matmul(H, W_hq) + b_q outputs.append(Y) return tf.concat(outputs, axis=0), (H,) .. raw:: html
.. raw:: html
Với tất cả các hàm cần thiết được định nghĩa, tiếp theo chúng ta create a class để bọc các hàm này và lưu trữ các tham số cho một mô hình RNN được thực hiện từ đầu. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python class RNNModelScratch: #@save """An RNN Model implemented from scratch.""" def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn): self.vocab_size, self.num_hiddens = vocab_size, num_hiddens self.params = get_params(vocab_size, num_hiddens, device) self.init_state, self.forward_fn = init_state, forward_fn def __call__(self, X, state): X = npx.one_hot(X.T, self.vocab_size) return self.forward_fn(X, state, self.params) def begin_state(self, batch_size, ctx): return self.init_state(batch_size, self.num_hiddens, ctx) .. raw:: html
.. raw:: html
.. code:: python class RNNModelScratch: #@save """A RNN Model implemented from scratch.""" def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn): self.vocab_size, self.num_hiddens = vocab_size, num_hiddens self.params = get_params(vocab_size, num_hiddens, device) self.init_state, self.forward_fn = init_state, forward_fn def __call__(self, X, state): X = F.one_hot(X.T, self.vocab_size).type(torch.float32) return self.forward_fn(X, state, self.params) def begin_state(self, batch_size, device): return self.init_state(batch_size, self.num_hiddens, device) .. raw:: html
.. raw:: html
.. code:: python class RNNModelScratch: #@save """A RNN Model implemented from scratch.""" def __init__(self, vocab_size, num_hiddens, init_state, forward_fn, get_params): self.vocab_size, self.num_hiddens = vocab_size, num_hiddens self.init_state, self.forward_fn = init_state, forward_fn self.trainable_variables = get_params(vocab_size, num_hiddens) def __call__(self, X, state): X = tf.one_hot(tf.transpose(X), self.vocab_size) X = tf.cast(X, tf.float32) return self.forward_fn(X, state, self.trainable_variables) def begin_state(self, batch_size, *args, **kwargs): return self.init_state(batch_size, self.num_hiddens) .. raw:: html
.. raw:: html
Hãy để chúng tôi kiểm tra xem các đầu ra có hình dạng chính xác, ví dụ, để đảm bảo rằng chiều của trạng thái ẩn vẫn không thay đổi. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python num_hiddens = 512 net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn) state = net.begin_state(X.shape[0], d2l.try_gpu()) Y, new_state = net(X.as_in_context(d2l.try_gpu()), state) Y.shape, len(new_state), new_state[0].shape .. parsed-literal:: :class: output ((10, 28), 1, (2, 512)) .. raw:: html
.. raw:: html
.. code:: python num_hiddens = 512 net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn) state = net.begin_state(X.shape[0], d2l.try_gpu()) Y, new_state = net(X.to(d2l.try_gpu()), state) Y.shape, len(new_state), new_state[0].shape .. parsed-literal:: :class: output (torch.Size([10, 28]), 1, torch.Size([2, 512])) .. raw:: html
.. raw:: html
.. code:: python # defining tensorflow training strategy device_name = d2l.try_gpu()._device_name strategy = tf.distribute.OneDeviceStrategy(device_name) num_hiddens = 512 with strategy.scope(): net = RNNModelScratch(len(vocab), num_hiddens, init_rnn_state, rnn, get_params) state = net.begin_state(X.shape[0]) Y, new_state = net(X, state) Y.shape, len(new_state), new_state[0].shape .. parsed-literal:: :class: output (TensorShape([10, 28]), 1, TensorShape([2, 512])) .. raw:: html
.. raw:: html
Chúng ta có thể thấy rằng hình dạng đầu ra là (số bước thời gian :math:`\times` kích thước lô, kích thước từ vựng), trong khi hình dạng trạng thái ẩn vẫn giữ nguyên, tức là, (kích thước lô, số đơn vị ẩn). Prediction ---------- Hãy để chúng tôi đầu tiên xác định hàm dự đoán để tạo ra các ký tự mới sau ``prefix`` người dùng cung cấp, đó là một chuỗi chứa một số ký tự. Khi lặp qua các ký tự bắt đầu này trong ``prefix``, chúng tôi tiếp tục chuyển trạng thái ẩn sang bước thời gian tiếp theo mà không tạo ra bất kỳ đầu ra nào. Đây được gọi là khoảng thời gian *warm-up*, trong đó mô hình tự cập nhật (ví dụ: cập nhật trạng thái ẩn) nhưng không đưa ra dự đoán. Sau thời gian khởi động, trạng thái ẩn thường tốt hơn giá trị khởi tạo của nó ở đầu. Vì vậy, chúng tôi tạo ra các nhân vật dự đoán và phát ra chúng. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python def predict_ch8(prefix, num_preds, net, vocab, device): #@save """Generate new characters following the `prefix`.""" state = net.begin_state(batch_size=1, ctx=device) outputs = [vocab[prefix[0]]] get_input = lambda: np.array([outputs[-1]], ctx=device).reshape((1, 1)) for y in prefix[1:]: # Warm-up period _, state = net(get_input(), state) outputs.append(vocab[y]) for _ in range(num_preds): # Predict `num_preds` steps y, state = net(get_input(), state) outputs.append(int(y.argmax(axis=1).reshape(1))) return ''.join([vocab.idx_to_token[i] for i in outputs]) .. raw:: html
.. raw:: html
.. code:: python def predict_ch8(prefix, num_preds, net, vocab, device): #@save """Generate new characters following the `prefix`.""" state = net.begin_state(batch_size=1, device=device) outputs = [vocab[prefix[0]]] get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1)) for y in prefix[1:]: # Warm-up period _, state = net(get_input(), state) outputs.append(vocab[y]) for _ in range(num_preds): # Predict `num_preds` steps y, state = net(get_input(), state) outputs.append(int(y.argmax(dim=1).reshape(1))) return ''.join([vocab.idx_to_token[i] for i in outputs]) .. raw:: html
.. raw:: html
.. code:: python def predict_ch8(prefix, num_preds, net, vocab): #@save """Generate new characters following the `prefix`.""" state = net.begin_state(batch_size=1, dtype=tf.float32) outputs = [vocab[prefix[0]]] get_input = lambda: tf.reshape(tf.constant([outputs[-1]]), (1, 1)).numpy() for y in prefix[1:]: # Warm-up period _, state = net(get_input(), state) outputs.append(vocab[y]) for _ in range(num_preds): # Predict `num_preds` steps y, state = net(get_input(), state) outputs.append(int(y.numpy().argmax(axis=1).reshape(1))) return ''.join([vocab.idx_to_token[i] for i in outputs]) .. raw:: html
.. raw:: html
Bây giờ chúng ta có thể kiểm tra hàm ``predict_ch8``. Chúng tôi chỉ định tiền tố là ``time traveller`` và có nó tạo ra 10 ký tự bổ sung. Cho rằng chúng tôi chưa đào tạo mạng, nó sẽ tạo ra những dự đoán vô nghĩa. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu()) .. parsed-literal:: :class: output 'time traveller iiiiiiiiii' .. raw:: html
.. raw:: html
.. code:: python predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu()) .. parsed-literal:: :class: output 'time traveller mkpbqhnnnn' .. raw:: html
.. raw:: html
.. code:: python predict_ch8('time traveller ', 10, net, vocab) .. parsed-literal:: :class: output 'time traveller oitvdhmvdh' .. raw:: html
.. raw:: html
Clipping Gradient ----------------- Đối với một chuỗi chiều dài :math:`T`, chúng tôi tính toán độ dốc trên các bước thời gian :math:`T` này trong một lần lặp lại, dẫn đến một chuỗi các sản phẩm ma trận có chiều dài :math:`\mathcal{O}(T)` trong quá trình truyền ngược. Như đã đề cập trong :numref:`sec_numerical_stability`, nó có thể dẫn đến sự bất ổn số, ví dụ, các gradient có thể phát nổ hoặc biến mất, khi :math:`T` lớn. Do đó, các mô hình RNN thường cần trợ giúp thêm để ổn định việc đào tạo. Nói chung, khi giải quyết vấn đề tối ưu hóa, chúng tôi thực hiện các bước cập nhật cho tham số mô hình, nói ở dạng vector :math:`\mathbf{x}`, theo hướng gradient âm :math:`\mathbf{g}` trên một minibatch. Ví dụ: với :math:`\eta > 0` là tốc độ học tập, trong một lần lặp lại, chúng tôi cập nhật :math:`\mathbf{x}` là :math:`\mathbf{x} - \eta \mathbf{g}`. Chúng ta hãy giả định thêm rằng chức năng khách quan :math:`f` được cư xử tốt, giả sử, \* Lipschitz liên tục\* với hằng số :math:`L`. Điều đó có nghĩa là, đối với bất kỳ :math:`\mathbf{x}` và :math:`\mathbf{y}` nào chúng tôi có .. math:: |f(\mathbf{x}) - f(\mathbf{y})| \leq L \|\mathbf{x} - \mathbf{y}\|. Trong trường hợp này, chúng ta có thể giả định một cách an toàn rằng nếu chúng ta cập nhật vector tham số bởi :math:`\eta \mathbf{g}`, thì .. math:: |f(\mathbf{x}) - f(\mathbf{x} - \eta\mathbf{g})| \leq L \eta\|\mathbf{g}\|, điều đó có nghĩa là chúng ta sẽ không quan sát một sự thay đổi hơn :math:`L \eta \|\mathbf{g}\|`. Đây vừa là một lời nguyền vừa là một phước lành. Về phía lời nguyền, nó giới hạn tốc độ tiến bộ; trong khi về phía phước lành, nó giới hạn mức độ mà mọi thứ có thể đi sai nếu chúng ta di chuyển sai hướng. Đôi khi các gradient có thể khá lớn và thuật toán tối ưu hóa có thể không hội tụ. Chúng tôi có thể giải quyết điều này bằng cách giảm tỷ lệ học tập :math:`\eta`. Nhưng nếu chúng ta chỉ \* hiếm có \* có được gradient lớn thì sao? Trong trường hợp này một cách tiếp cận như vậy có thể xuất hiện hoàn toàn không chính đáng. Một lựa chọn phổ biến là cắt gradient :math:`\mathbf{g}` bằng cách chiếu chúng trở lại một quả bóng có bán kính nhất định, giả sử :math:`\theta` qua .. math:: \mathbf{g} \leftarrow \min\left(1, \frac{\theta}{\|\mathbf{g}\|}\right) \mathbf{g}. Bằng cách đó, chúng tôi biết rằng định mức gradient không bao giờ vượt quá :math:`\theta` và gradient được cập nhật hoàn toàn phù hợp với hướng ban đầu là :math:`\mathbf{g}`. Nó cũng có tác dụng phụ mong muốn của việc hạn chế ảnh hưởng bất kỳ minibatch nhất định nào (và trong đó bất kỳ mẫu nhất định nào) có thể tác dụng lên vectơ tham số. Điều này ban cho một mức độ mạnh mẽ nhất định cho mô hình. Gradient clipping cung cấp một sửa chữa nhanh chóng cho sự bùng nổ gradient. Mặc dù nó không hoàn toàn giải quyết vấn đề, nhưng nó là một trong nhiều kỹ thuật để giảm bớt nó. Dưới đây chúng ta định nghĩa một hàm để cắt các gradient của một mô hình được triển khai từ đầu hoặc một mô hình được xây dựng bởi các API cấp cao. Cũng lưu ý rằng chúng tôi tính toán định mức gradient trên tất cả các tham số mô hình. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python def grad_clipping(net, theta): #@save """Clip the gradient.""" if isinstance(net, gluon.Block): params = [p.data() for p in net.collect_params().values()] else: params = net.params norm = math.sqrt(sum((p.grad ** 2).sum() for p in params)) if norm > theta: for param in params: param.grad[:] *= theta / norm .. raw:: html
.. raw:: html
.. code:: python def grad_clipping(net, theta): #@save """Clip the gradient.""" if isinstance(net, nn.Module): params = [p for p in net.parameters() if p.requires_grad] else: params = net.params norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params)) if norm > theta: for param in params: param.grad[:] *= theta / norm .. raw:: html
.. raw:: html
.. code:: python def grad_clipping(grads, theta): #@save """Clip the gradient.""" theta = tf.constant(theta, dtype=tf.float32) new_grad = [] for grad in grads: if isinstance(grad, tf.IndexedSlices): new_grad.append(tf.convert_to_tensor(grad)) else: new_grad.append(grad) norm = tf.math.sqrt(sum((tf.reduce_sum(grad ** 2)).numpy() for grad in new_grad)) norm = tf.cast(norm, tf.float32) if tf.greater(norm, theta): for i, grad in enumerate(new_grad): new_grad[i] = grad * theta / norm else: new_grad = new_grad return new_grad .. raw:: html
.. raw:: html
Đào tạo ------- Trước khi đào tạo mô hình, chúng ta hãy xác định một hàm để đào tạo mô hình trong một kỷ chức. Nó khác với cách chúng tôi đào tạo mô hình :numref:`sec_softmax_scratch` ở ba nơi: 1. Các phương pháp lấy mẫu khác nhau cho dữ liệu tuần tự (lấy mẫu ngẫu nhiên và phân vùng tuần tự) sẽ dẫn đến sự khác biệt trong việc khởi tạo các trạng thái ẩn. 2. Chúng tôi kẹp các gradient trước khi cập nhật các tham số mô hình. Điều này đảm bảo rằng mô hình không phân kỳ ngay cả khi gradient thổi lên tại một số điểm trong quá trình đào tạo. 3. Chúng tôi sử dụng sự bối rối để đánh giá mô hình. Như đã thảo luận trong :numref:`subsec_perplexity`, điều này đảm bảo rằng các chuỗi có độ dài khác nhau có thể so sánh được. Cụ thể, khi phân vùng tuần tự được sử dụng, chúng ta chỉ khởi tạo trạng thái ẩn ở đầu mỗi kỷ nguyên. Vì ví dụ dãy con :math:`i^\mathrm{th}` trong minibatch tiếp theo liền kề với ví dụ dãy thứ tự :math:`i^\mathrm{th}` hiện tại, trạng thái ẩn ở cuối minibatch hiện tại sẽ được sử dụng để khởi tạo trạng thái ẩn ở đầu minibatch tiếp theo. Bằng cách này, thông tin lịch sử của dãy được lưu trữ trong trạng thái ẩn có thể chảy qua các dãy tiếp giáp bên trong một kỷ nguyên. Tuy nhiên, việc tính toán trạng thái ẩn tại bất kỳ điểm nào phụ thuộc vào tất cả các minibatches trước đó trong cùng một kỷ nguyên, làm phức tạp tính toán gradient. Để giảm chi phí tính toán, chúng tôi tách gradient trước khi xử lý bất kỳ minibatch nào để tính toán gradient của trạng thái ẩn luôn bị giới hạn ở các bước thời gian trong một minibatch. Khi sử dụng lấy mẫu ngẫu nhiên, chúng ta cần khởi tạo lại trạng thái ẩn cho mỗi lần lặp lại vì mỗi ví dụ được lấy mẫu với một vị trí ngẫu nhiên. Tương tự như hàm ``train_epoch_ch3`` trong :numref:`sec_softmax_scratch`, ``updater`` là một hàm chung để cập nhật các tham số mô hình. Nó có thể là chức năng ``d2l.sgd`` được triển khai từ đầu hoặc chức năng tối ưu hóa tích hợp trong một khuôn khổ học sâu. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python #@save def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): """Train a model within one epoch (defined in Chapter 8).""" state, timer = None, d2l.Timer() metric = d2l.Accumulator(2) # Sum of training loss, no. of tokens for X, Y in train_iter: if state is None or use_random_iter: # Initialize `state` when either it is the first iteration or # using random sampling state = net.begin_state(batch_size=X.shape[0], ctx=device) else: for s in state: s.detach() y = Y.T.reshape(-1) X, y = X.as_in_ctx(device), y.as_in_ctx(device) with autograd.record(): y_hat, state = net(X, state) l = loss(y_hat, y).mean() l.backward() grad_clipping(net, 1) updater(batch_size=1) # Since the `mean` function has been invoked metric.add(l * d2l.size(y), d2l.size(y)) return math.exp(metric[0] / metric[1]), metric[1] / timer.stop() .. raw:: html
.. raw:: html
.. code:: python #@save def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): """Train a net within one epoch (defined in Chapter 8).""" state, timer = None, d2l.Timer() metric = d2l.Accumulator(2) # Sum of training loss, no. of tokens for X, Y in train_iter: if state is None or use_random_iter: # Initialize `state` when either it is the first iteration or # using random sampling state = net.begin_state(batch_size=X.shape[0], device=device) else: if isinstance(net, nn.Module) and not isinstance(state, tuple): # `state` is a tensor for `nn.GRU` state.detach_() else: # `state` is a tuple of tensors for `nn.LSTM` and # for our custom scratch implementation for s in state: s.detach_() y = Y.T.reshape(-1) X, y = X.to(device), y.to(device) y_hat, state = net(X, state) l = loss(y_hat, y.long()).mean() if isinstance(updater, torch.optim.Optimizer): updater.zero_grad() l.backward() grad_clipping(net, 1) updater.step() else: l.backward() grad_clipping(net, 1) # Since the `mean` function has been invoked updater(batch_size=1) metric.add(l * y.numel(), y.numel()) return math.exp(metric[0] / metric[1]), metric[1] / timer.stop() .. raw:: html
.. raw:: html
.. code:: python #@save def train_epoch_ch8(net, train_iter, loss, updater, use_random_iter): """Train a model within one epoch (defined in Chapter 8).""" state, timer = None, d2l.Timer() metric = d2l.Accumulator(2) # Sum of training loss, no. of tokens for X, Y in train_iter: if state is None or use_random_iter: # Initialize `state` when either it is the first iteration or # using random sampling state = net.begin_state(batch_size=X.shape[0], dtype=tf.float32) with tf.GradientTape(persistent=True) as g: y_hat, state = net(X, state) y = tf.reshape(tf.transpose(Y), (-1)) l = loss(y, y_hat) params = net.trainable_variables grads = g.gradient(l, params) grads = grad_clipping(grads, 1) updater.apply_gradients(zip(grads, params)) # Keras loss by default returns the average loss in a batch # l_sum = l * float(d2l.size(y)) if isinstance( # loss, tf.keras.losses.Loss) else tf.reduce_sum(l) metric.add(l * d2l.size(y), d2l.size(y)) return math.exp(metric[0] / metric[1]), metric[1] / timer.stop() .. raw:: html
.. raw:: html
Chức năng đào tạo hỗ trợ một mô hình RNN được thực hiện từ đầu hoặc sử dụng APIs cấp cao. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python def train_ch8(net, train_iter, vocab, lr, num_epochs, device, #@save use_random_iter=False): """Train a model (defined in Chapter 8).""" loss = gluon.loss.SoftmaxCrossEntropyLoss() animator = d2l.Animator(xlabel='epoch', ylabel='perplexity', legend=['train'], xlim=[10, num_epochs]) # Initialize if isinstance(net, gluon.Block): net.initialize(ctx=device, force_reinit=True, init=init.Normal(0.01)) trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr}) updater = lambda batch_size: trainer.step(batch_size) else: updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size) predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device) # Train and predict for epoch in range(num_epochs): ppl, speed = train_epoch_ch8( net, train_iter, loss, updater, device, use_random_iter) if (epoch + 1) % 10 == 0: animator.add(epoch + 1, [ppl]) print(f'perplexity {ppl:.1f}, {speed:.1f} tokens/sec on {str(device)}') print(predict('time traveller')) print(predict('traveller')) .. raw:: html
.. raw:: html
.. code:: python #@save def train_ch8(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False): """Train a model (defined in Chapter 8).""" loss = nn.CrossEntropyLoss() animator = d2l.Animator(xlabel='epoch', ylabel='perplexity', legend=['train'], xlim=[10, num_epochs]) # Initialize if isinstance(net, nn.Module): updater = torch.optim.SGD(net.parameters(), lr) else: updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size) predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device) # Train and predict for epoch in range(num_epochs): ppl, speed = train_epoch_ch8( net, train_iter, loss, updater, device, use_random_iter) if (epoch + 1) % 10 == 0: print(predict('time traveller')) animator.add(epoch + 1, [ppl]) print(f'perplexity {ppl:.1f}, {speed:.1f} tokens/sec on {str(device)}') print(predict('time traveller')) print(predict('traveller')) .. raw:: html
.. raw:: html
.. code:: python #@save def train_ch8(net, train_iter, vocab, lr, num_epochs, strategy, use_random_iter=False): """Train a model (defined in Chapter 8).""" with strategy.scope(): loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) updater = tf.keras.optimizers.SGD(lr) animator = d2l.Animator(xlabel='epoch', ylabel='perplexity', legend=['train'], xlim=[10, num_epochs]) predict = lambda prefix: predict_ch8(prefix, 50, net, vocab) # Train and predict for epoch in range(num_epochs): ppl, speed = train_epoch_ch8(net, train_iter, loss, updater, use_random_iter) if (epoch + 1) % 10 == 0: print(predict('time traveller')) animator.add(epoch + 1, [ppl]) device = d2l.try_gpu()._device_name print(f'perplexity {ppl:.1f}, {speed:.1f} tokens/sec on {str(device)}') print(predict('time traveller')) print(predict('traveller')) .. raw:: html
.. raw:: html
Bây giờ chúng ta có thể đào tạo mô hình RNN Vì chúng ta chỉ sử dụng 10000 mã thông báo trong tập dữ liệu, mô hình cần nhiều kỷ nguyên hơn để hội tụ tốt hơn. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python num_epochs, lr = 500, 1 train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu()) .. parsed-literal:: :class: output perplexity 1.0, 32184.1 tokens/sec on gpu(0) time traveller for so it will be convenient to speak of himwas e traveller with a slight accession ofcheerfulness really thi .. figure:: output_rnn-scratch_546c4d_159_1.svg .. raw:: html
.. raw:: html
.. code:: python num_epochs, lr = 500, 1 train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu()) .. parsed-literal:: :class: output perplexity 1.0, 75622.3 tokens/sec on cuda:0 time traveller with a slight accession ofcheerfulness really thi traveller with a slight accession ofcheerfulness really thi .. figure:: output_rnn-scratch_546c4d_162_1.svg .. raw:: html
.. raw:: html
.. code:: python num_epochs, lr = 500, 1 train_ch8(net, train_iter, vocab, lr, num_epochs, strategy) .. parsed-literal:: :class: output perplexity 1.0, 10388.9 tokens/sec on /GPU:0 time travelleryou can show black is white by argument said filby travelleryou can show black is white by argument said filby .. figure:: output_rnn-scratch_546c4d_165_1.svg .. raw:: html
.. raw:: html
Cuối cùng, chúng ta hãy kiểm tra kết quả của việc sử dụng phương pháp lấy mẫu ngẫu nhiên. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn) train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(), use_random_iter=True) .. parsed-literal:: :class: output perplexity 1.5, 26859.8 tokens/sec on gpu(0) time traveller proceeded anyreal body must have extension in fou travellerit s against reason said filbywhy argument said fi .. figure:: output_rnn-scratch_546c4d_171_1.svg .. raw:: html
.. raw:: html
.. code:: python net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn) train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(), use_random_iter=True) .. parsed-literal:: :class: output perplexity 1.4, 78034.7 tokens/sec on cuda:0 time traveller smiledcrination is to be accepted is an absolutel traveller after the pauserequired for the proper assimilati .. figure:: output_rnn-scratch_546c4d_174_1.svg .. raw:: html
.. raw:: html
.. code:: python with strategy.scope(): net = RNNModelScratch(len(vocab), num_hiddens, init_rnn_state, rnn, get_params) train_ch8(net, train_iter, vocab_random_iter, lr, num_epochs, strategy, use_random_iter=True) .. parsed-literal:: :class: output perplexity 1.5, 10678.1 tokens/sec on /GPU:0 time traveller proceeded anyreal body must have extension in fou traveller but now you begin to seethe object of my investig .. figure:: output_rnn-scratch_546c4d_177_1.svg .. raw:: html
.. raw:: html
Trong khi thực hiện mô hình RNN trên từ đầu là hướng dẫn, nó không thuận tiện. Trong phần tiếp theo, chúng ta sẽ thấy cách cải thiện mô hình RNN, chẳng hạn như cách thực hiện dễ dàng hơn và làm cho nó chạy nhanh hơn. Tóm tắt ------- - Chúng ta có thể đào tạo mô hình ngôn ngữ cấp ký tự dựa trên RNN để tạo văn bản theo tiền tố văn bản do người dùng cung cấp. - Một mô hình ngôn ngữ RNN đơn giản bao gồm mã hóa đầu vào, mô hình RNN và tạo ra đầu ra. - Các mô hình RNN cần khởi tạo trạng thái để đào tạo, mặc dù lấy mẫu ngẫu nhiên và phân vùng tuần tự sử dụng các cách khác nhau. - Khi sử dụng phân vùng tuần tự, chúng ta cần tách gradient để giảm chi phí tính toán. - Thời gian khởi động cho phép một mô hình tự cập nhật (ví dụ: có được trạng thái ẩn tốt hơn giá trị khởi tạo của nó) trước khi đưa ra bất kỳ dự đoán nào. - Gradient clipping ngăn chặn sự bùng nổ gradient, nhưng nó không thể sửa chữa độ dốc biến mất. Bài tập ------- 1. Cho thấy rằng mã hóa một nóng tương đương với việc chọn một nhúng khác nhau cho mỗi đối tượng. 2. Điều chỉnh các siêu tham số (ví dụ: số kỷ nguyên, số lượng đơn vị ẩn, số bước thời gian trong một minibatch và tốc độ học tập) để cải thiện sự bối rối. - Làm thế nào thấp bạn có thể đi? - Thay thế mã hóa một nóng bằng các embeddings có thể học được. Điều này có dẫn đến hiệu suất tốt hơn? - Nó sẽ hoạt động tốt như thế nào trên các cuốn sách khác của H Gwells, ví dụ, `*The War of the Worlds* `__? 3. Sửa đổi chức năng dự đoán như sử dụng lấy mẫu thay vì chọn ký tự tiếp theo có khả năng cao nhất. - Điều gì xảy ra? - Thiên vị mô hình hướng tới các đầu ra có khả năng cao hơn, ví dụ, bằng cách lấy mẫu từ :math:`q(x_t \mid x_{t-1}, \ldots, x_1) \propto P(x_t \mid x_{t-1}, \ldots, x_1)^\alpha` cho :math:`\alpha > 1`. 4. Chạy mã trong phần này mà không cần cắt gradient. Điều gì xảy ra? 5. Thay đổi phân vùng tuần tự để nó không tách các trạng thái ẩn khỏi biểu đồ tính toán. Thời gian chạy có thay đổi không? Làm thế nào về sự bối rối? 6. Thay thế chức năng kích hoạt được sử dụng trong phần này bằng ReLU và lặp lại các thí nghiệm trong phần này. Chúng ta vẫn cần cắt gradient? Tại sao? .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html