我們昨天學到的擴散模型如 Stable Diffusion,內部必須有一個文本編碼器來理解我們的文字提示 (prompt),並將其作為條件來引導圖像生成。這自然地引出了一個問題:視覺模型和語言模型,這兩個來自不同模態 (modality) 的模型,是如何進行融合的?
多模態學習 (multimodal learning) 旨在建立能夠同時處理和關聯來自多種不同模態資訊的模型,以 Stable Diffusion 來說,就是視覺與語言。
傳統的監督式圖像分類,其標籤通常是單一的、固定的詞彙(例如 "cat", "dog")。這種方式存在兩個問題:
標籤成本高:需要人工為數百萬張圖片,從一個固定的詞彙表中選擇標籤。
泛化能力弱:模型只能識別它在訓練時見過的那些類別。如果想讓它識別一個新的類別,就需要重新收集數據、標註、再訓練。
網際網路上,存在上億個現成、帶有自然語言描述的圖片(如文章配圖),有沒有辦法直接利用這些大量的圖文對來進行學習呢?
CLIP 正是為了解決這個問題而生的,它的核心思想與對比學習如出一轍,但它將對比的範圍從「圖像的不同視圖」擴展到了「圖像與文本」。
CLIP 的訓練過程如下:
數據準備:從網路上收集了 4 億個 (image, text) 圖文對作為訓練數據。
雙塔結構:CLIP 包含兩個獨立的編碼器
一個圖像編碼器:可以是 ResNet 或 ViT,負責將輸入的圖像,編碼成一個特徵向量 I_f。
一個文本編碼器:通常是一個 Transformer,負責將輸入的文本描述,編碼成一個特徵向量 T_f。
CLIP 最驚人的能力是零樣本分類 (zero-shot classification)。
假設我們想在一個數據集上進行分類,這個數據集包含「飛機」、「汽車」、「狗」、「鳥」四個類別。傳統模型必須在這個數據集上進行訓練或微調。而 CLIP 的做法是:
準備文本提示:我們將類別名稱,構建成描述性的句子,例如 ["a photo of a plane", "a photo of a car", "a photo of a dog", "a photo of a bird"]。
編碼文本:將這 4 個句子,通過 CLIP 的文本編碼器,得到 4 個文本特徵向量。
編碼圖像:拿一張我們想要分類的、從未見過的測試圖片(例如,一張狗的圖片),將它通過 CLIP 的圖像編碼器,得到 1 個圖像特徵向量。
4.計算相似度:計算這個圖像特徵向量,與那 4 個文本特徵向量之間的餘弦相似度。
在這個過程中,模型沒有看過任何一張帶有這四個類別標籤的圖片,它僅僅是透過在訓練時學到的、通用的圖文對應關係,就能夠理解「狗的圖片」在特徵空間中,應該離 "a photo of a dog" 這句話的特徵更近。這也使得我們可以構建出一個能夠識別任意開放詞彙的圖像分類器。
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import requests
# --- 1. 載入預訓練的 CLIP 模型和處理器 ---
# 我們使用 OpenAI 原始的 ViT-Base-Patch32 模型
model_name = "openai/clip-vit-base-patch32"
print(f"正在從 Hugging Face Hub 下載模型: {model_name}...")
# CLIPProcessor 是一個方便的類,它內部包含了圖像處理器和文本分詞器
processor = CLIPProcessor.from_pretrained(model_name)
model = CLIPModel.from_pretrained(model_name)
print("模型載入完成!")
# --- 2. 準備輸入圖片 ---
image_url = "http://images.cocodataset.org/val2017/000000039769.jpg"
response = requests.get(image_url, stream=True)
image = Image.open(response.raw)
# --- 3. 準備我們的候選文本標籤 ---
# 這些是我們自己定義的、模型在訓練時可能從未見過的分類任務
candidate_labels = ["a photo of a cat", "a photo of a dog", "a photo of a zebra", "a landscape painting"]
# --- 4. 進行零樣本分類預測 ---
# processor 會同時處理圖片和文本,並返回一個字典
inputs = processor(
text=candidate_labels,
images=image,
return_tensors="pt",
padding=True
)
# 進行前向傳播,得到圖像特徵和文本特徵
# .image_features 和 .text_features 是 CLIP 模型輸出的圖像/文本嵌入
outputs = model(**inputs)
image_features = outputs.image_embeds
text_features = outputs.text_embeds
# 計算 logits (圖像與文本特徵的點積,與餘弦相似度成正比)
# CLIP 論文中建議使用 logits_per_image
logits_per_image = outputs.logits_per_image
# 將 logits 轉換為機率分佈
probs = logits_per_image.softmax(dim=1)
# --- 5. 解讀輸出結果 ---
print("\n--- 零樣本分類結果 ---")
# 將機率與標籤打包並排序
results = zip(probs[0].tolist(), candidate_labels)
sorted_results = sorted(results, key=lambda x: x[0], reverse=True)
for prob, label in sorted_results:
print(f"{label:<30} | 機率: {prob:.4f}")
# 顯示圖片
import matplotlib.pyplot as plt
plt.imshow(image)
plt.title("Test Image")
plt.axis('off')
plt.show()
結果
--- 零樣本分類結果 ---
a photo of a cat | 機率: 0.9917
a photo of a dog | 機率: 0.0051
a photo of a zebra | 機率: 0.0029
a landscape painting | 機率: 0.0003