ベイズ機械学習のしくみ:事前分布・事後分布からガウス過程・ベイズ最適化まで
ふつうの機械学習のモデルは、「明日の気温は 18 度」のように、1 つの値を答えます。しかし、その答えにどれくらい自信があるかは分かりません。たくさんのデータで確かめた範囲の予測なのか、データがまったくない範囲を当て推量で答えているのかで、予測の信頼度は大きく違うはずです。
ベイズ機械学習は、モデルのパラメーターや予測を、1 つの値ではなく 確率分布として扱います。「18 度だが、16〜20 度のどこかである可能性が高い」のように、予測の不確かさまで含めて答えるのです。この記事では、その考え方を、小さな計算例で確かめます。
結論を先に書くと、次のとおりです。
- ベイズの定理で、データを見る前の考え(事前分布)を、データを見た後の考え(事後分布)に更新する。データが少ないうちは事前分布が効き、多くなるとデータが支配する
- ガウス過程は、「近い入力どうしは、出力も近い」という考えだけで回帰をする。データの近くでは自信を持って、データから離れると「分からない」と答える
- データの確からしさ(周辺尤度)を比べると、ハイパーパラメーターを、検証用のデータを分けずに選べる
- ベイズ最適化は、ガウス過程の予測と不確かさを使って、次に試す場所を選ぶ。1 回の試行が重いハイパーパラメーターの探索を、少ない回数で済ませられる
説明の途中に出てくる計算例は、Python と NumPy で実際に動かした結果です。
環境
計算例を動かした環境です。
| 項目 | バージョン |
|---|---|
| Python | 3.13 |
| NumPy | 2.5 |
ベイズの定理:考えをデータで更新する
事前分布 × 尤度 ∝ 事後分布
コインを投げて、表の出る確率 θ を推定する問題を考えます。ふつうの方法(最尤推定)では、「表が出た回数 ÷ 投げた回数」を答えます。3 回投げて 3 回とも表なら、θ = 1、つまり「このコインは必ず表が出る」と答えることになります。さすがに言いすぎでしょう。
ベイズの考え方では、θ そのものを確率分布で表します。
- 事前分布:データを見る前の、θ についての考え。「ふつうのコインなら、たぶん 0.5 前後」
- 尤度:θ がある値だったとき、実際に見たデータ(3 回とも表)が出る確率。θ³
- 事後分布:データを見た後の、θ についての考え。事前分布 × 尤度 に比例する(ベイズの定理)
事後分布が分かれば、θ の平均(推定値)だけでなく、「θ は 95% の確率でこの範囲にある」という区間(信用区間)も求められます。
事前分布の効き方
θ を 0〜1 の細かい目盛りで表して、事前分布と尤度を掛け算するだけで、事後分布を計算してみます。事前分布は「0.5 前後がありそう」という形(ベータ分布 Beta(2, 2))にします。比べるために、事前分布を一様にした(どの θ も同じくらいありうると考えた)場合も計算します。
import numpy as np
# 1. コイン投げ:表の出る確率 θ を、0〜1 の細かい目盛りの上で考える
theta = np.linspace(0, 1, 1001)
def summarize(post):
post = post / post.sum()
mean = (theta * post).sum()
cdf = np.cumsum(post)
lo, hi = theta[np.searchsorted(cdf, 0.025)], theta[np.searchsorted(cdf, 0.975)]
return round(float(mean), 3), (round(float(lo), 3), round(float(hi), 3))
prior = theta * (1 - theta) # 事前分布:「たぶん 0.5 前後」(ベータ分布 Beta(2, 2))
for heads, tails in [(3, 0), (7, 3), (70, 30)]:
likelihood = theta**heads * (1 - theta) ** tails
print(heads, tails, round(heads / (heads + tails), 3), summarize(prior * likelihood), summarize(likelihood))
実行結果:
3 0 1.0 (0.714, (0.359, 0.957)) (0.8, (0.398, 0.994))
7 3 0.7 (0.643, (0.386, 0.861)) (0.667, (0.39, 0.891))
70 30 0.7 (0.692, (0.601, 0.777)) (0.696, (0.604, 0.781))
左から、表の回数、裏の回数、最尤推定の値、「0.5 前後」の事前分布での事後分布の平均と 95% 信用区間、一様な事前分布での事後分布の平均と 95% 信用区間です。
- 3 回とも表:最尤推定は 1.0 ですが、事後分布の平均は 0.714 で、信用区間は 0.359〜0.957 と広くなりました。「表が出やすそうだが、3 回では何とも言えない」という答えです
- 10 回中 7 回が表:事前分布の「0.5 前後」に引っ張られて、平均は 0.643 です
- 100 回中 70 回が表:平均は 0.692 と最尤推定の 0.7 にほぼ一致し、信用区間も 0.601〜0.777 と狭くなりました。事前分布の違いも、ほとんど結果に影響しなくなっています
データが少ないうちは事前分布が効いて極端な答えを防ぎ、データが増えるとデータが支配するのが、ベイズ推定の性質です。過学習と正則化のしくみ で説明した L2 正則化は、実は「重みは 0 の近くにありそう」という事前分布を置いたベイズ推定と、同じ形をしています。
ガウス過程:近い入力どうしは出力も近い
関数そのものの確率分布
回帰の問題でも、ベイズの考え方を使えます。ガウス過程は、パラメーターではなく、関数そのものに事前分布を置く方法です。具体的な関数の形(直線か、2 次式か)は決めず、「入力が近ければ、出力も近い」ということだけを、カーネルと呼ばれる関数で表します。よく使われる RBF カーネルは、2 つの入力の距離が「長さの目盛り」より十分小さければ相関 1 に近く、離れるほど相関が 0 に近づきます。
観測したデータとカーネルから、新しい入力での予測の平均と標準偏差が、行列の計算だけで求まります。
データのない場所では「分からない」と答える
sin の関数から、雑音つきで 7 点を観測します。観測は x = −4〜1.5 の範囲にあり、x > 2 にはデータがありません。データの範囲の中と外の点で、ガウス過程の予測を見てみます。
import numpy as np
# 2. ガウス過程による回帰:近い入力どうしは、出力も近いと考える
def rbf(a, b, length):
return np.exp(-0.5 * (a[:, None] - b[None]) ** 2 / length**2)
def gp_predict(x_train, y_train, x_new, length, noise=0.1):
K = rbf(x_train, x_train, length) + noise**2 * np.eye(len(x_train))
Ks = rbf(x_new, x_train, length)
mean = Ks @ np.linalg.solve(K, y_train)
var = 1 - np.sum(Ks * np.linalg.solve(K, Ks.T).T, axis=1)
return mean, np.sqrt(np.maximum(var, 0))
def log_marginal_likelihood(x, y, length, noise=0.1):
# データ全体が「この長さの目盛りのガウス過程から出てきた」ときの確からしさ
K = rbf(x, x, length) + noise**2 * np.eye(len(x))
_, logdet = np.linalg.slogdet(K)
return -0.5 * y @ np.linalg.solve(K, y) - 0.5 * logdet - 0.5 * len(x) * np.log(2 * np.pi)
rng = np.random.default_rng(0)
x = np.array([-4.0, -3.0, -2.5, -1.0, 0.0, 0.5, 1.5]) # 観測した 7 点(x > 2 にはデータがない)
y = np.sin(x) + 0.1 * rng.standard_normal(len(x))
x_new = np.array([-3.5, 0.25, 1.0, 2.5, 4.0, 6.0])
mean, std = gp_predict(x, y, x_new, length=1.0)
for xn, m, s in zip(x_new, mean, std):
print(xn, round(float(np.sin(xn)), 2), round(float(m), 2), round(float(s), 2))
実行結果:
-3.5 0.35 0.36 0.14
0.25 0.25 0.23 0.08
1.0 0.84 0.96 0.14
2.5 0.6 0.66 0.7
4.0 -0.76 0.05 1.0
6.0 -0.28 0.0 1.0
左から、入力 x、本当の値 sin(x)、予測の平均、予測の標準偏差です。
- データの間(x = −3.5、0.25、1.0)では、予測は本当の値に近く、標準偏差は 0.08〜0.14 と小さくなっています。近くに観測があるので、自信を持って答えています
- データの端から少し外(x = 2.5)では、予測は 0.66 とまだそれらしい値ですが、標準偏差は 0.7 に広がりました
- データから遠い場所(x = 4.0、6.0)では、予測の平均は 0 付近、標準偏差は 1.0 です。「近くに手がかりがないので、事前分布(平均 0、標準偏差 1)のまま、分からない」と答えているのです
ニューラルネットワークのようなふつうのモデルは、データから遠い入力にも、平気で自信のありそうな値を返します。ガウス過程は、自分が知らない範囲を知っていることが大きな特徴です。
周辺尤度で長さの目盛りを選ぶ
カーネルの「長さの目盛り」は、どれくらい離れた入力まで似ているとみなすかを決める、ハイパーパラメーターです。ベイズの枠組みでは、これを 周辺尤度(観測したデータ全体が、そのハイパーパラメーターのモデルから出てくる確率)で比べられます。上のコードの続きです。
for length in [0.1, 0.3, 1.0, 3.0]:
print(length, round(float(log_marginal_likelihood(x, y, length)), 2))
実行結果:
0.1 -8.02
0.3 -7.96
1.0 -5.15
3.0 -7.89
周辺尤度の対数がいちばん大きいのは、長さの目盛り 1.0 でした。目盛りが短すぎる(0.1)と、どんなギザギザのデータにも合う複雑すぎるモデルになり、長すぎる(3.0)と、ゆるやかな曲線しか表せず、sin の山と谷に合いません。周辺尤度は、データに合うことと、モデルが単純であることの釣り合いを自動で取ってくれます。過学習の記事 で説明した交差検証のように検証用のデータを分けなくても、ハイパーパラメーターを選べるのが利点です。
ただし、ガウス過程は、データの数を n とすると n × n の行列の計算が必要で、計算量は n の 3 乗で増えます。データが数万件を超えると、そのままでは使えず、近似の方法が必要になります。
ベイズ最適化:少ない試行で探す
重い関数の最小値を探す
ニューラルネットワークの学習率のようなハイパーパラメーターを決めるには、その値で学習して、検証用のデータでの誤差を測る必要があります。1 回の学習に何時間もかかるなら、試せる回数は限られます。
ベイズ最適化は、それまでに試した結果からガウス過程で「誤差の地図」を作り、次にどこを試すと良くなりそうかを計算して選ぶ方法です。選ぶときの基準には、期待改善量(EI)がよく使われます。その場所を試したときに、今までの最良の値をどれだけ下回りそうかの期待値です。
- 予測の平均が低い場所(良さそうな場所)は、期待改善量が大きい(活用)
- 予測の標準偏差が大きい場所(まだよく分からない場所)も、たまたま良い可能性があるので、期待改善量が大きい(探索)
強化学習のしくみ の「探索と活用」の釣り合いを、ガウス過程の不確かさを使って自動で取っているわけです。
ランダム探索と比べる
学習率を 10 の何乗にするか(−5〜0)を変えたときの検証誤差を、山と谷のある関数で表し、その最小値を探します。ベイズ最適化(最初の 3 回はランダム、その後は期待改善量が最大の場所)と、ランダムに試す方法(ランダム探索)を、試行の回数を変えて 50 回ずつ比べます。上のコードの続きです。
# 3. ベイズ最適化:学習率(10 の何乗か)を変えたときの検証誤差を、少ない試行で最小にする
from math import erf, sqrt, pi
def val_error(u):
# 試すたびに 1 回の学習が必要な、重い関数のつもり。いちばん良いのは u ≒ -2.6 付近
return 0.3 - 0.2 * np.exp(-((u + 2.6) ** 2) / 0.3) - 0.06 * np.exp(-((u + 0.8) ** 2) / 0.1) + 0.02 * np.sin(5 * u)
grid = np.linspace(-5, 0, 501)
best_possible = val_error(grid).min()
def normal_pdf(z): return np.exp(-0.5 * z**2) / sqrt(2 * pi)
def normal_cdf(z): return 0.5 * (1 + np.vectorize(erf)(z / sqrt(2)))
def bayes_opt(budget, rng):
u = list(rng.uniform(-5, 0, 3)) # 最初の 3 回はランダム
e = [val_error(v) for v in u]
while len(u) < budget:
y = np.array(e)
mean, std = gp_predict(np.array(u), (y - y.mean()) / y.std(), grid, length=0.5, noise=0.01)
mean = mean * y.std() + y.mean(); std = std * y.std() + 1e-9
z = (y.min() - mean) / std
ei = (y.min() - mean) * normal_cdf(z) + std * normal_pdf(z) # 期待改善量
u.append(grid[ei.argmax()]); e.append(val_error(u[-1]))
return min(e)
def random_search(budget, rng):
return min(val_error(v) for v in rng.uniform(-5, 0, budget))
rng = np.random.default_rng(1)
print(round(float(best_possible), 4))
for budget in [5, 10, 20]:
bo = [bayes_opt(budget, rng) for _ in range(50)]
rs = [random_search(budget, rng) for _ in range(50)]
hit = lambda r: np.mean(np.array(r) < best_possible + 0.005)
print(budget, round(float(np.mean(rs)), 4), round(float(hit(rs)), 2), round(float(np.mean(bo)), 4), round(float(hit(bo)), 2))
実行結果:
0.089
5 0.1769 0.16 0.1482 0.46
10 0.1366 0.3 0.0933 0.94
20 0.1043 0.62 0.089 1.0
1 行目は、目盛りの上で求めた検証誤差の最小値 0.089 です。2 行目からは、左から、試行の回数、ランダム探索で見つけた最良の誤差の平均と、最小値の近く(+0.005 以内)まで届いた割合、ベイズ最適化の同じ値です。
10 回の試行では、ランダム探索は 30% しか最小値の近くに届きませんでしたが、ベイズ最適化は 94% が届きました。20 回では、ベイズ最適化は 50 回すべてで最小値(0.089)を見つけています。ランダム探索は 20 回試しても 62% です。1 回の学習が重い場合、この試行回数の差は、そのまま計算時間の差になります。
ただし、ベイズ最適化が得意なのは、ハイパーパラメーターが数個〜十数個くらいまでです。数が多いと、ガウス過程で地図を作るのが難しくなります。
ニューラルネットワークの不確かさ
ニューラルネットワークの何百万もの重みについて、正確に事後分布を求めるのは困難です。そこで、次のような近似の方法が使われています。
- ディープアンサンブル(2017 年):初期値を変えて同じモデルを何個か学習し、予測のばらつきを不確かさとみなす。簡単で、よく効く
- MC ドロップアウト(2016 年):予測のときにもドロップアウトを有効にしたまま何回も予測し、そのばらつきを不確かさとみなす
- 変分推論:重みの事後分布を、正規分布のような扱いやすい分布で近似して学習する(2015 年の Bayes by Backprop など)
事後分布から直接サンプルを取り出す マルコフ連鎖モンテカルロ法(MCMC)も、統計モデルのベイズ推定では広く使われています。
不確かさが分かると、「自信のない予測は人に確認してもらう」「医療の診断で、見たことのない種類の画像には答えを保留する」のような使い方ができます。また、モデルが「確率 0.9」と言ったときに、本当に 9 割当たるか(較正)も、予測の確率を判断に使うときには大切です。
ベイズ機械学習の手法の流れ
この記事で紹介した手法を年代順に並べます。
| 年 | 手法・出来事 | ポイント |
|---|---|---|
| 1763 | ベイズの定理(ベイズの遺稿) | データを見て、確率についての考えを更新する |
| 1951・1963 | クリギング | 鉱山の調査で、近い場所ほど似ていると考えて値を推定する(ガウス過程による回帰と同じ考え) |
| 1953 | メトロポリス法 | 事後分布からサンプルを取り出す MCMC の始まり |
| 1998 | 期待改善量による最適化(EGO) | ガウス過程で重い関数の最小値を少ない試行で探す |
| 2006 | 『Gaussian Processes for Machine Learning』 | ガウス過程を機械学習の道具としてまとめる |
| 2012 | 機械学習のハイパーパラメーターのベイズ最適化 | 深層学習などのハイパーパラメーターの探索に広まる |
| 2015 | Bayes by Backprop | ニューラルネットワークの重みの事後分布を変分推論で学ぶ |
| 2016 | MC ドロップアウト | ドロップアウトをベイズ推定の近似とみなす |
| 2017 | ディープアンサンブル | 複数のモデルのばらつきで不確かさを測る |
まとめ
- ベイズ機械学習は、パラメーターや予測を確率分布で表し、予測の不確かさまで扱う
- 事前分布に尤度を掛けると事後分布になる。データが少ないうちは事前分布が極端な答えを防ぎ、データが増えるとデータが支配する
- ガウス過程は、「近い入力どうしは出力も近い」という考えで回帰をし、データの近くでは自信を持って、遠くでは「分からない」と答える
- 周辺尤度を比べると、検証用のデータを分けずに、ハイパーパラメーターを選べる
- ベイズ最適化は、ガウス過程の予測と不確かさから期待改善量を計算して次に試す場所を選び、ランダム探索より少ない試行で最良の値を見つける
- ニューラルネットワークの不確かさは、アンサンブルや MC ドロップアウトなどで近似する
参考
- T. Bayes, "An Essay towards Solving a Problem in the Doctrine of Chances", Philosophical Transactions of the Royal Society of London 1763
- N. Metropolis et al., "Equation of State Calculations by Fast Computing Machines", The Journal of Chemical Physics 1953
- G. Matheron, "Principles of geostatistics", Economic Geology 1963
- D. R. Jones, M. Schonlau and W. J. Welch, "Efficient Global Optimization of Expensive Black-Box Functions", Journal of Global Optimization 1998
- C. E. Rasmussen and C. K. I. Williams, "Gaussian Processes for Machine Learning", MIT Press 2006
- J. Snoek, H. Larochelle and R. P. Adams, "Practical Bayesian Optimization of Machine Learning Algorithms", NeurIPS 2012(arXiv:1206.2944)
- C. Blundell et al., "Weight Uncertainty in Neural Networks", ICML 2015(arXiv:1505.05424)
- Y. Gal and Z. Ghahramani, "Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning", ICML 2016(arXiv:1506.02142)
- B. Lakshminarayanan, A. Pritzel and C. Blundell, "Simple and Scalable Predictive Uncertainty Estimation using Deep Ensembles", NeurIPS 2017(arXiv:1612.01474)
- C. Guo et al., "On Calibration of Modern Neural Networks", ICML 2017(arXiv:1706.04599)