Học sâugiáo trình tự học
Chương 816 phút đọcMạng tích chập và mạng hồi quy

8Mạng nơ-ron hồi quy#

Văn bản, tiếng nói và chuỗi thời gian là dữ liệu dạng chuỗi có độ dài thay đổi, và ý nghĩa của mỗi phần tử phụ thuộc vào những gì đứng trước nó. Một mạng truyền thẳng nhận đầu vào có kích thước cố định nên không xử lý trực tiếp được loại dữ liệu này. Mạng nơ-ron hồi quy (recurrent neural network, RNN) giải quyết bằng một ý tưởng đơn giản: đọc chuỗi từng phần tử một và giữ một trạng thái tóm tắt những gì đã đọc. Ý tưởng này gặp lại đúng vấn đề gradient của Chương 6, ở dạng nặng hơn, và cuối cùng bị Transformer thay thế vì một giới hạn mà không kỹ thuật huấn luyện nào sửa được.

8.1Mô hình#

Định nghĩa 8.1 (Mạng nơ-ron hồi quy)

Với chuỗi đầu vào x1,…,xTx_1, \dots, x_T, một RNN cập nhật trạng thái ẩn hth_t theo

ht=tanh⁡(Whh ht−1+Wxh xt+b),y^t=Why ht,h_t = \tanh\big(W_{hh}\, h_{t-1} + W_{xh}\, x_t + b\big), \qquad \hat y_t = W_{hy}\, h_t,

với h0h_0 cho trước, thường bằng 0. Cùng các ma trận WhhW_{hh}, WxhW_{xh}, WhyW_{hy} được dùng ở mọi bước.

Dùng chung trọng số ở mọi bước là dạng chia sẻ trọng số theo trục thời gian, giống chia sẻ trọng số theo không gian của tích chập (Mục 7.1). Nhờ đó RNN xử lý được chuỗi dài tuỳ ý với số tham số cố định, và cách xử lý một mẫu ở vị trí thứ 5 giống ở vị trí thứ 500.

Trường hợp một chiều cho thấy trạng thái ẩn nhớ và quên ra sao. Lấy ht=tanh⁡(0,5 ht−1+xt)h_t = \tanh(0{,}5\,h_{t-1} + x_t) với h0=0h_0 = 0 và chuỗi đầu vào 1,0,01, 0, 0. Ba trạng thái lần lượt là h1=tanh⁡1=0,76h_1 = \tanh 1 = 0{,}76, h2=tanh⁡0,38=0,36h_2 = \tanh 0{,}38 = 0{,}36 và h3=tanh⁡0,18=0,18h_3 = \tanh 0{,}18 = 0{,}18: sau mỗi bước, dấu vết của đầu vào đầu tiên còn chưa tới một nửa, vì nó bị nhân với hệ số 0,5 rồi đi qua tanh.

Có những bài toán không giải được nếu thiếu trạng thái ẩn. Karpathy (2015) minh hoạ điều này bằng một mô hình đoán ký tự tiếp theo trên chữ "hello". Một ví dụ tiếng Việt cùng kiểu là chuỗi "con cò", tính cả dấu cách, cho năm cặp gồm ký tự hiện tại và ký tự tiếp theo: c → o, o → n, n → dấu cách, dấu cách → c, c → ò. Hai cặp có cùng đầu vào "c" nhưng đáp án khác nhau, nên không mô hình nào chỉ nhìn ký tự hiện tại làm đúng được cả hai. Chỉ trạng thái ẩn, vốn nhớ rằng trước chữ "c" thứ hai đã có cả một từ "con", mới phân biệt được hai lần gặp chữ "c".

Để huấn luyện, RNN được trải ra theo thời gian thành một mạng truyền thẳng TT lớp dùng chung trọng số, rồi áp dụng lan truyền ngược lên mạng đó. Thuật toán này gọi là lan truyền ngược theo thời gian (backpropagation through time). Gradient theo trọng số dùng chung là tổng các gradient tại mọi bước. Với chuỗi rất dài, như một văn bản hàng triệu ký tự, lan truyền ngược được cắt thành từng đoạn, gọi là truncated BPTT: trạng thái ẩn vẫn được mang từ đoạn trước sang đoạn sau, nhưng gradient chỉ đi ngược trong phạm vi một đoạn, chẳng hạn 100 bước (CS231n).

8.2Gradient tiêu biến và bùng nổ theo thời gian#

Một câu như "Anh ấy lớn lên ở Nhật Bản, học hết phổ thông ở đó, rồi mới về nước đi làm, nên nói thạo tiếng ___" cần thông tin cách chỗ trống mười bảy âm tiết. Để học được phụ thuộc như vậy, gradient của mất mát tại chỗ trống phải truyền ngược về tận chữ "Nhật"; Olah (2015) và Andrew Ng đều mở đầu phần này bằng những câu cùng kiểu. Mạng trải ra theo thời gian là một mạng sâu TT lớp, nên nó gặp đúng vấn đề của Chương 6. Theo quy tắc dây chuyền, đạo hàm của trạng thái ở bước TT theo trạng thái ở bước t<Tt < T là

∂hT∂ht=∏i=t+1T∂hi∂hi−1=∏i=t+1Tdiag⁡(tanh⁡′(zi)) Whh,\frac{\partial h_T}{\partial h_t} = \prod_{i=t+1}^{T} \frac{\partial h_i}{\partial h_{i-1}} = \prod_{i=t+1}^{T} \operatorname{diag}\big(\tanh'(z_i)\big)\, W_{hh},

với ziz_i là giá trị trước hàm tanh ở bước ii. Với RNN một chiều ở Mục 8.1, mỗi thừa số là 0,5⋅tanh⁡′(zi)0{,}5 \cdot \tanh'(z_i), nên ∂h3/∂h1=0,5 (1−0,362)⋅0,5 (1−0,182)≈0,21\partial h_3/\partial h_1 = 0{,}5\,(1 - 0{,}36^2) \cdot 0{,}5\,(1 - 0{,}18^2) \approx 0{,}21, và qua 20 bước đạo hàm không vượt quá 0,520≈10−60{,}5^{20} \approx 10^{-6}. Tình huống giống Mục 6.1, nhưng nghiêm trọng hơn ở một điểm: cùng một ma trận WhhW_{hh} được nhân lặp lại T−tT - t lần, thay vì mỗi lớp một ma trận khác nhau. Pascanu, Mikolov và Bengio (2013) chỉ ra rằng nếu giá trị suy biến lớn nhất của WhhW_{hh} nhỏ hơn 1 thì gradient chắc chắn tiêu biến theo hàm mũ, vì ∣tanh⁡′∣≤1|\tanh'| \le 1. Điều ngược lại không đúng: giá trị suy biến lớn nhất lớn hơn 1 chỉ là điều kiện cần để gradient bùng nổ, chưa phải điều kiện đủ, vì tanh bão hoà có thể kéo các thừa số xuống. Trong thí nghiệm dưới đây, gg là bán kính phổ, tức trị tuyệt đối lớn nhất của các trị riêng, nên các con số cần được đọc với lưu ý đó.

Thí nghiệm trong code/models/experiments.py đo trực tiếp hiện tượng này. Một RNN tanh 64 chiều có WhhW_{hh} với các phần tử độc lập, phương sai g2/64g^2/64, nên bán kính phổ xấp xỉ gg; trạng thái đầu nhỏ, và mạng chạy 100 bước. Một gradient ngẫu nhiên được truyền ngược từ bước cuối, và thí nghiệm đo chuẩn của gradient sau 10, 50 và 100 bước lùi, chia cho chuẩn sau 1 bước và lấy trung bình trên 8 lần khởi tạo.

Hình 10
Hình 10. Chuẩn tương đối của gradient khi truyền ngược qua thời gian, theo số bước lùi; trục dọc theo thang log.
Mô hình Sau 10 bước Sau 50 bước Sau 100 bước
RNN, g=0,9g = 0{,}9 3,2×10−13{,}2 \times 10^{-1} 6,8×10−26{,}8 \times 10^{-2} 3,2×10−23{,}2 \times 10^{-2}
RNN, g=1,0g = 1{,}0 4,2×10−14{,}2 \times 10^{-1} 1,8×10−11{,}8 \times 10^{-1} 1,11{,}1
RNN, g=1,2g = 1{,}2 6,4×10−16{,}4 \times 10^{-1} 3,8×10−13{,}8 \times 10^{-1} 3,03{,}0
Đường ô nhớ của LSTM, hệ số chặn cổng quên 1 5,0×10−25{,}0 \times 10^{-2} 8,0×10−88{,}0 \times 10^{-8} 3,0×10−153{,}0 \times 10^{-15}
Đường ô nhớ của LSTM, hệ số chặn cổng quên 4 8,3×10−18{,}3 \times 10^{-1} 3,6×10−13{,}6 \times 10^{-1} 1,3×10−11{,}3 \times 10^{-1}

Với g=0,9g = 0{,}9, gradient giảm đều. Với g=1,0g = 1{,}0 và 1,21{,}2, gradient giảm trong khoảng 50 bước lùi đầu rồi tăng trở lại, tới 3,0 sau 100 bước với g=1,2g = 1{,}2. Lý do nằm ở trạng thái đầu nhỏ. Ở các bước đầu của chuỗi, hh còn nhỏ nên tanh gần như tuyến tính và Jacobi xấp xỉ WhhW_{hh}, có bán kính phổ lớn hơn 1; ở các bước sau, với g>1g > 1, trạng thái lớn dần, tanh bão hoà và đạo hàm của nó kéo Jacobi xuống. Như vậy độ lớn của gradient phụ thuộc cả vào WhhW_{hh} lẫn vào mức bão hoà của các đơn vị dọc theo chuỗi, và trong khi huấn luyện, cả hai đều thay đổi. Hai dòng cuối của bảng nói về LSTM và sẽ được giải thích ở Mục 8.3.

Hai loại sự cố cần cách xử lý khác nhau. Bùng nổ dễ xử lý: cắt ngưỡng gradient (gradient clipping) nhân gradient với τ/∥g∥\tau/\|g\| mỗi khi chuẩn của nó, tính trên toàn bộ tham số ghép lại, vượt ngưỡng τ\tau, giữ nguyên hướng và chỉ giới hạn độ lớn (Pascanu, Mikolov và Bengio, 2013). Kỹ thuật này được dùng rộng rãi, kể cả khi huấn luyện Transformer. Tiêu biến thì khó xử lý hơn nhiều. Một gradient đã quá nhỏ không thể phóng to lại một cách có ý nghĩa, vì thông tin về ảnh hưởng của các bước xa đã lẫn vào nhiễu. Tệ hơn, khi gradient từ xa đã tiêu biến, quá trình học không phân biệt được hai tình huống: dữ liệu thật sự không có phụ thuộc xa, hay có nhưng tham số hiện tại chưa nắm được nó (CS224n). Muốn sửa phải thay đổi kiến trúc, và đó là việc LSTM làm.

8.3LSTM và GRU#

Kiến trúc giải quyết được vấn đề tiêu biến là LSTM (long short-term memory; Hochreiter và Schmidhuber, 1997, với cổng quên do Gers, Schmidhuber và Cummins, 2000 bổ sung). LSTM thêm một trạng thái ô nhớ ctc_t bên cạnh trạng thái ẩn hth_t, và ba cổng có giá trị trong (0,1)(0, 1):

ft=σ(Wf[ht−1,xt]+bf),c~t=tanh⁡(Wc[ht−1,xt]+bc),it=σ(Wi[ht−1,xt]+bi),ct=ft⊙ct−1+it⊙c~t,ot=σ(Wo[ht−1,xt]+bo),ht=ot⊙tanh⁡(ct).\begin{aligned} f_t &= \sigma(W_f [h_{t-1}, x_t] + b_f), & \tilde c_t &= \tanh(W_c [h_{t-1}, x_t] + b_c), \\ i_t &= \sigma(W_i [h_{t-1}, x_t] + b_i), & c_t &= f_t \odot c_{t-1} + i_t \odot \tilde c_t, \\ o_t &= \sigma(W_o [h_{t-1}, x_t] + b_o), & h_t &= o_t \odot \tanh(c_t). \end{aligned}

Cổng quên ftf_t quyết định giữ lại bao nhiêu phần của ô nhớ cũ, cổng vào iti_t quyết định ghi thêm bao nhiêu thông tin mới, cổng ra oto_t quyết định đưa bao nhiêu phần của ô nhớ ra trạng thái ẩn. Chẳng hạn với một ô nhớ một chiều có ct−1=2c_{t-1} = 2, nếu ở bước này ft=0,9f_t = 0{,}9, it=0,5i_t = 0{,}5, c~t=−1\tilde c_t = -1 và ot=0,8o_t = 0{,}8 thì ct=0,9⋅2+0,5⋅(−1)=1,3c_t = 0{,}9 \cdot 2 + 0{,}5 \cdot (-1) = 1{,}3 và ht=0,8tanh⁡1,3≈0,69h_t = 0{,}8 \tanh 1{,}3 \approx 0{,}69. Ô nhớ giữ lại 90% giá trị cũ, ghi thêm một nửa giá trị mới, và chỉ một phần của nó được đưa ra ngoài. Olah (2015) ví trạng thái ô nhớ như một băng chuyền chạy thẳng qua mọi bước, chỉ bị tác động bởi một phép nhân và một phép cộng ở mỗi bước, nên thông tin đi dọc theo nó mà ít bị biến đổi.

Chỗ khác biệt quyết định là ô nhớ được cập nhật bằng phép cộng có trọng số, không qua phép nhân ma trận. Dọc theo đường đi qua ô nhớ, nếu bỏ qua các đường gián tiếp qua hh, ta có

∂cT∂ct=∏i=t+1Tdiag⁡(fi).\frac{\partial c_T}{\partial c_t} = \prod_{i=t+1}^{T} \operatorname{diag}(f_i).

Đây là tích của các số trong (0,1)(0, 1), không phải tích của các ma trận, và mạng học được giá trị của fif_i: khi cần giữ thông tin lâu, nó có thể đặt fif_i gần 1. Ở ví dụ trên, đạo hàm của ctc_t theo ct−1c_{t-1} dọc theo ô nhớ đúng bằng ft=0,9f_t = 0{,}9, không qua hàm tanh hay ma trận trọng số nào. Cơ chế này tương tự kết nối tắt ở Mục 6.5, nhưng theo trục thời gian.

Hai dòng cuối của bảng ở Mục 8.2 mô phỏng riêng đường đi này: gradient nhân với ft=σ(bf+ξt)f_t = \sigma(b_f + \xi_t) ở mỗi bước, với ξt\xi_t là nhiễu nhỏ. Kết quả cho thấy lợi ích của LSTM phụ thuộc vào giá trị cổng quên. Với hệ số chặn bf=1b_f = 1, cổng quên trung bình khoảng σ(1)=0,73\sigma(1) = 0{,}73, và sau 100 bước gradient chỉ còn 3×10−153 \times 10^{-15}. Với bf=4b_f = 4, cổng quên khoảng 0,980{,}98, và gradient còn 0,13.

Có thể hiểu các con số này qua thang thời gian của bộ nhớ. Với cổng quên không đổi ff, thông tin suy giảm theo ftf^t. Tổng các trọng số ∑tft=1/(1−f)\sum_t f^t = 1/(1-f) cho biết bộ nhớ kéo dài khoảng bao nhiêu bước, còn thời gian để tín hiệu giảm ee lần là −1/ln⁡f-1/\ln f; hai con số gần nhau khi ff gần 1. Với f=0,73f = 0{,}73, chúng là 3,7 và 3,2 bước, tức khoảng 4 bước; với f=0,98f = 0{,}98, là 50 và 49,5 bước. Khuyến nghị phổ biến là khởi tạo bf=1b_f = 1 (Jozefowicz, Zaremba và Sutskever, 2015), đủ cho các phụ thuộc cỡ vài bước tới vài chục bước ngay từ đầu huấn luyện. Khi biết trước chuỗi có phụ thuộc rất xa, Tallec và Ollivier (2018) đề xuất khởi tạo bfb_f theo thang thời gian mong muốn, để các đơn vị khác nhau có bộ nhớ dài ngắn khác nhau.

Một biến thể gọn hơn là GRU (gated recurrent unit; Cho và cộng sự, 2014). GRU gộp cổng quên và cổng vào thành một cổng cập nhật, bỏ ô nhớ riêng và cổng ra, nên có ba khối trọng số thay vì bốn, tức ít hơn khoảng 25% tham số. Chung và cộng sự (2014) so sánh trên nhiều bài toán và không thấy bên nào tốt hơn một cách nhất quán, nên chọn giữa hai loại là việc phải quyết định bằng thực nghiệm. Khi đọc các tài liệu về GRU cần để ý quy ước: d2l.ai viết cổng cập nhật gần 1 nghĩa là giữ trạng thái cũ, còn Andrew Ng và CS224n dùng quy ước ngược lại.

Với những bài toán có sẵn cả câu trước khi xử lý, như gán nhãn từ loại hay nhận dạng thực thể, có thể chạy thêm một RNN theo chiều ngược lại và ghép trạng thái của hai chiều tại mỗi vị trí, để mỗi vị trí dùng được cả ngữ cảnh phía sau. RNN hai chiều như vậy không dùng được cho mô hình ngôn ngữ hay cho dữ liệu đến dần theo thời gian, vì phần sau của chuỗi chưa có.

8.4Giới hạn về tính song song#

Vấn đề gradient giảm nhẹ được bằng LSTM, cắt ngưỡng gradient và khởi tạo tốt. Giới hạn không khắc phục được nằm ở chính định nghĩa: hth_t phụ thuộc ht−1h_{t-1}, nên các bước phải tính tuần tự. Xử lý một chuỗi dài TT cần TT bước nối tiếp nhau, dù có bao nhiêu phần cứng song song. Bảng sau so sánh một lớp RNN với một lớp self-attention (Chương 9), theo Vaswani và cộng sự (2017).

Lớp RNN Lớp self-attention
Số phép tính mỗi lớp O(Td2)O(T d^2) O(T2d)O(T^2 d)
Số bước phải thực hiện tuần tự O(T)O(T) O(1)O(1)
Độ dài đường đi dài nhất giữa hai vị trí O(T)O(T) O(1)O(1)

Self-attention tốn nhiều phép tính hơn khi T>dT > d, nhưng các phép tính đó thực hiện song song được trên GPU, còn các bước tuần tự thì không. Đường đi ngắn giữa hai vị trí bất kỳ còn có nghĩa là gradient nối hai token cách xa nhau không phải đi qua TT phép nhân như trong RNN. Hai điểm này khiến Transformer thay thế RNN trong phần lớn bài toán xử lý ngôn ngữ.

Hướng hồi quy chưa kết thúc. Các mô hình không gian trạng thái (state space model) như Mamba (Gu và Dao, 2023) dùng phép truy hồi tuyến tính theo thời gian, cho phép huấn luyện song song bằng thuật toán quét (scan) và suy luận với bộ nhớ không đổi theo độ dài chuỗi, và cạnh tranh được với Transformer ở một số quy mô.

8.5Tóm tắt#

RNN xử lý chuỗi bằng một trạng thái ẩn cập nhật qua từng bước với trọng số dùng chung, và được huấn luyện bằng lan truyền ngược trên mạng trải ra theo thời gian, cắt thành từng đoạn khi chuỗi dài. Trạng thái ẩn là điều kiện cần: với chuỗi "con cò", cùng đầu vào "c" phải cho hai đáp án khác nhau. Vì cùng một ma trận được nhân lặp lại, gradient tiêu biến hoặc bùng nổ theo số bước; bùng nổ xử lý được bằng cắt ngưỡng gradient, còn tiêu biến cần thay đổi kiến trúc. LSTM cập nhật ô nhớ bằng phép cộng có cổng, nên gradient dọc theo ô nhớ chỉ nhân với các giá trị cổng quên mà mạng tự học; thang thời gian của bộ nhớ khoảng 1/(1−f)1/(1-f) bước, và mô phỏng cho thấy sau 100 bước gradient còn 0,13 với cổng quên 0,98 nhưng chỉ 3×10−153 \times 10^{-15} với cổng quên 0,73. Giới hạn cuối cùng của RNN là các bước phải tính tuần tự, và đường đi giữa hai vị trí xa nhau dài O(T)O(T).

Self-attention bỏ hẳn phép truy hồi: mọi vị trí lấy thông tin trực tiếp từ mọi vị trí khác trong một bước. Chương 9 xây Transformer từ phép toán đó.


Tự kiểm tra

4 câu. Chọn đáp án để xem ngay lời giải thích.

  1. 1Giới hạn nào của RNN là lý do chính khiến Transformer thay thế nó?

  2. 2Dọc theo trạng thái ô nhớ của LSTM, gradient nhân với gì ở mỗi bước?

  3. 3Theo mô phỏng ở Mục 8.2, gradient dọc ô nhớ sau 100 bước còn bao nhiêu khi hệ số chặn của cổng quên bằng 1 và bằng 4?

  4. 4Vì sao gradient bùng nổ dễ xử lý hơn gradient tiêu biến?

Đã trả lời 0/4