単回帰分析
単回帰は、1 つの入力から 1 つの出力を予測する最小の回帰モデルです。扱う式は 1 本の直線ですが、その中には予測、誤差、汎化という機械学習の基本がすべて入っています。
入力が増えると出力もおおむね増える、という素朴な関係を題材にすると、係数が何を表し、残差が何を告げ、評価分割がなぜ必要かを無理なく追えます。
1 本の直線が表せるもの
観測点に直線を当てるという発想は単純ですが、単純だからこそ予測の骨格が見えます。傾きは入力が 1 増えたときに予測がどれだけ変わるかを表し、切片は入力が 0 のときの基準点を与えます。
直線 1 本で説明しきれない部分は残差として残ります。単回帰を読む要点は、線が当たったか外れたかだけでなく、どのように外れたかを見ることにあります。
小さなデータに対して直線を引き、係数を求め、予測を出し、外れを確かめる。この順序は単回帰だけの手順ではなく、より複雑なモデルを読むときの基本動作でもあります。
基本語彙
- 切片:
x=0のときの予測値 - 傾き:
xが 1 増えたときに予測がどれだけ変わるか - 残差: 実測値と予測値の差
- MSE: 残差を二乗して平均した指標
残差は個々の点で何が起きたかを示し、MSE は全体の外れ方を 1 つの数にまとめます。両方を行き来すると、平均値だけでは見えない偏りに気づきやすくなります。
直線・予測・残差
単回帰の流れは、データを置く、係数を決める、予測を出す、残差を確かめる、の 4 段です。式の見た目より、この順序を崩さずに追うことの方が理解には効きます。
指標の読み方
test MAE は、評価データで平均するとどれくらい外れているかを表す量です。小さいほど予測は実測に近いと読めます。
ただし、良い指標値だけでは十分ではありません。どの点で大きく外れたのか、外れ方に偏りがないかを残差でも確認すると、数字の意味が具体化します。
単回帰で起こりやすい誤読
係数が計算できることと、そのモデルが新しいデータでも当たることは別です。式が閉じた形で書けるために理解した気になりやすいのですが、本当に重要なのは、得られた直線が未知データでも関係を保てるかどうかです。
扱う範囲
焦点は、直線で説明する最小の予測問題に置きます。厳密な統計推定や信頼区間には踏み込まず、予測器としての回帰を読み解くことを主眼にします。
直線関係が見える最小データ
x が増えると y もほぼ比例して増える小さなデータを使います。関係が見えやすい例から始めると、あとで出てくる計算がどの点を説明しているのかを追いやすくなります。
x = [1, 2, 3, 4, 5]
y = [2.1, 4.2, 6.1, 8.2, 10.1]
pairs = list(zip(x, y))
print('task = simple-regression')
print('pairs =', pairs)
点を眺めた段階で、右上がりの直線が頭に浮かぶはずです。その直感と、あとで求まる傾きの値が対応すると、数式は抽象記号ではなく観測の要約として読めるようになります。
平均と差分から係数が決まる
最小二乗法の式は重く見えても、実際に使う部品は平均、平均との差、積、和です。入力が平均より大きいときに出力も平均より大きいなら、傾きは正の方向へ引かれます。
計算の核心は、点の並び方を 1 本の直線へ圧縮することにあります。
x_bar = sum(x) / len(x)
y_bar = sum(y) / len(y)
num = sum((xi - x_bar) * (yi - y_bar) for xi, yi in pairs)
den = sum((xi - x_bar) ** 2 for xi in x)
w1 = num / den; w0 = y_bar - w1 * x_bar
print('w0, w1 =', round(w0, 4), round(w1, 4))
抽象的な式も、コードに落とすと平均と差分の繰り返しです。回帰の式が読みにくく感じられたときは、まずその粒度まで分解すると意味を見失いません。
式は予測と誤差をつなぐ
回帰式は予測値を作るためにあり、損失関数はその予測がどれだけ外れたかを測るためにあります。学習とは、予測と誤差を往復しながら外れを小さくする営みだと捉えると整理しやすくなります。
残差を見る理由
係数が出た瞬間に仕事が終わるわけではありません。予測を各点へ戻し、どこでどれだけ外れたかを見ると、直線が捉えた部分と捉え損ねた部分が分かれます。モデル改善は、この観察から始まります。
pred = [w0 + w1 * xi for xi in x]
residual = [yi - pi for yi, pi in zip(y, pred)]
mse = sum((r ** 2) for r in residual) / len(residual)
print('pred =', pred)
print('mse =', round(mse, 8))
平均誤差が小さくても、特定の範囲だけ一方向に外れていれば、直線という表現自体が足りていないかもしれません。残差は、その不足を局所的に教えてくれます。
特徴量を増やすと表現力は増える
直線だけでは足りない関係も、特徴量の持ち方を変えると扱いやすくなります。二乗項のような特徴を加えると、元のモデルは線形のままでも、表現できる形は広がります。
ただし、表現力の増加は同時に過学習の危険も増やします。
x2 = [xi ** 2 for xi in x]
feature = list(zip(x, x2))
print('feature sample =', feature[:3])
scaled_x = [(xi - x_bar) / (max(x) - min(x)) for xi in x]
print('scaled_x =', [round(v, 4) for v in scaled_x])
訓練データにはよく合っても、偶然の揺れまで拾えば未知データで崩れます。特徴量追加は常に、説明力の増加と汎化の悪化の両面から読む必要があります。
学習用と評価用を分ける理由
訓練データだけで誤差を見ると、モデルは必ず実力以上によく見えます。まだ見ていないデータに対しても関係が保たれるかを確かめるには、評価用データを分ける以外にありません。
train_x, test_x = x[:3], x[3:]
train_y, test_y = y[:3], y[3:]
pred_test = [w0 + w1 * xi for xi in test_x]
mae_test = sum(abs(yi - pi) for yi, pi in zip(test_y, pred_test)) / len(test_y)
print('test mae =', round(mae_test, 6))
評価データで急に誤差が悪化するなら、係数の計算より前に、データ数の不足や分布差を疑うべきです。汎化の視点が入ると、回帰は単なる計算問題ではなく予測問題として姿を現します。
振り返り
単回帰で身につけたいのは、直線の公式そのものではありません。予測を作り、残差を見て、評価用データで見直すという流れです。
この流れが見えていれば、より複雑な回帰やニューラルネットワークへ進んでも、何を最適化し、何を検証しているのかを見失いにくくなります。