夢想:能看也能說的機器
你在這條電腦視覺學習階梯前面遇到的每一種模型,都有一個安靜的限制。分類器從訓練時固定的清單裡給你一個標籤:『貓』、『狗』、『卡車』。偵測器會畫出方框,但每個方框得到的標籤仍來自同一份封閉的選單。分割器替每個像素上色,一樣是從事先決定好的類別集合裡挑。它們很強大,但永遠只能回答當初選單設計好要回答的問題。你問分類器『照片裡的人開心嗎?』,它根本沒有對應的格子——這個問題落在它的詞彙之外。
現在想像一種不一樣的模型。你給它一張圖片,然後直接『跟它說話』。『描述這張圖。』『傘是什麼顏色?』『幫我找出有紅雨傘的那張照片。』它讀懂你的文字,看著像素,再用它自己的文字回答——流暢、開放的句子,而不是一個指向固定清單的數字。輸入是自然語言,輸出也是自然語言,所以同一個模型幾乎什麼都能被問。這個單一的轉變——從封閉選單變成開放語言——就是開啟整條學習軌的那一躍。
想想幼兒是怎麼學習的。她指著一個毛茸茸的東西,你說『狗狗』;她指著月亮,你說『月亮』。指物是她的視覺;你的話語則是包裹在她所見之上的語言。經過成千上萬個這樣的瞬間,看與說融合成一個理解的動作。這條軌上的模型,正是要重現這種融合。而語言之所以是對的黏著劑,是因為語言是通用介面:它一直是人類用來指明自己意思、表達自己想要什麼的方式。如果一台機器能用文字接收指令、給出答案,那麼任何人都能使用它——不需要固定選單,不需要特別的標籤集,只要說話就好。
我們把這一類模型稱為視覺語言模型:一個同時握有兩個世界——像素與文字——並讓它們互相對話的系統。本篇接下來會描繪這些模型『能做什麼』,以及它們為何都暗中依賴同一個共享的想法。然後接下來的四篇指南會一塊一塊掀開引擎蓋,精確地告訴你它們『如何』運作。把這篇當作我們走進街道之前的那張空拍照吧。
定義這個領域的四大任務
我們用一張會反覆回來看的圖片把這件事講具體。想像一張照片:一個人站在下著雨、濕漉漉的城市街道上,撐著一把鮮紅色的雨傘。把這個畫面放在腦海裡。我們將對它提出四種聽起來很不一樣的要求——而結尾的妙處在於:它們其實根本不是四件事。
第一,影像描述:輸入圖片,輸出一個流暢的句子。我們把照片餵進去,模型寫出『一個人撐著紅雨傘走在下雨的街道上。』請注意,它必須『選擇用字』並『按文法排序』——它是在依據所見來生成語言,而不是從清單裡挑。
第二,視覺問答(VQA):輸入圖片『加上一個問題』,輸出一個答案。我們給同一張照片,問『傘是什麼顏色?』,它回答『紅色。』我們也可以同樣輕鬆地問『有在下雨嗎?』(『有』)或『有幾個人?』(『一個』)。圖片維持不變;變的是『問題』,而模型必須找到每個問題對應的那一塊畫面。
第三,圖文檢索:在整個資料庫中做匹配,方向可以是任一邊。文字找圖:你在一個存了一萬張照片的相片 App 裡輸入『紅雨傘』,它把我們這張照片排到很前面。圖找文字:你把照片交給 App,它從一串說明文字裡挑出最貼切的那一句。檢索就是『排序』——依據每個項目與另一邊查詢的匹配程度,把一堆東西排好。
還有第四個,作為下一篇的預告:零樣本分類。你不必在固定標籤集上訓練,只要把候選標籤『寫成』文字——『一張雨傘的照片』、『一張腳踏車的照片』——再問圖片最符合哪一個。你可以在測試時臨時發明全新的類別,完全不用重新訓練。下一篇(CLIP)正是圍繞這個技巧建立的,所以細節留到那裡再講。
兩個世界,共用一張地圖
這是整條軌的概念核心,所以我們慢慢來。先從一個想法開始:嵌入(embedding)。嵌入把雜亂的東西——一張圖片、一個句子——變成一串數字,而你可以把這串數字讀成高維空間裡的『座標』。兩個座標把一個點放在一張紙上;三個座標把它放進一個房間裡。視覺語言模型可能用 512 或 768 個數字,把每樣東西放成 512 維『空間』裡的一個點——我們無法想像這個空間,但能對它推理。好的嵌入承諾的是:『意義變成了幾何』——意思相近的東西落得很近,意思不同的東西落得很遠。
現在來看讓視覺語言模型成為可能的那個訣竅:把圖片『和』文字『都』放進『同一個』空間,用『同一套』座標系。一張狗的照片,和『一張狗的照片』這幾個字,應該落在幾乎同一個位置。兩個比喻。(1)想像兩個說不同語言的人——比如說國語和法語——各自把自己的句子翻譯進一個共享的『意義空間』;一旦兩者都進到那個空間,你就能看出『紅雨傘』和 parapluie rouge 是同一個意思,即使原始符號毫無共通之處。(2)想像一張藏寶圖,每張照片和每句說明都是一根圖釘,而圖釘是依『意義』來插的,不是依原始像素或原始字母——所以『紅雨傘』和那張雨傘照片會被插在地圖上彼此緊鄰的地方。這張共享地圖,就是視覺語言模型建立的地基。
一張示意圖,顯示影像編碼器與文字編碼器都輸入到同一個共享嵌入空間,箭頭把相配的圖文成對拉近、把不相配的成對推遠。
如果意義現在是地圖上的一個位置,那麼『這兩樣東西意思一樣嗎?』就變成一個幾何問題:『它們兩個點有多近?』我們需要一個具體的數字來表示接近程度。我們可以量兩點之間的直線距離,但有一個更細緻、更好用的度量,它會忽略一個向量有多『大聲』,只看它指向的『方向』。這個度量就是餘弦相似度(cosine similarity),它是這條軌幾乎每個模型底下那台安靜的引擎。
影像嵌入 u 與文字嵌入 v 之間的餘弦相似度。
我們把每個部分都讀一遍。這裡 u 是影像嵌入——照片的座標串——而 v 是文字嵌入——說明的座標。上面的 u · v 是『內積』:把兩個向量逐元素相乘,再把所有乘積加起來。(若 u = (3, 4)、v = (6, 8),則 u · v = 3·6 + 4·8 = 18 + 32 = 50。)內積在兩向量指同方向時會很大,但它也會單純因為向量很長而變大——而這裡的長度大多是雜訊(描述生動還是平淡),不是意義。所以我們把它除掉。‖u‖ 和 ‖v‖ 是『範數』,也就是箭頭的長度,各自把座標平方、相加、再開根號得到(‖u‖ = √(3² + 4²) = √25 = 5,‖v‖ = √(6² + 8²) = √100 = 10)。把內積除以這兩個長度,就把所有大小都剝掉了,只留下方向——在幾何上,就是兩個箭頭夾角的餘弦值。結果永遠落在 −1(指相反方向,意義相反)、經過 0(互相垂直,毫不相關)、一路到 +1(指同方向,意義相同)之間。在我們的例子裡,sim = 50 /(5·10)= 50/50 = 1:v 只是 u 放大後的版本,所以指向完全相同的方向——完美對齊。整個直覺可以濃縮成一句口號:接近 = 夾角很小。
import numpy as np
def cosine_similarity(u, v):
# dot product: sum of element-wise products
dot = np.dot(u, v)
# norms: the lengths of each arrow
norm_u = np.linalg.norm(u)
norm_v = np.linalg.norm(v)
# divide out magnitude -> only direction (the angle) remains
return dot / (norm_u * norm_v)
image_emb = np.array([3.0, 4.0]) # 'photo of a red umbrella'
text_match = np.array([6.0, 8.0]) # 'a red umbrella' (same direction)
text_other = np.array([-4.0, 3.0]) # 'a blue bicycle' (right angle)
print(cosine_similarity(image_emb, text_match)) # 1.0 -> same meaning
print(cosine_similarity(image_emb, text_other)) # 0.0 -> unrelated把視覺與語言接起來的兩種方式
現在你有了共享地圖的想法,這裡給你『旅程本身』的地圖——也就是接下來四篇指南會拆解的架構光譜。它們的差別在於一個問題:視覺端和語言端到底『互動』多少?我們維持在直覺層次;方程式留到後面的指南。把它想成一個旋鈕上的三個位置,從『幾乎不交談』到『完全融合』。
在『幾乎不交談』這一端是雙編碼器(dual encoders),又叫雙塔。一座塔把圖片讀成一個向量;另一座獨立的塔把文字讀成一個向量;然後你只用餘弦相似度去『比較』這兩者——僅此而已。兩邊從不窺看彼此的內部;它們只在最後相遇,成為共享地圖上兩個完成的點。這很快(你可以把每張圖片的向量預先算一次,之後任何查詢都重複使用),而且這正是CLIP的運作方式——你的下一篇指南。
把旋鈕轉大,你會得到融合編碼器(fusion encoders)。這裡兩條串流不只在最後相遇——它們『邊想邊交談』,透過一種叫交叉注意力(cross-attention)的機制,讓語言端能直接看圖片裡特定的區域,反之亦然。這讓模型能處理單純一次比較做不到的問題,例如『站在雨傘「左邊」的人是不是撐傘的人?』,這需要文字和像素一起推理。這是BLIP的地盤,也就是你的第三篇指南,而交叉注意力機制本身則是再下一篇的重點。
在最遠的『完全融合』端是以大型語言模型為核心的模型——也就是多模態大型語言模型。這裡你拿一個強大、已經訓練好的語言模型,把圖片『餵進去』,就好像圖片是額外的文字一樣,讓這個大型語言模型能讀一張圖,然後用它本來就有的全部流暢度去推理、解釋、對話。這就是 LLaVA 和 Flamingo 這類系統的運作方式,也是你的第五篇、也是最後一篇指南。
我們怎麼知道它有效(以及何時會失敗)
一個什麼都能說的模型,也可能什麼都『說錯』,所以我們需要誠實的方式來衡量它——每個任務各一種。對圖文檢索,標準量尺是 recall@K,而且它非常直白:對每個查詢,看模型回傳的前 K 個結果;真正正確的那個項目有沒有出現在前 K 名裡?recall@1 問的是『最佳猜測對不對?』;recall@5 比較寬容——『正確答案有沒有出現在前五名裡的任一個?』。把這個是/否在許多查詢上取平均,就得到一個百分比。它呼應真實生活:你搜尋相片 App 時,本來就會樂意掃過前幾個結果,所以『正確的照片在不在前 5 名』正是你在乎的事。
def recall_at_k(ranked_results, correct_item, k):
# ranked_results: items sorted best-first by cosine similarity
# returns 1 if the correct item is in the top k, else 0
return 1 if correct_item in ranked_results[:k] else 0
# average this over every query in your test set, then x100 for a percent對影像描述而言,並不存在唯一『正確』的句子——很多種說法都行——所以我們把模型的描述拿來和好幾句人寫的對比,計算重疊程度。BLEU 大致在數機器和人類共享了多少短的字串;CIDEr 則是專為描述設計的分數,會加重人類傾向一致同意的字詞。把兩者都當成粗略的溫度計,而非真理:一個描述可能靠複誦常見片語拿到高分,卻錯過了實際的場景;也可能拿到低分,但其實是一句完全沒問題、只是『不同』的描述。它們引導開發,卻無法證明理解。
對視覺問答,衡量方式是其中最簡單的:準確率(accuracy)——模型回答得跟人類一樣的問題占多少比例。因為每個問題會蒐集好幾個人類答案,一個答案通常只要有足夠多人也這樣回答就算正確,這溫和地容忍了像『紅色』對『鮮紅色』這類用字差異。
一張說明精確率與召回率的示意圖,強調召回率是所有相關項目中出現在檢索結果裡的比例。
現在來到誠實的部分,請把它帶進之後的每一篇指南。這些模型會幻覺(hallucinate):一個描述模型可能自信地描述一隻根本不在照片裡的貓,因為它太常看到『沙發上的一隻貓』,導致無論像素支不支持,那些字都照樣流出來。它們從學習用的網路資料裡繼承偏見(bias)——可能依賴刻板印象(假設醫生是男性、護理師是女性),並且在網路上代表性不足的人群、地方與文化上表現較差。而且它們嚴重以英語為中心:主要在英語說明上訓練,因此對英語提示的理解遠勝其他語言,這對說國語或法語的人是個實在的限制。這些都不代表模型沒用——而是代表你應該帶著批判去讀它們流暢的答案,明白一句自信的話並不保證是正確的話。
一張關於機器學習公平性的示意圖,顯示模型在不同人口群體間表現不均。