重回帰分析
現実の予測問題では、出力は 1 つの理由だけで決まりません。面積、駅距離、築年数のように、複数の事情が同時に効いている状況を扱うのが重回帰です。
重回帰は単回帰の延長にありますが、係数の読み方は少し難しくなります。各係数は「ほかの変数を固定したまま、その変数だけを 1 増やしたときの変化量」として解釈されるためです。
複数の事情が同時に効く
説明変数が複数になると、予測の精度だけでなく寄与の切り分けが問題になります。x1 が効いているように見えても、実際には x2 と似た動きをしているだけかもしれません。
重回帰を読む鍵は、係数の値だけでなく、その値がどれくらい安定しているかまで意識することです。
複数要因を含む疑似データを作り、それを 1 本の式へまとめ、係数の安定性を確かめます。計算そのものより、係数がどの条件で素直に読めて、どの条件で怪しくなるかが中心になります。
基本語彙
- 重回帰: 複数の入力特徴量を同時に使う回帰
- 切片: すべての説明変数が 0 のときの基準値
- 係数: 他の変数を固定したとき、その特徴量が 1 増えると予測がどれだけ変わるか
- 多重共線性: 入力どうしが似すぎていて、係数の解釈が不安定になる状態
予測性能だけを見るなら見過ごせることもありますが、係数に意味を持たせたいなら多重共線性は避けて通れません。
係数を出して終わりにはならない
重回帰の難しさは、係数が求まったあとに始まります。数値が出たという事実と、その数値を寄与として読んでよいという判断は別物です。
出力の読み方
beta_hat は各特徴量の寄与を表す推定値です。正なら増やす方向、負なら減らす方向に効くと読めます。
ただし、その値が少しデータを変えただけで大きく揺れるなら、解釈の土台は弱いままです。
行列の式がしていること
(X^T X)^(-1) X^T y という形は威圧的に見えますが、やっていることは全データをまとめて見て、残差がいちばん小さくなる係数の組を探すことです。
見るべき本質は、式の長さよりも X の各列がどれだけ独立した情報を持っているかにあります。
扱う範囲
最小二乗解と多重共線性の入口に焦点を当てます。変数選択や正則化は先へ譲り、複数特徴量を同時に読むと何が変わるのかを明確にします。
複数要因を含むデータ
x1 と x2 の両方が y に効く疑似データを使います。片方だけを見ていたのでは、もう片方の寄与を取り違えやすい状況です。
import numpy as np
np.random.seed(11)
n = 120
x1 = np.random.randn(n)
x2 = 0.6 * x1 + 0.8 * np.random.randn(n) # 相関あり
eps = 0.5 * np.random.randn(n)
y = 1.2 + 2.0 * x1 - 1.5 * x2 + eps
X = np.column_stack([np.ones(n), x1, x2])
まとめて解くと係数はどう決まるか
単回帰では傾きと切片を手で追えましたが、変数が増えると一括で解く方が自然になります。先頭に入る 1 の列は切片のための列で、直線を原点へ固定しない役割を持ちます。
# 最小二乗解: beta = (X^T X)^(-1) X^T y
beta = np.linalg.pinv(X.T @ X) @ X.T @ y
pred = X @ beta
mse = np.mean((pred - y) ** 2)
print('beta_hat =', np.round(beta, 6))
print('train MSE=', round(mse, 6))
入力どうしが似すぎると何が起きるか
VIF を見る理由は、予測精度の確認ではなく、係数をどこまで信用して読めるかを確かめるためです。説明変数が似た情報を持ちすぎると、どちらが効いたのかを切り分けにくくなります。
# VIFで多重共線性を確認
# VIF_j = 1 / (1 - R_j^2)
def vif(x_target, x_other):
Xo = np.column_stack([np.ones(len(x_other)), x_other])
b = np.linalg.pinv(Xo.T @ Xo) @ Xo.T @ x_target
pred = Xo @ b
ss_res = np.sum((x_target - pred) ** 2)
ss_tot = np.sum((x_target - x_target.mean()) ** 2)
r2 = 1 - ss_res / ss_tot
return 1.0 / (1.0 - r2)
vif_x1 = vif(x1, x2)
vif_x2 = vif(x2, x1)
print('VIF(x1)=', round(vif_x1, 6), 'VIF(x2)=', round(vif_x2, 6))
重回帰では、係数推定と同じくらい係数の安定性が重要です。説明モデルとして使うなら、値の大小だけでなく、その値がどの程度揺れやすいかまで読む必要があります。