Mục lục
- Mục tiêu bài học
- Vì sao cần Seq2Seq
- Use case kinh điển
- Kiến trúc Seq2Seq (Sutskever 2014)
- Encoder
- Decoder
- Auto-regressive generation
- Teacher Forcing
- Scheduled sampling
- Special tokens
- Tokenization — word, char, BPE
- Vấn đề Bottleneck
- Attention — intuition
- Attention — công thức
- Từ Seq2Seq + attention tới Transformer
- Modern seq2seq landscape
- Inference — greedy vs beam search
- Evaluation — BLEU, ROUGE
- Code Python skeleton
- Bài tập
- Tổng kết Module 5
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Trả lời được vì sao vanilla RNN/LSTM không xử lý được task có input length \( T \) khác output length \( T' \), và Seq2Seq giải bằng cách nào.
- Vẽ được kiến trúc encoder–decoder LSTM, chỉ ra vị trí context vector \( c \).
- Giải thích teacher forcing — vì sao tăng tốc hội tụ, vì sao tạo exposure bias, scheduled sampling là gì.
- Phân biệt 4 special token
<SOS>,<EOS>,<PAD>,<UNK>và biết khi nào dùng. - Hiểu bottleneck của context vector cố định và lý do attention (Bahdanau, 2014; Luong, 2015) ra đời.
- Viết được attention weight \( \alpha_{t,i} = \mathrm{softmax}(\mathrm{score}(h_t^{\text{dec}}, h_i^{\text{enc}})) \) và context \( c_t = \sum_i \alpha_{t,i} h_i^{\text{enc}} \).
- Phân biệt greedy decoding, beam search, sampling cho inference.
- Implement Seq2Seq skeleton (encoder + decoder + teacher forcing) bằng PyTorch.
Bài này nối Bài 39 — Time Series LSTM và kết Module 5 (RNN). Module 6 mở đầu bằng Bài 41 — Autoencoder.
Vì sao cần Seq2Seq
Bài 35 (Vanilla RNN, mục 12) đã liệt kê 3 dạng task RNN:
- Many-to-one: chuỗi → 1 nhãn (sentiment analysis).
- Many-to-many same length: chuỗi → chuỗi cùng độ dài (POS tagging).
- Many-to-many different length: chuỗi → chuỗi khác độ dài.
Dạng thứ 3 không thể giải bằng một RNN đơn vì:
- Mỗi time step output 1 token → output length cố định bằng input length.
- Không có cơ chế "kết thúc" khác với input.
- Câu nguồn "I am happy" (3 từ) → câu đích "Tôi rất vui" (3 từ) — vô tình bằng nhau, nhưng "Hello world" (2 token) → "Xin chào thế giới" (4 token) thì không.
Trước Seq2Seq, dịch máy thống kê (SMT, vd Moses) dùng phrase table + n-gram language model, không phải mạng neural. Sutskever, Vinyals, Le (2014) — "Sequence to Sequence Learning with Neural Networks" — là một trong những paper đầu tiên cho thấy chỉ với LSTM end-to-end có thể đạt chất lượng cạnh tranh SMT trên WMT'14 Anh–Pháp (BLEU 34.8).
Cùng năm, Cho và cộng sự (2014) — "Learning Phrase Representations using RNN Encoder–Decoder" — đề xuất GRU (B38) chính trong ngữ cảnh seq2seq này.
Use case kinh điển
Seq2Seq là khuôn chung cho mọi task có dạng "chuỗi vào — chuỗi ra":
| Task | Input | Output |
|---|---|---|
| Machine Translation | "Hello world" | "Xin chào thế giới" |
| Summarization | Bài báo 1 000 từ | Tóm tắt 50 từ |
| Question Answering | "Thủ đô Việt Nam là gì?" | "Hà Nội" |
| Code generation | Spec "hàm tính giai thừa" | Đoạn code Python |
| Image captioning | Feature ảnh từ CNN | "Một con mèo trên bàn" |
| Speech recognition | Spectrogram audio | Transcript text |
| Speech synthesis (TTS) | Text | Spectrogram audio |
Image captioning (Show and Tell, Vinyals 2015) là biến thể: encoder là CNN (vd ResNet) thay cho RNN, decoder vẫn là LSTM sinh text. Khuôn encoder–decoder linh hoạt — chỉ cần encoder cho ra một (hoặc nhiều) vector biểu diễn, decoder ngôn ngữ vẫn dùng được.
Kiến trúc Seq2Seq (Sutskever 2014)
Hai mạng RNN riêng biệt (thường là LSTM, B37):
Input: x_1 x_2 x_3 <EOS>
│ │ │ │
▼ ▼ ▼ ▼
[ENCODER LSTM] ──► c (context vector)
│
▼ init hidden
[DECODER LSTM] ──► y_1 ─► y_2 ─► y_3 ─► y_4 ─► <EOS>
▲ ▲ ▲ ▲
<SOS> y_1 y_2 y_3 (auto-regressive)
Quy trình:
- Encode: encoder LSTM đọc input \( x_1, \ldots, x_T \). Hidden state cuối \( h_T^{\text{enc}} \) (và cell state \( c_T^{\text{enc}} \) với LSTM) là context vector \( c \).
- Init decoder: hidden state khởi đầu của decoder = \( c \). Token đầu vào step 1 là
<SOS>. - Decode: tại mỗi step, decoder nhận token trước đó và hidden state, output phân phối xác suất qua vocab; chọn token tiếp theo. Lặp đến khi sinh ra
<EOS>hoặc đạt max_length.
Encoder và decoder có vocab khác nhau (vd tiếng Anh vs tiếng Việt cho NMT), embedding khác nhau, weight không chia sẻ.
Mẹo thực dụng trong paper Sutskever 2014: đảo ngược thứ tự input ("c b a" thay vì "a b c") trước khi đưa vào encoder. Lý do: token đầu input gần token đầu output hơn trong unrolled graph → gradient ngắn hơn → train ổn định hơn. Mẹo này không còn cần khi có attention.
Encoder
Encoder là một RNN/LSTM/GRU đọc input sequence và trả về hidden state cuối:
\[ h_t^{\text{enc}} = \mathrm{LSTM}(x_t, h_{t-1}^{\text{enc}}), \quad t = 1, \ldots, T \] \[ c = h_T^{\text{enc}} \]
Một số biến thể:
- Bidirectional encoder (B35 mục 16): chạy 2 chiều, concat \( [\overrightarrow{h_T^{\text{enc}}}; \overleftarrow{h_1^{\text{enc}}}] \). Phổ biến vì encoder có sẵn toàn chuỗi input.
- Stacked encoder: 2–4 layer LSTM. Sutskever (2014) dùng 4 layer.
- Cell state truyền cùng hidden: với LSTM, decoder nhận cả \( (h_T^{\text{enc}}, c_T^{\text{enc}}) \).
Trực giác: context vector \( c \) được kỳ vọng "encode" mọi thông tin cần thiết về input để decoder reconstruct sang ngôn ngữ đích.
Decoder
Decoder là một RNN/LSTM/GRU khác với điều kiện khởi đầu khác:
\[
h_0^{\text{dec}} = c, \quad y_0 = \langle \mathrm{SOS} \rangle
\]
\[
h_t^{\text{dec}} = \mathrm{LSTM}(\mathrm{embed}(y_{t-1}), h_{t-1}^{\text{dec}})
\]
\[
p(y_t \mid y_{ Tại mỗi step decoder: Decoder là một conditional language model: \( p(y_1, \ldots, y_{T'} \mid x) = \prod_t p(y_t \mid y_{
Auto-regressive generation
Auto-regressive nghĩa là token thứ \( t \) phụ thuộc các token đã sinh ra trước đó. Tại inference:
def greedy_decode(encoder, decoder, src, max_len=50):
# 1. Encode toàn input
_, (h, c) = encoder(src) # (h, c): hidden + cell state LSTM
# 2. Bắt đầu với <SOS>
y_prev = torch.tensor([[SOS_IDX]])
outputs = []
for t in range(max_len):
logits, (h, c) = decoder(y_prev, (h, c)) # 1 step
y_next = logits.argmax(-1) # greedy: chọn argmax
outputs.append(y_next.item())
if y_next.item() == EOS_IDX:
break
y_prev = y_next # feed lại làm input step kế
return outputs
Điểm cần lưu ý:
- Decode là sequential — không thể parallel qua time, mỗi token cần token trước. Đây là nút thắt latency của tất cả seq2seq autoregressive (kể cả Transformer decoder GPT).
- Cần điều kiện dừng: gặp
<EOS>hoặc đạtmax_lenđể tránh vòng lặp vô hạn. - Cùng một input có thể sinh ra nhiều output khác nhau tuỳ chiến lược decoding (mục 17).
Teacher Forcing
Train decoder tự hồi quy bằng prediction của chính nó (giống inference) thì rất khó hội tụ ở đầu training — early prediction là noise, decoder "trượt" và không còn liên quan đến target nữa.
Teacher Forcing: trong training, thay vì feed prediction \( \hat{y}_{t-1} \) làm input cho step \( t \), feed thẳng ground truth \( y_{t-1} \) (từ target).
Training (teacher forcing):
step 1: input = <SOS>, target = y_1
step 2: input = y_1 (gt), target = y_2
step 3: input = y_2 (gt), target = y_3
...
Inference (no teacher):
step 1: input = <SOS>, predict ŷ_1
step 2: input = ŷ_1, predict ŷ_2
step 3: input = ŷ_2, predict ŷ_3
Lợi ích:
- Loss tại mỗi step độc lập về điều kiện input — train được song song qua time (vẫn forward tuần tự nhưng không phụ thuộc prediction).
- Hội tụ nhanh, gradient sạch.
Hạn chế — exposure bias:
- Train: decoder luôn thấy input "đúng".
- Inference: decoder thấy input chính nó sinh ra — có thể chứa lỗi.
- Lỗi tích luỹ (error compounds): một token sai → context lệch → các token sau càng sai.
Scheduled sampling
Scheduled sampling (Bengio và cộng sự, 2015) giảm exposure bias bằng curriculum:
- Bắt đầu training: \( p_{\text{teacher}} = 1.0 \) (luôn dùng ground truth).
- Cuối training: \( p_{\text{teacher}} \) giảm dần xuống 0.5 hoặc thấp hơn → đôi khi dùng prediction \( \hat{y}_{t-1} \).
- Lịch giảm: linear, exponential, hoặc inverse sigmoid trên epoch.
import random
teacher_forcing_ratio = max(0.5, 1.0 - epoch * 0.01) # giảm tuyến tính
input_token = tgt[:, 0] # <SOS>
for t in range(1, tgt.size(1)):
output, hidden = decoder(input_token, hidden)
if random.random() < teacher_forcing_ratio:
input_token = tgt[:, t] # teacher forcing
else:
input_token = output.argmax(-1) # dùng prediction
Đây không phải lời giải hoàn hảo (paper sau như Ranzato 2016, Wiseman 2016 chỉ ra một số lỗi lý thuyết) nhưng đủ tốt trong thực hành.
Special tokens
Vocab của Seq2Seq luôn có 4 token đặc biệt:
| Token | Ý nghĩa | Vị trí |
|---|---|---|
<SOS> / <BOS> |
Start / Beginning of Sequence | Input đầu tiên của decoder, trigger sinh token 1. |
<EOS> |
End of Sequence | Token cuối cùng decoder sinh ra → dừng inference. |
<PAD> |
Padding | Lấp đầy chuỗi ngắn về cùng độ dài trong batch. |
<UNK> |
Unknown | Thay cho token không có trong vocab (out-of-vocabulary). |
Quy ước thực dụng:
- Index thường:
PAD=0, SOS=1, EOS=2, UNK=3. PAD=0 để khớp vớipadding_idxmặc định củann.Embedding. - Loss bỏ qua PAD bằng
nn.CrossEntropyLoss(ignore_index=0)— nếu không, model học sinh ra PAD lung tung. - Target sequence luôn được thêm
<EOS>ở cuối khi train để model học khi nào dừng. - Một số framework còn thêm
<CLS>,<SEP>,<MASK>(BERT) cho task khác.
Tokenization — word, char, BPE
Cách chia text thành token ảnh hưởng cả vocab size và sequence length:
| Cách | Vocab | Sequence length | Vấn đề |
|---|---|---|---|
| Word-level | Rất lớn (50k–500k) | Ngắn (số từ) | OOV (từ chưa thấy → <UNK>), embedding khổng lồ. |
| Char-level | Nhỏ (~100) | Rất dài (5–10× word) | Chuỗi dài, khó học pattern ngữ nghĩa, train chậm. |
| BPE / WordPiece / SentencePiece | Vừa (16k–50k) | Vừa | Cân bằng — chuẩn modern. |
BPE (Byte Pair Encoding, Sennrich và cộng sự, 2016 cho NMT; gốc từ Gage 1994 cho data compression):
- Bắt đầu vocab = mọi ký tự.
- Đếm cặp ký tự liền kề xuất hiện nhiều nhất trong corpus.
- Merge cặp đó thành token mới, thêm vào vocab.
- Lặp đến khi vocab đạt size mong muốn.
Kết quả: từ thường ("the", "ing") thành 1 token; từ hiếm ("hyperparameter") bị split ("hyper", "parameter"). Giảm hẳn OOV.
WordPiece (Schuster & Nakajima, 2012; dùng trong BERT) tương tự BPE nhưng chọn merge tối đa hoá likelihood thay vì frequency. SentencePiece (Kudo & Richardson, 2018, dùng trong T5, mBART) học trực tiếp trên raw text không cần pre-tokenize, hỗ trợ ngôn ngữ không có khoảng trắng (tiếng Trung, Nhật).
Series 4 (LLM) sẽ deep dive tokenizer modern — tất cả đều là biến thể của BPE / WordPiece.
Vấn đề Bottleneck
Seq2Seq vanilla nén toàn bộ input \( T \) token vào một vector \( c \in \mathbb{R}^{d_h} \) — thường \( d_h = 256 \) hoặc \( 1024 \). Vấn đề:
- Capacity giới hạn: \( d_h \) float không đủ chứa thông tin cho câu 50 từ trở lên, đặc biệt với chi tiết như entity, danh từ riêng.
- Performance degradation với input dài: Bahdanau và cộng sự (2014) chứng minh thực nghiệm BLEU của vanilla seq2seq giảm mạnh khi câu dài hơn ~30 từ.
- Information loss qua time: token đầu input có gradient/information path dài \( T \) bước qua RNN — early input "phai" vào lúc decoder cần dùng.
Trực giác kiểu human: dịch câu dài, bạn không nhớ chính xác toàn bộ câu nguồn rồi viết câu đích từ trí nhớ — bạn nhìn lại câu nguồn nhiều lần, đặc biệt mỗi khi cần dịch một từ cụ thể. Attention chính là cơ chế đó cho neural network.
Attention — intuition
Bahdanau, Cho, Bengio (2014) — "Neural Machine Translation by Jointly Learning to Align and Translate" — đề xuất: decoder ở mỗi step không chỉ phụ thuộc \( c \) cố định, mà chú ý tới toàn bộ \( h_1^{\text{enc}}, \ldots, h_T^{\text{enc}} \) và tính một context vector mới \( c_t \) phụ thuộc step \( t \).
Encoder không còn nén tất cả vào 1 vector — nó giữ lại hidden state ở mọi step. Decoder tự quyết định step nào cần "nhìn vào" input nào nhiều hơn.
Vanilla seq2seq:
encoder ──► c (1 vector)
│
decoder step 1, 2, 3, ... đều dùng cùng c
Seq2seq + attention:
encoder ──► h_1^enc, h_2^enc, ..., h_T^enc (T vector)
│ │ │
└─ weighted sum ─────┘
│
decoder step t: c_t = Σ α_{t,i} h_i^enc (mỗi step 1 c_t khác)
Ví dụ NMT "Hello world" → "Xin chào thế giới": khi sinh "Xin chào" decoder attend mạnh vào "Hello"; khi sinh "thế giới" attend mạnh vào "world". Attention weight chính là alignment giữa input và output (giống alignment trong SMT, nhưng học end-to-end).
Attention — công thức
Tại decoder step \( t \), gọi \( h_t^{\text{dec}} \) là hidden decoder (query), \( h_i^{\text{enc}} \) là hidden encoder tại position \( i \) (key/value). Attention làm 3 bước:
Bước 1 — score: tính độ "phù hợp" giữa query và mỗi key:
\[ e_{t,i} = \mathrm{score}(h_t^{\text{dec}}, h_i^{\text{enc}}) \]
Một số hàm score phổ biến:
- Bahdanau (additive): \( e_{t,i} = v^\top \tanh(W_1 h_t^{\text{dec}} + W_2 h_i^{\text{enc}}) \). MLP nhỏ với param \( v, W_1, W_2 \).
- Luong dot: \( e_{t,i} = (h_t^{\text{dec}})^\top h_i^{\text{enc}} \). Đơn giản, không thêm param.
- Luong general: \( e_{t,i} = (h_t^{\text{dec}})^\top W h_i^{\text{enc}} \). Có 1 ma trận học được.
Bước 2 — normalize: softmax trên mọi position \( i \):
\[ \alpha_{t,i} = \frac{\exp(e_{t,i})}{\sum_{j=1}^{T} \exp(e_{t,j})} \]
\( \alpha_{t,i} \) là attention weight: tỷ trọng phần input thứ \( i \) đóng góp cho output step \( t \). \( \sum_i \alpha_{t,i} = 1 \).
Bước 3 — weighted sum: context vector mới cho step \( t \):
\[ c_t = \sum_{i=1}^{T} \alpha_{t,i} \cdot h_i^{\text{enc}} \]
Decoder dùng \( c_t \) cùng với \( h_t^{\text{dec}} \) để sinh output:
\[ \tilde{h}_t = \tanh(W_c [c_t \, ; \, h_t^{\text{dec}}]) \] \[ p(y_t \mid \cdot) = \mathrm{softmax}(W_o \tilde{h}_t) \]
Lưu ý quan trọng: attention không thay thế RNN, chỉ thêm một path "shortcut" từ encoder vào decoder. Gradient từ \( y_t \) có thể đi thẳng về \( h_i^{\text{enc}} \) qua \( \alpha_{t,i} \) — không phải đi qua \( T \) bước RNN. Đây là lý do attention giúp giảm vanishing gradient và xử lý chuỗi dài tốt hơn.
Từ Seq2Seq + attention tới Transformer
Nếu attention đủ tốt để "shortcut" qua mọi vị trí, vậy có cần RNN nữa không?
Vaswani và cộng sự (2017) — "Attention Is All You Need" — trả lời: không. Transformer bỏ hoàn toàn recurrence, chỉ giữ attention:
- Self-attention: mỗi token attend tới mọi token khác trong cùng chuỗi → biểu diễn token có context toàn câu. Encoder Transformer là stack các self-attention layer thay cho LSTM.
- Cross-attention: trong decoder, attend vào encoder output (giống Bahdanau attention).
- Positional encoding: vì không còn recurrence, vị trí token được encode bằng vector cộng vào embedding.
- Parallel hoá: self-attention tính song song mọi position — train trên GPU nhanh hơn LSTM hàng chục lần với cùng dung lượng.
Foundation của Transformer là chính xác cơ chế attention bài này mô tả. Hiểu Bahdanau attention là điều kiện cần để hiểu self-attention. Series 4 Module 2 sẽ deep dive: Q/K/V projection, multi-head, scaled dot-product, masked attention, positional encoding.
Câu nối: Seq2Seq cho dịch máy → attention giải bottleneck → Transformer parallel hoá → GPT/BERT/T5 dominate NLP hiện nay.
Modern seq2seq landscape
Seq2Seq vẫn là khuôn rộng được dùng hiện nay, chỉ thay backbone từ LSTM sang Transformer:
| Kiến trúc | Encoder | Decoder | Model ví dụ | Task chính |
|---|---|---|---|---|
| Encoder–decoder Transformer | Có | Có | T5, BART, mBART, MarianMT | NMT, summarization, QA, conditional generation |
| Decoder-only | — | Có (causal) | GPT, LLaMA, Mistral, Claude | LM, chat, in-context learning, mọi task qua prompt |
| Encoder-only | Có | — | BERT, RoBERTa, DeBERTa | Classification, NER, embedding, retrieval |
Encoder–decoder vẫn vượt trội cho task có cấu trúc input–output rõ ràng (dịch, tóm tắt). Decoder-only thắng ở tính tổng quát và khả năng học in-context — toàn bộ NLP có thể framework thành "tiếp tục text".
Mọi kiến trúc trên đều dùng cùng cơ chế attention đã mô tả ở mục 14.
Inference — greedy vs beam search
Decode \( y_1, \ldots, y_{T'} \) từ phân phối \( p(y_t \mid y_{ Cho NMT chuẩn, beam search với \( K = 4 \)–\( 10 \) là default. Cho LLM chat, sampling với temperature 0.7 phổ biến hơn (cần diversity, không cần optimal log-prob). Length penalty: beam search có xu hướng thiên về chuỗi ngắn (log-prob cộng dồn ít term hơn). Khắc phục bằng chia score cho \( T'^\alpha \) với \( \alpha \in [0.6, 1.0] \).
Evaluation — BLEU, ROUGE
Loss train (cross-entropy per token) không tương quan tốt với chất lượng output theo cảm nhận con người. Metric chuẩn cho seq2seq:
- BLEU (Papineni 2002) — Bilingual Evaluation Understudy. Đo n-gram precision của candidate so với reference, phạt chuỗi quá ngắn (brevity penalty). Default BLEU-4 (n=1,2,3,4). Standard cho NMT, range 0–100. WMT'14 En-Fr: SMT ~33, Sutskever 2014 LSTM ~34.8, Transformer base ~38, current SOTA > 45.
- ROUGE (Lin 2004) — Recall-Oriented Understudy for Gisting Evaluation. Đo n-gram recall, dùng chính cho summarization. ROUGE-1 (unigram), ROUGE-2 (bigram), ROUGE-L (longest common subsequence).
- METEOR (Banerjee & Lavie 2005) — kết hợp precision + recall + stemming + synonym matching. Tương quan với human judgment cao hơn BLEU trên một số task.
- BERTScore (Zhang 2020), COMET (Rei 2020) — metric neural dùng embedding so sánh ngữ nghĩa thay vì n-gram match. SOTA hiện cho NMT evaluation.
Lưu ý: BLEU/ROUGE đều dựa trên surface match, không phạt parafrase đúng nghĩa. Khi train xong nên xem cả metric tự động và spot-check output thủ công.
Code Python skeleton
Skeleton Seq2Seq LSTM với teacher forcing (chưa attention, để tách concept):
import random
import torch
import torch.nn as nn
class Encoder(nn.Module):
def __init__(self, src_vocab, emb_dim, hid_dim):
super().__init__()
self.embed = nn.Embedding(src_vocab, emb_dim, padding_idx=0)
self.lstm = nn.LSTM(emb_dim, hid_dim, batch_first=True)
def forward(self, src): # src: (B, T_src)
emb = self.embed(src) # (B, T_src, emb_dim)
_, (h, c) = self.lstm(emb) # h, c: (1, B, hid_dim)
return h, c # context = (h, c)
class Decoder(nn.Module):
def __init__(self, tgt_vocab, emb_dim, hid_dim):
super().__init__()
self.embed = nn.Embedding(tgt_vocab, emb_dim, padding_idx=0)
self.lstm = nn.LSTM(emb_dim, hid_dim, batch_first=True)
self.head = nn.Linear(hid_dim, tgt_vocab)
def forward(self, y_prev, h, c): # y_prev: (B, 1)
emb = self.embed(y_prev) # (B, 1, emb_dim)
out, (h, c) = self.lstm(emb, (h, c))
logits = self.head(out) # (B, 1, tgt_vocab)
return logits.squeeze(1), h, c # (B, tgt_vocab)
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder, sos_idx):
super().__init__()
self.encoder = encoder
self.decoder = decoder
self.sos_idx = sos_idx
def forward(self, src, tgt, teacher_forcing_ratio=0.5):
B, T_tgt = tgt.shape
h, c = self.encoder(src)
y_prev = torch.full((B, 1), self.sos_idx, device=src.device)
outputs = []
for t in range(T_tgt):
logits, h, c = self.decoder(y_prev, h, c)
outputs.append(logits)
use_teacher = random.random() < teacher_forcing_ratio
y_prev = tgt[:, t:t+1] if use_teacher else logits.argmax(-1, keepdim=True)
return torch.stack(outputs, dim=1) # (B, T_tgt, tgt_vocab)
# Training loop
encoder = Encoder(src_vocab=5000, emb_dim=128, hid_dim=256)
decoder = Decoder(tgt_vocab=5000, emb_dim=128, hid_dim=256)
model = Seq2Seq(encoder, decoder, sos_idx=1)
optim = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss(ignore_index=0) # ignore PAD
for src, tgt in train_loader: # src: (B, T_src), tgt: (B, T_tgt)
logits = model(src, tgt, teacher_forcing_ratio=0.5)
loss = loss_fn(logits.reshape(-1, 5000), tgt.reshape(-1))
optim.zero_grad(); loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optim.step()
Một số điểm thực dụng:
padding_idx=0trong Embedding → gradient của PAD bị zero, không học vector cho token vô nghĩa.ignore_index=0trong loss → PAD không đóng góp vào loss.- Clip grad norm 1.0 — bắt buộc cho LSTM seq2seq để tránh exploding.
- Để thêm attention: encoder trả về cả
outputs(mọi step) thay vì chỉ \( (h, c) \); decoder thêm 1 attention layer dùng outputs encoder.
Tutorial chuẩn: PyTorch NLP From Scratch: Translation with a Sequence to Sequence Network and Attention có code đầy đủ cho cả 2 phiên bản.
Bài tập
- Build Seq2Seq encoder–decoder LSTM cho task "reverse string": input "abcde" → output "edcba". Dataset sinh tự động (10k chuỗi ngẫu nhiên độ dài 3–10). Train với teacher forcing, đo accuracy token-level. Khi nào model bão hoà?
- Trên dataset bài 1, train 2 model: (a) teacher_forcing_ratio=1.0 cố định, (b) scheduled sampling giảm từ 1.0 xuống 0.5 trong 30 epoch. So sánh accuracy khi inference (không teacher) — model nào tổng quát hơn?
- Compute attention weight thủ công cho 1 example: \( T = 3 \), \( h_1^{\text{enc}} = [1, 0] \), \( h_2^{\text{enc}} = [0, 1] \), \( h_3^{\text{enc}} = [1, 1] \), \( h_t^{\text{dec}} = [0.5, 0.5] \). Dùng dot-product score, tính \( \alpha_{t,i} \) qua softmax và \( c_t \). Kiểm tra \( \sum_i \alpha_{t,i} = 1 \).
- Mở rộng skeleton mục 19: thêm Bahdanau attention vào decoder. Train lại trên task reverse string độ dài 20 (vượt khả năng vanilla seq2seq). So sánh accuracy với và không attention — chênh lệch trên chuỗi dài bao nhiêu?
- Implement beam search với \( K = 4 \) cho decoder. So sánh BLEU vs greedy decoding trên một subset Anh–Việt (vd dataset PhoMT hoặc IWSLT). Beam improve bao nhiêu điểm?
- Train BPE tokenizer (dùng
tokenizerscủa HuggingFace) với vocab 8000 trên một corpus tiếng Việt nhỏ. So sánh sequence length trung bình giữa word-level và BPE. Tỉ lệ OOV của word-level trên test set bao nhiêu? - Tính length penalty: cho 2 candidate "Tôi ăn" (log-prob -2.0) và "Tôi đang ăn cơm" (log-prob -4.0), với \( \alpha = 0.7 \) thì candidate nào được chọn? Vì sao length penalty quan trọng?
Gợi ý đáp án ngắn
- Vanilla seq2seq LSTM hid_dim=128 đạt ~98% token accuracy trên reverse 3–10 sau ~10 epoch. Bão hoà nhanh vì task có pattern đơn giản.
- Model (b) tổng quát hơn ~3–5% accuracy khi inference vì đã quen với prediction noise trong training.
- \( e = [0.5, 0.5, 1.0] \), softmax → \( \alpha \approx [0.27, 0.27, 0.45] \), \( c \approx 0.27 \cdot [1,0] + 0.27 \cdot [0,1] + 0.45 \cdot [1,1] = [0.72, 0.72] \). Kiểm \( 0.27 + 0.27 + 0.45 \approx 1.0 \) (rounding).
- Trên reverse string độ dài 20, vanilla accuracy giảm xuống ~60%, có attention ~95%. Bottleneck của context vector cố định lộ rõ khi sequence dài.
- Beam K=4 thường improve 0.5–1.5 BLEU so với greedy. Diminishing return ở K>10.
- Tiếng Việt BPE 8k: avg length tăng 1.3–1.5× so với word, OOV gần 0%; word-level OOV thường 3–8% tuỳ domain.
- Score = log-prob / \( T'^\alpha \). "Tôi ăn" T=2: \( -2.0 / 2^{0.7} \approx -1.23 \). "Tôi đang ăn cơm" T=4: \( -4.0 / 4^{0.7} \approx -1.52 \). Chọn "Tôi ăn". Length penalty quan trọng để beam không thiên về chuỗi ngắn quá ngắn — thường tinh chỉnh \( \alpha \) trên dev set.
Tổng kết Module 5
Module 5 (B34–B40) đi qua đầy đủ chuỗi kiến thức RNN cho dữ liệu chuỗi:
| Bài | Chủ đề | Đóng góp chính |
|---|---|---|
| B34 | Sequential data | Đặc trưng dữ liệu chuỗi, lý do MLP/CNN không phù hợp. |
| B35 | Vanilla RNN | Hidden state qua time, weight sharing, BPTT. |
| B36 | Vanishing gradient | Vì sao RNN không học được long-range dependency. |
| B37 | LSTM | Cell state + 3 cổng (forget, input, output) — giải vanishing. |
| B38 | GRU | 2 cổng (reset, update) — đơn giản hơn LSTM, perf tương đương. |
| B39 | Time series LSTM | Forecasting, windowing, normalize, multi-step prediction. |
| B40 | Seq2Seq + attention | Encoder–decoder cho variable length; foundation Transformer. |
Mạch xuyên Module: sequential data → vanilla RNN → vanishing → LSTM/GRU → time series → seq2seq + attention. Sau Module này, bạn có nền đủ để đọc paper NLP cổ điển (Sutskever 2014, Bahdanau 2014, Luong 2015) và hiểu Transformer (Vaswani 2017) — mục tiêu Series 4.
Module 6 (B41+) chuyển sang Generative foundation: Autoencoder (B41) → Variational Autoencoder → GAN. Series 4 tiếp tục với Transformer + LLM, xây trực tiếp trên Seq2Seq + attention của bài này.
Tóm tắt
- Seq2Seq (Sutskever 2014) giải many-to-many với input length \( T \) khác output length \( T' \) — task vanilla RNN không xử lý được.
- Encoder LSTM đọc input → context vector \( c = h_T^{\text{enc}} \). Decoder LSTM khởi tạo hidden = \( c \), sinh output tự hồi quy bắt đầu từ
<SOS>, dừng tại<EOS>. - Teacher forcing: train feed ground truth làm input step kế thay vì prediction → tăng tốc hội tụ; nhược điểm exposure bias, giảm nhẹ bằng scheduled sampling.
- 4 special token:
<SOS>,<EOS>,<PAD>,<UNK>. Loss cầnignore_index=PAD. - Tokenization: word (OOV), char (chuỗi dài), BPE/WordPiece/SentencePiece (cân bằng, modern standard).
- Bottleneck: nhồi toàn input vào 1 vector \( c \) → giảm chất lượng khi câu dài (Bahdanau 2014).
- Attention (Bahdanau 2014, Luong 2015): decoder mỗi step tính \( \alpha_{t,i} = \mathrm{softmax}_i(\mathrm{score}(h_t^{\text{dec}}, h_i^{\text{enc}})) \) và \( c_t = \sum_i \alpha_{t,i} h_i^{\text{enc}} \). Shortcut path qua mọi encoder hidden, giảm vanishing.
- Transformer (Vaswani 2017) bỏ recurrence, chỉ giữ attention — parallel hoá, dominate NLP. Foundation Series 4.
- Modern landscape: encoder–decoder (T5, BART), decoder-only (GPT, LLaMA), encoder-only (BERT) — đều dùng attention.
- Inference: greedy (nhanh, suboptimal), beam search K=4–10 (NMT chuẩn), sampling (LLM). Length penalty \( \alpha \in [0.6, 1.0] \) tránh beam thiên về chuỗi ngắn.
- Evaluation: BLEU (NMT), ROUGE (summarization), METEOR; BERTScore/COMET (neural metric) chính xác hơn.
- PyTorch skeleton: Encoder + Decoder LSTM riêng, Seq2Seq wrapper với teacher forcing ratio. Clip grad norm 1.0, ignore_index=PAD.
- Module 5 hoàn tất foundation NLP cổ điển. Module 6 (B41) chuyển sang Generative (Autoencoder, GAN); Series 4 deep dive Transformer + LLM.
- Sutskever, Vinyals, Le (2014) - Sequence to Sequence Learning with Neural Networks
- Cho và cộng sự (2014) - Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation
- Bahdanau, Cho, Bengio (2014) - Neural Machine Translation by Jointly Learning to Align and Translate
- Luong, Pham, Manning (2015) - Effective Approaches to Attention-based Neural Machine Translation
- Vaswani và cộng sự (2017) - Attention Is All You Need
- Sennrich, Haddow, Birch (2016) - Neural Machine Translation of Rare Words with Subword Units (BPE)
- Kudo & Richardson (2018) - SentencePiece: A simple and language independent subword tokenizer
- Bengio và cộng sự (2015) - Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks
- Vinyals và cộng sự (2015) - Show and Tell: A Neural Image Caption Generator
- Papineni và cộng sự (2002) - BLEU: a Method for Automatic Evaluation of Machine Translation
- Lin (2004) - ROUGE: A Package for Automatic Evaluation of Summaries
- Zhang và cộng sự (2020) - BERTScore: Evaluating Text Generation with BERT
- Raffel và cộng sự (2020) - Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer (T5)
- Lewis và cộng sự (2020) - BART: Denoising Sequence-to-Sequence Pre-training
- PyTorch Tutorial - NLP From Scratch: Translation with a Sequence to Sequence Network and Attention
- Dive into Deep Learning - Sequence to Sequence Learning
- Jay Alammar - Visualizing A Neural Machine Translation Model (Seq2Seq + Attention)
