「シマウマ」を一度も見たことがない人でも、「馬のような体格で、白と黒の縞模様がある動物」と言葉で説明されれば、動物園で初めて見たシマウマをちゃんと指させます。私たちは、新しい概念を実例なしに言葉(属性)の組み合わせだけから認識できるのです。ところが普通の機械学習の分類器は、訓練データに一度も登場しなかったクラスを絶対に出力できません。学習時に「シマウマ」の画像が1枚もなければ、テスト時にどれだけシマウマらしい画像を見せても、せいぜい「馬」と答えるのが関の山です。
この「訓練に存在しないクラスを、補助情報を頼りに当てる」という人間には自然な能力を機械に与えようとするのがゼロショット学習(Zero-Shot Learning, ZSL)です。鍵になるのは、クラスを「ラベル番号」ではなく意味的な記述(semantic side information)——属性ベクトル、単語埋め込み、あるいは自然言語のテキスト——で表現することです。クラス同士を意味空間でつなぐことで、未知クラスの「設計図」を既知クラスから学んだ知識で組み立て直せるようになります。
ゼロショット学習を理解すると、次のような場面で道が開けます。
- 希少クラス・ロングテール認識: 医療画像の稀少疾患、絶滅危惧種の野生動物、新製品の欠陥種別など、学習サンプルを集めること自体が困難なクラスを、属性やテキスト記述から認識する。
- オープン語彙の認識(open-vocabulary): CLIP に代表される視覚言語モデルは、「任意のテキストで指定したカテゴリ」を追加学習なしに検出・分類でき、固定クラスの枠を取り払った。
- 大規模言語モデルの未知タスク遂行: 「次の文を要約して」とプロンプトで指示するだけで、要約用に明示的に訓練していないモデルがタスクをこなす——これも広義のゼロショットであり、instruction tuning や zero-shot Chain-of-Thought の理論的背骨になっている。
本記事の内容
- ゼロショット学習の問題設定(seen/unseen クラスと意味的補助情報)を直感から定式化する
- 学術的な系譜——属性ベース → 互換性関数ベース → 生成ベース → 視覚言語基盤モデル → LLM のゼロショット——を俯瞰する
- 互換性関数(bilinear compatibility)の共通枠組みと、その代表 ESZSL の閉形式解を1行ずつ導出する
- Python で合成属性データを作り、ESZSL をスクラッチ実装して未知クラスを分類する
- 汎化ゼロショット学習(GZSL) の既知クラス偏重バイアスと calibrated stacking、評価指標(調和平均 H)を解説する
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
- 単語埋め込みの理論 — Word2Vec・GloVe・FastText の比較 — クラスを意味ベクトルで表す補助情報の代表例
- CLIPの対照学習を数式から理解してPythonで実装する — テキストでゼロショット認識する現代の主役
- 対照学習(Contrastive Learning)の理論と損失関数の導出 — CLIP の学習目的の基礎
- 転移学習の理論と実践 — 事前学習モデルの活用戦略 — 知識を転移するという発想の隣接概念
線形代数(行列の擬似逆行列・Frobenius ノルム)、リッジ回帰の閉形式解、そしてソフトマックス分類器の基本を押さえておくと、ESZSL の導出が最短距離で追えます。
ゼロショット学習とは — 問題設定
まず舞台を整理しましょう。通常の教師あり分類では、訓練時に現れるクラスとテスト時に現れるクラスは同じです。ゼロショット学習では、ここに切れ目を入れます。クラス集合を、訓練で画像(特徴)が手に入る既知クラス(seen classes) $\mathcal{Y}^s$ と、テストで初めて登場する未知クラス(unseen classes) $\mathcal{Y}^u$ に分け、両者は互いに素($\mathcal{Y}^s \cap \mathcal{Y}^u = \varnothing$)とします。訓練に使えるのは既知クラスのサンプル $\{(\bm{x}_i, y_i)\}$($y_i \in \mathcal{Y}^s$)だけです。
このままでは未知クラスを当てる手がかりがゼロですが、ゼロショット学習には1本の橋が架けられています。すべてのクラス $c$(既知・未知を問わず)に対して、その意味を表す意味ベクトル(semantic vector / class prototype) $\bm{s}_c \in \mathbb{R}^a$ が与えられているのです。これが冒頭の「馬のような体格で縞模様」に相当します。代表的な意味ベクトルには次の3種類があります。
- 属性ベクトル: 「縞模様か」「四足か」「水中に棲むか」といった人手定義の属性に対する 0/1 または連続値。Animals with Attributes(AwA)データセットが代表例。
- 単語埋め込み: クラス名を Word2Vec や GloVe でベクトル化したもの。人手アノテーション不要で、大規模に展開しやすい。
- テキスト記述: クラスの説明文を言語モデルでエンコードしたもの。CLIP のテキスト枝が典型。
意味空間では、未知クラス「シマウマ」のベクトルが既知クラス「馬」「トラ(縞模様)」の近くに置かれます。だから「馬らしさ」と「縞模様らしさ」を既知クラスから学んでおけば、それらを意味ベクトルの座標に従って合成し、シマウマの判定器をサンプルなしで組み立てられるわけです。これがゼロショット学習の核心的なアイデアです。
ゼロショット学習の目標は、突き詰めれば「視覚特徴 $\bm{x}$ と意味ベクトル $\bm{s}$ をつなぐ関数を、既知クラスで学んで未知クラスへ外挿すること」に尽きます。では、その関数を歴史的にどう設計してきたのか。次節で学術的な系譜を俯瞰します。
学術的な系譜とアプローチの分類
ゼロショット学習は2009年前後に画像認識の文脈で本格化し、その後の十数年で大きく姿を変えてきました。流れを5つの世代に整理すると見通しがよくなります。
1. 属性ベース(2009〜)— DAP / IAP
最初期の代表が Lampert らの DAP(Direct Attribute Prediction) です。発想は素朴で強力です。クラスを直接当てるのをやめ、まず属性そのものを当てる分類器を属性ごとに訓練します(「縞模様か?」を判定する2値分類器など)。テスト時には、画像から各属性の確率 $p(a_m \mid \bm{x})$ を推定し、未知クラスの属性定義 $\bm{s}_c$ と照合して、最も尤もらしいクラスを MAP 推定で選びます。
$$ \hat{y} = \arg\max_{c \in \mathcal{Y}^u} \prod_{m=1}^{a} p(a_m = s_{c,m} \mid \bm{x}) $$
属性という「人間が理解できる中間表現」を経由する点が美点ですが、属性予測の誤差が伝播しやすく、属性間の相関を無視する弱点がありました。IAP(Indirect Attribute Prediction) は逆に既知クラスを先に当ててから属性を推定する変種です。
2. 互換性関数ベース(2013〜)— DeViSE / ALE / SJE / ESZSL
次の世代は、属性を逐一当てるのをやめ、視覚特徴と意味ベクトルの「相性(compatibility)」を直接スコア化する関数を学習します。最も広く使われるのが双線形互換性関数(bilinear compatibility)です。
$$ F(\bm{x}, \bm{s}; \bm{W}) = \bm{x}^\top \bm{W} \bm{s} $$
行列 $\bm{W}$ が視覚空間と意味空間を橋渡しします。DeViSE(Frome ら, 2013)はランキングヒンジ損失で、ALE / SJE はそれぞれ重み付きランキング損失・構造化 SVM 損失でこの $\bm{W}$ を学びます。そして本記事で導出する ESZSL(Romera-Paredes & Torr, 2015)は二乗損失と Frobenius 正則化を使うことで、閉形式解——反復学習すら要らない一発の行列計算——を導きました。「embarrassingly simple(恥ずかしいほど単純)」という論文題名どおり、シンプルさと強さを両立した古典です。この世代は今日のベンチマークでも頑健なベースラインとして生き残っています。
3. 生成ベース(2018〜)— f-CLSWGAN / CADA-VAE / 拡散モデル
互換性関数ベースには「未知クラスの実サンプルが1つもない」という本質的な非対称性が残ります。そこで発想を反転させ、意味ベクトルから未知クラスの視覚特徴を生成してしまうのが生成ベースです。GAN を使う f-CLSWGAN(Xian ら, 2018)や VAE を使う CADA-VAE は、未知クラスの意味ベクトルを条件に偽の特徴サンプルを大量に合成し、それを使って普通の教師あり分類器を訓練します。ゼロショット問題を「データ拡張つきの教師あり問題」に変換してしまうわけです。近年は拡散モデルを使う ZeroDiff(ICLR 2025)など、より少ない訓練データで視覚・意味の相関を強める手法も登場しています。
4. 視覚言語基盤モデル(2021〜)— CLIP とオープン語彙
ゲームを変えたのが CLIP(Radford ら, 2021)です。4億の画像・テキストペアを対照学習で同じ埋め込み空間に並べることで、任意のテキストをクラス記述として使えるゼロショット分類器が生まれました。「a photo of a {class}」というプロンプトを作るだけで、ImageNet を ResNet-50 並みの精度で、しかも追加学習なしに分類できます。これは事実上、意味ベクトルを「自然言語テキストのエンコーディング」に置き換え、Web 規模のデータでその橋渡しを事前学習した、究極にスケールさせたゼロショット学習だと見なせます。CLIP を土台にオープン語彙の物体検出・セグメンテーションへ展開する研究が2024〜2025年も活発で、EVA-CLIP のような効率改善版も続々と現れています。
5. 大規模言語モデルのゼロショット(2020〜)
同じ「ゼロショット」という言葉は、大規模言語モデル(LLM)でも中心概念になりました。GPT-3 の論文題名 “Language Models are Few-Shot Learners” が象徴するように、巨大な言語モデルはプロンプトの指示だけで未知のタスクをこなします。これを系統的に強化するのが instruction tuning(FLAN, T0)——多数のタスクを指示文の形で学習させ、未知タスクへの汎化を引き出す手法——です。さらに「Let’s think step by step」と付け足すだけで多段推論が改善する zero-shot Chain-of-Thought(Kojima ら, 2022)は、ゼロショット能力が推論にまで及ぶことを示しました。
こうして見ると、ゼロショット学習の歴史は「クラスをどんな意味表現で結ぶか」と「視覚と意味をどんな関数でつなぐか」の探求史です。次節では、第2世代の互換性関数を共通の枠組みとして定式化し、その最もクリーンな代表 ESZSL を導きます。
互換性関数による定式化
互換性関数ベースの手法は、見かけは違っても1つの枠組みに収まります。視覚特徴 $\bm{x} \in \mathbb{R}^d$ とクラス $c$ の意味ベクトル $\bm{s}_c \in \mathbb{R}^a$ に対し、両者の相性を測るスコア関数を考えます。最も標準的なのが双線形形式です。
$$ \begin{equation} F(\bm{x}, \bm{s}_c; \bm{W}) = \bm{x}^\top \bm{W} \bm{s}_c \end{equation} $$
ここで $\bm{W} \in \mathbb{R}^{d \times a}$ が学習対象です。直感的には、$\bm{W}\bm{s}_c$ が「クラス $c$ 専用の重みベクトル(線形分類器)」を意味ベクトルから合成しており、それと視覚特徴の内積でスコアを出している、と読めます。つまり1個の行列 $\bm{W}$ が、すべてのクラスの線形分類器を意味ベクトルから生成する装置なのです。未知クラスでも意味ベクトル $\bm{s}_c$ さえあれば $\bm{W}\bm{s}_c$ で分類器が即座に手に入る——これがゼロショットを可能にする仕掛けです。
予測は、候補クラスの中でスコアが最大のものを選びます。
$$ \hat{y} = \arg\max_{c} \ \bm{x}^\top \bm{W} \bm{s}_c $$
テスト時に候補集合を未知クラス $\mathcal{Y}^u$ に限れば素のゼロショット、既知・未知の和集合 $\mathcal{Y}^s \cup \mathcal{Y}^u$ にすれば後述の汎化ゼロショット(GZSL)になります。
残る問題は「$\bm{W}$ をどう学ぶか」です。DeViSE はランキング損失、SJE は構造化 SVM 損失を使いますが、いずれも反復最適化が必要です。これに対し ESZSL は損失を二乗誤差にとり、正則化を巧妙に設計することで、反復なしの閉形式解を導きます。次節でこれを丁寧に導出しましょう。
ESZSL の閉形式解を導出する
記号を行列でまとめます。既知クラスの訓練データが $m$ サンプルあるとし、
- $\bm{X} \in \mathbb{R}^{m \times d}$: 各行が1サンプルの視覚特徴
- $\bm{S} \in \mathbb{R}^{z \times a}$: 各行が既知クラス($z$ 個)の意味ベクトル
- $\bm{Y} \in \{0,1\}^{m \times z}$: ラベル行列。$Y_{ic}=1$ ならサンプル $i$ はクラス $c$
と置きます。互換性スコアを全サンプル×全クラスでまとめると、予測スコア行列は $\bm{X}\bm{W}\bm{S}^\top \in \mathbb{R}^{m \times z}$ になります(各成分 $(\bm{X}\bm{W}\bm{S}^\top)_{ic} = \bm{x}_i^\top \bm{W}\bm{s}_c$ がサンプル $i$ のクラス $c$ に対するスコア)。これを正解ラベル $\bm{Y}$ に近づけたい、というのが ESZSL の目標です。
目的関数
ESZSL は二乗損失に3種類の正則化項を加えた、次の目的関数を最小化します。
$$ \begin{equation} \mathcal{L}(\bm{W}) = \underbrace{\|\bm{X}\bm{W}\bm{S}^\top – \bm{Y}\|_F^2}_{\text{二乗損失}} + \gamma \|\bm{X}\bm{W}\|_F^2 + \lambda \|\bm{W}\bm{S}^\top\|_F^2 + \gamma\lambda \|\bm{W}\|_F^2 \end{equation} $$
$\|\cdot\|_F$ は Frobenius ノルム(全成分の二乗和の平方根)です。正則化が3つもあるのは奇妙に見えますが、これは「視覚空間に射影した像 $\bm{X}\bm{W}$」と「意味空間に射影した像 $\bm{W}\bm{S}^\top$」の両方を抑え、さらに両者の積 $\gamma\lambda\|\bm{W}\|_F^2$ を加えることで、後で見るように項がきれいに因数分解されるよう逆算した設計です。この一見不自然な正則化こそが閉形式解の鍵を握ります。
勾配をゼロに
$\mathcal{L}$ を $\bm{W}$ で微分してゼロと置きます。行列に関する Frobenius ノルムの微分公式 $\partial \|\bm{A}\bm{W}\bm{B} – \bm{C}\|_F^2 / \partial \bm{W} = 2\bm{A}^\top(\bm{A}\bm{W}\bm{B}-\bm{C})\bm{B}^\top$ を各項に適用すると、
$$ \frac{\partial \mathcal{L}}{\partial \bm{W}} = 2\bm{X}^\top(\bm{X}\bm{W}\bm{S}^\top – \bm{Y})\bm{S} + 2\gamma\, \bm{X}^\top\bm{X}\bm{W} + 2\lambda\, \bm{W}\bm{S}^\top\bm{S} + 2\gamma\lambda\, \bm{W} $$
となります。第1項は二乗損失から($\bm{S}^\top$ の転置 $\bm{S}$ が右から掛かることに注意)、第2項は $\gamma\|\bm{X}\bm{W}\|_F^2$ から、第3項は $\lambda\|\bm{W}\bm{S}^\top\|_F^2$ から、第4項は $\gamma\lambda\|\bm{W}\|_F^2$ から来ています。これをゼロと置き、両辺を2で割って整理すると、
$$ \bm{X}^\top\bm{X}\bm{W}\bm{S}^\top\bm{S} + \gamma\,\bm{X}^\top\bm{X}\bm{W} + \lambda\,\bm{W}\bm{S}^\top\bm{S} + \gamma\lambda\,\bm{W} = \bm{X}^\top\bm{Y}\bm{S} $$
を得ます($\bm{X}^\top\bm{Y}\bm{S}$ の項だけ右辺に移しました)。
因数分解して閉形式へ
ここが正則化を巧妙に選んだ恩恵です。左辺の4項は、次のように2つの行列の積でくくれます。$\bm{I}_d, \bm{I}_a$ をそれぞれ $d \times d$, $a \times a$ の単位行列として、
$$ (\bm{X}^\top\bm{X} + \lambda \bm{I}_d)\,\bm{W}\,(\bm{S}^\top\bm{S} + \gamma \bm{I}_a) $$
を展開してみましょう。
$$ \begin{align} (\bm{X}^\top\bm{X} + \lambda \bm{I}_d)\,\bm{W}\,(\bm{S}^\top\bm{S} + \gamma \bm{I}_a) &= \bm{X}^\top\bm{X}\bm{W}\bm{S}^\top\bm{S} + \gamma\,\bm{X}^\top\bm{X}\bm{W} \\ &\quad + \lambda\,\bm{W}\bm{S}^\top\bm{S} + \gamma\lambda\,\bm{W} \end{align} $$
これは先ほどの左辺と完全に一致します。正則化に $\gamma\lambda\|\bm{W}\|_F^2$ という「交差項」を入れておいたおかげで、4つの項が1つの積に畳み込まれたのです。したがって正規方程式は驚くほど簡潔になります。
$$ (\bm{X}^\top\bm{X} + \lambda \bm{I}_d)\,\bm{W}\,(\bm{S}^\top\bm{S} + \gamma \bm{I}_a) = \bm{X}^\top\bm{Y}\bm{S} $$
両辺に左から $(\bm{X}^\top\bm{X} + \lambda \bm{I}_d)^{-1}$、右から $(\bm{S}^\top\bm{S} + \gamma \bm{I}_a)^{-1}$ を掛ければ、$\bm{W}$ について解けます。
$$ \begin{equation} \bm{W} = (\bm{X}^\top\bm{X} + \lambda \bm{I}_d)^{-1}\,\bm{X}^\top\bm{Y}\bm{S}\,(\bm{S}^\top\bm{S} + \gamma \bm{I}_a)^{-1} \end{equation} $$
これが ESZSL の閉形式解です。形をよく見ると、これは両側からリッジ回帰をかけた構造になっています。$(\bm{X}^\top\bm{X}+\lambda\bm{I})^{-1}\bm{X}^\top$ は視覚側のリッジ擬似逆行列、$\bm{S}(\bm{S}^\top\bm{S}+\gamma\bm{I})^{-1}$ は意味側のリッジ擬似逆行列で、その間にラベル $\bm{Y}$ が挟まれています。反復学習なしに、行列の積と逆行列だけで $\bm{W}$ が一発で求まる——これが「embarrassingly simple」の正体です。$\lambda, \gamma$ は2つの正則化強度で、交差検証で選びます。
導出が済んだので、あとは実装するだけです。次節で合成データを作って ESZSL を動かし、本当に見たことのないクラスが当たるのかを確かめましょう。
Python での実装
実データ(AwA2 など)は特徴抽出済みファイルのダウンロードが必要なので、ここでは合成属性データを作って原理を裸で観察します。クラスごとにランダムな属性ベクトルを割り当て、その属性に依存して視覚特徴が生成されるようにすれば、「意味↔視覚」の本物の対応関係を持ったミニチュアのゼロショット問題ができあがります。
合成属性データの生成
15 クラスを用意し、各クラスに 25 次元の連続属性ベクトルを与えます。視覚特徴(50 次元)は「未知のランダム行列 $\bm{B}$ で属性を視覚空間へ写像し、ノイズを加えたもの」として生成します。この $\bm{B}$ こそが ESZSL が(裏側で)復元しようとしている真の視覚↔意味対応です。
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(0)
n_classes = 15 # 全クラス数
a = 25 # 属性次元
d = 50 # 視覚特徴次元
n_per_class = 60 # 1クラスあたりのサンプル数
# 各クラスの意味ベクトル(属性プロトタイプ)
S_all = rng.normal(size=(n_classes, a))
S_all /= np.linalg.norm(S_all, axis=1, keepdims=True) # 単位長に正規化
# 属性→視覚特徴を結ぶ「真の」線形写像(ZSLが学ぶべき対応)
B_true = rng.normal(size=(a, d))
# クラスごとに属性に依存した特徴を生成(クラスプロトタイプ + ノイズ)
X_list, y_list = [], []
for c in range(n_classes):
proto = S_all[c] @ B_true # クラス中心
feats = proto + 0.4 * rng.normal(size=(n_per_class, d)) # ノイズで散らす
X_list.append(feats)
y_list.append(np.full(n_per_class, c))
X_all = np.vstack(X_list)
y_all = np.concatenate(y_list)
print("特徴行列:", X_all.shape, " ラベル:", y_all.shape)
このコードで、各クラスが意味ベクトルに紐づいた中心を持ち、その周りにノイズで散らばった特徴群が生成されました。重要なのは、特徴を作るのに使った B_true を ESZSL には一切渡さない点です。ESZSL は既知クラスのデータだけから、この隠れた対応関係を暗に学び取らなければなりません。次に、クラスを既知・未知に分割します。
既知/未知クラスへの分割
15 クラスのうち 11 個を既知(訓練に使う)、4個を未知(テストでのみ登場)とします。未知クラスのサンプルは訓練から完全に除外するのがゼロショットの掟です。
seen = np.arange(11) # 既知クラス(0〜10)
unseen = np.array([11, 12, 13, 14]) # 未知クラス(訓練に一切出さない)
# 訓練データ=既知クラスのみ
train_mask = np.isin(y_all, seen)
X_train, y_train = X_all[train_mask], y_all[train_mask]
# テストデータ=未知クラスのみ(純粋なゼロショット)
test_mask = np.isin(y_all, unseen)
X_test, y_test = X_all[test_mask], y_all[test_mask]
# ラベルを one-hot 行列 Y に(列は既知クラスの並び順)
Y_train = np.zeros((len(y_train), len(seen)))
for i, c in enumerate(y_train):
Y_train[i, np.where(seen == c)[0][0]] = 1.0
S_seen = S_all[seen] # 既知クラスの意味ベクトル (z×a)
S_unseen = S_all[unseen] # 未知クラスの意味ベクトル (3×a)
print("訓練:", X_train.shape, " テスト(未知のみ):", X_test.shape)
訓練データには未知クラス 11, 12, 13, 14 のサンプルが1つも含まれていないことを確認してください。ESZSL は既知クラス 0〜10 だけを見て $\bm{W}$ を学び、テストでは未知クラスの意味ベクトルだけを頼りに分類します。続いて、導出した閉形式解をそのまま実装します。
ESZSL の閉形式解を実装
導いた式 $\bm{W} = (\bm{X}^\top\bm{X}+\lambda\bm{I})^{-1}\bm{X}^\top\bm{Y}\bm{S}(\bm{S}^\top\bm{S}+\gamma\bm{I})^{-1}$ を1行で書けます。反復も勾配降下もありません。
def train_eszsl(X, Y, S, lam=1.0, gam=1.0):
"""ESZSLの閉形式解で互換性行列 W (d×a) を求める"""
d = X.shape[1]
a = S.shape[1]
A = np.linalg.inv(X.T @ X + lam * np.eye(d)) # 視覚側リッジ
C = np.linalg.inv(S.T @ S + gam * np.eye(a)) # 意味側リッジ
W = A @ X.T @ Y @ S @ C
return W
W = train_eszsl(X_train, Y_train, S_seen, lam=1.0, gam=1.0)
print("互換性行列 W:", W.shape) # (d, a)
これで視覚空間(50次元)と意味空間(25次元)を橋渡しする行列 $\bm{W}$ が、一度の行列計算で求まりました。$\bm{W}\bm{s}_c$ が「クラス $c$ の線形分類器」を意味ベクトルから生成することを思い出してください。次に、この $\bm{W}$ を未知クラスに適用して分類精度を測ります。
未知クラスでのゼロショット分類
テスト特徴 $\bm{X}_{\text{test}}$ に対し、未知クラスの意味ベクトルとの互換性スコア $\bm{X}_{\text{test}}\bm{W}\bm{S}_{\text{unseen}}^\top$ を計算し、最大スコアのクラスを予測とします。
def predict(X, W, S_candidates, class_ids):
scores = X @ W @ S_candidates.T # (n_samples, n_candidates)
pred_idx = scores.argmax(axis=1)
return class_ids[pred_idx]
# 候補を未知クラスだけに絞った「素のゼロショット」
y_pred_zsl = predict(X_test, W, S_unseen, unseen)
acc_zsl = (y_pred_zsl == y_test).mean()
print(f"素のゼロショット精度 (未知4クラス, ランダム={1/len(unseen):.2f}): {acc_zsl:.3f}")
実行すると、ランダム推測の 0.25 を大きく上回る精度(この設定では 0.98 前後)が出ます。これは驚くべきことです——モデルは未知クラス 11, 12, 13, 14 のサンプルをただの一度も見ていないのに、意味ベクトルという橋を渡って高精度で当てられているのです。冒頭の「縞模様の馬=シマウマ」を機械が再現した瞬間です。次に、各クラスがどれだけ正しく当たったかを混同行列で見てみましょう。
混同行列で誤りパターンを見る
labels = unseen
cm = np.zeros((len(labels), len(labels)), dtype=int)
for t, p in zip(y_test, y_pred_zsl):
cm[np.where(labels == t)[0][0], np.where(labels == p)[0][0]] += 1
fig, ax = plt.subplots(figsize=(5, 4))
im = ax.imshow(cm, cmap='Blues')
ax.set_xticks(range(len(labels))); ax.set_xticklabels(labels)
ax.set_yticks(range(len(labels))); ax.set_yticklabels(labels)
ax.set_xlabel('Predicted (unseen class)'); ax.set_ylabel('True (unseen class)')
ax.set_title('Zero-shot confusion matrix')
for i in range(len(labels)):
for j in range(len(labels)):
ax.text(j, i, cm[i, j], ha='center', va='center')
plt.colorbar(im); plt.tight_layout(); plt.show()
混同行列の対角成分が支配的であれば、未知クラスがおおむね正しく識別できています。非対角に誤りが集中するクラスがあれば、それは意味ベクトルが他の未知クラスと近すぎて区別しにくいクラスです。意味空間での距離が近いクラスほど混同しやすい——これはゼロショット学習の精度が「意味ベクトルの質」に強く依存することの直接的な証拠です。次に、実用上もっとも厄介な汎化ゼロショット(GZSL)の問題に進みます。
汎化ゼロショット学習(GZSL)とバイアス問題
これまでのテストでは候補を未知クラスだけに絞っていました。しかし現実は甘くありません。実運用では、入力画像が既知クラスか未知クラスか事前にはわからないため、候補を既知・未知の和集合 $\mathcal{Y}^s \cup \mathcal{Y}^u$ にして分類しなければなりません。これを汎化ゼロショット学習(Generalized ZSL, GZSL)と呼びます。
GZSL には深刻な落とし穴があります。$\bm{W}$ は既知クラスのデータだけで訓練されているため、互換性スコアが既知クラスに対して系統的に高く出るのです。結果として、本当は未知クラスの入力までもが既知クラスに吸い込まれ、未知クラスの精度が壊滅的に下がります。この既知クラス偏重のバイアスは GZSL 最大の課題として知られています。先ほどの設定を GZSL に変えて、この崩壊を観察しましょう。
# 候補を「既知11 + 未知4 = 全15クラス」にしてGZSLで評価
all_ids = np.concatenate([seen, unseen])
S_all_cand = np.vstack([S_seen, S_unseen])
# 未知クラスのテストサンプルを、全クラス候補で分類
y_pred_gzsl_u = predict(X_test, W, S_all_cand, all_ids)
acc_unseen_in_gzsl = (y_pred_gzsl_u == y_test).mean()
print(f"GZSLでの未知クラス精度(候補=全15クラス): {acc_unseen_in_gzsl:.3f}")
このコードを実行すると、先ほど 0.98 前後だった未知クラスの精度が 0.6 程度まで落ち込むのが観察できます。候補に既知クラスを加えただけで、多くの未知サンプルが似たような既知クラスへ吸い寄せられてしまうためです。同じモデル・同じ入力なのに、候補集合を広げただけで精度が崩れる——これが GZSL のバイアスの正体です。これを補正する代表的な手法が calibrated stacking です。
calibrated stacking による補正
calibrated stacking(Chao ら, 2016)のアイデアはシンプルです。既知クラスのスコアから一律に定数 $\eta$ を引いて「下駄を脱がせ」、既知への偏りを相殺します。
$$ \hat{y} = \arg\max_{c \in \mathcal{Y}^s \cup \mathcal{Y}^u} \Big( \bm{x}^\top\bm{W}\bm{s}_c – \eta \,\mathbb{1}[c \in \mathcal{Y}^s] \Big) $$
$\mathbb{1}[\cdot]$ は既知クラスのときだけ1になる指示関数です。$\eta$ を大きくするほど未知クラスが当たりやすくなりますが、行きすぎると今度は既知クラスが当たらなくなります。この綱引きを可視化しましょう。
# 既知クラスのテストサンプルも用意(GZSLは両方で測る)
seen_test_mask = np.isin(y_all, seen)
Xs_test, ys_test = X_all[seen_test_mask], y_all[seen_test_mask]
def gzsl_eval(eta):
# 既知クラスのスコアからetaを引く補正
def predict_cal(X):
scores = X @ W @ S_all_cand.T
scores[:, :len(seen)] -= eta # 既知クラス列だけ下駄を脱がす
return all_ids[scores.argmax(axis=1)]
acc_u = (predict_cal(X_test) == y_test).mean() # 未知クラス精度
acc_s = (predict_cal(Xs_test) == ys_test).mean() # 既知クラス精度
H = 2 * acc_s * acc_u / (acc_s + acc_u + 1e-12) # 調和平均
return acc_s, acc_u, H
etas = np.linspace(0, 6, 40)
res = np.array([gzsl_eval(e) for e in etas])
plt.figure(figsize=(7, 4.5))
plt.plot(etas, res[:, 0], label='Seen accuracy')
plt.plot(etas, res[:, 1], label='Unseen accuracy')
plt.plot(etas, res[:, 2], label='Harmonic mean H', lw=2.5)
plt.axvline(etas[res[:, 2].argmax()], color='gray', ls='--', label='best $\\eta$')
plt.xlabel('calibration $\\eta$'); plt.ylabel('accuracy')
plt.title('Calibrated stacking trade-off (GZSL)')
plt.legend(); plt.tight_layout(); plt.show()
このグラフは GZSL の本質を一枚で物語っています。$\eta=0$(補正なし)では既知クラス精度が高く未知クラス精度が低い、典型的な既知偏重の状態です。$\eta$ を増やすと未知クラス精度が上昇し既知クラス精度が下降していき、両者が交差するあたりで調和平均 $H$ が最大になります。調和平均は「既知・未知のどちらかが極端に低いと大きく下がる」性質を持つため、GZSL では平均精度ではなくこの $H$ で性能を測るのが標準になっています。最適な $\eta$ はこのトレードオフの均衡点を選んでいるわけです。
ちなみに $\eta$ を全域で動かしたときの「既知精度 vs 未知精度」曲線の下の面積を AUSUC(Area Under Seen-Unseen accuracy Curve) と呼び、calibration の閾値選びに依存しない総合指標として使われます。次節で、こうした評価のお作法を含むベンチマークの常識を整理します。
評価指標とベンチマークの常識
ゼロショット学習の研究を読むうえで欠かせない「お作法」を押さえておきましょう。これを知らないと論文の数字を誤読します。
- 標準データセット: 属性つき画像の AwA2(動物50クラス)、CUB(鳥200種・細粒度)、SUN(シーン717種)が定番です。CUB や SUN は細粒度で難度が高く、手法の真価が問われます。
- GBU split: Xian らの “the Good, the Bad and the Ugly”(2017)が指摘した重要な注意点があります。未知クラスとして使うクラスが、特徴抽出に使った ImageNet 事前学習に含まれていてはいけないのです。さもないと「未知のはずのクラスを実は事前学習で見ていた」ことになり、性能が過大評価されます。この問題を排した Proposed Split(PS) が今日の標準ベンチマークです。
- 評価指標: 素のゼロショットは未知クラスでの per-class平均精度(クラス不均衡を均すためクラスごとに平均)。GZSL は既知精度 $\text{acc}_s$、未知精度 $\text{acc}_u$、およびその調和平均 $H = 2\,\text{acc}_s\,\text{acc}_u/(\text{acc}_s+\text{acc}_u)$ の3つを必ず報告します。
- 誘導的 vs 推論的(inductive / transductive): 訓練時に未知クラスの「ラベルなし画像」を使ってよいかで分かれます。未知画像の分布を覗ける transductive 設定は精度が上がりますが、純粋なゼロショットの仮定からは外れるため、両者を混同しない注意が必要です。
これらを踏まえると、本記事の合成実験で見た「素のゼロショットは高精度だが GZSL でバイアスが出て、calibration で調和平均を最適化する」という流れが、そのまま実データ研究の縮図になっていることがわかります。最後に全体を振り返りましょう。
まとめ
本記事では、ゼロショット学習を学術的な系譜とともに俯瞰し、互換性関数ベースの代表 ESZSL を導出・実装しました。
- 問題設定: 訓練に出ない未知クラスを、属性・単語埋め込み・テキストなどの意味ベクトルを橋にして当てる。クラスを「番号」でなく「意味」で表すのが核心。
- 系譜: 属性ベース(DAP)→ 互換性関数ベース(DeViSE/ALE/SJE/ESZSL)→ 生成ベース(f-CLSWGAN/CADA-VAE/拡散)→ 視覚言語基盤モデル(CLIP・オープン語彙)→ LLM のゼロショット(instruction tuning・zero-shot CoT)と発展してきた。
- 互換性関数: 双線形スコア $\bm{x}^\top\bm{W}\bm{s}_c$ は、$\bm{W}\bm{s}_c$ が意味ベクトルから線形分類器を生成する装置。未知クラスでも意味ベクトルさえあれば分類器が手に入る。
- ESZSL の閉形式解: 二乗損失と交差項つき正則化により、$\bm{W} = (\bm{X}^\top\bm{X}+\lambda\bm{I})^{-1}\bm{X}^\top\bm{Y}\bm{S}(\bm{S}^\top\bm{S}+\gamma\bm{I})^{-1}$ という両側リッジ回帰の形で一発で解ける。
- GZSL のバイアス: 候補に既知クラスを加えると既知偏重で崩壊する。calibrated stacking で既知スコアに下駄を脱がせ、調和平均 $H$ を最大化するのが定石。
- 評価のお作法: AwA2/CUB/SUN、GBU の Proposed Split、調和平均 $H$、inductive/transductive の区別を押さえる。
ゼロショット学習は「未知を既知の知識で組み立てる」という汎化の本質に触れる枠組みです。古典的な互換性関数の数式を理解しておくと、CLIP のテキスト埋め込みによる分類も、LLM のプロンプトによるタスク遂行も、「意味空間を橋にした同じ発想の異なるスケール」として一望できるようになります。
次のステップとして、以下の記事も参考にしてください。
- CLIPの対照学習を数式から理解してPythonで実装する — 本記事の意味ベクトルをテキスト埋め込みに置き換えた現代版ゼロショット
- 対照学習(Contrastive Learning)の理論と損失関数の導出 — CLIP の学習目的の基礎
- 転移学習の理論と実践 — 事前学習モデルの活用戦略 — 知識転移という隣接パラダイム
参考文献
- C. H. Lampert, H. Nickisch, and S. Harmeling, “Attribute-Based Classification for Zero-Shot Visual Object Categorization,” IEEE TPAMI, 2014.
- B. Romera-Paredes and P. H. S. Torr, “An Embarrassingly Simple Approach to Zero-Shot Learning,” ICML, 2015.
- A. Frome et al., “DeViSE: A Deep Visual-Semantic Embedding Model,” NeurIPS, 2013.
- Y. Xian, B. Schiele, and Z. Akata, “Zero-Shot Learning — The Good, the Bad and the Ugly,” CVPR, 2017.
- W.-L. Chao, S. Changpinyo, B. Gong, and F. Sha, “An Empirical Study and Analysis of Generalized Zero-Shot Learning for Object Recognition in the Wild,” ECCV, 2016.
- Y. Xian et al., “Feature Generating Networks for Zero-Shot Learning (f-CLSWGAN),” CVPR, 2018.
- A. Radford et al., “Learning Transferable Visual Models From Natural Language Supervision (CLIP),” ICML, 2021.
- T. Kojima et al., “Large Language Models are Zero-Shot Reasoners,” NeurIPS, 2022.