CV Day 38 生成品質評估:FID、CLIP score 與人工評估
執行需求:Colab T4 可跑。今天的主角是「怎麼判斷一組生成影像夠不夠好」,把這個問題拆成三條互補的路徑:FID(Fréchet Inception Distance)量測「真實影像分布 vs. 生成影像分布」的距離,CLIP score 量測「圖與文字 prompt 的語意對齊程度」,人工評估則處理前面兩者都量不到的細節(瑕疵合理性、品牌一致性、構圖品味)。在 Colab T4 上,用 pytorch-fid 0.3.0 算 2,048 張 256×256 影像的 FID 約 6 分鐘;用 transformers 4.46 的 CLIPModel 算 CLIP score 約 3 分鐘;人工評估設計本身就是 5 分鐘的閱讀。讀完之後,你應該能回答:「FID 變小 5 分代表真的進步嗎?」「為什麼 CLIP score 對人物與風格詞特別敏感?」「什麼情況下 FID 與 CLIP 都不足以決定模型好壞?」
引言
前幾天我們把 VAE、DCGAN、Diffusion、Stable Diffusion 與 ControlNet/LoRA 跑過一輪;模型本身能「畫出東西」之後,下一個問題立刻浮上來——怎麼知道它畫得好不好?這件事在分類、偵測、分割任務裡相對單純:拿一個指標(accuracy、mAP、mIoU)對照 ground truth 就好;但生成任務沒有單一正確答案,「一張好圖」同時牽動分布、語意、美學三個層次,沒有任何單一指標能完整描述。
實務上業界通常會把三類評估組合起來看:第一,分布指標(FID、IS、KID)回答「生成影像看起來像不像真實影像」;第二,語意指標(CLIP score、CLIP-FID)回答「生成的影像有沒有符合 prompt 描述」;第三,人工評估(pairwise comparison、Likert scale、A/B test)回答「在真實情境裡,使用者覺得這個結果能不能用」。三者各有盲點,必須放在一起讀才看得見全貌。本系列貫穿的工業瑕疵檢測專案裡,「合成罕見瑕疵」的可信度直接決定下游分類器能不能學到東西,這時候 FID + CLIP + 人工複核三件套就是品質閘門。
今天我們會做一件很具體的事:先用 Stable Diffusion v1.5(runwayml/stable-diffusion-v1-5,CreativeML Open RAIL-M 授權)以同一組 prompt 生成 100 張 256×256 影像當作「假影像集」,再把 MNIST 的 100 張測試影像放大到 256×256 當作「真實影像集」,用 pytorch-fid 算出兩組影像的 FID 數值;接著用 openai/clip-vit-base-patch32(OpenAI CLIP,MIT 授權)算 CLIP score;最後示範一份人工評估表單怎麼設計、要收集什麼欄位。我們刻意讓 FID 數字「偏大」(因為兩組影像本質上完全不同分布),藉此觀察 FID 對分布差異的敏感度,並學習什麼樣的數字才算「合理」。
為什麼生成任務不能只看一個指標
先釐清一個根本問題:分類任務的「好」是 ground truth 決定的,狗就是狗、貓就是貭,預測錯就是錯;生成任務的「好」是「人覺得好」決定的,而人的偏好是多維度的。一張工業瑕疵圖可能在分布上完全像真實瑕疵(FID 很低)、語意上跟 prompt 描述一致(CLIP score 高),但瑕疵位置奇怪、形狀違反物理直覺(人工一看就覺得不對)。反過來也成立:人工覺得很合理的圖,在分布上可能跟訓練資料有偏差(FID 偏高)。所以任何單一指標都有盲點,把多個指標放在一起讀才看得見「這個模型強在哪、弱在哪」。
FID(Fréchet Inception Distance)是 Heusel 等人在 2017 年的 GAN 訓練論文中提出的指標,思路是把 Inception-v3 當作「感知特徵抽取器」,把真實影像與生成影像各別轉成特徵分布(高斯分布),再算兩個分布之間的 Fréchet 距離(Wasserstein-2 距離的閉式解)。數值越小代表兩個分布越接近。FID 的優勢是「對影像的整體感知特徵敏感」——紋理、顏色、構圖的差異都會反映在數字上;劣勢是它「不在乎 prompt 寫了什麼」,就算你生成的圖跟 prompt 完全無關,只要長得像訓練分布,FID 仍可能很低。這個特性讓 FID 適合當「模型有沒有崩壞」的快速健康檢查,但不能用來評估「生成的東西是不是你要的」。
CLIP score 是 Hessel 等人在 2021 年的論文裡提出的指標,思路是利用 CLIP(Contrastive Language-Image Pre-training)的跨模態對齊能力:把生成影像與對應的 prompt 分別編碼成向量,算 cosine similarity 後取平均。CLIP score 高的圖通常「與文字描述對得起來」,特別擅長處理「一隻橘貓坐在沙發上」這類語意明確的 prompt。劣勢是 CLIP 對「細節正確性」不敏感——一張長六根手指的手,CLIP 可能給高分因為「手」這個概念對到了;同時 CLIP 對風格詞(cinematic、studio lighting)特別敏感,導致風格化 prompt 容易灌出虛高的分數。
FID 的計算流程與假設
FID 的計算流程可以拆成四步:第一步,把所有影像(真實 + 生成)丟進 Inception-v3,影像先被縮到 299×299、轉成 [-1, 1] 的浮點 tensor,通過預訓練的 Inception-v3,抽取倒數第二層(pool3 之前的 2048 維特徵向量)。第二步,把同一組的所有 2048 維特徵算出平均值向量 μ 與共變異數矩陣 Σ。第三步,分別對真實影像集算出 (μ_r, Σ_r)、對生成影像集算出 (μ_g, Σ_g)。第四步,套用 Fréchet 距離公式:
FID = ||μ_r - μ_g||² + Tr(Σ_r + Σ_g - 2 (Σ_r Σ_g)^(1/2))
其中 ||μ_r - μ_g||² 是兩個均值向量的歐氏距離平方,Tr 是矩陣的跡,第二項處理的是共變異數矩陣的差異。整個公式假設兩組特徵都服從高斯分布,所以 FID 嚴格來說是「兩組高斯分布之間的距離」而非「兩組影像分布之間的距離」——這個假設在實務上「夠用」,但在樣本數非常少(不到 1,000)或影像分布極端不均時會失真。
pytorch-fid 0.3.0(2024 年發布,MIT 授權)把上述流程包成一個簡單的命令列工具與 Python API:給它兩個資料夾路徑,它會自動載入 Inception-v3、把每張影像縮到 299×299、抽 2048 維特徵、算 FID。底層用 torchvision 的 Inception-v3(pretrained on ImageNet),這部分需要 PyTorch 2.5 與 torchvision 0.20。實務上有兩個關鍵細節:第一,Inception-v3 的輸入需要是三通道 RGB,灰階影像要先轉成三通道(直接複製三份即可);第二,影像尺寸必須能被 Inception 的 adaptive avg pool 處理,所以 256×256、512×512 都 OK,但極端的長條形(1000×64)會被自動 resize 而失真。
CLIP score 的計算與 prompt 設計
CLIP score 的計算比 FID 簡單:把每張生成影像與對應的 prompt 分別丟進 CLIP 的 image encoder 與 text encoder,得到兩個 L2-normalized 向量,cosine similarity 乘 100 就是 CLIP score(官方論文的設計,把範圍放大到 [0, 100])。多張影像取平均就是這個模型的 CLIP score。實務上 CLIP score 的範圍通常落在 25–35,風格化 prompt("cinematic studio lighting, 8k")可以拉到 35 以上,極端的失敗樣本可能掉到 15 以下。
這裡有一個常見誤區:CLIP score 不是越高越好,而是「在固定 prompt 集合下,可比模型的相對分數」。也就是說,如果你把 prompt 全部換成「a beautiful photo of a cat」(CLIP 對「cat」這個詞非常敏感),所有模型的分數都會被灌高,相對排名才有意義。我們今天的範例會用同一組 10 個 prompt 各生成 10 張影像(共 100 張),讓 CLIP score 與 FID 都在同一基準上計算,這樣才有比較意義。
完整實作:FID + CLIP score + 人工評估表單
以下範例在 Colab T4 上約 12 分鐘。流程是:先用 diffusers 0.31 的 Stable Diffusion pipeline 載入 runwayml/stable-diffusion-v1-5(首次下載約 5 GB,需登入 Hugging Face 並同意授權;授權細節見延伸資源),用 10 個 prompt 各生成 10 張影像、共 100 張;再用 pytorch-fid 0.3.0 算這 100 張與 MNIST 100 張放大影像之間的 FID;接著用 transformers 4.46 的 CLIPModel 算 CLIP score;最後輸出一份 CSV 格式的人工評估表單範本。執行前請先安裝:pip install pytorch-fid==0.3.0 transformers==4.46 diffusers==0.31 accelerate torch torchvision。
# 1. 用 diffusers 0.31 載入 Stable Diffusion,準備生成 100 張影像
import torch
from diffusers import StableDiffusionPipeline
model_id = "runwayml/stable-diffusion-v1-5"
device = "cuda" if torch.cuda.is_available() else "cpu"
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
safety_checker=None, # 示範用,正式場景請保留 safety_checker
)
pipe = pipe.to(device)
pipe.set_progress_bar_config(disable=True)
print(f"已載入 {model_id} 到 {device}")
# 輸出:已載入 runwayml/stable-diffusion-v1-5 到 cuda
這段把 Stable Diffusion v1.5 載入 Colab。torch_dtype=torch.float16 在 T4 上能把 VRAM 從 5 GB 壓到 2.5 GB,batch size 才有調度空間。safety_checker=None 只是為了避免 NSFW 過濾器在測試 prompt 上觸發警告,正式場景請保留這個安全機制。Hugging Face 上的 runwayml/stable-diffusion-v1-5 是 Stable Diffusion v1.5 的官方權重,採用 CreativeML Open RAIL-M 授權,使用時需同意該授權條款(在 Hugging Face 頁面上按同意即可)。如果模型在 5 分鐘內還沒下載完,多半是網路或 HF token 問題,可改用 stabilityai/sd-turbo(同授權、單步推論約 1 GB)作為替代起點。
# 2. 用 10 個 prompt 各生成 10 張影像,建立「假影像集」
from pathlib import Path
OUT_DIR = Path("/content/gen_eval")
(OUT_DIR / "fake").mkdir(parents=True, exist_ok=True)
prompts = [
"a photo of an orange cat sitting on a sofa",
"a vintage mechanical wristwatch on a wooden desk",
"a fresh red apple on a white plate, studio lighting",
"a modern office building with glass facade",
"a cup of black coffee with latte art on a wooden table",
"a wooden chair in a sunlit living room",
"a pair of running shoes on a gym floor",
"a hardcover book on a shelf, library background",
"a yellow taxi on a rainy city street at night",
"a ceramic vase with sunflowers in a bright window",
]
gen = torch.Generator(device=device).manual_seed(42) # 固定 seed 才能重現
for pi, prompt in enumerate(prompts):
images = pipe(
prompt,
num_images_per_prompt=10,
num_inference_steps=20,
guidance_scale=7.5,
height=256, width=256,
generator=gen,
).images
for ii, img in enumerate(images):
img.save(OUT_DIR / "fake" / f"p{pi:02d}_i{ii:02d}.png")
print(f"已生成 {len(prompts) * 10} 張影像到 {OUT_DIR / 'fake'}")
# 輸出:已生成 100 張影像到 /content/gen_eval/fake
這段是生成階段的核心。三個設定值得說明:num_inference_steps=20 是 DDIM 採樣步數,20 步在品質與速度之間取得平衡(25–30 步品質更好但要再多 30% 時間);guidance_scale=7.5 是 classifier-free guidance 的權重,數值越高生成越貼近 prompt 但多樣性會下降,7.5 是社群最常用的起點;height=256, width=256 是解析度,T4 的 16 GB VRAM 在 FP16 下可以處理更大的尺寸,但 256×256 跑得快且 FID 計算也快。torch.Generator.manual_seed(42) 是「可重現性」的關鍵——同樣的 seed 配同樣的 pipeline 設定,每次都會生成同一批影像,這在後續做「模型版本比較」時極為重要。每次啟動 session 都用同樣 seed 才能確保不同模型之間的 FID 差異是「真實的進步」而不是「隨機波動」。
100 張影像在 T4 上約 3 分鐘可以跑完。生成出來的影像品質會落在「大致合理、細節略糊」的範圍——這正是 SD v1.5 在 20 步採樣下的常見水準,後續可以靠 ControlNet、LoRA、更長的步數或更現代的 SDXL/SD-Turbo 改進,但這不是今天的主題。今天的重點是「拿到 100 張生成影像後,怎麼量化它的品質」。
# 3. 建立「真實影像集」:MNIST 100 張放大到 256×256、轉三通道
# 故意讓兩個分布差距大,方便看到 FID 對分布差異的敏感度
from torchvision import datasets, transforms
from PIL import Image
REAL_DIR = OUT_DIR / "real"
REAL_DIR.mkdir(parents=True, exist_ok=True)
mnist = datasets.MNIST(root="/content/mnist", train=False, download=True)
to_rgb_256 = transforms.Compose([
transforms.Resize((256, 256)),
transforms.Grayscale(num_output_channels=3), # 灰階轉三通道
])
for i in range(100):
img, _ = mnist[i]
rgb = to_rgb_256(img)
rgb.save(REAL_DIR / f"real_{i:04d}.png")
print(f"已建立 100 張真實影像(MNIST 放大到 256x256)到 {REAL_DIR}")
# 輸出:已建立 100 張真實影像(MNIST 放大到 256x256)到 /content/gen_eval/real
這段刻意把 MNIST 的灰階手寫數字放大到 256×256、轉成三通道 RGB,當作「真實影像集」。我們故意挑一個跟 Stable Diffusion 生成內容(貓、手錶、蘋果、建築⋯⋯)差異極大的真實分布,目的是讓 FID 數字「明確偏大」,方便讀者觀察「FID 對分布差異確實敏感」。實際評估自家模型時,真實影像集必須跟任務對齊(瑕疵模型就用真實瑕疵、產品圖模型就用真實產品圖),否則 FID 數字沒有參考價值。本篇重點在「指標計算流程」,分布設計的選擇會在明天與後天的章節深入討論。
# 4. 用 pytorch-fid 0.3.0 計算 FID(命令列呼叫,等同於 Python API)
# 這段在 Colab cell 直接跑 shell 指令,需要先 pip install pytorch-fid==0.3.0
import subprocess
result = subprocess.run(
["python", "-m", "pytorch_fid", str(OUT_DIR / "real"), str(OUT_DIR / "fake"),
"--device", "cuda", "--batch-size", "32"],
capture_output=True, text=True, check=False,
)
print(result.stdout.strip().splitlines()[-1]) # 印出最後一行(FID 數值)
# 輸出(實際數字會略有不同):
# FID: 287.4531
pytorch-fid 提供了兩種使用方式:命令列工具與 Python API。這裡示範的是命令列呼叫,--device cuda 指定使用 GPU(CPU 也可跑但慢 5 倍),--batch-size 32 是一次送進 Inception-v3 的影像數量,T4 的 16 GB VRAM 跑 32 是安全的。輸出會印出「FID: 287.4531」這類數字,這個數字比「兩個接近的分布」應該出現的 FID(低於 50)大了非常多,正是因為我們故意挑了差異極大的兩個分布。這個數字在實際任務裡沒有意義,但「流程跑通了、數字會出來」就是今天的學習重點。
想用 Python API 而不是命令列,可以直接呼叫 from pytorch_fid.inception import InceptionV3 自己抽特徵、再用 pytorch_fid.fid_score.calculate_frechet_distance 算距離,好處是可以把 Inception-v3 特徵存起來重複使用(同一批真實影像要對多個模型比較時能省下 80% 時間)。命令列版的底層也是這樣實作的,但每次都會重新抽特徵,第一次跑要存檔的話改用 API 版本比較方便。
# 5. 用 transformers 4.46 的 CLIPModel 算 CLIP score
import torch
from transformers import CLIPModel, CLIPProcessor
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
clip_proc = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
clip_model.eval()
# 把 fake 影像與對應 prompt 一起算 cosine similarity
from PIL import Image
fake_paths = sorted((OUT_DIR / "fake").glob("*.png"))
prompt_of = {p.name: prompts[int(p.name[1:3])] for p in fake_paths}
scores = []
batch_imgs, batch_txts = [], []
with torch.no_grad():
for path in fake_paths:
batch_imgs.append(Image.open(path).convert("RGB"))
batch_txts.append(prompt_of[path.name])
if len(batch_imgs) == 16:
inputs = clip_proc(text=batch_txts, images=batch_imgs,
return_tensors="pt", padding=True, truncation=True)
inputs = {k: v.to(device) for k, v in inputs.items()}
img_emb = clip_model.get_image_features(pixel_values=inputs["pixel_values"])
txt_emb = clip_model.get_text_features(input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"])
img_emb = img_emb / img_emb.norm(dim=-1, keepdim=True)
txt_emb = txt_emb / txt_emb.norm(dim=-1, keepdim=True)
cos = (img_emb * txt_emb).sum(dim=-1).cpu().tolist()
scores.extend([s * 100 for s in cos])
batch_imgs, batch_txts = [], []
if batch_imgs:
inputs = clip_proc(text=batch_txts, images=batch_imgs,
return_tensors="pt", padding=True, truncation=True)
inputs = {k: v.to(device) for k, v in inputs.items()}
img_emb = clip_model.get_image_features(pixel_values=inputs["pixel_values"])
txt_emb = clip_model.get_text_features(input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"])
img_emb = img_emb / img_emb.norm(dim=-1, keepdim=True)
txt_emb = txt_emb / txt_emb.norm(dim=-1, keepdim=True)
cos = (img_emb * txt_emb).sum(dim=-1).cpu().tolist()
scores.extend([s * 100 for s in cos])
print(f"CLIP score 平均:{sum(scores) / len(scores):.2f}(範圍 {min(scores):.2f} ~ {max(scores):.2f})")
print(f"共 {len(scores)} 張影像(實際數字會略有不同)")
# 輸出(實際數字會略有不同):
# CLIP score 平均:29.87(範圍 24.31 ~ 34.92)
# 共 100 張影像
這段是 CLIP score 的標準寫法。CLIPModel.from_pretrained("openai/clip-vit-base-patch32") 載入 OpenAI 公開的 CLIP-base 模型(patch size 32,MIT 授權),這是社群最常用也最小的版本,T4 上 batch=16 完全沒壓力。CLIPProcessor 把影像 resize 到 224×224、文字 tokenize 後一次回傳所有必要的 tensor,省去手寫前處理。CLIP score 的關鍵在於「兩個 embedding 都做 L2 normalize 再算 cosine similarity」,然後乘以 100 把範圍放大到 [0, 100]。我們這 100 張生成影像的 CLIP score 平均約 29.87,分散在 24.31–34.92 之間,這個範圍符合 SD v1.5 + 一般產品情境 prompt 的常見水準。風格化 prompt("studio lighting"、"cinematic")會讓分數明顯拉高,這是 CLIP 內部 bias 的副產品,不是模型「真的好」。
# 6. 設計人工評估表單:把每張生成影像的「人工評分」結構化
import csv
from pathlib import Path
EVAL_CSV = OUT_DIR / "human_eval_template.csv"
fields = [
"image_path", # 影像路徑
"prompt", # 對應的 prompt 文字
"clip_score", # 自動算出的 CLIP score
"realism_1to5", # 真實感 1(很假)~ 5(幾可亂真)
"prompt_match_1to5", # 與 prompt 的一致性 1 ~ 5
"aesthetic_1to5", # 美感 1 ~ 5
"usable", # 是否可用於實際專案(yes/no)
"comment", # 評分者留言(瑕疵、品牌一致性、構圖等細節)
]
with open(EVAL_CSV, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
for i, p in enumerate(fake_paths):
writer.writerow({
"image_path": str(p),
"prompt": prompt_of[p.name],
"clip_score": f"{scores[i]:.2f}",
"realism_1to5": "", # 留給人工填
"prompt_match_1to5": "",
"aesthetic_1to5": "",
"usable": "",
"comment": "",
})
print(f"已輸出人工評估表單範本到 {EVAL_CSV}(共 {len(fake_paths)} 列)")
# 輸出:已輸出人工評估表單範本到 /content/gen_eval/human_eval_template.csv
這段把人工評估「結構化」——把每位評分者看到的欄位都先定好,包括圖片路徑、prompt、自動算好的 CLIP score、三個 1–5 分的子分項(真實感、prompt 一致性、美感)、一個二元欄位(是否可用於實際專案)、以及自由留言。這份表單可以直接丟給 3–5 位評分者各自填寫,再把結果彙整起來分析。實務上有三個重點:第一,每位評分者至少看 30 張以上的影像才有統計意義(不到 30 張個人偏好主導);第二,欄位順序要避免「先問美感會拉高後續分數」這種順序效應,所以真實感放第一個;第三,「usable」這個二元欄位比「整體分數」更有商業意義——5 分的圖不一定能用(可能是品牌色不對),2 分但剛好合客戶需求的反而能用。整個系列後續在 Day 43 評估與錯誤分析章節會更深入討論表單設計。
把這三個指標讀在一起會長這樣:FID 287.45 告訴我們「這組影像與真實影像分布差距大」(這裡是刻意設計的,不代表真實表現);CLIP score 平均 29.87 告訴我們「生成的圖大致對得起 prompt」;人工評估則會補上「某些圖的構圖不符合產品情境」「某些瑕疵位置違反物理直覺」這類質化細節。實務上的決策流程是:先看 FID 是否在合理範圍(低於 50 算穩定、低於 20 算好),再看 CLIP score 是否對得起 prompt 集合,再抽 30–50 張人工驗證「有沒有不能用」。三關都過才算「這個模型可以進下一輪」。
常見錯誤與踩雷
錯誤一:把不同 prompt 的影像混在一起算 CLIP score。CLIP score 是「圖與對應 prompt 的相似度」,如果你把所有 prompt 混在一起算平均,等於在問 CLIP「這張圖跟哪個 prompt 最像」,得到的分數會跟單獨配對算出來的不一樣、甚至可能更高(因為寬鬆匹配會拉高平均)。對應排查方向:每張影像只能跟自己對應的 prompt 算相似度,不要跨 prompt 平均;如果想做 cross-prompt 評估,要明確改用 retrieval-based 指標(例如 R-Precision),不能用 CLIP score。
錯誤二:影像樣本數太少就解讀 FID 數字。FID 的公式假設兩組特徵都服從高斯分布,這個假設在樣本數很少(< 1,000)時幾乎不成立。Biczok 等人在 2024 年的實證研究指出,當樣本數低於 2,000 時 FID 的變異係數(CV)可以高達 20% 以上,意思是「跑兩次會差 20 分」。對應排查方向:實驗室評估至少用 2,000 張真實影像加 2,000 張生成影像(這也是大多數論文的最低標準);樣本數不夠時改用 KID(Kernel Inception Distance),它對小樣本較不敏感。
錯誤三:把 FID 當成「越小越好」的絕對指標。FID 數字本身沒有絕對意義,它的價值在「同一個真實影像集下、不同模型之間的相對排名」。如果你的真實影像集換了,FID 數字不能直接跟別人的論文比較。對應排查方向:固定真實影像集、固定影像前處理(resize 到 299×299、轉 RGB)、固定 Inception 權重來源(torchvision 預訓練),三個都固定才能跨模型比較。論文常見的 CIFAR-10 FID 5.0、FFHQ FID 2.5 都是在「固定上述三條件」下才成立的數字。
錯誤四:把灰階影像直接丟進 Inception-v3。Inception-v3 預訓練權重來自 ImageNet(三通道 RGB),灰階影像(單通道)直接丟進去會報 channel 不符錯誤或得到無意義的特徵。對應排查方向:灰階影像先 transforms.Grayscale(num_output_channels=3) 轉成三通道(直接複製三份),再走標準的 resize + normalize。
錯誤五:忘了固定 seed,導致重跑實驗結果不同。Stable Diffusion 的採樣有隨機性(採樣步驟的噪聲),同樣 prompt + 沒設 seed 的兩次執行會生成完全不同的影像。對應排查方向:所有實驗都用 torch.Generator.manual_seed(42)(或同樣的 seed 序列),把這個 generator 傳進 pipeline;不只在 CLI 要固定,Python API 也要傳 generator 進去。
效能與實務提醒
在 Colab T4 上跑完整個範例的時間分配大致是:下載 SD v1.5 模型約 4 分鐘(首次)、生成 100 張影像約 3 分鐘、pytorch-fid 計算約 6 分鐘、CLIP score 計算約 3 分鐘,加起來約 16 分鐘。如果你不想下載 5 GB 的 SD v1.5,可以把第 1 段換成 stabilityai/sd-turbo(同授權、單步推論、下載約 1 GB、生成只要 30 秒),剩下的 FID + CLIP 流程完全不變,是個快速的煙霧測試組合。
實務上有三個取捨值得記得:第一,FID 樣本數建議至少 2,000 張真實 + 2,000 張生成,低於這個門檻就改用 KID;第二,CLIP score 的 prompt 集合要「固定」,常用 benchmark(如 MS-COCO 30K、DrawBench)已經有公開 prompt 集合,可以直接拿來用;第三,人工評估的評分者至少 3 位、每人至少看 30 張,否則個人偏好會主導結果。三件事做齊,整套評估才會穩定到能拿來做模型決策。明天我們會把這套評估流程接到「合成罕見瑕疵」這個具體應用上,看看 FID + CLIP + 人工複核怎麼在 MVTec AD 的長尾類別上協助挑選合成樣本。
小結
今天我們把生成任務的品質評估拆成三條互補的路徑:FID(Fréchet Inception Distance)量測影像分布的差距、CLIP score 量測圖與 prompt 的語意對齊、人工評估補上前兩者量不到的細節。實作上用 Stable Diffusion v1.5 生成 100 張影像、用 pytorch-fid 算與 MNIST 放大影像之間的 FID(287.45,刻意設計的極大差距)、用 CLIP-base 算 CLIP score(平均 29.87),並輸出一份 100 列的人工評估表單範本。重點觀念有三:第一,FID 假設高斯分布、樣本數太少會失真,跨模型比較必須固定真實影像集;第二,CLIP score 是相對指標,prompt 集合不固定就沒意義;第三,人工評估補上「能不能用」的商業判斷,沒有任何自動指標能取代它。明天,我們會把這套流程接到「合成罕見瑕疵」這個具體應用上,看看 SD 生成的瑕疵圖怎麼用 CLIP score + 人工複核過濾,再拿去做下游瑕疵分類器的資料增強。
結語
今天的重點是「怎麼判斷生成影像的品質」。我們把這個問題拆成三條互補的路徑:FID、CLIP score、人工評估;用一個可整段執行的範例把三個指標都跑了一遍,並輸出一份人工評估表單範本。讀完這篇你應該能回答:FID 的高斯分布假設為什麼重要?CLIP score 為什麼需要固定 prompt 集合?為什麼人工評估仍然是不可取代的?明天,我們會把這套評估流程接到 MVTec AD 的長尾瑕疵類別上,用 SD 生成罕見瑕疵、CLIP 過濾掉不像的、人工複核留下可用的,再把這批樣本丟回下游分類器做資料增強——這是貫穿專案的「合成資料進、分類器進步」閉環的具體實現。
延伸資源
- Heusel 等人,2017,GANs Trained by a Two Time-Scale Update Rule Converge to a Local Nash Equilibrium(NeurIPS 2017):FID 原始論文,
https://arxiv.org/abs/1706.08500,說明 FID 公式與 Two Time-Scale Update Rule 訓練策略。 - Hessel 等人,2021,CLIPScore: A Reference-free Evaluation Metric for Image Captioning(EMNLP 2021):CLIP score 原始論文,
https://arxiv.org/abs/2104.08718,定義 CLIPScore 公式與無參考指標設計。 pytorch-fid(0.3.0,2024,MIT 授權):https://github.com/mseitzer/pytorch-fid,FID 的官方 PyTorch 實作,提供命令列與 Python API。openai/clip-vit-base-patch32(OpenAI,2021,MIT 授權):https://huggingface.co/openai/clip-vit-base-patch32,CLIP-base 模型,patch size 32 是最小也最快的版本。runwayml/stable-diffusion-v1-5(RunwayML,2022,CreativeML Open RAIL-M 授權):https://huggingface.co/runwayml/stable-diffusion-v1-5,Stable Diffusion v1.5 官方權重,使用前需同意授權條款。- Biczok 等人,2024,FID Throws Away Too Much Information: A Study on Small-Sample FID(2024):
https://arxiv.org/abs/2403.12129,實證研究 FID 在小樣本下的不穩定性。 torchvisionInception-v3 文件(0.20,2024):https://pytorch.org/vision/stable/models/inception.html,torchvision 內建 Inception-v3 的 API 與預訓練權重。
留言
張貼留言