Deep Learning 5 — Dữ liệu tuần tự: RNN, LSTM & GRU
Deep Learning 5 — Dữ liệu tuần tự: RNN, LSTM & GRU
Không phải dữ liệu nào cũng là một tấm ảnh tĩnh hay một bảng số độc lập. Rất nhiều dữ liệu quan trọng nhất trong ngân hàng và đời sống là chuỗi (sequence): một câu văn là chuỗi các từ, lịch sử giao dịch của khách hàng là chuỗi các lần chi tiêu theo thời gian, giá cổ phiếu là chuỗi các mức giá theo ngày, giọng nói là chuỗi các mẫu âm. Điểm chung: thứ tự có ý nghĩa. Đảo trật tự các từ trong câu sẽ đổi ý nghĩa; đảo trật tự các giao dịch sẽ xoá mất mẫu hành vi.
Mạng nhiều lớp thông thường (MLP) và cả CNN đều giả định đầu vào có kích thước cố định và không tự nhiên biểu diễn "cái gì đến trước, cái gì đến sau". Bài này giới thiệu họ mô hình được thiết kế riêng cho chuỗi: mạng nơ-ron hồi quy (Recurrent Neural Network — RNN) và hai biến thể có cổng nhớ của nó là LSTM và GRU.
Bài giả định bạn đã nắm nơ-ron, lớp ẩn, hàm kích hoạt và lan truyền ngược từ bài Nền tảng Deep Learning và Huấn luyện & lan truyền ngược.
Đây là danh mục AI. Các đoạn code Python trong bài là minh hoạ để bạn thấy hình dạng phép tính, không phải sandbox chạy được.
1. Dữ liệu tuần tự khác gì?
Với dữ liệu dạng bảng (một khách hàng = một dòng đặc trưng), mỗi mẫu độc lập và có số chiều cố định. Chuỗi thì khác ở hai điểm.
Thứ tự mang thông tin. Câu "ngân hàng chặn giao dịch" và "giao dịch chặn ngân hàng" dùng cùng bộ từ nhưng nghĩa khác hẳn. Chuỗi giao dịch "nạp 100 triệu → rút ngay 99 triệu ra nước ngoài" có ý nghĩa cảnh báo rất khác với hai giao dịch đó đảo ngược. Mô hình phải biết vị trí và quan hệ trước–sau.
Độ dài thay đổi. Câu có thể dài 3 từ hoặc 30 từ; một khách hàng có 10 giao dịch tháng này, 500 giao dịch tháng sau. Mô hình cần xử lý được chuỗi dài ngắn tuỳ ý mà không phải cố định số đầu vào.
Ba loại chuỗi hay gặp trong thực tế:
- Văn bản (ngôn ngữ tự nhiên): chuỗi từ hoặc token — phân loại, dịch, gán nhãn.
- Chuỗi thời gian (time series): giá trị theo mốc thời gian — giao dịch, doanh số, nhu cầu tiền mặt, chỉ số kinh tế.
- Âm thanh / tín hiệu: chuỗi mẫu biên độ — nhận dạng giọng nói, phát hiện tín hiệu bất thường.
Câu hỏi thiết kế: làm sao để một mô hình nhớ được những gì đã thấy ở các bước trước khi xử lý bước hiện tại? Đó chính là ý tưởng của RNN.
2. RNN — trạng thái ẩn và vòng lặp theo thời gian
Ý tưởng cốt lõi của RNN đơn giản một cách đẹp đẽ: xử lý chuỗi từng phần tử một, và duy trì một trạng thái ẩn (hidden state) đóng vai trò "bộ nhớ" tóm tắt mọi thứ đã thấy cho tới hiện tại.
Ở mỗi bước thời gian t, RNN nhận hai thứ: đầu vào hiện tại x_t (ví dụ từ thứ t) và trạng thái ẩn của bước trước h_{t-1}. Nó trộn cả hai để tính ra trạng thái ẩn mới h_t:
h_t = tanh( W_x · x_t + W_h · h_{t-1} + b )
Trong đó W_x là trọng số cho đầu vào, W_h là trọng số cho trạng thái ẩn cũ, tanh là hàm kích hoạt. Trạng thái h_t vừa được truyền sang bước sau, vừa có thể dùng để sinh đầu ra y_t tại bước đó.
Chia sẻ trọng số theo thời gian. Điểm mấu chốt: cùng một bộ trọng số W_x, W_h, b được dùng ở mọi bước thời gian. Giống như CNN dùng lại filter ở mọi vị trí không gian, RNN dùng lại cùng phép biến đổi ở mọi vị trí thời gian. Nhờ vậy mô hình xử lý được chuỗi dài ngắn tuỳ ý với số tham số cố định, và học được quy luật "áp dụng ở bất kỳ thời điểm nào".
Unfold — trải vòng lặp theo thời gian
RNN thường được vẽ gọn thành một khối có vòng lặp tự trỏ về chính nó. Nhưng để hiểu và huấn luyện, ta trải (unfold) vòng lặp đó ra thành một chuỗi các bản sao của cùng một khối, mỗi bản cho một bước thời gian:
Khi trải ra, RNN trông giống một mạng rất sâu — nhưng "độ sâu" ở đây là chiều thời gian, và mọi lớp dùng chung trọng số. Trạng thái ẩn chảy từ trái sang phải như một sợi chỉ mang theo bộ nhớ: h_3 về nguyên tắc chứa dấu vết của x_1, x_2 lẫn x_3.
BPTT — lan truyền ngược qua thời gian
Vì bản chất RNN trải ra là một mạng sâu theo thời gian, việc huấn luyện dùng đúng lan truyền ngược đã học ở bài trước, nhưng áp dụng dọc theo chuỗi — gọi là Backpropagation Through Time (BPTT). Ta tính lỗi ở đầu ra, rồi lan gradient ngược qua từng bước thời gian về đầu chuỗi, cộng dồn đóng góp của cùng bộ trọng số ở mọi bước. Với chuỗi dài, người ta thường dùng truncated BPTT: chỉ lan ngược qua một cửa sổ số bước nhất định để giữ chi phí tính toán và bộ nhớ ở mức chấp nhận được.
3. Vấn đề trí nhớ dài — vanishing & exploding gradient
Về lý thuyết, h_t chứa thông tin của toàn bộ quá khứ. Trên thực tế, RNN thuần rất khó nhớ xa. Lý do nằm ở chính cơ chế BPTT.
Khi gradient lan ngược qua nhiều bước thời gian, nó bị nhân liên tiếp với cùng ma trận trọng số (và đạo hàm của hàm kích hoạt) ở mỗi bước. Đây là đúng vấn đề vanishing/exploding gradient đã gặp ở mạng sâu, nhưng phóng đại theo chiều dài chuỗi:
- Vanishing gradient (gradient tiêu biến): nếu các thừa số nhỏ hơn 1, tích của nhiều thừa số nhỏ dần về 0 theo hàm mũ. Gradient tới các bước xa gần như bằng 0 → mô hình không học được phụ thuộc dài hạn. Nó nhớ được vài bước gần nhất nhưng "quên" những gì xảy ra 50, 100 bước trước.
- Exploding gradient (gradient bùng nổ): nếu các thừa số lớn hơn 1, tích lớn dần về vô cực → cập nhật trọng số nhảy loạn, huấn luyện mất ổn định (thường thấy loss thành
NaN).
Exploding gradient có cách chữa đơn giản là gradient clipping (cắt gradient khi vượt ngưỡng). Vanishing gradient khó hơn nhiều: đây là hạn chế cấu trúc. Một câu như "Tôi lớn lên ở Pháp… (nhiều câu sau) … nên tôi nói thạo tiếng Pháp" đòi hỏi mô hình giữ được manh mối "Pháp" qua rất nhiều từ — điều RNN thuần thường làm không nổi.
Chính nhu cầu giữ trí nhớ dài một cách chọn lọc đã dẫn tới LSTM và GRU.
4. LSTM — bộ nhớ có cổng
Long Short-Term Memory (LSTM) giải bài toán trí nhớ dài bằng cách thêm một "băng nhớ" chạy suốt chuỗi và các cổng (gate) điều khiển thông tin ra vào băng nhớ đó.
Điểm khác biệt then chốt so với RNN thuần: bên cạnh trạng thái ẩn h_t, LSTM có thêm một cell state C_t — coi như một băng chuyền thông tin chạy gần như thẳng qua các bước, chỉ bị chỉnh sửa nhẹ bởi các cổng. Nhờ đường đi "gần như tuyến tính" này, gradient lan ngược ít bị tiêu biến hơn, cho phép giữ thông tin qua khoảng cách rất dài.
Ba cổng, mỗi cổng là một lớp nhỏ với hàm kích hoạt sigmoid (cho ra giá trị 0–1, như một "van" đóng/mở từng phần):
- Forget gate (cổng quên): nhìn
x_tvàh_{t-1}, quyết định giữ lại bao nhiêu phần của cell state cũC_{t-1}. Giá trị gần 0 = "quên đi", gần 1 = "giữ nguyên". - Input gate (cổng vào): quyết định thông tin mới nào từ đầu vào hiện tại được ghi vào cell state.
- Output gate (cổng ra): quyết định phần nào của cell state được lộ ra thành trạng thái ẩn
h_tđể dùng ở bước này.
Cell state mới = (cell cũ đã lọc qua forget gate) + (thông tin mới đã lọc qua input gate). Cơ chế cộng vào một băng nhớ được bảo vệ này chính là lý do LSTM giữ được phụ thuộc dài: mô hình học cách chủ động quyết định khi nào nhớ, khi nào quên, khi nào dùng — thay vì bị ép trộn mọi thứ ở mỗi bước như RNN thuần.
Quan trọng: các cổng không do ta lập trình tay. Trọng số của chúng được học từ dữ liệu qua lan truyền ngược, giống mọi tham số khác.
5. GRU — phiên bản gọn hơn
Gated Recurrent Unit (GRU) là biến thể đơn giản hoá của LSTM, ra đời sau. Nó gộp bớt cơ chế lại còn hai cổng và bỏ cell state riêng (chỉ dùng một trạng thái ẩn):
- Reset gate: quyết định quên bao nhiêu trạng thái cũ khi tính ứng viên trạng thái mới.
- Update gate: quyết định pha trộn giữa trạng thái cũ và trạng thái ứng viên mới — vừa đóng vai trò "quên" vừa "ghi", gộp chức năng của forget gate và input gate trong LSTM.
Vì ít cổng và ít tham số hơn, GRU huấn luyện nhanh hơn và cần ít dữ liệu hơn một chút. Trên nhiều bài toán thực tế, GRU và LSTM cho kết quả xấp xỉ nhau; không có quy tắc tuyệt đối cái nào tốt hơn. Kinh nghiệm phổ biến: thử GRU trước vì nhẹ hơn; nếu bài toán cần trí nhớ rất dài và phức tạp, LSTM đôi khi nhỉnh hơn. Cả hai đều vượt xa RNN thuần về khả năng nhớ dài.
6. Các dạng bài toán chuỗi
Sức mạnh của kiến trúc hồi quy là linh hoạt về hình dạng đầu vào/đầu ra. Có thể phân theo quan hệ số lượng:
-
Many-to-one (nhiều vào, một ra). Đọc cả chuỗi, cho ra một kết quả duy nhất ở cuối. Điển hình: phân loại văn bản / phân tích cảm xúc (sentiment) — đọc hết bình luận rồi phán "tích cực/tiêu cực"; hoặc phân loại một chuỗi giao dịch là bình thường hay đáng ngờ.
-
One-to-many (một vào, nhiều ra). Từ một đầu vào sinh ra cả chuỗi. Ví dụ kinh điển là mô tả ảnh (image captioning): đưa vào một ảnh (đặc trưng từ CNN), sinh ra câu mô tả từng từ một.
-
Many-to-many (nhiều vào, nhiều ra). Có hai kiểu:
- Đồng bộ (gán nhãn chuỗi): mỗi bước vào có một bước ra tương ứng, ví dụ gán nhãn từ loại cho từng từ, hay nhận diện thực thể (tên người, tổ chức, số tài khoản) trong văn bản.
- Lệch pha (seq2seq): đọc hết chuỗi vào rồi mới sinh chuỗi ra có độ dài khác, ví dụ dịch máy.
Seq2seq: encoder–decoder và attention
Với dịch máy hay tóm tắt, độ dài vào và ra thường khác nhau, nên người ta dùng kiến trúc encoder–decoder:
- Encoder (thường là một RNN/LSTM) đọc toàn bộ câu nguồn và nén nó thành một vector trạng thái — "bản tóm tắt ngữ nghĩa" của cả câu.
- Decoder (một RNN/LSTM khác) nhận vector đó và sinh câu đích từng từ một.
Nút thắt của seq2seq thuần: ép cả câu nguồn dài vào một vector cố định khiến thông tin đầu câu dễ bị mất khi câu dài. Giải pháp là attention (cơ chế chú ý): thay vì chỉ dựa vào một vector tóm tắt duy nhất, ở mỗi bước sinh từ, decoder được phép "nhìn lại" toàn bộ các trạng thái của encoder và tự chọn tập trung vào phần liên quan nhất của câu nguồn.
Attention là bước ngoặt: nó cho thấy mô hình không nhất thiết phải xử lý tuần tự để nắm quan hệ giữa các phần tử xa nhau. Chính ý tưởng này, khi được đẩy tới cùng và bỏ hẳn phần hồi quy, đã sinh ra Transformer — chủ đề của bài tiếp theo.
7. Ví dụ minh hoạ: LSTM nhỏ cho chuỗi
Một mô hình LSTM đơn giản cho bài phân loại chuỗi (many-to-one) — ví dụ đọc một cửa sổ các giao dịch và dự đoán "đáng ngờ hay không". Cú pháp kiểu Keras:
# (minh hoạ) — LSTM many-to-one phân loại chuỗi giao dịch
# Đầu vào: (số_mẫu, số_bước_thời_gian, số_đặc_trưng_mỗi_bước)
model = Sequential([
Masking(mask_value=0.0, # bỏ qua các bước đệm (padding)
input_shape=(30, 8)), # tối đa 30 bước, 8 đặc trưng/bước
LSTM(64, return_sequences=True), # lớp LSTM 1: trả cả chuỗi h_t
Dropout(0.3), # chống overfitting
LSTM(32), # lớp LSTM 2: chỉ trả h cuối cùng
Dense(16, activation="relu"),
Dense(1, activation="sigmoid"), # xác suất "đáng ngờ" (0..1)
])
model.compile(optimizer="adam",
loss="binary_crossentropy",
metrics=["AUC"]) # AUC hợp với dữ liệu mất cân bằng
Vài điểm đáng chú ý trong minh hoạ:
- Đầu vào có ba chiều: số mẫu × số bước thời gian × số đặc trưng mỗi bước. Đây là điểm khác biệt so với dữ liệu bảng phẳng.
- Chuỗi dài ngắn khác nhau thường được đệm (padding) cho bằng độ dài, rồi dùng
Maskingđể mô hình bỏ qua phần đệm. return_sequences=Trueở lớp đầu để xếp chồng nhiều lớp LSTM (mỗi lớp cần cả chuỗi làm đầu vào); lớp cuối chỉ lấy trạng thái cuối để phân loại.- Đổi
LSTMthànhGRUgần như không phải sửa gì khác — hai loại lớp dùng chung được cho phần lớn bài toán.
Với chuỗi thời gian dự báo (regression), ta chỉ đổi lớp cuối thành Dense(1) không có sigmoid và dùng loss mse.
8. Use case thực tế
1. Dự báo chuỗi thời gian giao dịch / nhu cầu tiền mặt. Ngân hàng cần dự báo lượng tiền mặt rút tại từng ATM hoặc chi nhánh theo ngày để tiếp quỹ đúng lượng — thiếu thì khách phàn nàn, thừa thì đọng vốn. Đây là bài toán chuỗi thời gian có mùa vụ phức tạp: cuối tuần, ngày lương, cận Tết, ngày lễ đều tạo mẫu riêng, lại chịu ảnh hưởng của thời tiết hay sự kiện. LSTM/GRU có thể học các mẫu phụ thuộc dài và phi tuyến này từ nhiều năm dữ liệu, kèm các đặc trưng ngoại sinh (ngày trong tuần, cờ ngày lễ). Trong thực tế người ta thường so với đường cơ sở (baseline) như trung bình trượt hay mô hình thống kê trước khi kết luận DL có đáng dùng không.
2. Phát hiện bất thường trên chuỗi giao dịch (AML / fraud). Gian lận và rửa tiền hiếm khi lộ ra ở một giao dịch đơn lẻ — chúng nằm trong mẫu hành vi theo thời gian: một chuỗi nạp–rút bất thường, tần suất tăng đột biến, hay trình tự bước lạ so với thói quen của chính khách hàng đó. Mô hình hồi quy đọc chuỗi giao dịch của khách theo thời gian và học "hành vi bình thường trông thế nào", từ đó chấm điểm mức bất thường của chuỗi hiện tại. Đây có thể làm theo hướng giám sát (khi có nhãn fraud, dạng many-to-one như ví dụ trên) hoặc không giám sát — ví dụ dùng autoencoder trên chuỗi để phát hiện giao dịch "khó tái tạo" là đáng ngờ, cùng tinh thần với các phương pháp phát hiện bất thường không giám sát. Lưu ý thực tế: dữ liệu fraud cực kỳ mất cân bằng, nên chọn ngưỡng và đánh giá phải dùng chỉ số phù hợp (AUC, precision–recall) chứ không phải accuracy.
9. Hạn chế của RNN & vì sao Transformer thắng
RNN/LSTM/GRU là bước tiến lớn, nhưng chúng có hai hạn chế cấu trúc mà Transformer khắc phục triệt để.
Khó song song hoá. Bản chất RNN là tuần tự: muốn tính h_t phải có h_{t-1} trước. Không thể tính đồng thời tất cả các bước, nên khó tận dụng hết sức mạnh song song của GPU. Với chuỗi dài và tập dữ liệu lớn, điều này khiến huấn luyện chậm. Transformer bỏ hồi quy, xử lý cả chuỗi cùng lúc bằng attention, tận dụng GPU tối đa → huấn luyện nhanh hơn nhiều lần trên cùng phần cứng.
Trí nhớ dài vẫn hạn chế. LSTM/GRU nhớ tốt hơn RNN thuần nhưng vẫn phải truyền thông tin qua từng bước một; với phụ thuộc rất xa, tín hiệu vẫn suy giảm. Attention của Transformer cho mọi vị trí nối trực tiếp với mọi vị trí trong một bước — khoảng cách giữa hai token luôn là "một bước nhảy" bất kể chúng cách nhau bao xa. Đây là lý do Transformer nắm ngữ cảnh dài tốt hơn hẳn.
Kết quả: từ khoảng cuối những năm 2010, Transformer gần như thay thế RNN trong NLP và ngày càng lấn sang chuỗi thời gian, âm thanh. Dù vậy, LSTM/GRU vẫn hữu ích khi dữ liệu ít, chuỗi ngắn, hoặc cần mô hình nhẹ, chạy trên thiết bị hạn chế tài nguyên — chúng đơn giản và tốn ít bộ nhớ hơn Transformer. Chi tiết cơ chế attention xem bài Transformer.
So với ARIMA — khi nào cần deep learning?
Với chuỗi thời gian, đừng mặc định phải dùng deep learning. Các mô hình thống kê kinh điển như ARIMA (và biến thể SARIMA cho mùa vụ) rất mạnh khi:
- Dữ liệu ít (vài chục đến vài trăm điểm) — DL dễ overfit khi thiếu dữ liệu.
- Quan hệ chủ yếu tuyến tính, có xu hướng và mùa vụ rõ ràng.
- Cần mô hình dễ giải thích, dễ kiểm định thống kê, huấn luyện nhanh.
LSTM/GRU (hay Transformer) đáng cân nhắc khi: có nhiều dữ liệu, quan hệ phi tuyến phức tạp, nhiều chuỗi liên quan cần học chung, hoặc có nhiều đặc trưng ngoại sinh. Nguyên tắc thực dụng: luôn dựng baseline đơn giản trước (trung bình trượt, ARIMA) rồi mới xem DL có cải thiện đủ để bù cho chi phí phức tạp và khó giải thích hay không.
Ghi nhớ
- Dữ liệu tuần tự (văn bản, chuỗi thời gian, âm thanh) có thứ tự mang thông tin và độ dài thay đổi — MLP/CNN không tự nhiên xử lý được.
- RNN giữ một trạng thái ẩn làm bộ nhớ, cập nhật ở mỗi bước với trọng số chia sẻ theo thời gian; huấn luyện bằng BPTT (lan truyền ngược qua thời gian).
- RNN thuần gặp vanishing / exploding gradient trên chuỗi dài → khó nhớ phụ thuộc xa. Clipping chữa exploding; vanishing cần đổi kiến trúc.
- LSTM thêm cell state và ba cổng (forget / input / output) để nhớ dài có chọn lọc; GRU gọn hơn với hai cổng, huấn luyện nhanh hơn, kết quả thường tương đương.
- Các dạng bài toán: many-to-one (phân loại/sentiment), one-to-many, many-to-many (gán nhãn, dịch), và seq2seq encoder–decoder + attention.
- Attention là cầu nối tới Transformer — cho mọi vị trí nối trực tiếp, song song toàn chuỗi.
- Vì sao Transformer thắng: RNN tuần tự khó song song và trí nhớ dài vẫn hạn chế; attention song song hoá và nối trực tiếp mọi vị trí. RNN/GRU vẫn hợp khi dữ liệu ít, chuỗi ngắn, mô hình nhẹ.
- So với ARIMA: dùng thống kê kinh điển khi dữ liệu ít / quan hệ tuyến tính / cần giải thích; dùng DL khi nhiều dữ liệu và quan hệ phi tuyến phức tạp — luôn có baseline trước.
- Ứng dụng ngân hàng: dự báo nhu cầu tiền mặt / chuỗi giao dịch và phát hiện bất thường trên chuỗi giao dịch (AML/fraud).
Đọc tiếp: Nền tảng Deep Learning · Huấn luyện & lan truyền ngược · Transformer & attention · Học không giám sát & phát hiện bất thường.
Bài viết liên quan
Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.
Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.
Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.
Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.