第5編 — 多言語プロンプトセットに対するMetaCLIP 2の性能を検証し、精度と確信度を評価
はじめに
こんにちは、しゅんです。
今回は、MetaCLIP 2における多言語プロンプトの性能を、COCOデータセットを使用して比較します。この評価は、12時間以上の計算を経て得られた結果です。これまでの記事では、MetaCLIP 2がいかに強力な多言語対応能力を持ち、また「多言語の呪い」を克服する能力に優れているかについて触れてきましたが、今回はその性能をさらに深掘りし、多言語プロンプトセット(英語、日本語、フランス語、スペイン語、ドイツ語など)を使ったゼロショット評価を通じて、精度と確信度を詳しく評価します。
特に注目すべきは、プロンプトセットに含まれる言語の数が増えることで、モデルの予測精度や確信度がどのように変化するのかを示すことです。結果として、複数の言語が組み合わさると精度が低下する傾向が見られましたが、同時に**GT確率(正解カテゴリ群に対する確信度)**においても興味深い変化が観察されました。この現象がどのようにモデルに影響を与えるかについて、詳細に分析していきます。
Code
# python benchmark_compare_prompts.py --num-images -1 --batch-size 64
import argparse
import random
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Set
import matplotlib.pyplot as plt
import torch
from PIL import Image
from pycocotools.coco import COCO
from tqdm import tqdm
from transformers import AutoModel, AutoProcessor
# Paths and dataset settings
data_dir = Path("/Users/syun/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))
# Category selection (None = all 80 classes)
target_category_ids: List[int] | None = None
if target_category_ids is None:
target_category_ids = sorted(coco.getCatIds())
# COCOカテゴリ英語名→日本語訳(80クラス)
cat_name_to_jp = {
"person": "人",
"bicycle": "自転車",
"car": "車",
"motorcycle": "オートバイ",
"airplane": "飛行機",
"bus": "バス",
"train": "電車",
"truck": "トラック",
"boat": "ボート",
"traffic light": "信号機",
"fire hydrant": "消火栓",
"stop sign": "一時停止標識",
"parking meter": "駐車メーター",
"bench": "ベンチ",
"bird": "鳥",
"cat": "猫",
"dog": "犬",
"horse": "馬",
"sheep": "羊",
"cow": "牛",
"elephant": "象",
"bear": "クマ",
"zebra": "シマウマ",
"giraffe": "キリン",
"backpack": "バックパック",
"umbrella": "傘",
"handbag": "ハンドバッグ",
"tie": "ネクタイ",
"suitcase": "スーツケース",
"frisbee": "フリスビー",
"skis": "スキー板",
"snowboard": "スノーボード",
"sports ball": "ボール",
"kite": "凧",
"baseball bat": "野球バット",
"baseball glove": "野球グローブ",
"skateboard": "スケートボード",
"surfboard": "サーフボード",
"tennis racket": "テニスラケット",
"bottle": "ボトル",
"wine glass": "ワイングラス",
"cup": "カップ",
"fork": "フォーク",
"knife": "ナイフ",
"spoon": "スプーン",
"bowl": "ボウル",
"banana": "バナナ",
"apple": "リンゴ",
"sandwich": "サンドイッチ",
"orange": "オレンジ",
"broccoli": "ブロッコリー",
"carrot": "ニンジン",
"hot dog": "ホットドッグ",
"pizza": "ピザ",
"donut": "ドーナツ",
"cake": "ケーキ",
"chair": "椅子",
"couch": "ソファ",
"potted plant": "観葉植物",
"bed": "ベッド",
"dining table": "ダイニングテーブル",
"toilet": "トイレ",
"tv": "テレビ",
"laptop": "ノートパソコン",
"mouse": "マウス",
"remote": "リモコン",
"keyboard": "キーボード",
"cell phone": "スマートフォン",
"microwave": "電子レンジ",
"oven": "オーブン",
"toaster": "トースター",
"sink": "シンク",
"refrigerator": "冷蔵庫",
"book": "本",
"clock": "時計",
"vase": "花瓶",
"scissors": "ハサミ",
"teddy bear": "テディベア",
"hair drier": "ヘアドライヤー",
"toothbrush": "歯ブラシ",
}
# 簡易フランス語訳
cat_name_to_fr = {
"person": "personne",
"bicycle": "vélo",
"car": "voiture",
"motorcycle": "moto",
"airplane": "avion",
"bus": "bus",
"train": "train",
"truck": "camion",
"boat": "bateau",
"traffic light": "feu de signalisation",
"fire hydrant": "borne d'incendie",
"stop sign": "panneau stop",
"parking meter": "parcmètre",
"bench": "banc",
"bird": "oiseau",
"cat": "chat",
"dog": "chien",
"horse": "cheval",
"sheep": "mouton",
"cow": "vache",
"elephant": "éléphant",
"bear": "ours",
"zebra": "zèbre",
"giraffe": "girafe",
"backpack": "sac à dos",
"umbrella": "parapluie",
"handbag": "sac à main",
"tie": "cravate",
"suitcase": "valise",
"frisbee": "frisbee",
"skis": "skis",
"snowboard": "snowboard",
"sports ball": "ballon de sport",
"kite": "cerf-volant",
"baseball bat": "batte de baseball",
"baseball glove": "gant de baseball",
"skateboard": "skateboard",
"surfboard": "planche de surf",
"tennis racket": "raquette de tennis",
"bottle": "bouteille",
"wine glass": "verre à vin",
"cup": "tasse",
"fork": "fourchette",
"knife": "couteau",
"spoon": "cuillère",
"bowl": "bol",
"banana": "banane",
"apple": "pomme",
"sandwich": "sandwich",
"orange": "orange",
"broccoli": "brocoli",
"carrot": "carotte",
"hot dog": "hot-dog",
"pizza": "pizza",
"donut": "beignet",
"cake": "gâteau",
"chair": "chaise",
"couch": "canapé",
"potted plant": "plante en pot",
"bed": "lit",
"dining table": "table de salle à manger",
"toilet": "toilettes",
"tv": "télé",
"laptop": "ordinateur portable",
"mouse": "souris",
"remote": "télécommande",
"keyboard": "clavier",
"cell phone": "téléphone portable",
"microwave": "micro-ondes",
"oven": "four",
"toaster": "grille-pain",
"sink": "évier",
"refrigerator": "réfrigérateur",
"book": "livre",
"clock": "horloge",
"vase": "vase",
"scissors": "ciseaux",
"teddy bear": "ours en peluche",
"hair drier": "sèche-cheveux",
"toothbrush": "brosse à dents",
}
# 簡易スペイン語訳
cat_name_to_es = {
"person": "persona",
"bicycle": "bicicleta",
"car": "coche",
"motorcycle": "motocicleta",
"airplane": "avión",
"bus": "autobús",
"train": "tren",
"truck": "camión",
"boat": "barco",
"traffic light": "semáforo",
"fire hydrant": "hidrante",
"stop sign": "señal de stop",
"parking meter": "parquímetro",
"bench": "banco",
"bird": "pájaro",
"cat": "gato",
"dog": "perro",
"horse": "caballo",
"sheep": "oveja",
"cow": "vaca",
"elephant": "elefante",
"bear": "oso",
"zebra": "cebra",
"giraffe": "jirafa",
"backpack": "mochila",
"umbrella": "paraguas",
"handbag": "bolso",
"tie": "corbata",
"suitcase": "maleta",
"frisbee": "frisbi",
"skis": "esquís",
"snowboard": "tabla de snowboard",
"sports ball": "balón deportivo",
"kite": "cometa",
"baseball bat": "bate de béisbol",
"baseball glove": "guante de béisbol",
"skateboard": "patineta",
"surfboard": "tabla de surf",
"tennis racket": "raqueta de tenis",
"bottle": "botella",
"wine glass": "copa de vino",
"cup": "taza",
"fork": "tenedor",
"knife": "cuchillo",
"spoon": "cuchara",
"bowl": "cuenco",
"banana": "plátano",
"apple": "manzana",
"sandwich": "sándwich",
"orange": "naranja",
"broccoli": "brócoli",
"carrot": "zanahoria",
"hot dog": "perrito caliente",
"pizza": "pizza",
"donut": "dona",
"cake": "pastel",
"chair": "silla",
"couch": "sofá",
"potted plant": "planta en maceta",
"bed": "cama",
"dining table": "mesa de comedor",
"toilet": "inodoro",
"tv": "televisor",
"laptop": "portátil",
"mouse": "ratón",
"remote": "mando",
"keyboard": "teclado",
"cell phone": "teléfono móvil",
"microwave": "microondas",
"oven": "horno",
"toaster": "tostadora",
"sink": "fregadero",
"refrigerator": "refrigerador",
"book": "libro",
"clock": "reloj",
"vase": "jarrón",
"scissors": "tijeras",
"teddy bear": "oso de peluche",
"hair drier": "secador de pelo",
"toothbrush": "cepillo de dientes",
}
# 簡易ドイツ語訳
cat_name_to_de = {
"person": "Person",
"bicycle": "Fahrrad",
"car": "Auto",
"motorcycle": "Motorrad",
"airplane": "Flugzeug",
"bus": "Bus",
"train": "Zug",
"truck": "Lastwagen",
"boat": "Boot",
"traffic light": "Ampel",
"fire hydrant": "Hydrant",
"stop sign": "Stoppschild",
"parking meter": "Parkuhr",
"bench": "Bank",
"bird": "Vogel",
"cat": "Katze",
"dog": "Hund",
"horse": "Pferd",
"sheep": "Schaf",
"cow": "Kuh",
"elephant": "Elefant",
"bear": "Bär",
"zebra": "Zebra",
"giraffe": "Giraffe",
"backpack": "Rucksack",
"umbrella": "Regenschirm",
"handbag": "Handtasche",
"tie": "Krawatte",
"suitcase": "Koffer",
"frisbee": "Frisbee",
"skis": "Skier",
"snowboard": "Snowboard",
"sports ball": "Sportball",
"kite": "Drachen",
"baseball bat": "Baseballschläger",
"baseball glove": "Baseballhandschuh",
"skateboard": "Skateboard",
"surfboard": "Surfbrett",
"tennis racket": "Tennisschläger",
"bottle": "Flasche",
"wine glass": "Weinglas",
"cup": "Tasse",
"fork": "Gabel",
"knife": "Messer",
"spoon": "Löffel",
"bowl": "Schüssel",
"banana": "Banane",
"apple": "Apfel",
"sandwich": "Sandwich",
"orange": "Orange",
"broccoli": "Brokkoli",
"carrot": "Karotte",
"hot dog": "Hotdog",
"pizza": "Pizza",
"donut": "Donut",
"cake": "Kuchen",
"chair": "Stuhl",
"couch": "Sofa",
"potted plant": "Zimmerpflanze",
"bed": "Bett",
"dining table": "Esstisch",
"toilet": "Toilette",
"tv": "Fernseher",
"laptop": "Laptop",
"mouse": "Maus",
"remote": "Fernbedienung",
"keyboard": "Tastatur",
"cell phone": "Handy",
"microwave": "Mikrowelle",
"oven": "Ofen",
"toaster": "Toaster",
"sink": "Spüle",
"refrigerator": "Kühlschrank",
"book": "Buch",
"clock": "Uhr",
"vase": "Vase",
"scissors": "Schere",
"teddy bear": "Teddybär",
"hair drier": "Haartrockner",
"toothbrush": "Zahnbürste",
}
cats = coco.loadCats(target_category_ids)
# Prompt templates
def en_label(cat: Dict) -> str:
return f"a photo of a {cat['name']}"
def ja_label(cat: Dict) -> str:
return f"{cat_name_to_jp.get(cat['name'], cat['name'])}の写真"
def ja_en_label(cat: Dict) -> str:
return f"{ja_label(cat)} ({en_label(cat)})"
def fr_label(cat: Dict) -> str:
name = cat_name_to_fr.get(cat["name"], cat["name"])
return f"photo de {name}"
def es_label(cat: Dict) -> str:
name = cat_name_to_es.get(cat["name"], cat["name"])
return f"foto de {name}"
def de_label(cat: Dict) -> str:
name = cat_name_to_de.get(cat["name"], cat["name"])
return f"ein Foto von {name}"
def build_prompt_set(name: str, templates: list) -> Dict:
"""Build a prompt set with all templates for every category."""
labels: List[str] = []
cat_to_indices: Dict[int, List[int]] = defaultdict(list)
label_to_category: Dict[str, int] = {}
for cat in cats:
for tmpl in templates:
label = tmpl(cat)
labels.append(label)
idx = len(labels) - 1
cat_to_indices[cat["id"]].append(idx)
label_to_category[label] = cat["id"]
return {"name": name, "labels": labels, "cat_to_indices": cat_to_indices, "label_to_category": label_to_category}
def build_prompt_set_one(name: str, template) -> Dict:
"""One label per category (80 labels)."""
return build_prompt_set(name, [template])
def build_prompt_set_mixed(name: str, templates: list, seed: int = 42) -> Dict:
"""One label per category, but language chosen randomly per category (keeps 80 labels)."""
rng = random.Random(seed)
labels: List[str] = []
cat_to_indices: Dict[int, List[int]] = defaultdict(list)
label_to_category: Dict[str, int] = {}
for cat in cats:
tmpl = rng.choice(templates)
label = tmpl(cat)
labels.append(label)
idx = len(labels) - 1
cat_to_indices[cat["id"]].append(idx)
label_to_category[label] = cat["id"]
return {"name": name, "labels": labels, "cat_to_indices": cat_to_indices, "label_to_category": label_to_category}
prompt_sets = [
# 80 labels (言語ごと)
build_prompt_set_one("English", en_label),
build_prompt_set_one("Japanese", ja_label),
# 80 labelsだがランダムに言語を混在(ラベル数は一定) -> 言語の呪いをラベル数と切り分けて観察
build_prompt_set_mixed("Mixed EN/JA (80 labels)", [en_label, ja_label], seed=1),
build_prompt_set_mixed("Mixed 5-lang (80 labels)", [en_label, ja_label, fr_label, es_label, de_label], seed=2),
# ラベル数を増やして多言語を全展開(160〜400ラベル)
build_prompt_set("JA+EN (160 labels)", [ja_label, en_label]),
build_prompt_set("JA+EN bilingual (80 labels)", [ja_en_label]),
build_prompt_set("JA+EN+FR (240 labels)", [ja_label, en_label, fr_label]),
build_prompt_set("JA+EN+FR+ES+DE (400 labels)", [ja_label, en_label, fr_label, es_label, de_label]),
]
num_images: int | None = None if args.num_images in (-1, 0) else args.num_images
batch_size: int = args.batch_size
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 files
txt_path = Path(__file__).with_name("benchmark_prompts_compare.txt")
plot_path = Path(__file__).with_name("benchmark_prompts_compare.png")
scatter_path = Path(__file__).with_name("benchmark_prompts_compare_scatter.png")
# Optionally dump prompts and exit
if args.dump_prompts:
dump_path = Path(args.dump_path) if args.dump_path else Path(__file__).with_name("benchmark_prompts_list.txt")
with dump_path.open("w", encoding="utf-8") as f:
for ps in prompt_sets:
f.write(f"=== {ps['name']} (labels={len(ps['labels'])}) ===\n")
for lbl in ps["labels"]:
f.write(lbl + "\n")
f.write("\n")
print(f"Prompt list saved to: {dump_path.resolve()}")
raise SystemExit(0)
def load_local_image(img_info: Dict) -> Image.Image:
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."""
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
def max_gt_prob(probs: torch.Tensor, gt_set: Set[int], cat_to_indices: Dict[int, List[int]]) -> float:
"""Return the max probability among ground-truth categories."""
indices: List[int] = []
for cid in gt_set:
indices.extend(cat_to_indices.get(cid, []))
if not indices:
return 0.0
return probs[indices].max().item()
if num_images:
print(f"Limiting to {num_images} images for speed. Pass --num-images -1 to use all.")
selected_infos = select_images_with_targets(num_images)
print(f"Selected {len(selected_infos)} images for evaluation.")
# Model
model_name = "facebook/metaclip-2-worldwide-giant-378"
processor = AutoProcessor.from_pretrained(model_name, use_fast=True)
model = AutoModel.from_pretrained(
model_name,
attn_implementation="sdpa",
dtype=preferred_dtype,
).to(device)
model.eval()
records: List[Dict] = []
with txt_path.open("w", encoding="utf-8") as f:
f.write(f"Model: {model_name}\n")
f.write(f"Images: {len(selected_infos)}\n\n")
for prompt_set in prompt_sets:
name = prompt_set["name"]
labels = prompt_set["labels"]
cat_to_indices = prompt_set["cat_to_indices"]
label_to_category = prompt_set["label_to_category"]
print(f"\nRunning prompt set: {name} (labels={len(labels)})")
f.write(f"=== Prompt set: {name} (labels={len(labels)}) ===\n")
predictions: List[str] = []
confidences: List[float] = []
correct: List[bool] = []
gt_probs_all: List[float] = []
for start in tqdm(range(0, len(selected_infos), batch_size), desc=name, 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
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)
for i, gt_set in enumerate(batch_ground_truth):
gt_prob = max_gt_prob(probs[i], gt_set, cat_to_indices)
gt_probs_all.append(gt_prob)
accuracy = sum(correct) / len(correct)
mean_top_prob = sum(confidences) / len(confidences)
mean_gt_prob = sum(gt_probs_all) / len(gt_probs_all) if gt_probs_all else 0.0
records.append(
{
"name": name,
"accuracy": accuracy,
"mean_top_prob": mean_top_prob,
"mean_gt_prob": mean_gt_prob,
"num_labels": len(labels),
}
)
f.write(f"Accuracy: {accuracy:.4f}\n")
f.write(f"Mean top prob: {mean_top_prob:.4f}\n")
f.write(f"Mean GT prob: {mean_gt_prob:.4f}\n\n")
f.flush()
print(f"\nDetailed summary saved to: {txt_path.resolve()}")
# Plot accuracy and mean GT probability per prompt set
names = [r["name"] for r in records]
accs = [r["accuracy"] for r in records]
gt_means = [r["mean_gt_prob"] for r in records]
num_labels = [r["num_labels"] for r in records]
x = range(len(records))
fig, ax1 = plt.subplots(figsize=(10, 5))
ax1.bar(x, accs, color="skyblue", label="Accuracy")
ax1.set_ylim(0, 1)
ax1.set_ylabel("Accuracy")
ax1.set_xticks(list(x))
ax1.set_xticklabels(names, rotation=20, ha="right")
ax2 = ax1.twinx()
ax2.plot(x, gt_means, color="tab:red", marker="o", label="Mean GT prob")
ax2.set_ylim(0, 1)
ax2.set_ylabel("Mean GT prob", color="tab:red")
ax2.tick_params(axis="y", labelcolor="tab:red")
fig.tight_layout()
fig.legend(loc="upper right", bbox_to_anchor=(1, 1), bbox_transform=ax1.transAxes)
plt.title("MetaCLIP: multilingual prompt sets")
plt.savefig(plot_path, bbox_inches="tight")
plt.close(fig)
print(f"Accuracy/GT plot saved to: {plot_path.resolve()}")
# Scatter: number of labels vs accuracy
fig2, ax = plt.subplots(figsize=(6, 4))
scatter = ax.scatter(num_labels, accs, c=num_labels, cmap="viridis")
ax.set_xlabel("Number of labels in prompt set")
ax.set_ylabel("Accuracy")
ax.set_ylim(0, 1)
ax.set_title("Labels count vs accuracy (prompt set)")
plt.colorbar(scatter, label="#labels")
plt.tight_layout()
plt.savefig(scatter_path, bbox_inches="tight")
plt.close(fig2)
print(f"Scatter plot saved to: {scatter_path.resolve()}")
実行方法
以下のコマンドを使ってベンチマークを実行し、結果を得ることができます。適切なパスに変更してから実行してください。
cd /path/to/meta/metaclip
python benchmark_compare_prompts.py --num-images -1 --batch-size 64
実行後、結果はテキストファイル benchmark_prompts_compare.txt と、可視化されたグラフファイル benchmark_prompts_compare.png とbenchmark_prompts_compare_scatter.pngとして保存されます。
全ラベルをファイルで見たいときは、コードを変えずに --dump-prompts オプションで benchmark_prompts_list.txt を出力できます。
各セットで実際に生成される文の例を5本ずつ挙げます(80カテゴリすべて同じ形で展開されます)。Mixed系はseed固定ですが、ここでは代表例を示します。
English (80)
a photo of a cat
a photo of a dog
a photo of a car
a photo of an airplane
a photo of a person
Japanese (80)
猫の写真
犬の写真
車の写真
飛行機の写真
人の写真
Mixed EN/JA (80, 英日をカテゴリごとにランダム)
a photo of a cat
犬の写真
a photo of a car
飛行機の写真
a photo of a person
(実際の並びはseed=1で固定)
Mixed 5-lang (80, 英/日/仏/西/独をカテゴリごとにランダム)
a photo of a cat
犬の写真
photo de voiture
foto de avión
ein Foto von Person
(実際の並びはseed=2で固定)
JA+EN (160, 各カテゴリに日・英の2本)
猫の写真
a photo of a cat
犬の写真
a photo of a dog
車の写真
JA+EN bilingual (80, 英日併記1本/カテゴリ)
猫の写真 (a photo of a cat)
犬の写真 (a photo of a dog)
車の写真 (a photo of a car)
飛行機の写真 (a photo of an airplane)
人の写真 (a photo of a person)
JA+EN+FR (240, 各カテゴリに日・英・仏の3本)
猫の写真
a photo of a cat
photo de chat
犬の写真
a photo of a dog
JA+EN+FR+ES+DE (400, 各カテゴリに日・英・仏・西・独の5本)
猫の写真
a photo of a cat
photo de chat
foto de gato
ein Foto von Katze
実行結果とグラフ
結果
CLI output
((.venv) ) syun@syunnoMacBook-Pro metaclip % python benchmark_compare_prompts.py --num-images -1 --batch-size 64
loading annotations into memory...
Done (t=0.16s)
creating index...
index created!
Selected 4952 images for evaluation.
Loading weights: 100%|███████████████████████████████████████████████████| 1294/1294 [00:00<00:00, 5120.31it/s, Materializing param=visual_projection.weight]
Running prompt set: English (labels=80)
Running prompt set: Japanese (labels=80)
Running prompt set: Mixed EN/JA (80 labels) (labels=80)
Running prompt set: Mixed 5-lang (80 labels) (labels=80)
Running prompt set: JA+EN (160 labels) (labels=160)
Running prompt set: JA+EN bilingual (80 labels) (labels=80)
Running prompt set: JA+EN+FR (240 labels) (labels=240)
Running prompt set: JA+EN+FR+ES+DE (400 labels) (labels=400)
Detailed summary saved to: /Users/syun/python_project/meta/metaclip/benchmark_prompts_compare.txt
Accuracy/GT plot saved to: /Users/syun/python_project/meta/metaclip/benchmark_prompts_compare.png
Scatter plot saved to: /Users/syun/python_project/meta/metaclip/benchmark_prompts_compare_scatter.png
((.venv) ) syun@syunnoMacBook-Pro metaclip %
benchmark_prompts_compare.txt
Model: facebook/metaclip-2-worldwide-giant-378
Images: 4952
=== Prompt set: English (labels=80) ===
Accuracy: 0.9334
Mean top prob: 0.8177
Mean GT prob: 0.7958
=== Prompt set: Japanese (labels=80) ===
Accuracy: 0.9017
Mean top prob: 0.7924
Mean GT prob: 0.7577
=== Prompt set: Mixed EN/JA (80 labels) (labels=80) ===
Accuracy: 0.9015
Mean top prob: 0.7814
Mean GT prob: 0.7467
=== Prompt set: Mixed 5-lang (80 labels) (labels=80) ===
Accuracy: 0.8538
Mean top prob: 0.7406
Mean GT prob: 0.6941
=== Prompt set: JA+EN (160 labels) (labels=160) ===
Accuracy: 0.9332
Mean top prob: 0.6993
Mean GT prob: 0.6798
=== Prompt set: JA+EN bilingual (80 labels) (labels=80) ===
Accuracy: 0.9309
Mean top prob: 0.8369
Mean GT prob: 0.8091
=== Prompt set: JA+EN+FR (240 labels) (labels=240) ===
Accuracy: 0.9243
Mean top prob: 0.6081
Mean GT prob: 0.5905
=== Prompt set: JA+EN+FR+ES+DE (400 labels) (labels=400) ===
Accuracy: 0.9116
Mean top prob: 0.4943
Mean GT prob: 0.4763
グラフ
GT(Ground Truth)およびグラフの詳細説明
GT(Ground Truth) とは、機械学習モデルの評価において、実際に「正解」とされるデータを指します。特に画像認識タスクでは、GTは「正しいラベル」の集合を指し、モデルが画像に対して予測すべき対象を示します。画像分類モデルでは、与えられた画像に対して「どのカテゴリに属するか」を予測し、その予測が実際の正解ラベルと一致するかを評価します。COCO(Common Objects in Context)データセットを使用する場合、GTは画像における「正解カテゴリ(category_id)」に基づいて定義されます。
今回の評価におけるGT確率(Ground Truth Probability)は、モデルがその画像に対して正解カテゴリにどれだけ確信を持っているかを示す指標です。これは、モデルが出力する確率分布から、正解カテゴリに関連するラベル群に対して最大確率を取ることで算出されます。GT確率が高ければ、モデルはその予測に自信を持っていると解釈できます。
GT設定の詳細
今回のコードにおけるGT設定は、次のように定義されています:
-
元データ:COCOアノテーションの
category_id(画像に写っている正解カテゴリ)。具体的には、/Users/syun/datasets/coco/annotations/instances_val2017.jsonに格納されているアノテーションデータを基に評価が行われます。これに基づき、画像に関連する正解カテゴリが決定されます。 -
画像選択:
select_images_with_targets関数により、アノテーションにターゲットカテゴリが1つ以上含まれている画像が選ばれます。選ばれた画像にはその画像に関連するGT集合(gt_set)が設定され、これが正解ラベルとして扱われます。各画像のgt_setには、対象となるカテゴリIDが格納されます。 -
プロンプトセットごとのマッピング:
-
label_to_category:ラベル→カテゴリIDのマッピング。トップ1予測ラベルが正解かどうかを判定するために使用されます。予測ラベルのカテゴリIDがgt_setに含まれていれば、それが正解とみなされます。 -
cat_to_indices:カテゴリID→そのカテゴリに対応するラベルのインデックス群を保持します。多言語プロンプトや併記ラベルがある場合に、同じカテゴリに複数のラベルが対応するため、これを使って評価を行います。
-
-
GT確率(Mean GT prob):GT確率は、モデルが予測した確率分布(
probs)から、gt_setに含まれるカテゴリに対応するラベル群の確率の最大値を取得することで算出されます。これにより、「この画像の正解カテゴリ群のうち、最も高く割り当てた確率」を示し、モデルの確信度を評価します。具体的には、max_gt_prob(probs, gt_set, cat_to_indices)関数で計算されます。 -
精度(Accuracy):トップ1予測ラベルのカテゴリIDが
gt_setに含まれているかどうかで、精度が判定されます。gt_setに一致するカテゴリが予測されれば、それを正解としてカウントします。
つまり、GTは、画像認識タスクにおいて「正解ラベル」の集合であり、COCOデータセットのcategory_idに基づいて定義されます。GT確率は、モデルが正解カテゴリにどれだけ確信を持っているかを示す指標であり、画像ごとの確率分布から最大値を取ることで計算されます。精度(Accuracy)は、トップ1予測ラベルが正解カテゴリに一致しているかで決まります。
グラフの意味と解説
2つのグラフは、MetaCLIP 2の多言語プロンプトに対する精度と確信度を評価した結果です。それぞれのグラフは、モデルの性能を視覚的に示すもので、プロンプトセットのラベル数の増加や言語混在が精度に与える影響を可視化しています。
1. 精度比較のグラフ
精度(Accuracy)は、モデルが与えられた画像に対して、最も確信を持って予測したラベル(トップ1予測ラベル)が実際の正解ラベルと一致した割合を示します。ここでは、プロンプトセットごとにこの精度を比較しています。以下は、各プロンプトセットでのトップ1精度です:
- English (80 labels): 93.34%
- Japanese (80 labels): 90.17%
- Mixed EN/JA (80 labels): 90.15%
- Mixed 5-lang (80 labels): 85.38%
- JA+EN (160 labels): 93.32%
- JA+EN bilingual (80 labels): 93.09%
- JA+EN+FR (240 labels): 92.43%
- JA+EN+FR+ES+DE (400 labels): 91.16%
このグラフから、言語数やラベル数が増加するにつれて、精度が若干低下する傾向が見られることがわかります。特に、Mixed 5-lang(80ラベル)の場合、他のプロンプトセットと比較して精度が低下しています。これは、複数の言語が混在することで多言語の呪いが影響を与えている可能性があります。
2. Top-1確率とGT確率の比較
次に、Top-1確率(最も高い確率を持つ予測)とGT確率(正解カテゴリに対する確信度)を比較します。グラフには、青いバーでトップ1精度(Accuracy)を、赤い線でGT確率を示しています。
- 青いバー(精度): 各プロンプトセットにおけるトップ1精度を示し、モデルが最も高い確率で予測したラベルが正解と一致した割合を示します。
- 赤い線(GT確率): GT確率は、正解カテゴリ群に対する最大確率を示し、モデルがその画像において正解カテゴリにどれだけ確信を持っているかを表します。
このグラフを通じて、多言語プロンプトセットでは、ラベル数が増えることでGT確率が低下する傾向が確認できます。特に、JA+EN+FR+ES+DE(400ラベル)では、精度は91.16%であるのに対し、GT確率は0.4763と低く、モデルの確信度が分散していることが確認できます。
3. ラベル数 vs 精度の関係のグラフ
最後に、ラベル数とトップ1精度の関係を示す散布図です。横軸にはラベル数、縦軸にはトップ1精度を配置し、色でラベル数の増加を視覚的に表現しています。このグラフから、ラベル数や言語数が増えるにつれて精度が低下することが確認できます。特に、JA+EN+FR+ES+DE(400ラベル)の場合、ラベル数が増加することで精度が顕著に低下しています。
考察
1. プロンプト数と精度の関係
MetaCLIP 2の多言語プロンプトセットにおいて、ラベル数や言語数が増えることで精度が低下することが確認されました。**JA+EN+FR+ES+DE (400 labels)**の精度は91.16%に対し、**English (80 labels)**の精度は93.34%であり、言語が増えることでモデルの精度が若干低下する傾向があります。これは、多言語の呪いによる影響と考えられます。
2. GT確率の低下
GT確率の比較からは、多言語プロンプトセットでは、モデルの確信度が分散していることが確認できます。特に、JA+EN+FR+ES+DE(400ラベル)では、GT確率が0.4763と低く、モデルが複数の言語に対応することで確信度が分散していることが分かります。
3. 日本語と英語の比較
日本語と英語のプロンプトを混在させた場合(Mixed EN/JA)でも、MetaCLIP 2は高い精度を維持しました。日本語シンプルと英語シンプルの両方で高精度を達成しており、多言語対応の強さを示しました。
結論
MetaCLIP 2は、多言語プロンプトに対して非常に強力な性能を発揮し、多言語の呪いを最小限に抑えながら高い精度を維持しています。言語間の補完能力があり、複数の言語が混在しても優れたパフォーマンスを発揮します。しかし、ラベル数や言語数の増加に伴い、精度が若干低下する傾向が見られました。それでも、MetaCLIP 2は多言語対応において非常に強力なことがわかりました。今後は、より複雑な多言語シナリオにおいても、モデルの補完能力や信頼度の分散を理解し、改善を目指していくことが求められます。
今回も最後まで見てくれて、ありがとうございました!

