9.4. Mạng Nơ-ron Hồi tiếp Hai chiều

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:

  1. Tôi _____
  2. Tôi _____ đói lắm.
  3. Tôi _____ đói lắm, tôi thể ăn một nửa con lợn.

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.

9.4.1. Quy hoạch Động

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.

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 \(h_t\) quyết định giá trị quan sát \(x_t\) qua xác suất \(p(x_t \mid h_t)\). Hơn nữa, quá trình chuyển đổi \(h_t \to h_{t+1}\) được cho bởi xác suất chuyển trạng thái \(p(h_t+1 \mid h_{t})\). Mô hình đồ thị khi đó là mô hình Markov ẩn (Hidden Markov Model - HMM) như trong Fig. 9.4.1.

../_images/hmm.svg

Fig. 9.4.1 Mô hình Markov ẩn.

Như vậy, với chuỗi có \(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:

(9.4.1)\[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).\]
Bây giờ giả sử chúng ta đã có tất cả các quan sát \(x_i\) ngoại trừ một vài quan sát \(x_j\), mục tiêu là tính xác suất \(p(x_j \mid x^{-j})\), trong đó \(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 \(h = (h_1, \ldots, h_T)\). Trong trường hợp \(h_i\) nhận \(k\) giá trị khác nhau, chúng ta cần tính tổng của \(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 \(h_1\)\(h_2\). Ta có:
(9.4.2)\[\begin{split}\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}\end{split}\]

Cơ bản, chúng ta có công thức đệ quy xuôi như sau:

(9.4.3)\[\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).\]

Phép đệ quy được khởi tạo với \(\pi_1(h_1) = p(h_1)\). Nói chung, công thức đệ quy có thể được viết lại là \(\pi_{t+1} = f(\pi_t, x_t)\), trong đó \(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:

(9.4.4)\[\begin{split}\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}\end{split}\]

Từ đó, chúng ta có thể viết đệ quy ngược như sau:

(9.4.5)\[\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}),\]
khi khởi tạo \(\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ả \(T\) biến trong khoảng \((h_1, \ldots, h_T)\) với thời gian \(\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 [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:
(9.4.6)\[p(x_j \mid x_{-j}) \propto \sum_{h_j} \pi_j(h_j) \rho_j(h_j) p(x_j \mid h_j).\]
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 \(\rho_{t-1} = g(\rho_t, x_t)\), trong đó \(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 [Doucet et al., 2001] về các thuật toán Monte Carlo tuần tự để biết thêm chi tiết.

9.4.2. Mô hình Hai chiều

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. Fig. 9.4.2 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.

../_images/birnn.svg

Fig. 9.4.2 Cấu trúc của mạng nơ ron hồi tiếp hai chiều.

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.

9.4.2.1. Định nghĩa

Các mạng nơ-ron hồi tiếp hai chiều đã được giới thiệu bởi [Schuster & Paliwal, 1997]. Ta có thể xem thêm [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.

Cho một bước thời gian \(t\), đầu vào minibatch là \(\mathbf{X}_t \in \mathbb{R}^{n \times d}\) (\(n\) là số lượng mẫu, \(d\) là số lượng đầu vào) và hàm kích hoạt của tầng ẩn là \(\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à \(\overrightarrow{\mathbf{H}}_t \in \mathbb{R}^{n \times h}\)\(\overleftarrow{\mathbf{H}}_t \in \mathbb{R}^{n \times h}\). \(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:

(9.4.7)\[\begin{split}\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}\end{split}\]

Ở đây, các trọng số \(\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 \(\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.

Sau đó, chúng ta nối các trạng thái ẩn xuôi và ngược (\(\overrightarrow{\mathbf{H}}_t\), \(\overleftarrow{\mathbf{H}}_t\)) để thu được trạng thái ẩn \(\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 \(\mathbf{O}_t \in \mathbb{R}^{n \times q}\) (\(q\) là số lượng đầu ra) như sau:

(9.4.8)\[\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q.\]

Ở đây, trọng số \(\mathbf{W}_{hq} \in \mathbb{R}^{2h \times q}\) và độ chệch \(\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.

9.4.2.2. Chi phí Tính toán và Ứng dụng

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).

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.

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!

9.4.2.3. Huấn luyện Mạng RNN Hai chiều cho Ứng dụng không Phù hợp

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.

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)
perplexity 1.2, 65233.4 tokens/sec on gpu(0)
time travellerererererererererererererererererererererererererer
travellerererererererererererererererererererererererererer
../_images/output_bi-rnn_vn_1f8ce9_1_1.svg

Đầ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 Section 15.2.

9.4.3. Tóm tắt

  • 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.

9.4.4. Bài tập

  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 \(\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ó \((\mathbf{o}_1, \mathbf{o}_2, \mathbf{o}_3)\), ta sẽ tính \(\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.

9.4.5. Thảo luận

9.4.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 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