02 序列模型 & 多模態
0 / 8

「世界上很多資料,不是看一眼就懂的,而是要照順序讀下去才有意義。」

上一堂的 CNN 在做什麼?

上一堂我們講的 CNN,本質上是在處理「空間」的問題 — 一張圖片每個 pixel 的位置是固定的,CNN 透過 kernel 在空間上滑來滑去,找出 local pattern,再一層一層組合成更大的概念。

但「順序」很重要

世界上很多資料不是「空間性」的,而是「時間性 / 順序性」的:

EXAMPLES 語句 — 字的順序決定意義 今天 開心 → 正面 語音 — 每個時間點的音訊都要連起來 影片 — 每一幀跟前後幀關聯 frame 1 frame 2 ... 時序資料 — 今天跟昨天有關 這些資料的共同點 — 「順序很重要」、「長度不固定」、「過去會影響現在」
圖 0.1 — 序列性資料的四個典型例子(每個元素皆可 hover)

CNN/MLP 處理不來的兩個問題

輸入長度不固定
MLP 要求輸入是固定維度,但句子有長有短 — 一句 5 個字、另一句 50 個字,要怎麼塞進同一個模型?
沒辦法記住前面的資訊
每個輸入都被獨立處理,模型不知道「之前發生了什麼」。但語言、音訊、影片,缺了上下文就毫無意義。

所以我們需要一種新架構:它要能記住過去,然後用這個記憶幫助理解現在。
這就是 RNN 出場的時刻。

本堂課的演進地圖

ROADMAP RNN 1986 LSTM 1997 ELMo biLSTM 最後光輝 Transformer 2017 BERT Encoder-only · 理解 GPT Decoder-only · 生成 多模態 CLIP/BLIP/LLaVA 從「記憶」到「注意力」,再到「跨模態對齊」 — 整個故事的脈絡
圖 0.2 — 演進地圖(每個方塊都可 hover)

頂部 tabs 直接跳到任何章節 ;底部箭頭 順序往下走,左右鍵盤 也能切換
所有 虛線標記的詞 跟圖中元素都可以 hover,會出現詳細解釋
• 互動 demo 在 RNN/LSTM/Self-Attention 三頁都有,可以親自玩

「讓網路擁有記憶 — 這個想法簡單到令人感動。」

核心想法

普通的神經網路是「輸入 → 隱藏層 → 輸出」,資訊只往前流。RNN 做了一件很簡單的事:把上一個時間點的隱藏層輸出,當作這一個時間點的額外輸入。 這條多出來的線,就是所謂的 recurrent connection

把 RNN 攤開來看

UNFOLD 折疊版 RNN h_t → h_{t+1} x_t y_t 攤開 → 攤開後(在時間軸上) cell x₁ "我" y₁ cell x₂ "今天" y₂ cell x₃ "很" y₃ cell x₄ "開心" y₄
圖 1.1 — RNN 折疊 vs 攤開(注意紅色小圓點:hidden state 在時間軸上的傳遞)

數學長這樣

① 更新 hidden state:

h_t = tanh( W_xh · x_t + W_hh · h_{t-1} + b_h )

② 產生輸出:

y_t = W_hy · h_t + b_y

兩個重要觀念

RNN 處理一句 10 個字的句子,不是用 10 組不同的權重,而是同一組 W_xh, W_hh, W_hy 用 10 次。 這跟 CNN 的 kernel 共享是同樣的精神 — 不管字出現在哪個位置,處理它的方式都應該一樣。

→ 結論:RNN 可以處理任意長度的輸入,因為參數量跟序列長度無關。

訓練 RNN 的方法叫做 「時間上的反向傳播」。 把 RNN 在時間軸攤開(如上圖右),就會變成一個很深的前饋網路 — 然後用一般的反向傳播算梯度就好。

RNN 的致命傷 — 梯度消失/爆炸

當序列很長,反向傳播要經過很多層 W_hh 的乘法。
想像一下:如果 W_hh 的某個 eigenvalue 是 0.9:

0.9¹⁰ ≈ 0.35  0.9³⁰ ≈ 0.04  0.9⁵⁰ ≈ 0.005 ← 梯度消失
1.1¹⁰ ≈ 2.6  1.1³⁰ ≈ 17.4  1.1⁵⁰ ≈ 117 ← 梯度爆炸

結論:RNN 在實務上很難記住超過 10~20 步以前的資訊。下面這個小 demo 給你直接看數字。

梯度消失/爆炸 互動模擬器

拉動 slider 改變權重大小(W_hh 的 eigenvalue)和序列長度,看看梯度在 BPTT 過程中會變成什麼樣子。

0.90
30

RNN 即時句子處理器

輸入一段文字,看 RNN 如何逐字更新 hidden state。h_t 是累積到第 t 步為止的「記憶摘要」。越後面的字,前面的記憶就越淡(梯度消失)。

點擊「逐步播放」看每個字如何改變 hidden state 的向量值。

假設 hidden size = 2,embedding size = 2:

字的 embedding:好=[1, 0]、開=[0, 1]、心=[1, 1]
W_xh = [[0.5, 0.3], [0.2, 0.7]]
W_hh = [[0.1, 0.4], [0.6, 0.2]]
初始 h₀ = [0, 0]

1
t=1 讀到「好」,x₁=[1, 0]
h₁ = tanh(W_xh·[1,0] + W_hh·[0,0]) = tanh([0.5, 0.2]) = [0.46, 0.20]
2
t=2 讀到「開」,x₂=[0, 1]
h₂ = tanh([0.3, 0.7] + [0.126, 0.316]) = tanh([0.426, 1.016]) = [0.40, 0.77]
3
t=3 讀到「心」,x₃=[1, 1]
h₃ = tanh(W_xh·[1,1] + W_hh·[0.40, 0.77]) — h₃ 包含了三個字的全部資訊

注意 h₂ 裡面同時包含了「好」和「開」的資訊 — 這就是 RNN 的「記憶」。到 h₃ 時,三個字的語意全部被壓縮在一個 2 維向量裡。

RNN 的 hidden state h_t 同時包含了哪些資訊?
從 t=1 到 t 的所有輸入的摘要
只有當前時間步 t 的輸入
未來所有時間步的輸入
整個 batch 的平均值
正確!h_t 是從第一步到目前為止所有輸入的壓縮摘要。這也是為什麼序列太長時,早期的資訊會被「壓碎」(梯度消失的直觀解釋)。

這個梯度問題就是 LSTM 要解決的事情。

「與其反覆改寫記憶,不如另外開一條路讓它幾乎原封不動傳下去。」

核心 insight

LSTM 的關鍵想法是:與其讓記憶被反覆 matrix multiply 搞到消失,不如另外開一條通道,讓記憶可以幾乎原封不動地一路傳下去,只在必要的時候才修改它。 這條額外的通道就是 cell state

LSTM Cell — 完整解剖圖

ANATOMY LSTM Cell C_{t-1} C_t × forget f_t = σ(...) + input i_t · C̃_t tanh × output o_t tanh h_{t-1}, x_t h_t 圖例 Gate tanh cell state
圖 2.1 — LSTM Cell(紅色高速公路上有三顆流動的小點 — 那就是記憶的傳遞)

三個 Gate 在做什麼?

① Forget Gate

要「忘掉什麼」
f_t = σ(W_f · [h_{t-1}, x_t] + b_f)

Sigmoid 把每個維度壓到 0~1。0 代表完全忘掉,1 代表完全保留。

② Input Gate

要「寫入什麼」
i_t = σ(W_i·[h_{t-1}, x_t]+b_i)
C̃_t = tanh(W_C·[h_{t-1}, x_t]+b_C)

分兩部分:i_t 是「寫入閥門」,C̃_t 是「準備要寫的內容」。

③ Output Gate

要「輸出什麼」
o_t = σ(W_o·[h_{t-1}, x_t]+b_o)
h_t = o_t ⊙ tanh(C_t)

cell state 可能存了一堆東西,但這個時間點不見得每個都要輸出。

關鍵公式 — Cell State 更新

C_t = f_t ⊙ C_{t-1} + i_t ⊙ C̃_t

這個式子有多漂亮 — 注意它是加法不是乘法:因為是相加 → 梯度可以沿 cell state 高速公路幾乎無損傳回去 → 不會梯度消失

LSTM 時間步動畫 — 一步一步看記憶怎麼更新

用「The cat sat on the mat」逐步展示 4 維 cell state 的演化。色塊深淺 = gate 開啟強度,所有過渡都是 GPU 加速的 CSS transition。

step 0 / 6
當前 Tokenx_t
Cell StateC_t(記憶高速公路)
Forget Gatef_t(要忘多少)
Input Gatei_t(要寫多少)
Output Gateo_t(要露多少)
h_t 輸出= o_t ⊙ tanh(C_t)

RNN 是「強迫每個時間點都重寫記憶」,
LSTM 是「讓模型自己決定什麼時候要改記憶、改哪裡、改成什麼」。

Gradient Flow 動畫 — RNN vs LSTM

觀察梯度如何在 RNN 和 LSTM 中反向傳播。RNN 的梯度經過每層會被乘一個小於 1 的數(衰減),LSTM 的 cell state 高速公路讓梯度幾乎不變。

8
RNN 梯度(逐層衰減)
LSTM 梯度(高速公路保護)
紅色:RNN 的梯度隨步數指數衰減。綠色:LSTM 的 cell state 讓梯度保持穩定。這就是為什麼 LSTM 能記住長距離依賴。
具體例子:LSTM 如何記住主詞「貓」

假設模型在處理:「那隻黑色的貓,在經過了很多條街道和巷弄之後,最後終於回到了家

1
讀到「貓」
Input gate i_t ≈ 1,把「主詞=貓」寫進 cell state。Forget gate f_t ≈ 1,保留之前的資訊。
2
讀到「經過了很多條街道和巷弄」(中間十幾個字)
Forget gate 持續 ≈ 1(保留「貓」)。Input gate ≈ 0.1(幾乎不寫入,這些字不重要)。Output gate ≈ 0.3(只輸出少量)。
3
讀到「回到了」→ 需要知道主詞是誰
Output gate o_t ≈ 1,把 cell state 中「貓」的資訊完整輸出。模型知道是「貓」回到了家。

即使中間隔了十幾個字,因為 forget gate 一直輸出接近 1,cell state 上的「貓」資訊就被原封不動地保留下來。這就是「高速公路」的威力。

為什麼 LSTM 的 cell state 更新用「加法」而不是「乘法」?
因為加法計算速度比較快
因為加法讓梯度可以幾乎無損地沿 cell state 傳回去,不會消失
因為加法可以讓模型記住更多東西
純粹是 Hochreiter 的個人偏好
正確!反向傳播時 ∂C_t/∂C_{t-1} = f_t(接近 1 的值),而不是像 RNN 的 W_hh 連乘。加法結構讓梯度沿著 cell state 高速公路幾乎無損傳播。

順帶一提:GRU

GRU 是 LSTM 的簡化版 — 把三個 gate 合成兩個(update + reset),cell state 和 hidden state 也合併。參數比較少,效果通常跟 LSTM 差不多。

「字的向量,應該根據它在句子裡的上下文來決定。」

在 ELMo 之前 — Word2Vec / GloVe 的限制

在 ELMo 之前,大家用的是 Word2Vec 或 GloVe — 每個字對應一個固定向量。問題是:

I deposit money in the bank
→ "bank" 是金融機構

I sit by the river bank
→ "bank" 是河岸

這兩個 "bank" 在 Word2Vec 裡是完全一樣的向量,但意思完全不同。
ELMo 的核心想法:字的向量應該根據它在句子裡的上下文動態產生

ELMo 的三層架構

ARCHITECTURE 輸入 b a n k _ a c c o u n t (每個字拆成字元) 第 0 層 Character CNN + 2-layer Highway + Linear Projection 2048 個 char n-gram filter → 512 維 token representation 第 1 層 → Forward LSTM (Layer 1) 4096 hidden, 512 dim projection ← Backward LSTM (Layer 1) 4096 hidden, 512 dim projection 第 2 層 → Forward LSTM (Layer 2) + residual ← Backward LSTM (Layer 2) + residual ELMo = γ · Σ_j s_j · h_j 三層的加權平均(s_j 對下游任務 learnable) Contextualized Embedding
圖 3.1 — ELMo 完整三層架構

數學表達

對每個 token t_k,L 層 biLM 會產生 2L+1 = 5 個 representation(L=2):

R_k = { x_k^{LM}, →h_{k,j}^{LM}, ←h_{k,j}^{LM} | j = 1, 2 }
ELMo_k^{task} = γ^{task} · Σ_{j=0}^{L} s_j^{task} · h_{k,j}^{LM}

不同層學到不同東西

傾向學到什麼適合的下游任務
Char CNN(第 0 層)字形、拼寫、字根對未知詞的處理
biLSTM 第 1 層語法(POS、依存關係)詞性標註(POS tagging)
biLSTM 第 2 層語意(word sense)語意消歧(WSD)

Word Embedding 向量算術遊樂場

Word2Vec 最知名的特性:向量之間可以做算術!選不同的詞組合看結果。這展示了 embedding 空間有「語意結構」。

+ queen
「king - man + woman = queen」代表 embedding 空間中存在一個「性別」方向。沿著這個方向移動,就是在改變詞的性別屬性,而保留其他語意(如「皇室」)。
深入理解:Word2Vec vs ELMo 的核心差異

Word2Vec / GloVe(靜態 embedding):

1
訓練:用大語料庫學「字跟字之間的共現關係」
CBOW:用周圍的字預測中間的字。Skip-gram:用中間的字預測周圍的字。
2
結果:每個字得到一個「固定向量」,不管出現在什麼句子裡都一樣
king - man + woman ≈ queen 這類向量算術是它的經典特性

ELMo(動態 embedding):

1
訓練:在大語料上做 Language Model(預測下一個字 / 前一個字)
Forward LM:P(w_t | w_1, ..., w_{t-1})。Backward LM:P(w_t | w_{t+1}, ..., w_T)。
2
使用時:把句子丟進去,取 biLSTM 每一層的輸出做加權平均
同一個 "bank" 在不同句子裡得到不同的向量 — 因為上下文不同!

OOV(Out-Of-Vocabulary)問題:Word2Vec 遇到沒見過的字(如 "ChatGPT")就完全崩潰。但 ELMo 用字元 CNN 從字母 C-h-a-t-G-P-T 中抽特徵,即使沒見過也能猜出大致含義(如 "-GPT" 可能暗示技術相關)。

ELMo 為什麼用「字元級 CNN」作為第一層,而不是直接用 word embedding?
因為字元 CNN 計算比較快
因為 word embedding 維度太高
為了處理 OOV(未登錄詞)— 即使沒見過的字,也能從字元組合中抽出特徵
因為 LSTM 只能接受 CNN 的輸出
正確!用字元 CNN 讓模型可以處理任何字串 — 新詞、拼錯的字、人名、外來語等。它能從字元 n-gram(如 "-ing"、"un-"、"-tion")學到字根和詞綴的意義。

ELMo 還是有 LSTM 的限制:sequential 計算,沒辦法平行化。在 GPU 時代這是非常奢侈的浪費 — 於是 Transformer 來了。

「把 sequential 計算徹底丟掉。只用 attention 就好。」

動機 — 為什麼要丟掉 RNN?

LSTM 解決了長序列記憶問題,但它還是要一個時間步一個時間步算。在 GPU 時代很不友善。Transformer 的野心:完全不要 RNN,只用 attention 機制就好

核心 — Self-Attention

讀「The animal didn't cross the street because it was too tired」。讀到 "it" 的時候,你的腦袋會自動回去看:「it 指的是什麼?」然後發現它指 "animal"。

Self-attention 就是讓每個字都去看句子裡所有其他字,然後決定要從哪些字「拿資訊」過來組合成自己的新表示

Q, K, V — 圖書館比喻

Query (Q)

「我在找什麼」

當前 token 想要找的資訊類型 — 像是你在 Google 打的搜尋字。

Key (K)

「我有什麼可以被找」

每個 token 對外的「索引標籤」 — 像是書的標題、tag。

Value (V)

「如果有人找我,我給他什麼」

實際的資訊內容 — 像是書的真正內容。

Attention 公式拆解

Attention(Q, K, V) = softmax( Q · KT / √d_k ) · V
STEP-BY-STEP ① Q · K^T [N × d_k] · [d_k × N] = [N × N] 每個 query 對每個 key 的內積(相似度) ② ÷ √d_k scale 避免內積太大 導致 softmax 飽和 ③ softmax attention weights 每列總和 = 1 ④ × V attention · V = output 每個位置 = 所有 value 的加權和 每個位置的新表示 = 所有位置 value 的加權和,權重由 query-key 相似度決定
圖 4.1 — Self-Attention 計算的四步拆解
具體計算:「I love cats」的 Self-Attention 逐步拆解

假設 d_k = 2(極簡化)。每個 token 有自己的 Q, K, V 向量:

TokenQKV
I[1, 0][0, 1][1, 0]
love[0, 1][1, 1][0, 1]
cats[1, 1][1, 0][1, 1]
1
Q · K^T(算每對 token 的原始分數)
score(I→I)=1×0+0×1=0 score(I→love)=1×1+0×1=1 score(I→cats)=1×1+0×0=1
2
÷ √d_k = ÷ √2 ≈ 1.41
score(I→I)=0 score(I→love)=0.71 score(I→cats)=0.71
3
softmax(轉成機率)
attn(I→I)=0.20 attn(I→love)=0.40 attn(I→cats)=0.40
4
加權求和 V
output_I = 0.20×[1,0] + 0.40×[0,1] + 0.40×[1,1] = [0.60, 0.80]

「I」的新表示 [0.60, 0.80] 融合了三個 token 的資訊,其中「love」和「cats」的貢獻更大。這就是 attention 的本質:每個 token 的新表示 = 所有 token 的加權組合

Multi-Head Attention — 多重視角

只用一個 attention 不夠 — 一個字可能同時要關注「語法上的主詞」、「意義上的相關物」、「位置上的鄰居」。所以 Transformer 用多個 attention 平行跑,每個叫一個 "head"。

MultiHead(Q,K,V) = Concat(head_1, ..., head_h) · W^O
head_i = Attention(Q·W_i^Q, K·W_i^K, V·W_i^V)

研究發現:訓練後,不同 head 會自發分工 — 有些學語法依賴、有些學位置鄰近、有些學共指。沒人預先指派誰做什麼,這種「emergent specialization」是 Transformer 強大的關鍵之一。下一頁的 demo 你可以親自玩玩看。

Positional Encoding

Self-attention 是 permutation-invariant!「The cat sat on the mat」跟「mat the on sat cat The」對 self-attention 是一樣的

解法:把位置資訊「」到 input embedding 上。原始 Transformer 用 sin/cos:

PE(pos, 2i) = sin(pos / 10000^{2i/d_model})
PE(pos, 2i+1) = cos(pos / 10000^{2i/d_model})

後來的 BERT、GPT 改成 learnable positional embedding,再後來有 RoPEALiBi 等更新方法。

Positional Encoding 互動熱力圖

觀察 sin/cos 如何隨「位置」和「維度」變化。低維度像秒針(變化快),高維度像時針(變化慢)。每個位置的 pattern 都是唯一的「位置指紋」。

32
32

X 軸 = 維度(偶數 sin / 奇數 cos),Y 軸 = 位置。紅色 = 正值,藍色 = 負值。注意左邊變化快(高頻),右邊變化慢(低頻)。

Softmax Temperature 探索器

Temperature τ 控制 softmax 的「尖銳程度」。τ→0 時分布極度集中(只看最高分),τ→∞ 時均勻分布(全部平等)。Transformer 用 √d_k 當 τ。

1.00
原始分數 (logits)
[2.0, 1.0, 0.5, 0.1, -0.5]
softmax(logits / τ) 結果

為什麼 Transformer 這麼成功?

① 完全平行化

所有位置可以同時計算 — GPU 吃滿。不像 RNN 要等前一步算完。

② 長距離依賴一步到位

任何兩個位置都直接連接(attention),不用像 RNN 一樣靠記憶慢慢傳。

③ Scalable

層數加深、寬度加寬都能持續變強 — 這是 GPT-3、GPT-4 的基礎。

完整 Transformer 架構:Encoder-Decoder + Cross-Attention

原始 Transformer 論文("Attention Is All You Need")是做機器翻譯的,所以它有 encoder 跟 decoder 兩部分:

Encoder(6 層)

每層:Self-Attention → Add & Norm → FFN → Add & Norm。
看完整個輸入序列,產生「理解後的表示」。

Decoder(6 層)

每層:Masked Self-Attention → Cross-Attention → FFN。
Cross-Attention 的 K, V 來自 encoder 輸出,Q 來自 decoder 自己。

Cross-Attention 是什麼?Decoder 在生成每個字時,用自己當前的狀態當 Query,去 attend encoder 的輸出(Key, Value)。這讓 decoder 可以「回頭看」源語言的哪些部分跟要生成的字最相關。

後來 BERT 只用 encoder,GPT 只用 decoder(且去掉 cross-attention),就分出兩條路線了。

Self-Attention 為什麼要除以 √d_k?
讓計算更快
讓 attention 權重加起來等於 1
當 d_k 很大時,內積值會很大,softmax 會飽和(梯度趨近 0),除以 √d_k 讓分數回到合理範圍
這是 bias correction
正確!當 d_k = 512 時,兩個隨機向量的內積期望值的標準差約為 √512 ≈ 22.6。這麼大的值進 softmax 會讓輸出極度集中,梯度接近零。除以 √d_k 把方差標準化回 1。

「Q、K、V 算出來的注意力權重長什麼樣?玩玩看就知道。」

Self-Attention 注意力熱力圖

輸入句子(用空格分字),點擊任一個 token 看它對其他 token 的注意力分布。 切換不同 head 風格看不同 pattern。所有顏色都用 CSS transition,順暢過渡。

注意力熱力圖(i 列 → j 行)

每一列總和為 1。深色 = 注意力高。點任一格切換焦點。

所選 token 的注意力分布

三個常見的注意力 pattern

對角線 pattern

每個 token 主要注意自己附近的 token。常見於早期層。

共指 pattern

「it」「he」「she」會強烈 attend 到它們指的名詞。

句法依賴 pattern

動詞 attend 主詞、形容詞 attend 修飾的名詞。

上面的 demo 用規則化的模擬,真正訓練出來的 Transformer 注意力 pattern 更複雜。但你可以從這個 demo 體會核心概念:Q-K 內積 = token 之間的相關性

「Transformer 原本是 encoder-decoder 一起用的,但有時候只需要其中一邊。」

SIDE-BY-SIDE BERT Encoder-only · 雙向 · 適合理解 I love [MASK] learning Encoder × 12 (base) or × 24 (large) 每個 token 都能看到所有 token ↔ 雙向 self-attention ↔ 110M params (base) 340M params (large) 預測 [MASK] = "deep" GPT Decoder-only · 單向(causal)· 適合生成 I love deep ___ Decoder × 12 (GPT-2) → 96 (GPT-3) 每個 token 只能看到自己跟之前的 → Causal Mask → 沒有 cross-attention (因為沒有 encoder) 預測下一個字 = "learning"
圖 6.1 — BERT vs GPT 核心差異

對照表

BERTGPT
架構Encoder-onlyDecoder-only
注意力 雙向 單向(causal mask)
訓練目標 MLM + NSP 預測下一個 token
強項理解(分類、QA、NER)生成(對話、寫作、寫程式)
代表後繼RoBERTa, DeBERTa, ELECTRAGPT-2/3/4, LLaMA, Claude

為什麼最後 GPT 路線贏了?

雖然 BERT 在理解任務上很強,但有一個關鍵差異:GPT 的訓練目標跟「人類使用語言」更接近。我們講話、寫文章都是一個字一個字往下產生的,所以 GPT 的能力可以很自然地用在所有任務上。

所有 NLP 任務都可以重新表達成「文字接龍」

• 翻譯 → "Translate to French: I love you →" 接龍出 "Je t'aime"
• 分類 → "Sentiment of '太爛了': →" 接龍出 "negative"
• 摘要 → "Summarize: <長文章> →" 接龍出摘要
• 問答 → "Q: 太陽從哪裡升起? A:" 接龍出答案

這就是為什麼現在大家都做 decoder-only 的大模型。

同一任務兩種做法:用 BERT vs GPT 做情感分類

任務:判斷「這部電影太爛了」是正面還是負面。

BERT 做法

1
[CLS] 這部電影太爛了 [SEP]
加上特殊 token,餵進 BERT encoder
2
取 [CLS] 的輸出向量
這個向量濃縮了整句話的雙向理解
3
接一個 Linear + Softmax 做分類
需要額外 fine-tune 一個分類 head

GPT 做法

1
"「這部電影太爛了」的情感是:"
把任務包裝成 prompt(自然語言指令)
2
GPT 用 next-token prediction 接龍
不需要改架構,直接用原本的生成能力
3
輸出 → "負面"
不需要額外 fine-tune!Prompt 就是任務定義

GPT 路線的關鍵優勢:Prompt as Task Definition。你不用為每個新任務設計新的模型頭、收集標註資料、重新 fine-tune。只要改 prompt 就能做新任務。當模型夠大時(GPT-3 175B 以上),這種 zero-shot / few-shot 能力就會「湧現」。

Attention Mask 視覺化 — BERT vs GPT 看到什麼?

點擊切換 BERT(雙向)和 GPT(causal mask)模式,看每個 token 能「看到」哪些其他 token。綠色 = 可以 attend,紅色 = 被遮住。

目前:BERT 雙向
BERT:每個 token 可以看到所有其他 token(包括自己後面的)。這就是「雙向理解」的來源。
為什麼 GPT(decoder-only)最後成為主流路線?
因為 GPT 的參數比較少、比較便宜
因為 GPT 的 attention 是雙向的,比較強
因為所有任務都能表達成「文字接龍」,不需要為每個任務設計特殊架構
因為 BERT 的程式碼比較難寫
正確!GPT 的核心優勢是通用性 — 翻譯、分類、問答、寫程式、摘要全部可以用 prompt 表達成 next-token prediction。一個模型解決所有任務,不需要 task-specific fine-tuning。

「多模態的核心其實很簡單:把圖、文、音、影片都壓到同一個向量空間,剩下的就是常見的 ML 任務了。」

CORE IDEA 圖片 RGB tensor [3, 224, 224] Vision Encoder ViT or CNN CLIP 用 ViT-L/14 文字 tokens "a cat on the mat" Text Encoder Transformer 通常類似 BERT 共用 Embedding Space (例:512 維) 下游任務 檢索 / 分類 問答 / 生成
圖 7.1 — 多模態通用架構

三個經典模型

① CLIP — Contrastive Language-Image Pre-training (OpenAI 2021)

訓練方式叫 contrastive learning — 給 N 對 (圖, 文),組成 N×N 矩陣,對角線是 positive pair(推近),其他是 negative(推遠)。

具體例子:Contrastive Learning 的 Batch 計算

假設一個 batch 有 4 對 (圖, 文):

#圖片文字描述
1🐱 一隻貓"a photo of a cat"
2🐶 一隻狗"a dog playing fetch"
3🚗 一台車"a red car on the road"
4🌅 日落"sunset over the ocean"

計算 4×4 的 cosine similarity 矩陣:

對角線(正樣本):圖₁↔文₁, 圖₂↔文₂, 圖₃↔文₃, 圖₄↔文₄
這些是真正配對的 → 訓練目標是讓相似度最大化
非對角線(負樣本):圖₁↔文₂, 圖₁↔文₃, ... 共 12 對
這些是不配對的 → 訓練目標是讓相似度最小化

Loss 使用 InfoNCE(其實就是兩個方向的 cross-entropy):從圖找文、從文找圖。Batch 越大,negative pair 越多,學到的 embedding 品質越好 — CLIP 論文用 batch size = 32,768!

CLIP 訓練完可以做 zero-shot classification:把 "a photo of a cat"、"a photo of a dog" 編成文字向量,跟圖片向量算 cosine similarity,誰高分到哪類 — 完全不用為了分類任務再訓練!

Cosine Similarity 互動計算器

拖動 slider 改變兩個向量的方向,即時看 cosine similarity 如何變化。這就是 CLIP 判斷「圖文是否匹配」的核心計算。

30°
60°
0.87
直觀理解:
• cos = 1.0 → 方向相同(完美匹配)
• cos = 0.0 → 正交(完全無關)
• cos = -1.0 → 方向相反(完全對立)
在 CLIP 中,配對的圖文 cos ≈ 0.9,不配對的 cos ≈ 0.1

② BLIP — Bootstrapping Language-Image Pre-training (Salesforce 2022)

CLIP 主要做理解/檢索,BLIP 想要連生成(caption)也一起做。BLIP 同時訓練三個任務:

任務做什麼
ITC(Image-Text Contrastive)跟 CLIP 一樣的對比學習
ITM(Image-Text Matching)給一對圖文,二分類判斷是否匹配
LM(Language Modeling)看圖片產生 caption(CLIP 沒有這個)

BLIP-2 引入 Q-Former — 用 learnable query 從 vision encoder 抽視覺資訊,再餵給凍結的 LLM,省錢又有效。

③ LLaVA — Large Language and Vision Assistant (2023)

LLAVA ARCH 圖片 image CLIP ViT-L/14 vision encoder ❄ 凍結 Projection linear / MLP 🔥 可訓練 視覺 Tokens [v_1, v_2, ..., v_N] 與文字 token 同維度 使用者 prompt "What's in this image?" 文字 Tokens [v_1, ..., v_N, w_1, w_2, ..., w_M] ← 串接 LLM (LLaMA / Vicuna) → 產生回答
圖 7.3 — LLaVA 的「視覺翻譯」設計
LLaVA 的兩階段訓練策略

LLaVA 的訓練非常聰明 — 不是一次全部訓練,而是分兩個階段:

S1
Stage 1:Pre-training(只訓練 Projection Layer)
❄ 凍結 Vision Encoder + LLM,只訓練中間的 projection layer。用 595K image-caption pairs。目標:讓 projection 學會把視覺特徵「翻譯」成 LLM 看得懂的 token 空間。
S2
Stage 2:Fine-tuning(訓練 Projection + LLM)
❄ 只凍結 Vision Encoder,🔥 解凍 LLM + Projection。用 158K multimodal instruction-following data(GPT-4 生成的高品質問答)。讓模型學會遵循指令回答視覺問題。

為什麼分兩階段?如果一開始就解凍 LLM,projection 還沒學好(輸出的向量是垃圾),LLM 會被帶壞。先讓 projection 學會「翻譯」,再讓 LLM 學會「理解翻譯後的視覺語言」。

LLaVA-1.5 的改進:把 linear projection 換成 2-layer MLP,讓翻譯能力更強。這個小改動帶來顯著效果提升。

三個模型對照

CLIPBLIPLLaVA
主要能力檢索、零樣本分類檢索 + 生成 caption對話式視覺問答
Vision EncoderViTViTCLIP ViT(凍結)
連接方式共享 embeddingQ-Former (BLIP-2)Projection layer
哲學對齊兩個空間多任務聯合訓練把視覺翻譯成 LLM 看得懂的「語言」

融合策略(作業會用到)

策略說明例子
Early Fusion 很早就把不同模態串起來直接 concat 兩種向量丟同一個模型
Late Fusion 各自處理到最後才合併CLIP(兩個 encoder 各跑完才算 similarity)
Cross-Attention 一邊的 Q 去 attend 另一邊的 K/VFlamingo, BLIP-2 的 Q-Former
Unified Token 所有模態變成 token 給同一個 TransformerLLaVA, GPT-4V
CLIP 的 contrastive learning 為什麼需要大 batch size?
因為 GPU 記憶體很大需要填滿
batch 越大,negative pairs 越多,模型越能學會精細區分不同概念
因為小 batch 會導致梯度消失
純粹是為了訓練速度
正確!在 contrastive learning 中,batch 裡的其他樣本就是 negative examples。假設 batch=32768,每張圖有 32767 個 negative text 去比較。negative 越多,模型越能學出細緻的 embedding(不只分辨「貓 vs 車」,還能分辨「橘貓 vs 黑貓」)。
HOMEWORK

「資料集規模不用大,重點是流程跑得通、設計有思考。」

作業要求

① 自己建資料集

不能用 COCO、Flickr 等現成資料集。
規模 30~100 對就夠:自己拍的照 + 描述、影片片段 + 字幕、音樂 + 歌詞、心情日記 + emoji…

② 設計自己的架構

兩個模態自選(圖+文、音+文、影片+字幕…)。選一個融合策略並說明為什麼。可以用 pretrained encoder(CLIP, BERT 都 OK)。

③ 簡短報告

PDF 或 Markdown 都可以。重點不在 accuracy,在思考過程

報告必須回答的四個問題

為什麼選這兩個模態? 這個組合可以解什麼問題、有什麼有趣的應用?

兩個模態怎麼融合? Early / Late / Cross-attention / Unified Token — 你選哪個?為什麼?這個選擇有什麼 trade-off?

訓練上遇到什麼問題?怎麼解決的?(沒遇到問題反而可疑)

有 demo 結果就放上來(不管成功失敗)。失敗的 demo 配上分析,比成功但沒思考的 demo 分數還高。

起手式建議

步驟建議
① 想題目選一個你「真的覺得有趣」的應用 — 動力是最重要的
② 收資料30~100 對就夠。寧可少而精,不要多而髒
③ 切 train/test20% 留 test,千萬別偷看
④ 用 pretrained 起步CLIP、BERT、ResNet — 不要從零訓練
⑤ 先用最簡單的融合Late fusion + cosine similarity 是最容易跑通的 baseline
⑥ 出問題 → 分析 → 改進這個 loop 走幾遍才是學習發生的地方

重要程度排序:
① 思考清晰度 > ② 流程完整性 > ③ 創意 > ④ 結果效能

做不出 SOTA 沒關係,不知道自己為什麼這樣做才是大問題。

交件方式

GitHub repo 包含:(1) 程式碼、(2) 資料集(或下載連結)、(3) 報告 PDF/Markdown、(4) README 寫怎麼跑。給未來會回來看自己 code 的你,留一份體面的 repo。

期待看到你們的設計 — 玩得開心。