JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

注意力的核心:讓圖塊彼此對話

拆開每個 Transformer 的引擎——自注意力——看清每個 token 如何決定要聽誰說話。

直覺:每個圖塊都在發問

在上一篇指南中,我們把一張影像切成一連串的圖塊 token——每個 token 是一個向量,概括了畫面中的一小塊方格。但單一圖塊本身幾乎是「盲」的:一塊棕色毛茸茸的紋理,可能屬於一隻狗、一匹馬,或一張地毯,在它與周圍的圖塊互通有無之前,根本無從判斷。自注意力的全部用意,正是讓每個 token 做到這件事:透過從其他 token 拉取資訊來豐富自己。

想像一場喧鬧的雞尾酒派對。你站在人群中,心裡默默問一個問題:「這裡有誰跟我有關?」你不會平等地聽每個人說話——你會把注意力調到那少數幾個對你重要的人身上,把其餘的人調成背景雜音。自注意力對一個 token 做的也是同一件事:每個 token 發出一個問題,其他每個 token 都回應自己有多相關,然後這個 token 就會多聽相關的、少聽不相關的。

具體來說,每個 token 的輸出是所有 token 的加權平均。權重是介於 0 與 1 之間、且加起來等於 1 的數字,用來編碼「有多相關」。權重接近 1 表示「仔細聽這個 token」;接近 0 表示「基本上忽略它」。於是一塊毛茸茸紋理的圖塊,最後可能會融入附近一塊耳朵圖塊與一塊鼻吻圖塊的資訊,因而開始「意識到」自己屬於一隻狗。token 並不會移動或改變位置——它只是把自己的內容改寫成「以相關度加權」的、所有其他 token 內容的混合。

Query、Key、Value:每個 token 的三種角色

要把雞尾酒派對的直覺變成可運作的機制,每個 token 會同時扮演三種角色。想像你在圖書館查資料。你帶著一個 Query(查詢)走進去——也就是你要找的東西的描述(「關於邊緣與角點的書」)。書架上的每一本書都帶著一個 Key(鍵)——一張簡短的標籤或索引卡,說明它的主題,好讓它能跟查詢做比對。而每一本書也都有一個 Value(值)——它真正的內容,是你一旦判定它相關後會帶回家的東西。這就是查詢—鍵—值的拆解,也是自注意力內部的變速齒輪。

每個 token 的嵌入向量會被三個各自獨立、可學習的矩陣,分別投影成 Query、Key 與 Value 三種角色。

一個 token 的嵌入向量分別輸入三個標示為 W_Q、W_K、W_V 的方塊,產生三個分別標示為 Query、Key、Value 的輸出向量。

Q = X\,W_Q,\qquad K = X\,W_K,\qquad V = X\,W_V

三個線性投影把 token 的嵌入矩陣 X 變成查詢、鍵與值。

我們把每個符號拆開來看。X 是 token 嵌入矩陣,形狀為 N×D:共有 N 個 token(列),每個 token 是一個 D 維向量(X 的一列)。W_Q、W_K、W_V 是三個各自獨立、可學習的投影矩陣,每個形狀為 D×d_k。「可學習」表示它們的數值由訓練調整——一開始是隨機的,再由梯度下降把它們塑形。用 X(N×D)乘上 W_Q(D×d_k)得到形狀為 N×d_k 的 Q:Q 的每一列就是某個 token 的查詢向量。同理 K 與 V 也都是 N×d_k,每個 token 一個鍵、一個值。具體而言,在標準的 ViT-Base 中你可能有 N = 197 個 token(196 塊圖塊 + 1 個類別 token),D = 768,d_k = 64;那麼單一 token 的嵌入(一個 1×768 的列)乘上 W_Q(768×64),就產生這個 token 的 1×64 查詢。這裡唯一被學習的就是這三個矩陣——它們決定了一塊圖塊如何措辭它的問題如何宣傳自己、以及願意提供什麼

縮放點積注意力:核心公式

現在來組裝這台引擎。第一步是衡量相關度。我們要怎麼為「一個查詢跟一個鍵有多匹配」打分?用點積。兩個向量的點積,在它們指向相同方向時又大又正,在它們互相垂直時接近零,在它們指向相反方向時為負——想像兩支箭:方向一致的箭給出大數字,交叉的箭給出小數字。所以把 token i 的查詢和 token j 的鍵做點積,就得到一個數字:「j 對 i 有多相關?」一次對所有 (i, j) 配對都這樣做,正好就是矩陣乘積 Q·Kᵀ——一個 N×N 的、裝著所有兩兩分數的方格。這就是縮放點積注意力的核心,建立在我們剛剛造出的查詢—鍵—值向量之上。

\mathrm{Attention}(Q,K,V) = \mathrm{softmax}\!\left(\frac{Q K^{\top}}{\sqrt{d_k}}\right) V

縮放點積注意力:打分、縮放、正規化,再混合。

由內往外讀這條公式。QKᵀ 是 N×N 的所有兩兩相似度分數矩陣;它在第 i 列、第 j 行的元素,就是 token i 的查詢和 token j 的鍵的點積——「j 對 i 的原始相關度」。接著我們把每個分數都除以 √d_k,一個固定的數(鍵維度的平方根);這就是把數字維持在合理範圍的縮放——為什麼這麼做,下一節專門討論。然後,softmax 會逐列獨立地套用:它把分數取指數再除以總和,把一列任意實數變成一列正的權重、且剛好加總為 1——這些就是注意力權重,也就是「我對每個 token 各聽多少」的機率。最後我們把那個 N×N 的權重矩陣乘上 V(N×d_k):每一列輸出就成為所有值向量的加權平均,權重就是該 token 的注意力。結果形狀為 N×d_k——每個 token 一個被豐富過的向量。其中 softmax 那一步本身就是:

\mathrm{softmax}(s)_j = \frac{e^{\,s_j}}{\sum_{m} e^{\,s_m}}

softmax 把一列分數 s 變成加總為 1 的正權重。

我們拿真實數字,為單一 token 跑一遍,設 d_k = 2、三個 token。假設 token 1 的查詢是 q₁ = [1, 0],三個鍵是 k₁ = [1, 0]、k₂ = [0, 1]、k₃ = [1, 1]。原始分數 q₁·kⱼ 為 1、0、1。除以 √2 ≈ 1.414 得到 0.707、0、0.707。取指數:e^0.707 ≈ 2.03、e^0 = 1、e^0.707 ≈ 2.03,總和 ≈ 5.06;所以注意力權重是 0.40、0.20、0.40(確實加總為 1)。再取值 v₁ = [1, 0]、v₂ = [0, 1]、v₃ = [1, 1]。token 1 的輸出就是 0.40·[1,0] + 0.20·[0,1] + 0.40·[1,1] = [0.80, 0.60]。注意發生了什麼:token 1 對 token 1 與 3 最為關注(它們的鍵和 q₁ 對齊),對 token 2 關注很少,而它的新向量正是由那些值主導的混合。這就是一次完整的注意力運算,用手算出來的。

import numpy as np

def softmax(x, axis=-1):
    # subtract the row max for numerical stability (no change to result)
    x = x - x.max(axis=axis, keepdims=True)
    e = np.exp(x)
    return e / e.sum(axis=axis, keepdims=True)

def self_attention(X, W_Q, W_K, W_V):
    # X: (N, D) token embeddings; W_*: (D, d_k) learned projections
    Q = X @ W_Q                         # (N, d_k) what each token is looking for
    K = X @ W_K                         # (N, d_k) how each token advertises itself
    V = X @ W_V                         # (N, d_k) what each token will hand over
    d_k = Q.shape[-1]
    scores  = (Q @ K.T) / np.sqrt(d_k)  # (N, N) scaled pairwise similarity
    weights = softmax(scores, axis=-1)  # (N, N) each row sums to 1
    out     = weights @ V               # (N, d_k) relevance-weighted blend
    return out, weights
整套機制用十幾行寫完——投影、縮放後的分數、softmax、加權混合。

為什麼要除以 √d_k?

√d_k 看起來像個小細節,但少了它會悄悄毀掉訓練。問題如下。點積是 d_k 個乘積項的和。你加總的維度越多,這個和在數量級上往往就越大。當餵進 softmax 的分數變得非常大時,softmax 會飽和:它把幾乎所有權重都推到那個最大的分數上,其餘的都壓到接近零——分佈變得「尖銳」。尖銳的 softmax 有兩個壞處:token 實際上只聽一個鄰居(丟掉了讓注意力強大的那種柔和、混合式平均),更糟的是,穿過飽和 softmax 的梯度幾乎為零,於是網路幾乎學不動。把分數縮小,能讓 softmax 留在溫和、有反應的區域。

用數字看看。假設三個原始、表現良好的分數是 [2, 1, 0]。softmax 給出大約 [0.67, 0.24, 0.09]——一個健康的分佈,每個 token 都還聽得到。現在想像維度很大,於是同樣型態的分數放大了十倍:[20, 10, 0]。它的 softmax 大約是 [0.99995, 0.000045, 0.000000002]——權重幾乎全部塌縮到第一個 token,另外兩個消失了。混合退化成了硬性挑選。把 [20, 10, 0] 除以 √d_k(這裡 √100 = 10)就讓我們回到 [2, 1, 0],恢復了健康的分佈。相關度的排序一樣,但變成了一個可用、可訓練的分佈。

\mathrm{Var}(q\cdot k) = \sum_{i=1}^{d_k}\mathrm{Var}(q_i k_i) \approx d_k \;\;\Longrightarrow\;\; \mathrm{Var}\!\left(\frac{q\cdot k}{\sqrt{d_k}}\right) \approx 1

若查詢與鍵的各分量變異數為 1,原始點積的變異數約為 d_k。

把推理用白話講一遍。先做一個乾淨的假設:查詢 q 與鍵 k 的各分量是彼此獨立、平均數為 0、變異數為 1 的隨機數。點積 q·k = Σ qᵢkᵢ 是 d_k 個項的和。每一項 qᵢkᵢ 是兩個獨立、平均 0、變異數 1 的數的乘積,它本身的變異數為 1。當你把獨立的量相加,它們的變異數也會相加,所以總變異數約為 d_k——它隨維度數線性成長。變異數是「典型大小」的平方,所以 q·k 的典型數量級約為 √d_k。這就是元兇:d_k 越大 ⇒ 分數越大。要抵消它,我們就剛好除以 √d_k;把一個量除以 c,它的變異數會除以 c²,所以變異數 d_k 變成 (√d_k)²·1/d_k = 1。不論維度多大,分數都被拉回到約為 1 的典型大小。舉個實數:當 d_k = 64,原始點積大約落在 √64 = 8 附近,除以 8 就把它們帶回 ≈ 1。

看見注意力:解讀注意力圖

回想 softmax 那一步得到的 N×N 權重矩陣——每一列就是某個 token 的「我對其他每個 token 各聽多少」的一組權重。因為這些權重不過是介於 0 與 1 之間的數字,我們可以把它們畫出來。挑一個 token(一列),把它的 N 個權重重新排回圖塊位置的網格上,再依權重為每塊圖塊上色:注意力高的地方亮、低的地方暗。那張圖就是注意力圖——它把一個 token 在自注意力下到底在關注什麼,實實在在地視覺化出來。

類別 token 的注意力圖:把權重疊在影像上,往往會在模型正在分類的物體上亮起來。

一張動物照片上疊著半透明的熱力圖;熱力圖較亮的區域集中在動物身上,而非背景。

最能透露訊息的,是去看類別 token 的那一列——也就是上一篇提到的那個特殊 token,它最終的向量會被送進分類器。當你把類別 token 的注意力視覺化時,較亮的區域往往落在真正的物體上:狗的身體、襯著天空的鳥、桌上的杯子。這就是前面所有數學讓人滿意的回報——它暗示網路自己學會了從前景物體蒐集證據、並把背景的權重壓低,而我們也因此得到一扇窗,看見它在做決定時「望向」哪裡。

你現在已經掌握了 Transformer 完整的核心:token 形成查詢、鍵、值;查詢與鍵互相比較(並以 √d_k 縮放)後經 softmax 化為權重;值被混合成更豐富的輸出;而那些權重又能被讀回成一張「每個 token 在關注什麼」的圖。在下一篇指南中,我們會把這套機制複製多份、包成多頭注意力——讓模型一次問好幾個不同的問題——再把結果與位置和深度疊起來,組成完整的編碼器。