8.6. Lập trình súc tích Mạng nơ-ron Hồi tiếp

Section 8.5 đã mô tả cách lập trình mạng nơ-ron hồi tiếp từ đầu một cách chi tiết, tuy nhiên cách làm này không được nhanh và thuận tiện. Phần này sẽ hướng dẫn cách lập trình cùng một mô hình ngôn ngữ nhưng hiệu quả hơn bằng các hàm của Gluon. Như trước, ta cũng bắt đầu với việc đọc kho ngữ liệu “Cỗ máy Thời gian”.

from d2l import mxnet as d2l
from mxnet import np, npx
from mxnet.gluon import nn, rnn
npx.set_np()

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

8.6.1. Định nghĩa Mô hình

Mô-đun rnn của Gluon đã lập trình sẵn mạng nơ-ron hồi tiếp (cùng với các mô hình chuỗi khác). Ta xây dựng tầng hồi tiếp rnn_layer với một tầng ẩn có 256 nút rồi khởi tạo các trọng số.

num_hiddens = 256
rnn_layer = rnn.RNN(num_hiddens)
rnn_layer.initialize()

Việc khởi tạo trạng thái cũng khá đơn giản, chỉ cần gọi phương thức rnn_layer.begin_state(batch_size). Phương thức này trả về một trạng thái ban đầu cho mỗi phần tử trong minibatch, có kích thước là (số tầng ẩn, kích thước batch, số nút ẩn). Số tầng ẩn mặc định là 1. Thực ra ta chưa thảo luận việc mạng có nhiều tầng sẽ như thế nào — điều này sẽ được đề cập ở Section 9.3. Tạm thời, có thể nói rằng trong mạng nhiều tầng, đầu ra của một RNN sẽ là đầu vào của RNN tiếp theo.

batch_size = 1
state = rnn_layer.begin_state(batch_size=batch_size)
len(state), state[0].shape
(1, (1, 1, 256))

Với một biến trạng thái và một đầu vào, ta có thể tính đầu ra với trạng thái vừa được cập nhật.

num_steps = 1
X = np.random.uniform(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
Y.shape, len(state_new), state_new[0].shape
((1, 1, 256), 1, (1, 1, 256))

Tương tự Section 8.5, ta định nghĩa khối RNNModel bằng cách kế thừa lớp Block để xây dựng mạng nơ-ron hồi tiếp hoàn chỉnh. Chú ý rằng rnn_layer chỉ chứa các tầng hồi tiếp ẩn và ta cần tạo riêng biệt một tầng đầu ra, trong khi ở phần trước tầng đầu ra được tích hợp sẵn trong khối rnn.

# Saved in the d2l package for later use
class RNNModel(nn.Block):
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.dense = nn.Dense(vocab_size)

    def forward(self, inputs, state):
        X = npx.one_hot(inputs.T, self.vocab_size)
        Y, state = self.rnn(X, state)
        # The fully connected layer will first change the shape of Y to
        # (num_steps * batch_size, num_hiddens). Its output shape is
        # (num_steps * batch_size, vocab_size).
        output = self.dense(Y.reshape(-1, Y.shape[-1]))
        return output, state

    def begin_state(self, *args, **kwargs):
        return self.rnn.begin_state(*args, **kwargs)

8.6.2. Huấn luyện và Dự đoán

Trước khi huấn luyện, hãy thử dự đoán bằng mô hình có trọng số ngẫu nhiên.

ctx = d2l.try_gpu()
model = RNNModel(rnn_layer, len(vocab))
model.initialize(force_reinit=True, ctx=ctx)
d2l.predict_ch8('time traveller', 10, model, vocab, ctx)
'time travellervmjznnngii'

Khá rõ ràng, mô hình này không tốt. Tiếp theo, ta gọi hàm train_ch8 với các siêu tham số định nghĩa trong Section 8.5 để huấn luyện mô hình bằng Gluon.

num_epochs, lr = 500, 1
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, ctx)
perplexity 1.3, 148171.2 tokens/sec on gpu(0)
time traveller came back for anachronisms  one might get ohe thr
traveller with a slight accession ofcheerfulness really thi
../_images/output_rnn-gluon_vn_d54b89_13_1.svg

So với phần trước, mô hình này đạt được perplexity tương đương, nhưng thời gian huấn luyện tốt hơn do các đoạn mã được tối ưu hơn.

8.6.3. Tóm tắt

  • Mô-đun rnn của Gluon đã lập trình sẵn tầng mạng nơ-ron hồi tiếp.
  • Mỗi thực thể của nn.RNN trả về đầu ra và trạng thái ẩn sau lượt truyền xuôi. Lượt truyền xuôi này không bao gồm tính toán tại tầng đầu ra.
  • Như trước, đồ thị tính toán cần được tách khỏi các bước trước đó để đảm bảo hiệu năng.

8.6.4. Bài tập

  1. So sánh với cách lập trình từ đầu ở phần trước.
    • Tại sao lập trình bằng Gluon chạy nhanh hơn?
    • Nếu bạn nhận thấy khác biệt đáng kể nào khác ngoài tốc độ, hãy thử tìm hiểu tại sao.
  2. Bạn có thể làm quá khớp mô hình này không? Hãy thử
    • Tăng số nút ẩn.
    • Tăng số vòng lặp.
    • Thay đổi tham số gọt (clipping) thì sao?
  3. Hãy lập trình mô hình tự hồi quy ở phần giới thiệu của chương này bằng RNN.
  4. Nếu tăng số tầng ẩn của mô hình RNN thì sao? Bạn có thể làm mô hình hoạt động không?
  5. Có thể nén văn bản bằng cách sử dụng mô hình này không?
    • Nếu có thì cần bao nhiêu bit?
    • Tại sao không ai sử dụng mô hình này để nén văn bản? Gợi ý: bản thân bộ nén thì sao?

8.6.5. Thảo luận

8.6.6. Những người thực hiện

Bản dịch trong trang này được thực hiện bởi:

  • Đoàn Võ Duy Thanh
  • Nguyễn Văn Cường
  • Phạm Hồng Vinh
  • Lê Khắc Hồng Phúc