⚠️ このページは機械翻訳されています

この日本語訳は機械翻訳によるもので、誤りや不自然な表現が含まれている場合があります。正確な内容については英語版をご参照ください。

This page was machine-translated and may contain errors. Please refer to the English version for accuracy.

誤りを見つけられた場合は、joseph.austerweil@gmail.com までご連絡いただくか、GitHub リポジトリにプルリクエストをお送りください

数字ゲームとサイズ原理

汎化は事後分布で重み付けされた投票

いよいよ核心に入ります。新しいアイテム $y$ がその性質を持つかどうかを予測する方法を、一行で表すことができます。その確率は、$y$ を含む仮説に乗っている事後信念の合計です。

$$p(y \in C \mid X) = \sum_{h \in \mathcal{H}} \mathbf{1}[y \in h] \cdot p(h \mid X).$$

平易な言葉で読んでみましょう。すべての仮説が一票を投じます。 それぞれの仮説の票は、私たちが今それをどれほど信じているか — 事後分布 $p(h \mid X)$ — で重み付けされており、仮説は $y$ を実際に含んでいる場合にのみ $y$ に「賛成」票を投じます(それが $\mathbf{1}[y \in h]$ です)。賛成票を合計すれば予測が得られます。これは §2 の二規則計算を、任意の数の規則に対して書き直したものです。その中のすべての記号は §4 で定義されました。

列挙による投票の計算

このセクションで唯一の本当に新しいコードのアイデアを紹介します — そしてそれは物事を複雑にするのではなく、シンプルにします。$\mathcal{H}$ は有限なリストであるため、何もサンプリングする必要はありません。すべての仮説を直接スコアリングして正規化できます。それだけです。(§4 で学んだ「行に対する vmap」は、まさに「すべての仮説を一度にスコアリングする」ためのツールです。)

このセクションでは §2 の弱いサンプリングの尤度を使い続けます。ある規則が観測データと一致するのは、それがすべての観測例を含む場合(尤度 1)であり、もし一つでも見逃せば不可能です(尤度 0)。それだけで投票を機能させるには十分です。§6 では、より鋭い尤度 — 強いサンプリング — を導入します。それは一致する規則の中でより小さな規則を優先します。

10 という数字にその性質があると教えられたとして、他のどの数字がその性質を持つか予測したいとします。まず規則に対する事後分布を求めます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import jax.numpy as jnp
# (H と prior は §4 で定義された仮説行列と一様事前分布です。)

# 数字 10 がその性質を持つことが観測されました。10 は列 9 にあります(数字 n → 列 n-1)。
observed = 9    # 数字 10 の列インデックス

# 尤度:規則が観測された数字を含む場合は一致(1)、そうでなければ不可能(0)。
# H[:, observed] は列 `observed` を縦に読む -- すべての規則から、その数字の 0/1 の帰属を
# 一度に取り出します(":" は「すべての行」を意味します;行は規則です。つまり、これは
# データサンプルごとではなく、規則ごとに一つのエントリです -- GenJAX チュートリアルで
# 列がどのように機能したかとは異なります)。
consistent = H[:, observed]

# 規則に対する事後分布:事前分布 x 尤度、次に正規化。(ベイズ則、規則ごとにスコアリング。)
post = prior * consistent
post = post / post.sum()

for label, p in zip(labels, post):
    print(f"  P({label:16s} | saw 10) = {round(float(p), 3)}")

出力:

  P(all numbers      | saw 10) = 0.25
  P(even numbers     | saw 10) = 0.25
  P(multiples of 3   | saw 10) = 0.0
  P(multiples of 10  | saw 10) = 0.25
  P(powers of 2      | saw 10) = 0.0
  P(numbers 1-10     | saw 10) = 0.25
  P(numbers 20-30    | saw 10) = 0.0

四つの規則が生き残ります — 「すべての数」「偶数」「10 の倍数」「1–10 の数」はすべて 10 を含んでいます — 一様事前分布のもとでは信念を均等に 0.25 ずつ分け合います(他の三つは排除されるため、生き残った四つは 1/7 から 1/4 へと再正規化されます)。「3 の倍数」「2 の累乗」「20–30 の数」は 10 を含まないため、それらのもとでは 10 を観測することは不可能です。(生き残った規則はまだ同点です — 弱いサンプリングは、タイトな「10 の倍数」とゆるい「すべての数」をまだ区別できません。§6 でこれを修正します。)

次に投票です。各数字 $y$ について、それを含む規則の事後分布を合計します — これが章の方程式を一度に一つの数字ずつ計算したものです。

1
2
3
4
5
6
7
8
9
import jax.numpy as jnp
# (続き:H と post は直前のセルから来ています。)

# すべての数字 1..30 について P(数字 n がその性質を持つ | データ)を予測する。
predictive = jnp.array([jnp.sum(post * H[:, n - 1]) for n in numbers])

# いくつかを表示:
for n in [10, 2, 5, 7, 25]:
    print(f"  P({n:2d} has the property | saw 10) = {round(float(predictive[n - 1]), 3)}")

出力:

  P(10 has the property | saw 10) = 1.0
  P( 2 has the property | saw 10) = 0.75
  P( 5 has the property | saw 10) = 0.5
  P( 7 has the property | saw 10) = 0.5
  P(25 has the property | saw 10) = 0.25

数字のグラデーションを読んでみましょう。10 自体は 1.0 — 生き残ったすべての規則がそれを含みます。2 は 0.75:「偶数」かつ「1–10 の数」かつ「すべての数」に含まれます — 生き残った四つの規則のうち三つ、それぞれ 0.25 の価値があります。57 はそれぞれ 0.5 — どちらも「1–10 の数」と「すべての数」(四つの生き残りのうち二つ)に含まれます。25 は 0.25 — 25 に届く唯一の生き残り規則は、包括的な「すべての数」だけです(「1–10 の数」には大きすぎ、偶数でもありません)。その 0.25 という下限は包括規則の特徴です。それはあらゆる数を含むため、それが生き残っている限り、どの数もゼロに下がることはありません。一つの例からの汎化は広くかつ段階的です。多くの数が適度なサポートを得ており、まだ明確な規則はありません。その拡散した広がりは、一つの例の後の人間のパターンとまさに一致します。(§6 でサイズ原理がオンになると、このグラデーションを可視化し、それが規則に鮮明に収束していく様子を見ることになります。)

現在地

完全な汎化グラデーションを計算できるようになりました:仮説空間を設定し、ベイズ則ですべての規則をスコアリングし、それらを含む規則の事後分布で重み付けされた投票として新しい数字を予測します。しかし、生き残った規則はそれぞれ信念の単位あたり等しくカウントされていました — タイトな「10 の倍数」もゆるい包括規則「すべての数」もそれぞれ 0.25 でした。これが話の全部であるはずはありません。直感的に、10、20、そして 30 を見たなら、タイトな規則「10 の倍数」が「すべての数」に勝るべきです。それを実現すること — 規則のサイズがその尤度を変えるようにすること — がサイズ原理であり、次はそれです。


例はどこから来たのか?弱いサンプリングと強いサンプリング

§5 で使った尤度を再び見てみましょう。規則はデータを含む場合に一致(尤度 1)、そうでなければ不可能(尤度 0)でした。その規則はサイズに無頓着です — 「10 の倍数」も「すべての数」も 10 を含むため、両方とも尤度 1 を得て、同点になりました。しかし直感的には、データにかろうじて合うタイトな規則は、偶然にしか合わないゆるい規則より優先されるべきです。そこに到達するには、これまで飛ばしてきた問いを考えなければなりません:観測された例はそもそもどのように生成されたのか?

自然な答えは二つあり、それらは二つの異なる尤度を与えます。

  • 弱いサンプリング。 例は規則の外側のどこかから来ました — 世界がそれらをあなたに渡したのです — そしてあなたはただ各例が $h$ に落ちるかどうかを確認しただけです。この話のもとでの尤度は、§5 のサイズに無頓着なものとまったく同じです:

    $$p(X \mid h) = \begin{cases} 1 & \text{if every } x_i \in h \\ 0 & \text{otherwise} \end{cases}$$

    規則はデータを含むかどうかだけで、そのサイズは入ってきません。

  • 強いサンプリング。 例は規則の内側から引き出されました — あたかも誰かが集合 $h$ に手を入れてメンバーをランダムに取り出したかのようです。$h$ が $|h|$ 個のメンバーを持ち、各例がそこからの独立した一様サンプリングであれば、各サンプリングの確率は $1/|h|$ であり、$n$ 個の独立サンプルの確率は

    $$p(X \mid h) = \left(\frac{1}{|h|}\right)^{\!n}$$

    となります。 今ではサイズが非常に重要です:小さな規則は任意のメンバーに高い確率を割り当てます。なぜなら選ぶメンバーが少ないからです。

(§4 の $|h|$ を思い出してください — 規則のサイズ、その行の 1 の数で、jax.vmap(jnp.sum)(H) ですべての規則に対して計算したものです。そして $n$ は観測された例の数で、第 4 章から使ってきたのと同じ $n$ です。)

サイズ原理

あの小さな指数 $(1/|h|)^n$ が全ての鍵です。強いサンプリングのもとでは、小さな仮説ほど高い尤度を得ます — そしてその優位性は例の数 $n$ とともに指数的に大きくなります。これがサイズ原理です:データと一致する規則の中で、データは最も小さな規則に最も強く票を投じ、例が増えるほどますます決定的になります。

この直感には名前があります:疑わしい一致です。数字 $\{10, 20, 30\}$ を見たとしましょう。それらはすべて 10 の倍数ですが、同時にすべて偶数でもあります。もし真の規則が「偶数」であれば、三つすべてが偶然に 10 の倍数に当たったことは疑わしい一致でしょう。1–30 の数の中で偶数は 15 個あり、そのうち 10 の倍数はたった 3 個なので、「偶数」から偶然に 3 つの 10 の倍数を引くのは難しいことです。強いサンプリングはその疑いを形式化します:「偶数」はこのデータを生成しえますが、運が良くなければなりません。「10 の倍数」はまさにこの種のデータを予測します — 三つのメンバーしかなく、三つすべてが現れたのです — だから勝ちます。

数字ゲームにおける強いサンプリング

サイズ原理が §5 の結果をどのように作り変えるかを見てみましょう。10 を観測すると四つの規則が同点になったことを思い出してください — タイトな「10 の倍数」(サイズ 3)はゆるい包括規則「すべての数」(サイズ 30)よりも優れていませんでした。強いサンプリングはその同点を崩し、新しい例が増えるたびにより強く崩します。

新しいコードは一つの関数 log_likelihood で、二つのサンプリングの話が組み込まれています。対数空間で作業します。対数は積を和に変えるという事実を使います:$\log(ab) = \log a + \log b$。複数の独立した例の尤度は例ごとの確率のなので、対数空間ではになります — だから下の関数は .sum() で例ごとの対数確率を足し算するだけです。対数を使うのは $(1/|h|)^n$ が非常に小さくなりうるからで、対数の和は多くの小さな数を掛け合わせる数値的に安全な方法です。最後のステップ — jnp.exp(... - max) してから合計で割る — は GenJAX チュートリアルで実行した重要度重み付け正規化そのものです。唯一新しいのは、generate から対数重みを受け取る代わりに、jnp.log で手動で対数空間に入ることです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import jax
import jax.numpy as jnp
# (H、prior、labels、numbers は §4 から:7 規則の数字ゲームと一様事前分布。)

def log_likelihood(rule, observed, strong):
    """log p(X | rule) for one rule, under weak or strong sampling.

    `observed` is a jnp.array of observed COLUMN indices; `strong` is a plain Python bool.
    """
    # rule[observed] indexes the rule by an ARRAY of positions, returning one entry per
    # observed number at once -- e.g. rule[jnp.array([9, 19])] gives [rule[9], rule[19]].
    # Each entry is 1.0 if the rule contains that observed number, else 0.0.
    # (JAX wants a jnp.array here, not a plain Python list -- see how `observed` is built below.)
    in_rule = rule[observed]
    size = rule.sum()                 # |h|, the number of numbers this rule includes

    # We work in LOG space: take the log of each probability, so multiplying probabilities
    # becomes adding logs (the .sum() at the end). jnp.where does its FAMILIAR job from
    # Chapter 5 -- pick a value per element:
    #   strong: an in-rule example contributes log(1/size) = -log(size);
    #   weak:   an in-rule example contributes log(1)      = 0.
    # A MISSED example contributes -jnp.inf, which is log(0): "impossible under this rule,"
    # driving its whole likelihood to exactly 0 once we exponentiate at the end.
    per_example_strong = jnp.where(in_rule > 0, -jnp.log(size), -jnp.inf)
    per_example_weak   = jnp.where(in_rule > 0, 0.0,            -jnp.inf)

    # The genuinely NEW idiom: a plain Python `if` on `strong` switches the whole FORMULA.
    # `strong` is an ordinary Python True/False, fixed before any array math, so a normal
    # `if` just chooses which formula to build. (Contrast jnp.where above, which decides
    # element-by-element *inside* the array math. Rule of thumb: a plain `if` to pick a whole
    # formula from a Python flag; jnp.where to pick a value per array element.)
    per_example = per_example_strong if strong else per_example_weak

    return per_example.sum()          # sum of logs = log of the product of per-example probabilities

def posterior(observed, strong):
    """Posterior over all rules, by enumeration, under the chosen sampling story."""
    log_post = jnp.log(prior) + jax.vmap(lambda r: log_likelihood(r, observed, strong))(H)
    log_post = log_post - log_post.max()      # subtract the max for numerical stability
    p = jnp.exp(log_post)
    return p / p.sum()

# Column indices for the numbers we observe (number n -> column n-1).
one_example  = jnp.array([9])             # {10}
three_example = jnp.array([9, 19, 29])    # {10, 20, 30}

print("STRONG, after {10}:")
for label, p in zip(labels, posterior(one_example, strong=True)):
    print(f"   {label:16s} {round(float(p), 3)}")

print("STRONG, after {10, 20, 30}:")
for label, p in zip(labels, posterior(three_example, strong=True)):
    print(f"   {label:16s} {round(float(p), 3)}")

print("WEAK, after {10, 20, 30}:")
for label, p in zip(labels, posterior(three_example, strong=False)):
    print(f"   {label:16s} {round(float(p), 3)}")

出力:

STRONG, after {10}:
   all numbers      0.062
   even numbers     0.125
   multiples of 3   0.0
   multiples of 10  0.625
   powers of 2      0.0
   numbers 1-10     0.188
   numbers 20-30    0.0
STRONG, after {10, 20, 30}:
   all numbers      0.001
   even numbers     0.008
   multiples of 3   0.0
   multiples of 10  0.991
   powers of 2      0.0
   numbers 1-10     0.0
   numbers 20-30    0.0
WEAK, after {10, 20, 30}:
   all numbers      0.333
   even numbers     0.333
   multiples of 3   0.0
   multiples of 10  0.333
   powers of 2      0.0
   numbers 1-10     0.0
   numbers 20-30    0.0

これが — 有名な数字ゲーム効果を定量化したものです。一つの例(10)で、強いサンプリングはすでにタイトな規則に傾いています — 「10 の倍数」は 0.625 を得ます。各例がそこでは $1/3$ の確率ですが、「すべての数」のもとでは $1/30$ の確率しかないからです — しかし「偶数」(0.125)と「1–10 の数」(0.188)は実質的な質量を保っており、汎化は広くかつ段階的なままです。三つの例(10, 20, 30)では、サイズの優位性が三乗になります:$(1/3)^3$ 対 $(1/30)^3$ は 1000 対 1 の尤度比で、「10 の倍数」は 0.99 へと急上昇し、他のすべては崩壊します — 汎化が規則に収束します。段階的な類似から確信ある規則へのその切り替えは、たった三つの例で起き、まさに人間がやることと一致します。

二つの事後分布を並べて比較します。

七つの規則の事後確率の二つの横棒グラフ。左、{10} のみを観測した後:信念が広がっています — 「10 の倍数」が約 0.63 でリードしていますが、「1–10 の数」(約 0.19)、「偶数」(約 0.13)、「すべての数」(約 0.06)も意味のある確率を保っています。右、{10, 20, 30} を観測した後:信念のほぼすべて(約 0.99)が「10 の倍数」に集中し、他のすべての規則はゼロに近い。一つの例は信念を段階的なままにし、三つの例はそれを一つの規則に収束させます。 七つの規則の事後確率の二つの横棒グラフ。左、{10} のみを観測した後:信念が広がっています — 「10 の倍数」が約 0.63 でリードしていますが、「1–10 の数」(約 0.19)、「偶数」(約 0.13)、「すべての数」(約 0.06)も意味のある確率を保っています。右、{10, 20, 30} を観測した後:信念のほぼすべて(約 0.99)が「10 の倍数」に集中し、他のすべての規則はゼロに近い。一つの例は信念を段階的なままにし、三つの例はそれを一つの規則に収束させます。

そして比較のための弱いサンプリング:{10, 20, 30} のすべてを含む三つの規則 — 「すべての数」「偶数」「10 の倍数」 — は 0.333 で永遠に同点のままです。10 の倍数をいくら積み重ねても変わりません。弱いサンプリングはサイズに無頓着なので、疑わしい一致に気づくことができません。弱い結果(三つ巴の同点で膠着)と強い結果(タイトな規則に 0.99)のギャップがまさにサイズ原理です。

次に事後分布を数字に対する予測に変換します — §5 の事後分布で重み付けられた投票を、強いサンプリングのもとで一例の事後分布と三例の事後分布の両方に対して実行します。

数字 1 から 30 が強いサンプリングのもとでその性質を持つ予測確率の棒グラフ、二つのケースを比較しています。薄い棒({10} のみ観測した後):10 は 1.0 ですが、すでに他の 10 の倍数 — 20 と 30 — は約 0.81 で高くなっています。サイズ原理が一つの例からでさえタイトな「10 の倍数」規則に信念の大半を押しやっているからです。小さな偶数 2, 4, 6, 8 は約 0.38 付近にあり、他のほとんどの数は 0.25 以下です。濃い棒({10, 20, 30} を観測した後):10, 20, 30 だけが 1.0 近くに立ち、他のすべての数は本質的にゼロです。点線の縦線は三つの観測された数字を示しています。例が増えると、すでに傾いていたグラデーションが 10 の倍数上の三つの鋭いスパイクになります。 数字 1 から 30 が強いサンプリングのもとでその性質を持つ予測確率の棒グラフ、二つのケースを比較しています。薄い棒({10} のみ観測した後):10 は 1.0 ですが、すでに他の 10 の倍数 — 20 と 30 — は約 0.81 で高くなっています。サイズ原理が一つの例からでさえタイトな「10 の倍数」規則に信念の大半を押しやっているからです。小さな偶数 2, 4, 6, 8 は約 0.38 付近にあり、他のほとんどの数は 0.25 以下です。濃い棒({10, 20, 30} を観測した後):10, 20, 30 だけが 1.0 近くに立ち、他のすべての数は本質的にゼロです。点線の縦線は三つの観測された数字を示しています。例が増えると、すでに傾いていたグラデーションが 10 の倍数上の三つの鋭いスパイクになります。

一つの例の後、予測はすでに 10 の倍数(20 と 30 は 0.81 近く)に強く傾いており、偶数の上に低く広がったすそ野を保っています — 段階的ですが、タイトな規則がすでに優勢です。三つの例の後、三つの鋭いスパイクに収束します。モデルは戦略を変えませんでした — 同じ事後分布で重み付けされた投票を両方に対して実行しました。変わったのは、サイズ原理がどれほど決定的に事後分布を集中させたかだけであり、汎化グラデーションはそれに従いました。

包括規則が完全には消えない理由

「すべての数」は小さなスライスを保っていることに注目してください(一例の後 0.062、三例の後 0.001)、決して正確にゼロにはなりません。包括規則はすべてを含むため、決して排除されません — どんなデータも説明できます。サイズ原理はそれを除去しません;データをより鋭く予測するタイトな規則と比べて起こりにくくするだけです。これを覚えておいてください:§9 では、包括規則(とそれに似た規則)が唯一残ったときに何が起きるかを見ます。


今計算したもの:数字ゲーム

上で計算したのはテネンバウムの数字ゲームです。人間の概念学習で最もよく研究されている効果の一つです — そして自分の頭の中でそれを感じることができるので、少し立ち止まる価値があります。ゲームの内容:誰かがいくつかの数字を選ぶ隠れた規則を持っていて、それに合う数字をいくつか見せます。あなたは他のどの数字が合うかを判断します。

  • 10 が規則に合うと言われます。他にどの数字が合いますか?ほとんどの人は確信が持てません — 20? 5? 30? 多くが妥当に思えます。推測は広くかつ曖昧です。({10} 後のモデル:広くて段階的なグラデーション。)
  • 次に 10、20、そして 30 がすべて合うと言われます。今度はどれが合いますか?ほとんどすべての人が明確な規則に収束します — 「10 の倍数」:40 は yes、25 は no。({10, 20, 30} 後のモデル:10 の倍数上の鋭いスパイク。)

同じ最初の例 10 が、その後に何が続くかによってまったく異なる汎化をもたらします — そして私たちのモデルは戦略を変えることなく、サイズ原理が証拠の蓄積とともに事後分布を集中させるだけで、両方の状態を再現しました。それが要点のすべてです:一つのメカニズムが類似性的(段階的)な汎化と規則的(全か無か)な汎化の両方を生み出し、データが示すにつれて自動的に切り替わります。

一つのフレームワーク、仮説を交換するだけ

この章全体を通じて何が変わらなかったかに注目してください。金色のステッカー(§2)とここの数字は、まったく同じ事後分布で重み付けされた投票の機構を使っています — 仮説空間に対するベイズ則と強いサンプリングのサイズ原理です。異なるのは$\mathcal{H}$ にどの集合があるかだけです。仮説を交換すれば、同じコードでまったく異なる領域をモデル化します — これが、この章の課題でやることとまさに同じです。自分で設計した仮説空間(動物に対して)にこの機構を適用します。古典的な数字ゲームもスケールアップします:テネンバウムは数字 1–100 と数十の規則 — 「$k$ の倍数」「$k$ の累乗」、平方数、大きさの区間など多数 — を使い、それらすべてに対して同じ列挙を実行し、人間の完全な汎化曲線を再現しました。1–30 に対する私たちの七つの規則は、それの読みやすいミニチュアです。

現在地

完全な離散フレームワークが手に入りました:集合の仮説空間、それらをスコアリングするベイズ則、タイトな規則を優先するサイズ原理(強いサンプリングから)、そして予測するための事後分布で重み付けられた投票です。同じ方程式が金色のステッカーと数字ゲームを扱いました。二つの問いが残っています。まず:仮説が連続的な場合はどうなるか — あらゆる可能な区間のように無限に多い場合は?それが次の長方形ゲームで、モデルから単にシェパードの指数法則を目指すのではなく、それが導かれるのを最終的に見ることができます。次に:仮説空間自体はどこから来て、間違えるとどうなるか?それが §9 のノーフリーランチです。

2026/07/02