第 2 編 — MetaCLIP 2 の性能評価と COCO データセットでのゼロショット比較
はじめに
こんにちは、しゅんです。
第1編では、MetaCLIP 2 の概要と基本的な使い方について紹介しました。今回は、MetaCLIP 2 の実力をより深く理解するために、COCO val2017 データセットを用いてゼロショット評価を行います。さらに、評価結果を数値とグラフで視覚化し、MetaCLIP 2 の性能が他のCLIP系モデルとどのように異なるかを比較します。
COCO(Common Objects in Context)は、画像認識タスクの中でも広く使用されているデータセットで、80クラスの物体分類が行えるため、ゼロショット評価においても非常に有用です。また、カスタムベンチマークを設定することで、特定の条件での性能評価や、モデルの汎用性を直接測ることができます。
COCOデータセットとカスタムベンチマーク選定の理由
-
COCO データセットの選定理由
COCOは、物体認識や画像キャプション生成、セグメンテーションタスクなど、多くの視覚的なタスクで使用されるデータセットです。COCOの特徴は、80クラスの物体に関する情報を持つ画像が豊富で、現実世界のシーンにおける物体認識に非常に適しています。そのため、ゼロショット学習の効果を十分に引き出すことができ、MetaCLIP 2 のような多言語対応のモデルが、どれだけの精度で物体を認識できるかを確認するには最適なデータセットです。
-
カスタムベンチマークの選定理由
ゼロショット学習のテストにおいて、標準のベンチマークだけでなく、自分の使用するデータセットやカスタマイズされたタスクを設定することは非常に有効です。カスタムベンチマークを使用することで、以下の点が明確になります:
- モデルの適応性を特定のデータセットで直接確認できる
- 実際の使用シナリオに即した評価ができる
- 他のタスクやデータセットに対する汎用性や誤分類の傾向を詳細に分析できる
COCO データセットを使うことによって、MetaCLIP 2 が実際に現実的なタスクにどれだけ適応できるかを試すことができます。これにより、精度やエラーの傾向を観察し、モデルの改善点や特長をより明確に把握できます。
ベンチマーク設定
-
使用モデル:
-
facebook/metaclip-2-worldwide-giant-378(MetaCLIP 2) -
openai/clip-vit-large-patch14(CLIP-L/14) -
openai/clip-vit-base-patch32(CLIP-B/32)
-
-
データセット: COCO val2017(80クラス、ローカルパスは
path/to/cocoを指定)
path は自分のパスにして -
推論方法: AutoProcessor と AutoModel を使用したゼロショット分類(80カテゴリ全てをテキストラベル化)
-
バッチサイズ:
batch_size=16(GPU環境で最適化、必要に応じて調整) -
出力: 結果は
path/to/benchmark_results.txtとpath/to/benchmark_results.pngに保存
Code
from pathlib import Path
from typing import Dict, List, Set
# Short labels for plotting readability
plot_labels = {
"facebook/metaclip-2-worldwide-giant-378": "MetaClip2-Giant",
"openai/clip-vit-large-patch14": "CLIP-L/14",
"openai/clip-vit-base-patch32": "CLIP-B/32",
}
import torch
from PIL import Image
from pycocotools.coco import COCO
from tqdm import tqdm
from transformers import AutoModel, AutoProcessor
import matplotlib.pyplot as plt
# COCO setup
data_dir = Path("path/to/datasets/coco")
data_type = "val2017"
ann_file = data_dir / "annotations" / f"instances_{data_type}.json"
if not ann_file.exists():
raise FileNotFoundError(f"Annotation file not found at {ann_file}. Please adjust data_dir.")
coco = COCO(str(ann_file))
# Target categories you want to evaluate.
# - Set to None to use all COCO categories (80).
# - Or provide a subset of category IDs, e.g., [3, 8, 18] for car, truck, dog.
target_category_ids: List[int] | None = None
if target_category_ids is None:
target_category_ids = sorted(coco.getCatIds())
cats = coco.loadCats(target_category_ids)
labels: List[str] = [f"a photo of a {cat['name']}" for cat in cats]
label_to_category: Dict[str, int] = {label: cat["id"] for label, cat in zip(labels, cats)}
num_images: int | None = None # None = use all images that contain the target categories
batch_size: int = 64 # images per forward pass for speed/memory trade-off
device = torch.device(
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
preferred_dtype = torch.bfloat16 if device.type == "cuda" else torch.float32
output_path = Path(__file__).with_name("benchmark_results.txt")
def load_local_image(img_info: Dict) -> Image.Image:
"""Load a COCO image from the local dataset and convert to RGB."""
img_path = data_dir / data_type / img_info["file_name"]
if not img_path.exists():
raise FileNotFoundError(f"Image not found at {img_path}")
return Image.open(img_path).convert("RGB")
def select_images_with_targets(limit: int | None) -> list[tuple[Dict, Set[int]]]:
"""Pick image infos that contain at least one of the target categories (no image pixels loaded)."""
selected: list[tuple[Dict, Set[int]]] = []
for img_id in coco.getImgIds():
ann_ids = coco.getAnnIds(imgIds=img_id, catIds=target_category_ids)
anns = coco.loadAnns(ann_ids)
if not anns:
continue
selected.append((coco.loadImgs(img_id)[0], {ann["category_id"] for ann in anns}))
if limit is not None and len(selected) >= limit:
break
return selected
selected_infos = select_images_with_targets(num_images)
image_infos = [info for info, _ in selected_infos]
model_names = [
"facebook/metaclip-2-worldwide-giant-378",
"openai/clip-vit-large-patch14",
"openai/clip-vit-base-patch32",
]
results = {}
accuracy_records: list[tuple[str, float]] = []
for model_name in model_names:
print(f"Running zero-shot classification with {model_name} ...")
output_lines = [f"Model: {model_name}"]
processor = AutoProcessor.from_pretrained(model_name, use_fast=True)
model = AutoModel.from_pretrained(
model_name,
attn_implementation="sdpa",
torch_dtype=preferred_dtype,
).to(device)
model.eval()
predictions: list[str] = []
confidences: list[float] = []
correct: list[bool] = []
for start in tqdm(range(0, len(selected_infos), batch_size), desc=f"{model_name} batches", leave=False):
end = min(start + batch_size, len(selected_infos))
batch_infos = selected_infos[start:end]
batch_images = [load_local_image(info) for info, _ in batch_infos]
batch_ground_truth = [gt_set for _, gt_set in batch_infos]
inputs = processor(text=labels, images=batch_images, return_tensors="pt", padding=True)
inputs = {k: v.to(device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
with torch.inference_mode():
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image # shape: (batch, num_labels)
probs = logits_per_image.softmax(dim=1)
top_indices = probs.argmax(dim=1)
batch_predictions = [labels[idx] for idx in top_indices.tolist()]
batch_confidences = [probs[i, idx].item() for i, idx in enumerate(top_indices)]
batch_correct = [
label_to_category[pred] in gt for pred, gt in zip(batch_predictions, batch_ground_truth)
]
predictions.extend(batch_predictions)
confidences.extend(batch_confidences)
correct.extend(batch_correct)
accuracy = sum(correct) / len(correct)
results[model_name] = {
"predictions": predictions,
"confidences": confidences,
"correct": correct,
"accuracy": accuracy,
}
print(f"-> {model_name} accuracy on {len(selected_infos)} images: {accuracy:.2f}")
output_lines.append(f"Accuracy: {accuracy:.4f} on {len(selected_infos)} images")
accuracy_records.append((model_name, accuracy))
for info, pred, conf, is_correct in zip(image_infos, predictions, confidences, correct):
status = "OK" if is_correct else "NG"
img_path = (data_dir / data_type / info["file_name"]).resolve()
line = f"- {info['id']}: {pred} ({conf:.3f}) {status} | path={img_path}"
print(line)
output_lines.append(line)
# Append results to output file for easy later inspection
with output_path.open("a", encoding="utf-8") as f:
f.write("\n".join(output_lines))
f.write("\n\n")
print(f"\nDetailed predictions saved to: {output_path.resolve()}")
# Plot accuracy comparison across models
if accuracy_records:
fig, ax = plt.subplots(figsize=(8, 5))
model_labels = [plot_labels.get(name, name) for name, _ in accuracy_records]
model_accs = [acc for _, acc in accuracy_records]
ax.bar(model_labels, model_accs, color="skyblue")
ax.set_ylim(0, 1)
ax.set_ylabel("Accuracy")
ax.set_title("Zero-shot accuracy per model")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plot_path = output_path.with_suffix(".png").resolve()
plt.savefig(plot_path)
print(f"Accuracy plot saved to: {plot_path}")
実行方法
以下のコマンドを使ってベンチマークを実行します。データパスは適切な場所に置き換えてください。
cd path/to/meta/metaclip
python benchmark_meta.py
実行後、各モデルの精度は benchmark_results.png に表示され、詳細な予測結果は benchmark_results.txt に記録されます。
実行サンプル(抜粋)
実行後、benchmark_results.txt には画像ID、予測、確信度、正誤、画像のフルパスが記録されます。例えば、次のような行が出力されます:
- 407646: a photo of a tennis racket (0.873) OK | path=path/to/coco/val2017/000000407646.jpg
- 220310: a photo of a teddy bear (0.751) OK | path=path/to/coco/val2017/000000220310.jpg
- 512403: a photo of a tie (0.422) NG | path=path/to/coco/val2017/000000512403.jpg
各モデルの精度比較は、benchmark_results.png にバーグラフ形式で表示されます。
観察ポイント
- ゼロショットで80クラスの一括判定が行われており、画像認識性能が一目で分かります。
- MetaCLIP 2 は多言語データで学習されているため、COCOの英語ラベルでも高い再現性を見込めます。結果の精度差や信頼度分布に注目することで、傾向をつかむことができます。
- **誤分類例(NG)**を追跡すると、カテゴリ間の混同しやすい例(例: tie vs. scarf)の分析に役立ちます。
パラメータ調整のヒント
-
スループット優先: GPU環境なら
batch_sizeを 32 以上に設定、CPU/MPS 環境では 16 前後を目安にします。OOM(メモリ不足)を避けつつ、バッチサイズを上げることで処理速度が向上します。 -
サブセット評価:
num_imagesに数値を指定することで、評価する画像数を制限し、検証サイクルを短縮できます。 -
カテゴリ限定: 特定のカテゴリのみを評価したい場合は、
target_category_idsに対象カテゴリのIDを配列で指定します(例:[1, 3, 18])。
ちなみに、一時間四十分以上かかりました。(MacbookPro M4 32GBです。)
結果と考察
この編では、MetaCLIP 2 と他の CLIP系モデル の COCO val2017 におけるゼロショット評価を行い、その結果を数値とグラフで示しました。MetaCLIP 2 は他のモデルと比較して、より優れた性能を示す結果となり、多言語学習の強みを確認できました。また、誤分類の詳細を確認することで、分類ミスの傾向や学習データにおける課題を理解することができました。
参考文献
- Meta CLIP 2 論文: Meta CLIP 2: A Worldwide Scaling Recipe (arXiv)
- GitHub リポジトリ: Meta CLIP 2 (GitHub)
これで、第2編ではゼロショット評価を通じて、MetaCLIP 2 と CLIP系モデル の性能比較と、評価結果の可視化方法について紹介しました。次回以降ではさらに深堀りした解析を行う予定です。
今回も最後まで見てくれてありがとうございます。
