Positional Encoding
Mã hóa vị trí
"Mèo bắt chuột" và "Chuột bắt mèo" — nghĩa hoàn toàn khác! Nhưng Self-attention xử lý tất cả từ song song, không biết thứ tự. Nó sẽ cho 2 câu này output giống nhau. Làm sao sửa?
Hãy tưởng tượng mỗi căn nhà trên phố có số nhà (positional encoding). Nhưng thay vì số đơn giản (1, 2, 3...), ta dùng hệ thống giống đồng hồ: kim giây quay nhanh (phân biệt nhà gần), kim giờ quay chậm (phân biệt đầu phố vs cuối phố).
Hình minh họa
Di chuột qua hàng (vị trí) hoặc cột (chiều) để highlight. Quan sát: cột trái thay đổi nhanh (tần số cao), cột phải thay đổi chậm (tần số thấp).
Bạn vừa thấy pattern: cột đầu thay đổi liên tục (phân biệt từng vị trí), cột sau thay đổi chậm (phân biệt "nhóm" vị trí). Giống hệ nhị phân!
Positional Encoding gắn "mã vạch" duy nhất cho mỗi vị trí bằng sin/cos ở nhiều tần số. Tần số cao → phân biệt vị trí gần. Tần số thấp → phân biệt vị trí xa. Giống hệ nhị phân: 0110 vs 0111 chỉ khác bit cuối!
Cộng PE vào word embedding: input = embedding + PE. Từ "mèo" ở vị trí 0 sẽ có vector khác "mèo" ở vị trí 5.
Sinusoidal PE (Transformer gốc, 2017)
Cộng sin/cos vào embedding. Cố định (không học). Ngoại suy được cho chuỗi dài hơn lúc train.
Learned PE (BERT, GPT-2)
Embedding vị trí được học (lookup table). Linh hoạt hơn nhưng giới hạn chuỗi dài (max 512 tokens cho BERT).
RoPE (LLaMA, GPT-NeoX, 2021+)
Xoay Q, K vectors theo vị trí. Mã hóa khoảng cách tương đối trực tiếp vào attention score. Ngoại suy tốt hơn.
ALiBi (BLOOM, 2022+)
Thêm bias tuyến tính vào attention score dựa trên khoảng cách. Rất đơn giản, ngoại suy xuất sắc.
Train với max 2048 tokens. Test với 4096 tokens. Sinusoidal PE vs Learned PE — cái nào xử lý tốt hơn?
Giải thích
Positional Encoding thêm thông tin vị trí vào word embedding vì self-attention không có khái niệm thứ tự (permutation equivariant). Đây là lý do Transformer luôn cần một dạng mã hoá vị trí nào đó trước khi vào các lớp attention.
Công thức sinusoidal:
Bước sóng thay đổi từ (chiều 0, tần số cao) đến (chiều cuối, tần số thấp).
(1) Giá trị luôn trong [-1, 1] → ổn định. (2) PE(pos+k) biểu diễn được bằng phép biến đổi tuyến tính của PE(pos) → model dễ học khoảng cách tương đối. (3) Mỗi vị trí có vector duy nhất. (4) Ngoại suy được cho chuỗi dài hơn lúc train.
import torch
import math
class SinusoidalPE(torch.nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(
torch.arange(0, d_model, 2).float()
* -(math.log(10000.0) / d_model)
)
pe[:, 0::2] = torch.sin(position * div_term) # Chiều chẵn
pe[:, 1::2] = torch.cos(position * div_term) # Chiều lẻ
self.register_buffer('pe', pe.unsqueeze(0)) # (1, max_len, d_model)
def forward(self, x):
# x: (batch, seq_len, d_model)
return x + self.pe[:, :x.size(1)] # Cộng PE vào embedding
# Sử dụng
pe = SinusoidalPE(d_model=512)
x = word_embedding(tokens) # (batch, seq_len, 512)
x = pe(x) # embedding + positional encoding- Self-attention không biết thứ tự → cần PE để phân biệt 'mèo bắt chuột' vs 'chuột bắt mèo'.
- Sinusoidal PE dùng sin/cos ở nhiều tần số: cao phân biệt gần, thấp phân biệt xa — giống hệ nhị phân.
- input = word_embedding + positional_encoding. PE được cộng trực tiếp vào embedding.
- Biến thể: Learned PE (BERT), RoPE (LLaMA — xoay Q,K theo vị trí), ALiBi (bias khoảng cách).
- RoPE phổ biến nhất hiện nay: mã hóa khoảng cách tương đối, ngoại suy tốt cho context dài.
Kiểm tra hiểu biết
Tại sao Transformer cần positional encoding? Self-attention không tự biết thứ tự sao?