過去十幾天,我們所介紹的所有模型,基本上都建立在 CNN 上。CNN 在電腦視覺領域統治了約 10 年之久,但來自自然語言處理 (Natural Language Processing, NLP) 挑戰者,在過去幾年撼動了這個地位。
Transformer,它最初是為了解決機器翻譯等 NLP 任務而設計的,它運用了自注意力機制 (self-attention) 來解決傳統循環神經網路 (Recurrent Neural Network, RNN) 不易捕捉到相距很遠的單詞之間依賴關係的問題。
自注意力機制的思想是:在處理一個序列中的某個元素(例如一個單詞)時,模型應該能夠動態地、有選擇性地「關注」到序列中所有其他與其最相關的元素,並根據這些相關性來更新自身的表示。
它的計算流程大致如下:
生成 Q, K, V:對於序列中的每一個輸入 token(例如,每個單詞的嵌入向量),都通過三個不同的線性變換,生成三個向量:查詢 (Query, Q)、鍵 (Key, K) 和 值 (Value, V)。
Q 代表:「我正在尋找什麼?」
K 代表:「我擁有什麼樣的資訊?」
V 代表:「我實際攜帶的內容是什麼?」
計算注意力分數:用每個 token 的 Q 向量,去和所有其他 token 的 K 向量進行點積運算。這個點積的結果,就代表了這兩個 token 之間的「相關性分數」或「注意力權重」。
Softmax 歸一化:將這些分數通過一個 Softmax 函數,將其轉換為總和為 1 的機率分佈。
加權求和:用這些歸一化後的注意力權重,去對所有 token 的 V 向量進行加權求和。
最終,每個 token 的輸出,都包含了整個序列中所有其他 token 的資訊,並且是根據「相關性」動態加權融合後的結果。這種機制使得 Transformer 具有強大的全局感受域 (global receptive field),能夠捕捉長距離的依賴關係。
那麼,如何才能將一張二維的圖像,轉換成 Transformer 可以處理的一維序列呢?Google Brain 團隊在 2020 年提出了一個簡單有效的方案:Vision Transformer (ViT)。
ViT 的工作流程如下:
圖像分塊 (Image Patching):這是 ViT 的核心思想。將一張輸入的圖片(例如 224×224),像切蛋糕一樣,分割成 N 個不重疊的、固定大小的小圖像塊 (Patch)。例如,如果每個 Patch 的大小是 16×16,那麼我們就會得到 (224/16) × (224/16) = 14 × 14 = 196 個 Patch。
展平與線性投射:將每個 16×16×3 的 Patch,「攤平」成一個一維的向量,然後通過一個線性投射層(全連接層),將其嵌入到一個固定的維度 D 中。現在我們得到了一個長度為 196 的、由 D 維向量組成的序列。一張圖片,就這樣被轉換成了一句句子,而每個 Patch 就是一個單詞。
[CLS] Token:借鑒 BERT 模型的思想,在序列的最前面,額外加入一個可學習的 [class] 嵌入向量。在經過 Transformer 編碼後,這個 [class] token 對應的最終輸出,將被用作整張圖片的聚合特徵,送入分類頭進行分類。
位置編碼 (positional encoding):Transformer 本身並不包含任何關於序列順序的資訊。為了讓模型知道每個 Patch 的原始空間位置,我們需要為每個 Patch 嵌入向量,都加上一個可學習的「位置編碼」,來保留其空間資訊。
Transformer 編碼器:將這個帶有位置編碼的 Patch 序列,送入一個標準的 Transformer 編碼器(由多個自注意力層和 ANN 層堆疊而成)進行處理。
分類頭:最後,取出 [class] token 對應的輸出,通過一個小型的 ANN(分類頭),得到最終的分類結果。
歸納偏置:CNN 具有很強的歸納偏置(局部性、平移不變性),這使得它在數據量較小時,也能學得很好。而 ViT 的歸納偏置則弱得多,它對數據的結構幾乎沒有任何先驗假設,因此需要極其龐大的數據集(例如 Google 內部的 JFT-300M,包含 3 億張圖片)進行預訓練,才能學到普適的視覺模式。
性能:當在超大規模的數據集上進行預訓練,然後遷移到中等規模的下游任務(如 ImageNet)上時,ViT 的表現超越了當時所有最先進的 CNN 模型。
首先安裝 transformers
pip install transformers
from transformers import ViTFeatureExtractor, ViTForImageClassification
from PIL import Image
import requests
# --- 1. 載入預訓練的 ViT 模型和特徵提取器 ---
# 我們使用 Google 在 ImageNet-21k 上預訓練,並在 ImageNet-1k 上微調的版本
model_name = 'google/vit-base-patch16-224'
print(f"正在從 Hugging Face Hub 下載模型: {model_name}...")
# 特徵提取器負責處理圖片的預處理 (尺寸調整、正規化等)
feature_extractor = ViTFeatureExtractor.from_pretrained(model_name)
model = ViTForImageClassification.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. 進行預測 ---
# 特徵提取器會將 PIL 圖片轉換為模型需要的 Tensor 格式
inputs = feature_extractor(images=image, return_tensors="pt")
# 進行前向傳播
outputs = model(**inputs)
logits = outputs.logits
# --- 4. 解讀輸出結果 ---
# 模型在 ImageNet-1k (1000類) 上微調過
# 找出分數最高的那個類別的索引
predicted_class_idx = logits.argmax(-1).item()
# model.config.id2label 是一個字典,可以將索引映射到類別名稱
predicted_class = model.config.id2label[predicted_class_idx]
print(f"\n--- 預測結果 ---")
print(f"模型預測的類別是: {predicted_class}")
# 顯示圖片
import matplotlib.pyplot as plt
plt.imshow(image)
plt.title(f"Prediction: {predicted_class}")
plt.axis('off')
plt.show()
結果