🍐 nguyen
Overview

Một chút về Transformers (Phần 1): Attention, Attention, Attention

June 19, 2025
12 min read
index

Đây là một chuỗi bài viết của mình dùng để (giới thiệu/nói về) Transformers (một trong những “phát minh” đỉnh nhất thời điểm hiện tại) đồng thời mình cũng muốn giới thiệu về Mechanistic Interpretability [] (một trong những mảng nghiên cứu làm mình mê nhất đồng thời cũng là mảng mà khoá luận của mình đang hướng tới). Đa số kiến thức mà mình nói các bạn đều có thể tìm được ở đây [] (các bạn nên đọc, cuốn sách này 10/10 no cap 😩)

1. Từ và nghĩa

(1) Làm sao để ta biết 2 từ, hay nhiều từ có nghĩa tương tự nhau (similar) ? (2) Nhưng mà tại sao ta lại cần biết nếu 2 từ tương tự nhau về nghĩa ?

Trả lời câu hỏi (2), một trong những ứng dụng chính của các mô hình ngôn ngữ là dịch máy, giả sử ta có câu “Nhà này to” và “Nhà này bự”, khi đưa qua tiếng anh, ta có thể dịch thành “This house is huge” và “This house is big”. Để có thể dịch được, mô hình phải biết được từ “House” và “Nhà” có nghĩa tương tự nhau, cũng như vậy, “To” và “Big” có nghĩa tương tự nhau, “Bự” và “Huge” có nghĩa tương tự nhau.

Quay lại câu hỏi (1), ý tưởng chính sẽ được dựa vào Distributional Hypothesis 1, giả thuyết này được đưa ra như sau:

Từ mà xuất hiện trong ngữ cảnh (context) tương tự nhau thường sẽ có nghĩa tương tự nhau.

Lại thêm câu hỏi, (3) ngữ cảnh hay context của từ là gì ? (4) Và thế nào là context tương tự nhau ?

Đầu tiên, ở (3), thì các context (ngữ cảnh) đơn giản là những từ xung quanh từ mà ta đang quan tâm (ta gọi từ ấy là target word), các từ nằm trong ngữ cảnh được gọi là context word của target word. Ví dụ ta có thể xem các context word là NN từ trước và NN từ sau, hoặc toàn bộ từ trong câu cùng với target word, hoặc toàn bộ từ trong cả một văn bản cùng với target word, bla bla.

Tiếp theo, để xét “context” tương tự nhau ở (4), ta cần một cách nào đó để so sánh giữa các context với nhau, đương nhiên là mô hình không thể như hu mần được, ta cần các con số. Dễ hiểu hơn, ta xem xét một ví dụ giữa hai từ tobự, trong một văn bản nào đó, ta có các context word của 2 từ là [nhà,cây,học,ăn].

Thực hiện đếm số lần các context word xuất hiện chung với target word, ta sẽ có một vector như sau:

vto=[1000,900,100,50]vbự=[970,920,130,30]\begin{aligned} v_{\text{to}} &= [1000, 900, 100, 50] \\ v_{\text{bự}} &= [970, 920, 130, 30] \end{aligned}

Có thể thấy giữa hai vector vtov_{\text{to}}vbựv_{\text{bự}} rất giống nhau (ta có thể dùng cosine similarity để check lại cái này), vì vậy dựa theo distributional hypothesis, ta nói rằng tobự có nghĩa tương tự nhau. Ta gọi các vector này là distributional vector. Ở các mô hình phức tạp hơn, các vector này không chỉ đơn giản là đếm mà là các high-dimensional (nhiều chiều) vector được tính thông qua một cơ chế “phức tạp” nào đó, ta gọi các vector này là word embedding. Vì vậy những từ có nghĩa tương tự nhau nếu có các vector embedding tương tự nhau.

2. Một chút về RNN

Một Recurrent Neural Networks (RNN) là bất kì mạng neural nào mà chứa vòng (tức là node output có thể là input của một node trước nó). Ta sẽ xét đến một class RNN gọi là Elman Networks [] hay simple recurrent neural network.

Elman Network
Kiến trúc của mạng Elman (nguồn: https://pabloinsente.github.io/the-recurrent-net)

Có thể thấy ở hình trên, kiến trúc của mạng Elman đơn giản là rất nhiều mạng linear 2 copy với nhau và được stack theo chiều ngang, các mạng linear này sẽ được kết nối thông qua một vector gọi là cc, ví dụ giữa hai mạng ở bước thời gian 3 t1t-1tt thì được nối bởi vector ct1c_{t-1}. Hơn nữa, ta thấy ctc_t là các clone (hay copy) của vector hidden state hth_t trong mạng linear tại bước thời gian tt, vì vậy nếu bỏ đi bước trung gian ctc_t mà sử dụng thẳng hth_t ta sẽ có mạng RNN (như hình dưới).

RNN
Kiến trúc RNN (nguồn: https://en.wikipedia.org/wiki/Recurrent_neural_network)

Khác với các mạng trước đó (MLP, Linear Regression, etc.) thì RNN tận dụng được representation (biểu diễn) của các từ trước đó (hay nói cách khác chính là context của target word của tại tt hiện tại). Có thể thấy, RNN đang tận dụng distributional hypothesis một cách “gián tiếp”. Nói cách khác để hiểu nghĩa một từ ở time step tt, RNN phải có được representation (hay distributional vector) của tt context word trước đó.

Các representation của context word được encode vào một vector hidden state, ví dụ ht1\mathbf{h}_{t-1} cho t1t-1 từ trước đó. Ta xem vector hidden state này như một memory. Memory này sẽ chứa toàn bộ thông tin trước đó và đóng góp vào thông tin ở bước thời gian hiện tại. Việc các hidden layer tích lũy thông tin từ các hidden layer trước dẫn đến ta có memory rất dài (đến cả đầu sequence).

Ví dụ như ta có chuỗi “bạn là con chó” thì tại bước thời gian t=2t=2, ta sẽ “đứng” ở từ “con” (bắt đầu từ t=0t=0) và hidden state trước đó là ht1=h1\mathbf{h}_{t-1} = \mathbf{h}_{1} sẽ chứa thông tin của toàn bộ chuỗi trước đó là “bạn là”. Nhưng thông tin sẽ được “chứa” như nào, hay nói cách khác, RNN nhét toàn bộ thông tin của chuỗi trước đó vào một vector hidden state như nào ?

Tại một bước thời gian tt, RNN sẽ thực hiện hai việc: (1) Tính toán hidden state hiện tại bằng thông tin của hidden state trước đó và input (2) Dùng hidden state hiện tại để đưa ra output của bước thời gian hiện tại. Công thức ở như phía dưới:

ht=g(Vht1+Uxt)yt=f(Wht)\begin{aligned} \mathbf{h}_{t} &= g(\mathbf{V}\mathbf{h}_{t-1} + \mathbf{U}\mathbf{x}_{t}) \\ \mathbf{y}_{t} &= f(\mathbf{W}h_{t}) \end{aligned}

Trong đó ggff là các hàm kích hoạt (thường được sử dụng là hàm tanh), WW, VV, UU là các trọng số của mạng linear tại từng bước thời gian tt. Riêng input của mạng Linear là hai vector gồm hidden state trước đó và input hiện tại. Ngoài ra các ma trận trọng số WW, VV, UU sẽ giống nhau ở toàn bộ mạng Linear ở từng bước thời gian tt, ta còn gọi là weight tying.

Tuy nhiên, việc phải encode quá nhiều thông tin vào một vector dẫn đến các từ ở các bước thời gian đầu sẽ dần bị thông tin của các từ ở bước thời gian sau “ghi đè” lên và dẫn đến mất mát thông tin. Hiện tượng này còn có tên là Vanishing Gradient, các bạn có thể tự tìm hiểu thêm.

3. Dịch máy và mô hình Encoder-Decoder

Có thể thấy, kiến trúc RNN mà ta tìm hiểu trước đó sẽ có số lượng output bằng với số lượng token input (ở đây ta xem một token = một từ), ngoài ra input tại bước thời gian tt sẽ tương ứng với output tại bước thời gian tt. Hoặc đôi khi ta có thể dùng output cuối cùng (ở bước thời gian cuối), ví dụ ở tác vụ text classification (phân loại văn bản), ta có thể dùng output ở hidden state cuối cùng và dựa vào output ấy để dự đoán nhãn của văn bản.

Nhưng với bài toán như Dịch máy (Machine Translation) thì sao?

  1. Vấn đề độ dài: Câu gốc và câu dịch hiếm khi có cùng độ dài.
    • “This house is big.” (4 từ) → “Nhà này to.” (3 từ)
  2. Vấn đề trật tự từ: Trật tự từ trong hai ngôn ngữ có thể hoàn toàn khác nhau do đó một input ở bước thời gian tt sẽ không tương ứng với output ở bước thời gian tt.
    • “I love red cats.” → “Tôi yêu những con mèo màu đỏ.” (Từ “red” được dịch thành “màu đỏ” và nằm ở cuối câu).

Do đó ta cần mô hình output không phụ thuộc vào độ dài hay bước thời gian ở input. Vì vậy ta dùng đến mô hình Encoder-Decoder hay còn được gọi là mô hình Sequence-to-Sequence (seq2seq).

Encoder-Decoder Architecture
Kiến trúc Encoder-Decoder. Ở bài viết, tác giả dùng GRU, cũng là một mạng tương tự RNN, ta có thể thay thế thành RNN và vẫn okay. (Nguồn: https://jeddy92.github.io/ts_seq2seq_intro/)

Kiến trúc này bao gồm hai mạng RNN (thật ra có thể sử dụng các version khác của RNN như LSTM, GRU, …):

  1. Encoder: Là một mạng RNN mà đọc toàn bộ câu input (ví dụ: “This house is big”) và nén toàn bộ ý nghĩa của nó vào một vector duy nhất ở bước cuối cùng.

  2. Context Vector: Là vector ở bước cuối cùng của Encoder, vector này thường được gọi là context vector (vector ngữ cảnh), kí hiệu là c\mathbf{c}. Vector c\mathbf{c} này chính là “bản tóm tắt” của cả câu input. Ngoài ra vector c\mathbf{c} “tóm tắt” câu input dựa vào hidden state ở từng bước thời gian, do đó ta có thể nói c\mathbf{c} là một hàm của các hidden state của Encoder, tức là c=f(h1,...,hN)\mathbf{c} = f(\mathbf{h}_1, ..., \mathbf{h}_N).

  3. Decoder: Là một mạng RNN hoàn toàn khác so với Encoder, nhận context vector từ Encoder làm điểm khởi đầu (làm hidden state ban đầu h0h_0). Nhiệm vụ của nó là tạo ra câu output (“Nhà”, “này”, “to”) từng từ một, sử dụng “bản tóm tắt” đó làm kim chỉ nam.

Câu hỏi tiếp theo, ta chọn context vector c\mathbf{c} như nào cho đủ tốt bởi vì chất lượng dịch hay performance của Decoder phụ thuộc hoàn toàn vào vector c\mathbf{c} này.

  • Đầu tiên ta có thể dùng hidden state cuối làm c\mathbf{c} luôn, tức là c=f(h1,...,hN)=hN\mathbf{c} = f(\mathbf{h}_1, ..., \mathbf{h}_N) =\mathbf{h}_N. Điều này sẽ dẫn đến hiện tượng gọi là bottleneck, tức là hidden state cuối cùng bắt buộc phải encode được toàn bộ thông tin từ input text (hay từ encoder stage), ngoài ra thông tin duy nhất mà decoder biết về input text chính là hidden state cuối cùng.
  • Ta có thể lấy trung bình các hidden state, tức là:
c=f(h1,...hN)=1Ni=1Nhi\mathbf{c} = f(\mathbf{h}_1, ... \mathbf{h}_N) = \dfrac{1}{N}\sum_{i=1}^N \mathbf{h}_i
  • Nhưng ở cách này, ta lại đang giả định rằng mỗi hidden state đều có tính quan trọng như nhau thông qua việc mỗi hidden state đều có trọng số đóng góp vào thông tin cuối cùng là 1/N1/N.
  • Vì vậy ta phải có một cách nào đó để có thể đánh giá được độ quan trọng (hay khả năng đóng góp) của từng hidden state vào input của Decoder. Ta sẽ dùng đến cơ chế Attention. Và thay vì chỉ duy nhất một context vector cuối cùng c\mathbf{c}, ta sẽ có các context vector ci\mathbf{c}_i cho từng bước thời gian của Decoder và các context vector ấy sẽ biết cách để đánh giá độ quan trọng của các hidden state trên Decoder (hay nói cách khác, chú ý vào những từ input phù hợp nhất cho từ output).

Thay vì bắt Decoder phải dựa vào một bản tóm tắt nghèo nàn duy nhất, tại sao chúng ta không cho phép nó ‘nhìn lại’ toàn bộ câu input ở mỗi bước tạo từ. Tại sao không để nó tự chú ý (pay attention) vào những từ input phù hợp nhất cho từ output mà nó sắp tạo ra.

4. Attention, Attention, Attention

Ta sẽ có context vector ci\mathbf{c}_i (là context vector tại hidden state ii hay bước thời gian ii của Decoder), cơ chế này sẽ weight (đánh giá độ quan trọng/khả năng đóng góp) tất cả hidden state của Encoder cho context vector ci\mathbf{c}_i (hay nói cách khác Decoder sẽ chú ý bao nhiêu đến từng hidden state của Encoder):

ci=Attention(hi1d,h1:Ne)\mathbf{c}_{i} = \text{Attention}(\mathbf{h}^d_{i-1}, \mathbf{h}^e_{1:N})

Trong đó hi1d\mathbf{h}^d_{i-1} là hidden state của Decoder tại bước thời gian i1i-1 còn h1:Ne\mathbf{h}^e_{1:N} là toàn bộ hidden state của Encoder (gồm NN bước thời gian).

Đầu tiên, ta xác định độ liên quan (hay nói cách khác độ tương đồng) giữa hidden state trước đó của decoder hi1d\mathbf{h}^d_{i-1} với từng hidden state của encoder, gọi là hje\mathbf{h}^e_{j}, thông qua hàm score:

score(hi1d,hje)\text{score}(\mathbf{h}^d_{i-1}, \mathbf{h}^e_{j})

Dựa theo paper của Luong et al [], hàm score được tính bằng dot product (tính vô hướng) hay còn gọi là dot-product attention:

score(hi1d,hje)=hi1dhje\text{score}(\mathbf{h}^d_{i-1}, \mathbf{h}^e_{j}) = \mathbf{h}^d_{i-1} \cdot \mathbf{h}^e_{j}

Còn dựa theo bài báo của Bahdanau et al [], hàm score sử dụng một feedforward layer, hay còn gọi là additive attention:

score(hi1d,hje)=vaTtanh(Wahi1d+Uahje)\text{score}(\mathbf{h}^d_{i-1}, \mathbf{h}^e_{j}) = \mathbf{v}_{a}^T \tanh(\mathbf{W}_{a}\mathbf{h}^d_{i-1} + \mathbf{U}_{a}\mathbf{h}^e_{j})

trong đó Wa,Ua\mathbf{W}_a, \mathbf{U}_a là các ma trận tham số và va\mathbf{v}_a là vector tham số.

Sau khi đã có score của từng hidden state của encoder với hidden state hiện tại của decoder, ta chuẩn hoá (normalized) score về khoảng (0,1)(0, 1) thông qua hàm softmax (mục đích này là đưa vector score về thành một phân phối xác suất, lúc này mỗi hidden state của Encoder sẽ có giá trị từ 00 đến 11, giá trị càng gần 11 tức là hidden state ấy càng quan trọng đối với hidden state hiện tại của Decoder):

αij=exp(score(hi1d,hje))k=1Nexp(score(hi1d,hke))\alpha_{ij} = \frac{\exp(\text{score}(\mathbf{h}^d_{i-1}, \mathbf{h}^e_{j}))}{\sum_{k=1}^N \exp(\text{score}(\mathbf{h}^d_{i-1}, \mathbf{h}^e_{k}))}

Cuối cùng, context vector ci\mathbf{c}_i của bước thời gian hiện tại ii chính là weighted average sum (trung bình cộng có trọng số) của các hidden state với trọng số là các softmax score được tính trước đó:

ci=j=1Nαijhje\mathbf{c}_{i} = \sum_{j=1}^N \alpha_{ij} \mathbf{h}^e_{j}

References

  1. Speech and Language Processing: An Introduction to Natural Language Processing, Computational Linguistics, and Speech Recognition with Language Models, Daniel Jurafsky and James H. Martin
    2025
    https://web.stanford.edu/~jurafsky/slp3/
  2. Mechanistic Interpretability for AI Safety -- A Review, Leonard Bereska and Efstratios Gavves
    2024
    https://arxiv.org/abs/2404.14082
  3. Distributed representations, simple recurrent networks, and grammatical structure, Elman, Jeffrey L.
    Machine Learning, 1991
    https://doi.org/10.1007/BF00114844
  4. Effective Approaches to Attention-based Neural Machine Translation, Minh-Thang Luong and Hieu Pham and Christopher D. Manning
    2015
    https://arxiv.org/abs/1508.04025
  5. Neural Machine Translation by Jointly Learning to Align and Translate, Dzmitry Bahdanau and Kyunghyun Cho and Yoshua Bengio
    2016
    https://arxiv.org/abs/1409.0473
  6. Attention Is All You Need, Ashish Vaswani and Noam Shazeer and Niki Parmar and Jakob Uszkoreit and Llion Jones and Aidan N. Gomez and Lukasz Kaiser and Illia Polosukhin
    2023
    https://arxiv.org/abs/1706.03762
  7. Advancing Transformer Architecture in Long-Context Large Language Models: A Comprehensive Survey, Yunpeng Huang and Jingwei Xu and Junyu Lai and Zixu Jiang and Taolue Chen and Zenan Li and Yuan Yao and Xiaoxing Ma and Lijuan Yang and Hao Chen and Shupeng Li and Penghao Zhao
    2024
    https://arxiv.org/abs/2311.12351
  8. A Mathematical Framework for Transformer Circuits, Elhage, Nelson and Nanda, Neel and Olsson, Catherine and Henighan, Tom and Joseph, Nicholas and Mann, Ben and Askell, Amanda and Bai, Yuntao and Chen, Anna and Conerly, Tom and DasSarma, Nova and Drain, Dawn and Ganguli, Deep and Hatfield-Dodds, Zac and Hernandez, Danny and Jones, Andy and Kernion, Jackson and Lovitt, Liane and Ndousse, Kamal and Amodei, Dario and Brown, Tom and Clark, Jack and Kaplan, Jared and McCandlish, Sam and Olah, Chris
    Transformer Circuits Thread, 2021
    https://transformer-circuits.pub/2021/framework
  9. Zoom In: An Introduction to Circuits, Olah, Chris and Cammarata, Nick and Schubert, Ludwig and Goh, Gabriel and Petrov, Michael and Carter, Shan
    Distill, 2020
    https://distill.pub/2020/circuits/zoom-in

Footnotes

  1. https://en.wikipedia.org/wiki/Distributional_semantics#

  2. Một mạng neural network Linear hay còn gọi là MLP sẽ gồm một input layer, một hoặc nhiều hidden layer và output layer. Ở RNN, ta chỉ có một hidden layer duy nhất, và được tính như sau: y=g(Wx+b)y = g(Wx + b) với WW là trọng số của hidden layer, bb là bias và gg là một hàm kích hoạt nào đó.

  3. Bước thời gian là đang nói đến, hiện tại, ta đang ở từ thứ mấy trong câu. Ví dụ câu “tôi đẹp trai phết” thì ở bước thời gian thứ 2 hay t=2t=2 (bước thời gian khởi đầu là 00), ta đang xem xét từ “trai” và ta cũng xem từ đó là target word ở bước thời gian hiện tại.