.. raw:: html
.. raw:: html
.. raw:: html
.. _sec_bi_rnn:
Mạng Nơ-ron Hồi tiếp Hai chiều
==============================
.. raw:: html
Cho đến nay ta giả định mục tiêu là để mô hình hóa bước thời gian kế
tiếp dựa trên những thông tin trước đó, điển hình như chuỗi thời gian
hay một mô hình ngôn ngữ. Tuy nhiên, đây không phải là trường hợp duy
nhất chúng ta có thể gặp. Để minh họa cho vấn đề này, hãy xem xét ba tác
vụ điền vào chỗ trống dưới đây:
.. raw:: html
1. ``Tôi _____``
2. ``Tôi _____ đói lắm.``
3. ``Tôi _____ đói lắm, tôi có thể ăn một nửa con lợn.``
.. raw:: html
Tuỳ thuộc vào số lượng thông tin có sẵn, chúng ta có thể điền vào chỗ
trống với các từ khác nhau như “hạnh phúc”, “không”, và “đang”. Rõ ràng
phần kết (nếu có) của câu mang thông tin quan trọng ảnh hưởng lớn đến
việc chọn từ. Một mô hình chuỗi sẽ thực hiện các tác vụ liên quan kém
hiệu quả nếu nó không khai thác tốt được đặc điểm này. Chẳng hạn, để
nhận dạng thực thể có tên (ví dụ: phân biệt từ “Bảy” đề cập đến “ông
Bảy” hay là số bảy) một cách hiệu quả, ngữ cảnh khoảng dài cũng không
kém phần quan trọng. Chúng ta sẽ dành một chút thời gian tìm hiểu các mô
hình đồ thị để tìm nguồn cảm hứng giải quyết bài toán trên.
.. raw:: html
.. raw:: html
.. raw:: html
Quy hoạch Động
--------------
.. raw:: html
Trong phần này, chúng ta sẽ tìm hiểu bài toán quy hoạch động. Không cần
thiết phải hiểu chi tiết về quy hoạch động để hiểu kỹ thuật tương ứng
trong học sâu nhưng chúng góp phần giải thích lý do tại sao học sâu được
sử dụng và tại sao một vài kiến trúc mạng nhất định lại được lựa chọn.
.. raw:: html
Nếu muốn giải quyết bài toán bằng mô hình đồ thị thì chúng ta có thể
thiết kế một mô hình biến tiềm ẩn như ví dụ sau đây. Giả sử tồn tại biến
tiềm ẩn :math:`h_t` quyết định giá trị quan sát :math:`x_t` qua xác suất
:math:`p(x_t \mid h_t)`. Hơn nữa, quá trình chuyển đổi
:math:`h_t \to h_{t+1}` được cho bởi xác suất chuyển trạng thái
:math:`p(h_t+1 \mid h_{t})`. Mô hình đồ thị khi đó là mô hình Markov ẩn
(*Hidden Markov Model* - HMM) như trong :numref:`fig_hmm`.
.. raw:: html
.. _fig_hmm:
.. figure:: ../img/hmm.svg
Mô hình Markov ẩn.
.. raw:: html
Như vậy, với chuỗi có :math:`T` quan sát, chúng ta có phân phối xác suất
kết hợp của các trạng thái ẩn và các quan sát như sau:
.. math:: p(x, h) = p(h_1) p(x_1 \mid h_1) \prod_{t=2}^T p(h_t \mid h_{t-1}) p(x_t \mid h_t).
.. raw:: html
| Bây giờ giả sử chúng ta đã có tất cả các quan sát :math:`x_i` ngoại
trừ một vài quan sát :math:`x_j`, mục tiêu là tính xác suất
:math:`p(x_j \mid x^{-j})`, trong đó
:math:`x^{-j} = (x_1, x_2, \ldots, x_{j-1})`. Để thực hiện điều này,
chúng ta cần tính tổng xác suất trên tất cả các khả năng có thể của
:math:`h = (h_1, \ldots, h_T)`. Trong trường hợp :math:`h_i` nhận
:math:`k` giá trị khác nhau, chúng ta cần tính tổng của :math:`k^T` số
hạng - đây là một nhiệm vụ bất khả thi. May mắn thay có một phương
pháp rất hiệu quả cho bài toán trên, đó là quy hoạch động.
| Để hiểu hơn về phương pháp này, hãy xem xét tổng của hai biến ẩn đầu
tiên :math:`h_1` và :math:`h_2`. Ta có:
.. math::
\begin{aligned}
p(x) & = \sum_{h_1, \ldots, h_T} p(x_1, \ldots, x_T; h_1, \ldots, h_T) \\
& = \sum_{h_1, \ldots, h_T} p(h_1) p(x_1 \mid h_1) \prod_{t=2}^T p(h_t \mid h_{t-1}) p(x_t \mid h_t) \\
& = \sum_{h_2, \ldots, h_T} \underbrace{\left[\sum_{h_1} p(h_1) p(x_1 \mid h_1) p(h_2 \mid h_1)\right]}_{=: \pi_2(h_2)}
p(x_2 \mid h_2) \prod_{t=3}^T p(h_t \mid h_{t-1}) p(x_t \mid h_t) \\
& = \sum_{h_3, \ldots, h_T} \underbrace{\left[\sum_{h_2} \pi_2(h_2) p(x_2 \mid h_2) p(h_3 \mid h_2)\right]}_{=: \pi_3(h_3)}
p(x_3 \mid h_3) \prod_{t=4}^T p(h_t \mid h_{t-1}) p(x_t \mid h_t)\\
& = \dots \\
& = \sum_{h_T} \pi_T(h_T) p(x_T \mid h_T).
\end{aligned}
.. raw:: html
Cơ bản, chúng ta có công thức *đệ quy xuôi* như sau:
.. math:: \pi_{t+1}(h_{t+1}) = \sum_{h_t} \pi_t(h_t) p(x_t \mid h_t) p(h_{t+1} \mid h_t).
.. raw:: html
Phép đệ quy được khởi tạo với :math:`\pi_1(h_1) = p(h_1)`. Nói chung,
công thức đệ quy có thể được viết lại là
:math:`\pi_{t+1} = f(\pi_t, x_t)`, trong đó :math:`f` là một hàm được
học. Trông rất giống với phương trình cập nhật trong các mô hình biến ẩn
mà chúng ta đã thảo luận trong phần RNN. Tương tự, chúng ta có thể tính
*đệ quy ngược* như sau:
.. math::
\begin{aligned}
p(x) & = \sum_{h_1, \ldots, h_T} p(x_1, \ldots, x_T; h_1, \ldots, h_T) \\
& = \sum_{h_1, \ldots, h_T} \prod_{t=1}^{T-1} p(h_t \mid h_{t-1}) p(x_t \mid h_t) \cdot p(h_T \mid h_{T-1}) p(x_T \mid h_T) \\
& = \sum_{h_1, \ldots, h_{T-1}} \prod_{t=1}^{T-1} p(h_t \mid h_{t-1}) p(x_t \mid h_t) \cdot
\underbrace{\left[\sum_{h_T} p(h_T \mid h_{T-1}) p(x_T \mid h_T)\right]}_{=: \rho_{T-1}(h_{T-1})} \\
& = \sum_{h_1, \ldots, h_{T-2}} \prod_{t=1}^{T-2} p(h_t \mid h_{t-1}) p(x_t \mid h_t) \cdot
\underbrace{\left[\sum_{h_{T-1}} p(h_{T-1} \mid h_{T-2}) p(x_{T-1} \mid h_{T-1}) \rho_{T-1}(h_{T-1}) \right]}_{=: \rho_{T-2}(h_{T-2})} \\
& = \ldots, \\
& = \sum_{h_1} p(h_1) p(x_1 \mid h_1)\rho_{1}(h_{1}).
\end{aligned}
.. raw:: html
Từ đó, chúng ta có thể viết *đệ quy ngược* như sau:
.. math:: \rho_{t-1}(h_{t-1})= \sum_{h_{t}} p(h_{t} \mid h_{t-1}) p(x_{t} \mid h_{t}) \rho_{t}(h_{t}),
.. raw:: html
.. raw:: html
.. raw:: html
| khi khởi tạo :math:`\rho_T(h_T) = 1`. Hai biểu thức đệ quy này cho
phép ta tính tổng trên tất cả :math:`T` biến trong khoảng
:math:`(h_1, \ldots, h_T)` với thời gian :math:`\mathcal{O}(kT)` tăng
tuyến tính thay vì luỹ thừa. Đây là một trong những điểm mạnh của kỹ
thuật suy luận xác suất với các mô hình đồ thị. Đây là một trường hợp
đặc biệt của kỹ thuật được trình bày trong :cite:`Aji.McEliece.2000`
bởi Aji và McEliece vào năm 2000.
| Kết hợp cả biểu thức xuôi và ngược ta có thể tính được:
.. math:: p(x_j \mid x_{-j}) \propto \sum_{h_j} \pi_j(h_j) \rho_j(h_j) p(x_j \mid h_j).
.. raw:: html
| Cần phải chú ý rằng khi suy rộng ra, biểu thức đệ quy ngược có thể
được viết dưới dạng :math:`\rho_{t-1} = g(\rho_t, x_t)`, trong đó
:math:`g` là một hàm số được học. Một lần nữa, nó trông giống như một
phương trình cập nhật chỉ chạy ngược lại, không giống như những gì
chúng ta thấy ở RNN.
| Thật vậy, HMM sẽ có lợi từ việc học các dữ liệu trong tương lai (nếu
có thể). Các nhà khoa học chuyên về xử lí tín hiệu sẽ tách biệt 2
trường hợp biết trước và không biết trước các kết quả trong tương lai
thành nội suy và ngoại suy. Ta có thể tham khảo chương giới thiệu của
cuốn :cite:`Doucet.De-Freitas.Gordon.2001` về các thuật toán Monte
Carlo tuần tự để biết thêm chi tiết.
.. raw:: html
.. raw:: html
.. raw:: html
Mô hình Hai chiều
-----------------
.. raw:: html
Nếu chúng ta muốn mạng RNN có một cơ chế nhìn trước giống như HMM thì ta
cần phải chỉnh sửa một chút thiết kế của các mạng hồi tiếp truyền thống.
May mắn là, điều này khá đơn giản về mặt khái niệm. Thay vì chỉ vận hành
một RNN chạy từ kí tự đầu đến cuối, ta sẽ khởi tạo một RNN nữa chạy từ
kí tự cuối lên đầu. *Mạng nơ ron hồi tiếp hai chiều* (*Bidirectional
recurrent neural network*) sẽ thêm một tầng ẩn cho phép xử lý dữ liệu
theo chiều ngược lại một cách linh hoạt hơn so với RNN truyền thống.
:numref:`fig_birnn` mô tả cấu trúc của mạng nơ-ron hồi tiếp hai chiều
với một tầng ẩn.
.. raw:: html
.. _fig_birnn:
.. figure:: ../img/birnn.svg
*Cấu trúc của mạng nơ ron hồi tiếp hai chiều.*
.. raw:: html
Trên thực tế, điều này không quá khác biệt với phép đệ quy xuôi và ngược
mà ta đã đề cập ở phần trước. Điểm khác biệt chính là trước đây các
phương trình này có một ý nghĩa thống kê nhất định. Còn bây giờ thì
chúng không còn mang một ý nghĩa dễ hiểu nào nhất định, thay vào đó ta
sẽ chỉ xét chúng như những hàm tổng quát. Quá trình chuyển đổi này là
điển hình cho nhiều nguyên tắc thiết kế các mạng học sâu hiện đại: đầu
tiên, sử dụng các dạng quan hệ phụ thuộc hàm của các mô hình thống kê cổ
điển, sau đó sử dụng các mô hình này dưới dạng tổng quát.
.. raw:: html
.. raw:: html
.. raw:: html
Định nghĩa
~~~~~~~~~~
.. raw:: html
Các mạng nơ-ron hồi tiếp hai chiều đã được giới thiệu bởi
:cite:`Schuster.Paliwal.1997`. Ta có thể xem thêm
:cite:`Graves.Schmidhuber.2005` về những thảo luận chi tiết của các
kiến trúc khác nhau. Còn giờ ta hãy đi vào chi tiết của một mạng như
vậy.
.. raw:: html
Cho một bước thời gian :math:`t`, đầu vào minibatch là
:math:`\mathbf{X}_t \in \mathbb{R}^{n \times d}` (:math:`n` là số lượng
mẫu, :math:`d` là số lượng đầu vào) và hàm kích hoạt của tầng ẩn là
:math:`\phi`. Trong kiến thúc hai chiều, ta giả định rằng trạng thái ẩn
xuôi và ngược của bước thời gian này lần lượt là
:math:`\overrightarrow{\mathbf{H}}_t \in \mathbb{R}^{n \times h}` và
:math:`\overleftarrow{\mathbf{H}}_t \in \mathbb{R}^{n \times h}`.
:math:`h` ở đây chỉ số lượng nút ẩn. Chúng ta tính toán việc cập nhật
xuôi và ngược của trạng thái ẩn như sau:
.. math::
\begin{aligned}
\overrightarrow{\mathbf{H}}_t &= \phi(\mathbf{X}_t \mathbf{W}_{xh}^{(f)} + \overrightarrow{\mathbf{H}}_{t-1} \mathbf{W}_{hh}^{(f)} + \mathbf{b}_h^{(f)}),\\
\overleftarrow{\mathbf{H}}_t &= \phi(\mathbf{X}_t \mathbf{W}_{xh}^{(b)} + \overleftarrow{\mathbf{H}}_{t+1} \mathbf{W}_{hh}^{(b)} + \mathbf{b}_h^{(b)}).
\end{aligned}
.. raw:: html
Ở đây, các trọng số
:math:`\mathbf{W}_{xh}^{(f)} \in \mathbb{R}^{d \times h}, \mathbf{W}_{hh}^{(f)} \in \mathbb{R}^{h \times h}, \mathbf{W}_{xh}^{(b)} \in \mathbb{R}^{d \times h}, \text{ và } \mathbf{W}_{hh}^{(b)} \in \mathbb{R}^{h \times h}`
và các độ chệch
:math:`\mathbf{b}_h^{(f)} \in \mathbb{R}^{1 \times h} \text{ và } \mathbf{b}_h^{(b)} \in \mathbb{R}^{1 \times h}`
đều là tham số mô hình.
.. raw:: html
Sau đó, chúng ta nối các trạng thái ẩn xuôi và ngược
(:math:`\overrightarrow{\mathbf{H}}_t`,
:math:`\overleftarrow{\mathbf{H}}_t`) để thu được trạng thái ẩn
:math:`\mathbf{H}_t \in \mathbb{R}^{n \times 2h}` và truyền nó đến tầng
đầu ra. Trong các mạng nơ-ron hồi tiếp hai chiều sâu, thông tin được
truyền đi như là *đầu vào* cho tầng hai chiều (*bidirectional layer*)
tiếp theo. Cuối cùng, tầng đầu ra sẽ tính toán đầu ra
:math:`\mathbf{O}_t \in \mathbb{R}^{n \times q}` (:math:`q` là số lượng
đầu ra) như sau:
.. math:: \mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q.
.. raw:: html
Ở đây, trọng số :math:`\mathbf{W}_{hq} \in \mathbb{R}^{2h \times q}` và
độ chệch :math:`\mathbf{b}_q \in \mathbb{R}^{1 \times q}` là các tham số
mô hình của tầng đầu ra. Hai chiều ngược và xuôi có thể có số nút ẩn
khác nhau.
.. raw:: html
.. raw:: html
.. raw:: html
Chi phí Tính toán và Ứng dụng
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. raw:: html
Một trong những tính năng chính của RNN hai chiều là thông tin từ cả hai
đầu của chuỗi được sử dụng để ước lượng kết quả đầu ra. Chúng ta sử dụng
thông tin từ các quan sát trong tương lai và quá khứ để dự đoán hiện tại
(như để làm mượt). Trong trường hợp mô hình ngôn ngữ, đây không hẳn là
điều chúng ta muốn. Rốt cuộc, chúng ta không thể biết biểu tượng tiếp
sau biểu tượng đang cần dự đoán. Do đó, nếu chúng ta sử dụng RNN hai
chiều một cách ngây thơ, chúng ta sẽ không có được độ chính xác đủ tốt:
trong quá trình huấn luyện, chúng ta có cả dữ liệu quá khứ và tương lai
để ước tính hiện tại. Trong quá trình dự đoán, chúng ta chỉ có dữ liệu
trong quá khứ và do đó kết quả dự đoán có độ chính xác kém (điều này
được minh họa trong thí nghiệm bên dưới).
.. raw:: html
Tệ hơn, RNN hai chiều cũng cực kỳ chậm. Những lý do chính cho điều này
là vì chúng cần cả lượt truyền xuôi và lượt truyền ngược, và lượt truyền
ngược thì phụ thuộc vào kết quả của lượt truyền xuôi. Do đó, gradient sẽ
có một chuỗi phụ thuộc rất dài.
.. raw:: html
Trong thực tế, các tầng hai chiều được sử dụng rất ít và chỉ dành cho
một số ít ứng dụng, chẳng hạn như điền từ còn thiếu, chú thích token (ví
dụ cho nhận dạng thực thể có tên) hoặc mã hóa nguyên chuỗi tại một bước
trong pipeline xử lý chuỗi (ví dụ trong dịch máy). Tóm lại, hãy sử dụng
nó một cách cẩn thận!
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
.. raw:: html
Huấn luyện Mạng RNN Hai chiều cho Ứng dụng không Phù hợp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. raw:: html
Nếu chúng ta bỏ qua tất cả các lời khuyên về việc LSTM hai chiều sử dụng
cả dữ liệu trong quá khứ và tương lai, và cứ áp dụng nó cho các mô hình
ngôn ngữ, chúng ta sẽ có được các ước lượng với perplexity chấp nhận
được. Tuy nhiên, khả năng dự đoán các ký tự trong tương lai của mô hình
bị tổn hại nghiêm trọng như minh họa trong ví dụ dưới đây. Mặc dù đạt
được mức perplexity hợp lý, nó chỉ sinh ra các chuỗi vô nghĩa ngay cả
sau nhiều vòng lặp. Chúng tôi sử dụng đoạn mã dưới đây như một ví dụ
cảnh báo về việc sử dụng chúng ở sai bối cảnh.
.. code:: python
from d2l import mxnet as d2l
from mxnet import npx
from mxnet.gluon import rnn
npx.set_np()
# Load data
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
# Define the model
vocab_size, num_hiddens, num_layers, ctx = len(vocab), 256, 2, d2l.try_gpu()
lstm_layer = rnn.LSTM(num_hiddens, num_layers, bidirectional=True)
model = d2l.RNNModel(lstm_layer, len(vocab))
# Train the model
num_epochs, lr = 500, 1
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, ctx)
.. parsed-literal::
:class: output
perplexity 1.2, 65233.4 tokens/sec on gpu(0)
time travellerererererererererererererererererererererererererer
travellerererererererererererererererererererererererererer
.. figure:: output_bi-rnn_vn_1f8ce9_1_1.svg
.. raw:: html
Đầu ra rõ ràng không hề tốt vì những lý do trên. Để thảo luận về việc sử
dụng hiệu quả hơn các mô hình hai chiều, vui lòng xem bài toán phân loại
cảm xúc trong :numref:`sec_sentiment_rnn`.
.. raw:: html
Tóm tắt
-------
.. raw:: html
- Trong các mạng nơ-ron hồi tiếp hai chiều, trạng thái ẩn tại mỗi bước
thời gian được xác định đồng thời bởi dữ liệu ở trước và sau bước
thời gian đó.
- Các RNN hai chiều có sự tương đồng đáng kinh ngạc với thuật toán
xuôi–ngược trong các mô hình đồ thị.
- RNN hai chiều chủ yếu hữu ích cho việc tạo embedding chuỗi và việc
ước lượng dữ liệu quan sát được khi biết bối cảnh hai chiều.
- Việc huấn luyện RNN hai chiều rất tốn kém do các chuỗi gradient dài.
.. raw:: html
Bài tập
-------
.. raw:: html
1. Nếu các hướng khác nhau sử dụng số nút ẩn khác nhau, kích thước của
:math:`\mathbf{H}_t` sẽ thay đổi như thế nào?
2. Thiết kế một mạng nơ-ron hồi tiếp hai chiều với nhiều tầng ẩn.
3. Lập trình thuật toán phân loại chuỗi bằng cách sử dụng các RNN hai
chiều. **Gợi ý:** sử dụng RNN để tạo embedding cho từng từ và sau đó
tổng hợp (lấy trung bình) tất cả các embedding đầu ra trước khi đưa
chúng vào mô hình MLP để phân loại. Chẳng hạn, nếu chúng ta có
:math:`(\mathbf{o}_1, \mathbf{o}_2, \mathbf{o}_3)`, ta sẽ tính
:math:`\bar{\mathbf{o}} = \frac{1}{3} \sum_i \mathbf{o}_i` trước rồi
sử dụng nó để phân loại cảm xúc.
.. raw:: html
.. raw:: html
Thảo luận
---------
- `Tiếng Anh `__
- `Tiếng Việt `__
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 Quang
- Nguyễn Văn Cường
- Lê Khắc Hồng Phúc
- Nguyễn Lê Quang Nhật
- Đinh Phước Lộc
- Võ Tấn Phát
- Nguyễn Thanh Hòa
- Trần Yến Thy
- Phạm Hồng Vinh