ゼロショット回帰 — データの無い条件でも連続値を予測する仕組み

新しい製造条件(温度や材料の配合)で製品の強度がどうなるか、まだ一度もその条件で作っていないのに予測したい——こうした「データの無い条件での予測」は、現場では切実なニーズです。実験には費用も時間もかかるので、「やってみる前に当てたい」のは当然でしょう。

通常の回帰は「学習した条件と同じ条件」でしか予測できません。しかし、いくつかの条件でデータを集め、条件そのものを数値で表すことができれば、一度もデータを取っていない未知の条件についても予測できる——これが ゼロショット回帰(zero-shot regression) です。「ゼロショット」とは「その条件のサンプルがゼロ(一度も見ていない)」を意味します。

鍵となる発想は意外なほどシンプルです。条件ごとに回帰モデルが違うなら、「モデルのパラメータそのものを、条件の関数として学ぶ」。そうすれば、未知の条件を関数に入れるだけでモデルが再構成でき、予測できます。本記事では、この仕組みを直観から実装まで解説し、Pythonで「条件を無視したプール回帰」「最近傍の条件のモデル」「ゼロショット回帰」の予測誤差を実測して比べます。

土台としてゼロショット学習(分類)の発想と、回帰の基礎、関連するPLS回帰、条件の近さを測るカーネル密度推定を随所で参照します。

ゼロショット回帰の概念

図が全体像です。いくつかの条件ではデータがありますが、未知条件3にはデータがありません。それでも「条件で橋渡しする回帰モデル」を作れば、データの無い条件でも $y$ を予測できる——この橋渡しをどう作るかが本記事のテーマです。

ゼロショット分類との違い

「ゼロショット」という言葉は、もともと分類で広まりました。両者を対比すると、ゼロショット回帰の位置づけがはっきりします。

ゼロショット分類と回帰

ゼロショット分類は「学習時に存在しなかった未知クラス」を当てる問題です。たとえば「シマウマ」の画像を一枚も見ずに、「馬に似て縞模様」という属性(意味記述)を頼りに分類します。離散的なラベルが対象です。

ゼロショット回帰は、これを連続値に持ち込んだものです。当てるのは離散クラスではなく、未知の条件における連続的な出力値。そして属性の代わりに使うのが、条件を数値ベクトルで表した条件記述子です。「未知の対象を、補助情報(属性/記述子)を介して既知のものと繋ぐ」という骨格は両者で共通しています。

問題設定:条件で関係がずれる

ゼロショット回帰が必要になるのは、条件によって入力 $x$ と出力 $y$ の関係そのものが変わるときです。

条件で関係がずれる

図では、条件 $c$ が変わると直線の傾きと切片が変わっています。条件 $c=0.0,\,0.4,\,0.8$ ではデータがあり、それぞれの直線が引けます。しかし知りたいのは赤い破線——データの無い未知条件 $c=0.6$ での関係です。

ここで素朴な手は2つ考えられますが、どちらも不十分です。1つは「条件を無視して全データを混ぜ、1本の直線を引く」(プール回帰)。これは各条件の関係を平均してしまい、どの条件にも最適でなくなります。もう1つは「未知条件に最も近い既知条件のモデルをそのまま使う」(最近傍)。これは近い条件が1つしかないと、その条件固有のずれをそのまま引きずります。では、どうすればよいのか。

核心:パラメータを条件の関数にする

ゼロショット回帰の核心は、回帰モデルのパラメータ(傾き・切片など)を、条件記述子 $c$ の関数として表すことです。

条件記述子で橋渡し

まず、各条件を数値ベクトル $c$(条件記述子)で表します。温度・配合比・装置設定など、その条件を特徴づける測定可能な量です。そのうえで、予測モデルを

$$ \begin{equation} y = f(x, c) \end{equation} $$

という「入力 $x$ と条件 $c$ の両方を受け取る関数」として学習します。こうしておけば、未知条件 $c^*$ の記述子を入れるだけで予測できます。条件 $c$ がモデルの中に明示的に入っているので、見たことのない $c^*$ でも値が定まるのです。

最もわかりやすい実装は、線形回帰のパラメータ自体を $c$ の関数にするものです。条件 $c$ での関係が $y = a(c)\,x + b(c)$ だとして、傾き $a(c)$ と切片 $b(c)$ を、既知条件での実測値から $c$ の関数として回帰します。

パラメータを条件の関数に

図のように、各ソース条件で求めた傾き(左)と切片(右)を点として打ち、それを $c$ の関数(ここでは直線)で回帰します。すると、データの無い $c^*=0.6$ でも、その関数上で傾きと切片を内挿して求められます。「個々の条件のモデル」を一段上から眺め、「モデルがどう変化するか」を学ぶ——この二段構えがゼロショット回帰の本質です。

未知条件の「近さ」をKDEで測る

ただし、内挿が信頼できるのは「未知条件がソース条件の近くにある」ときだけです。そこで、未知条件がソース条件にどれだけ近いかを測る必要があります。ここでカーネル密度推定(KDE)が役立ちます。

KDEで近さを測る

ソース条件の集合からKDEで「条件の密度」を推定すると、未知条件 $c^*$ がソースの密集地帯にあるのか、それとも疎な場所にあるのかが分かります。図で $c^*=0.6$ は密度が高く、周囲にソース条件があるので信頼できます。一方、$c=1.25$ のような密度の低い場所は、近くにソースが無く、予測の信頼性が下がります。KDEは「この予測をどれくらい信じてよいか」の目安を与えてくれるのです。実際の研究では、このKDEによる密度(近さ)を予測の重み付けや信頼度評価に組み込むアプローチが提案されています。

もう一つの実装戦略として、パラメータを関数化する代わりに、近い条件のモデルを重み付きで混ぜる方法もあります。

近い条件のモデルを混ぜる

図のように、未知条件に近いソース条件のモデルほど大きな重みを与えて加重平均します。重みは条件記述子の距離(KDEのカーネルがそのまま使えます)で決めます。パラメータ関数化が「滑らかな関係を仮定して内挿する」のに対し、こちらは「近傍の実測モデルをブレンドする」——どちらも「条件の近さ」を頼りにする点で共通しています。

内挿は安全、外挿は危険

ゼロショット回帰で最も重要な注意点が、内挿と外挿の区別です。

内挿と外挿

未知条件がソース条件の囲む範囲の内側にあれば、それは内挿であり、比較的信頼できます(図の緑帯、$c^*=0.6$)。しかし範囲の外側にある外挿(図の赤帯、$c^*=1.4$)では、関係がその先でも同じ形で続く保証がなく、予測は当てにならなくなります。これは回帰一般に言えることですが、「データの無い条件を狙う」ゼロショット回帰では特に致命的です。先ほどのKDE密度は、まさにこの「内側か外側か」を定量化する道具でもあります。ソース条件は、予測したい未知条件を取り囲むように設計する——これがゼロショット回帰を成功させる実務上の鉄則です。

ここまでの主張を、Pythonで実際に確かめましょう。

Pythonで確かめる

条件 $c$ によって傾き $a(c)=1+2c$、切片 $b(c)=0.5-c$ が変わる線形関係を考えます。ソース条件 $c=0.0,0.2,0.4,0.8,1.0$ でデータを集め、データの無い未知条件 $c^*=0.6$ での予測精度を、3つの方法で比べます。

import numpy as np
rng = np.random.default_rng(0)

def gen(c, n, rng):                       # 条件cでのデータ生成
    x = rng.uniform(0, 4, n)
    y = (1 + 2*c)*x + (0.5 - c) + rng.normal(0, 0.3, n)
    return x, y

src_c = np.array([0.0, 0.2, 0.4, 0.8, 1.0])   # ソース条件(データあり)
src = [gen(c, 60, rng) for c in src_c]
c_star = 0.6                                    # 未知条件(データ無し)
xt, yt = gen(c_star, 400, rng)                  # 評価用(予測には使わない)

def fit_line(x, y):
    A = np.vstack([x, np.ones_like(x)]).T
    coef, *_ = np.linalg.lstsq(A, y, rcond=None)
    return coef                                 # [傾き, 切片]
def rmse(p, y):
    return float(np.sqrt(np.mean((p - y)**2)))

まず素朴な2手法です。「プール回帰(条件を無視して全データに1本の直線)」と「最近傍条件のモデル($c^*$ に最も近い既知条件のモデルを流用)」。

# (1) プール回帰:条件を無視
Xp = np.concatenate([s[0] for s in src]); Yp = np.concatenate([s[1] for s in src])
a, b = fit_line(Xp, Yp); r_pool = rmse(a*xt + b, yt)

# (2) 最近傍条件のモデル
near = np.argmin(np.abs(src_c - c_star))
a, b = fit_line(*src[near]); r_near = rmse(a*xt + b, yt)

次がゼロショット回帰です。各ソース条件で傾き・切片を求め、それらを条件 $c$ の関数(1次式)として回帰し、$c^*=0.6$ で内挿します。

# (3) ゼロショット:パラメータを c の関数として内挿
ab = np.array([fit_line(*s) for s in src])      # 各条件の[傾き,切片]
Ac = np.vstack([src_c, np.ones_like(src_c)]).T
ca, *_ = np.linalg.lstsq(Ac, ab[:, 0], rcond=None)   # 傾き a(c) を c で回帰
cb, *_ = np.linalg.lstsq(Ac, ab[:, 1], rcond=None)   # 切片 b(c) を c で回帰
a_star = ca[0]*c_star + ca[1]
b_star = cb[0]*c_star + cb[1]
r_zs = rmse(a_star*xt + b_star, yt)

print(f"プール回帰(条件無視)     RMSE = {r_pool:.3f}")
print(f"最近傍条件のモデル       RMSE = {r_near:.3f}")
print(f"ゼロショット(条件の関数) RMSE = {r_zs:.3f}")
print(f"真の傾き={1+2*c_star:.2f} 推定={a_star:.2f} / 真の切片={0.5-c_star:.2f} 推定={b_star:.2f}")

出力は次の通りです。

プール回帰(条件無視)     RMSE = 0.553
最近傍条件のモデル       RMSE = 0.822
ゼロショット(条件の関数) RMSE = 0.307
真の傾き=2.20 推定=2.18 / 真の切片=-0.10 推定=-0.07

手法の比較

結果は明快です。ゼロショット回帰のRMSEは0.307で、プール回帰(0.553)の約半分、最近傍(0.822)の4割以下に抑えられています。一度もデータを取っていない $c^*=0.6$ にもかかわらず、真の傾き2.20を2.18、真の切片−0.10を−0.07とほぼ正確に再構成できました。

興味深いのは、最近傍がプール回帰よりむしろ悪いことです。$c^*=0.6$ の最近傍は $c=0.4$ か $0.8$(どちらも距離0.2)ですが、それらの傾きは未知条件と0.4ずれており、その固有のずれをそのまま被ってしまいます。プール回帰は全条件の平均でたまたま中間に来るぶんマシですが、それでも各条件に最適化されていません。「最も近い1つ」でも「全部の平均」でもなく、「条件に応じてモデルがどう変化するかの規則」を学ぶことが、未知条件で効く——これがゼロショット回帰の核心を数値で示した結果です。

線形を超えて:f(x, c) を柔軟にする

上の実装では、傾き・切片を $c$ の1次式で内挿しました。これは「条件に対して関係が滑らかに、しかも単純に変化する」場合に有効です。しかし現実には、条件と関係の対応がもっと複雑なこともあります。そのときは $f(x, c)$ をより柔軟なモデルにします。

考え方は2系統あります。1つは、$x$ と $c$ を結合した入力 $(x, c)$ を1つの回帰器に渡すやり方です。たとえば $(x, c, x\cdot c)$ のような交互作用項を加えた線形回帰や、勾配ブースティング・ニューラルネットに $(x, c)$ をそのまま入れると、条件依存の複雑な関係も表現できます。もう1つは、条件で「予測器の振る舞いを変調する」やり方です。条件 $c$ から生成したパラメータでネットワークの中間特徴をスケール・シフトする——これはFiLM(条件変調)の発想そのもので、画像生成だけでなく条件付き回帰でも有効です。条件付き生成の条件付きVAEとも同じ「条件で出力を制御する」系譜にあります。

いずれにせよ核心は変わりません。条件 $c$ をモデルに明示的に組み込み、未知条件を入れれば予測が定まるようにすること。線形内挿はその最小実装であり、表現力を上げたいときは $f(x,c)$ を差し替えればよい、という拡張性がこの枠組みの強みです。

分布シフト対策の中での位置づけ

ゼロショット回帰は、「訓練と本番で状況が変わる」問題への対処法の一つとして、近縁の手法と地続きです。整理しておきましょう。

  • 共変量シフトと重要度重み付け:入力 $x$ の分布がずれるが、$x\to y$ の関係は不変という設定。
  • 転移学習・ドメイン適応:ターゲットのデータ(少量/未ラベル)を使ってモデルを寄せる。
  • ドメイン汎化:ターゲットのデータ無しで、未知環境に通用させる。
  • ゼロショット回帰:ターゲット条件のデータ無しだが、条件記述子という補助情報を使って、その条件に合わせた予測を能動的に構成する。

違いは「ターゲットについて何が手元にあるか」です。ゼロショット回帰は、データは無くても条件を記述する情報はあるという状況をうまく突いています。条件記述子という橋があるからこそ、データのない場所へ予測を届けられるわけです。条件を測れる現場——製造・材料・センサーなど——では、この前提が現実的に成り立ちやすく、実用価値が高いのです。

パイプラインとまとめ

ゼロショット回帰の流れを1枚に整理します。

ゼロショット回帰のパイプライン

(1) 各ソース条件で $(x,y)$ から回帰モデルを学習し、(2) 各条件に条件記述子 $c$ を付与し、(3) モデルのパラメータを $c$ の関数として接続し、(4) 未知条件 $c^*$ を内挿して予測する——これがゼロショット回帰の基本パイプラインです。

実務上の注意点をまとめます。

  • ソースは未知条件を取り囲むように:内挿は信頼できるが外挿は危険。予測したい条件域を、ソース条件で囲むのが鉄則です。
  • 条件記述子の質が決め手:条件の違いを的確に表す記述子を選べるかで精度が決まります。記述子と関係の変化が滑らかに対応しているほど内挿が効きます。
  • KDEなどで信頼度を併記する:未知条件がソースから遠ければ予測の信頼度は下がります。密度や距離で「どれくらい信じてよいか」を添えるべきです。
  • 関係の変化が滑らかである前提:条件に対してモデルが急変・不連続に変わる場合、単純な内挿は破綻します。その場合はより柔軟な $f(x,c)$(ニューラルネットなど)が必要です。

「データの無い条件を、条件記述子という橋で繋いで予測する」——ゼロショット回帰は、実験コストの高い現場で“やる前に当てる”ための強力な枠組みです。その本質は、個々のモデルではなくモデルの変化の規則を学ぶことにあります。

まとめ

ゼロショット回帰を、理論から実装まで解説しました。

  • ゼロショット回帰は、学習データが一切ない未知の条件でも連続値を予測する枠組み。ゼロショット分類の「属性で未知クラスを繋ぐ」発想を、連続値・条件記述子に拡張したもの。
  • 核心は、回帰モデルのパラメータを条件記述子 $c$ の関数として学ぶこと。未知条件 $c^*$ を関数に入れれば、見たことのない条件でもモデルが再構成できる。
  • KDEで未知条件がソースにどれだけ近いかを測り、内挿(安全)か外挿(危険)かを判断する。ソース条件は予測対象を取り囲むように設計するのが鉄則。
  • 実測では、プール回帰 0.553・最近傍 0.822 に対し、ゼロショット回帰は RMSE 0.307 と最良。真の傾き・切片もほぼ正確に再構成できた。「最も近い1つ」でも「全部の平均」でもなく、条件によるモデルの変化規則を学ぶことが効く。

次のステップとして、$f(x,c)$ をニューラルネットで表す手法(FiLMによる条件変調などが条件の組み込みに使えます)や、条件記述子の学習、予測の不確実性評価へ進むと、ゼロショット回帰の実装力がさらに深まります。