モデル¶
線形回帰(linear regression)は、予測の目的変数yと特徴量(説明変数)x1,x2,...,xdの間に次のような線形関係を仮定したモデルを置いて予測する手法。
y=β0+β1x1+⋯+βdxd+ε ここでβ1,β2,...,βdは回帰係数と呼ばれるパラメータで、モデル内で推定される。εはデータ取得時の測定誤差などの偶然による誤差を表し、次の3つの条件を満たす。
期待値は0:E[ε]=0
分散は一定: V[ε]=σ2
異なった誤差項は無相関: j=iならばCov(εi,εj)=E(εi,εj)=0
サンプルサイズがnのデータセット{xi,yi}i=1nがあるとして、目的変数をy=(y1,y2,...,yn)⊤、特徴量をX=(x1,x2,...,xn)⊤とおくと、このモデルは
y=Xβ+ε と表記することができる。
パラメータの推定¶
一般的に線形回帰ではパラメータの推定に最小二乗法(least squares method)という方法が使われる。
これは誤差関数J(β)を実測値yと予測値y^=Xβ^の二乗誤差の和(誤差二乗和 sum of squared error: SSE)
J(β)=∣∣y−Xβ^∣∣2=i=1∑n(yi−y^i)2=i=1∑nεi2=ε⊤ε として定義し、この二乗誤差を最小にするパラメータ(最小二乗推定量 ordinary least square’s estimator: OLSE)
β^LS=βarg mini=1∑n(yi−y^i)2 を採用するという方法。
二乗誤差(yi−y^i)2=εi2はU字型になるため傾きがゼロになる点が最小値になる。そのため最小二乗法は解析的に解を求めることができる。
import matplotlib.pyplot as plt
import numpy as np
def square(error):
return error ** 2
errors = np.linspace(-1, 1, 100)
squared_errors = [square(error) for error in errors]
fig, ax = plt.subplots()
ax.plot(errors, squared_errors)
_ = ax.set(xlabel='error', ylabel='squared error')
誤差二乗和は
ε⊤ε=(y−Xβ^)⊤(y−Xβ^)=y⊤y−y⊤Xβ−(Xβ)⊤y+(Xβ)⊤(Xβ)=y⊤y−2β⊤X⊤y+β⊤X⊤Xβ であるから、二乗誤差の傾きがゼロになる点は
∂β∂ε⊤ε=−2X⊤y+2(X⊤X)β=0 と表すことができる。
これを整理して
2(X⊤X)β=2X⊤y これの両辺を2で割ると(あるいは誤差関数の定義の際に1/2を掛けておくと)、正規方程式(normal equation)とよばれる次の式が得られる。
(X⊤X)β=X⊤y これをβについて解けば
β=(X⊤X)−1X⊤y となり、最小二乗推定量β^LSが得られる。
numpyでは、行列やベクトルの積は@という演算子で書くことができる。そのため、
import numpy as np
beta = np.linalg.inv(X.T @ X) @ X.T @ y
のように書けば上の式とおなじ演算を行うことができる。
乱数を発生させて架空のデータを作る。
y=10+3x1+5x2+εx1∼Uniform(0,10)x2∼Normal(3,1)ε∼Normal(0,1) ここでεは測定誤差などのランダムなノイズとする
import numpy as np
import pandas as pd
n = 100 # sample size
np.random.seed(0)
x0 = np.ones(shape=(n, ))
x1 = np.random.uniform(0, 10, size=n)
x2 = np.random.normal(3, 1, size=n)
noise = np.random.normal(size=n)
beta = [10, 3, 5] # 真の回帰係数
y = beta[0] * x0 + beta[1] * x1 + beta[2] * x2 + noise
特徴量xと目的変数yの関係を散布図で描くと次の図のようになった。
import matplotlib.pyplot as plt
import seaborn as sns
fig, axes = plt.subplots(ncols=2, figsize=(10, 3))
axes[0].scatter(x1, y)
axes[0].set(xlabel='x1', ylabel='y')
axes[1].scatter(x2, y)
axes[1].set(xlabel='x2', ylabel='y')
[Text(0.5, 0, 'x2'), Text(0, 0.5, 'y')]
X = np.array([x0, x1, x2]).T
# Xの冒頭5行は以下のようになっている
print(X[0:5])
[[1. 5.48813504 1.83485016]
[1. 7.15189366 3.90082649]
[1. 6.02763376 3.46566244]
[1. 5.44883183 1.46375631]
[1. 4.23654799 4.48825219]]
# 最小二乗法で推定
beta_ = np.linalg.inv(X.T @ X) @ X.T @ y
print(f"""
推定された回帰係数: {beta_.round(3)}
データ生成過程の係数: {beta}
""")
推定された回帰係数: [9.564 2.98 5.119]
データ生成過程の係数: [10, 3, 5]
# scikit-learnに準拠した形で実装
from sklearn.base import BaseEstimator, RegressorMixin
class LinearRegression(BaseEstimator, RegressorMixin):
def fit(self, X, y):
self.coef_ = np.linalg.inv(X.T @ X) @ X.T @ y
return self
def predict(self, X):
return X @ self.coef_
model = LinearRegression()
model.fit(X, y)
model.coef_
array([9.56372548, 2.97972446, 5.11931302])
root mean squared error (RMSE)
RMSE=N1i=1∑N(yi−y^i)2 を使って予測値を評価してみる。
# 予測値を算出
y_pred = model.predict(X)
# 予測誤差を評価
from sklearn.metrics import mean_squared_error
rmse = mean_squared_error(y, y_pred, squared=False)
print(f"RMSE: {rmse:.3f}")
fig, ax = plt.subplots()
ax.plot(y_pred, y_pred, color='gray', alpha=.7)
ax.scatter(y_pred, y, alpha=.7)
_ = ax.set(xlabel='Predicted', ylabel='Actual')
[Text(0.5, 0, 'Predicted'), Text(0, 0.5, 'Actual')]