今回はRidge回帰を実装していきます!
実はリッジ回帰は重回帰とほとんど同じコードで作成することができるのです!
なので、先に重回帰の実装を見てから読むことをオススメします。




リッジ(Ridge)回帰とは?
重回帰のパラメータを求める際に、正則化項を入れることで、重回帰よりもロバストな結果を返すモデルです。
k近傍法などのノンパラメトリック手法や、ニューラルネットと比べて計算が速い上に、
だいたいどんな回帰問題でも解ける問題なら安定してそこそこの精度を出します。(経験則)
なので、方針を決める際に、この問題は回帰問題として解く価値があるか否かを決定するために一番最初に使用したり、ニューラルネットなどのベースラインとして設定することが多いです。
重回帰では多重共線性 multicollinearity がある際に、訓練データに対する最適解として$w$にとんでもなく絶対値の大きい、正の値と負の値を出力してくることがあります。
これでは、全然一般的なモデルにならないです。
そういった問題も少し改善してくれるのがリッジ回帰だったりします。(基本的に多重共線性は避けるべきです。だた、最初から多重共線性があることを知っている場合の方が少ないので、リッジ回帰が良いよという意味です。)
文字の説明
- $y$ : 目的変数 ( 従属変数 )
- $x$: 説明変数 ( 独立変数 )
- $ϕ_j$ : 基底関数 ( $x_j$, $x_j^2$, $ln{(x_j)}$ など )
- $ω_j$ : 回帰係数
- $ε$ : 撹乱項、誤差項
- $λ_2$:正則化項 ← New!!
どんな考え方をするの?
基本的には重回帰と全く同じです。$y$を求めるための$w$を求めます。
$y =ω_0+ω_1 ϕ_1 (x)+ω_2 ϕ_2 (x)+⋯+ω_{K-1} ϕ_{K-1} (x)+ε$
$=w^T ϕ+ε$
ただし、
$w= (ω_0,ω_1,⋯,ω_{K-1})^T, ϕ=(ϕ_0 (x),ϕ_1 (x) , ⋯,ϕ_{K-1} (x))^T$
すなわち、$y$を求めるために必要なものは、
- 重みとなる$ω$
- 独立関数$ϕ$
この二つです。
$ϕ$については、$x$の何乗までを入力にしてあげるかを関数の中で決めれるようにします。
なので、ここでは解説を飛ばします。
どうやってωを求めるの?
良い感じの$f(x)$について考えるために誤差$E$を調べます。
ここで初めて、重回帰との違いが現れます!
$E = \frac{1}{2}||w^T ϕ – y || + \frac{1}{2}λ_2||w||^2$
のように、$λ_2||w||^2$のようにL2ノルムと呼ばれる、正則化項を加えます。
これによって、$w$がとんでもなく大きな値となることを防ぎます。
それ以外は全く一緒です。
やはり、$E$が最小になってくれると嬉しいので、最小二乗法を用いて、$E$が最小となる$ω$を計算します。
最初値を求めたいので、$E$を$ω$で微分した結果が0になればよいです。
微分を計算をして$ω$について整理すると、
$ω=(ϕ^T ϕ + λ_2 I)^{-1} ϕ^T y$
となります。(ただし、$I$は単位行列)
これで、$ω$を求めるコトができました。
$y$を求める
$y=w^T ϕ$
と適応をしてあげれば完成です。
リッジ(Ridge)回帰の実装
ちなみに、fitの部分では一番最初のインデックスには切片のための全て1の列が入っています。
predictの部分ではfitの際取得した切片、self.intercept_があるので、全て1の列は削除しています。
import numpy as np
class MyRidgeRegressor:
def __init__(self, alpha=1.0):
"""
alpha : float, default=1.0
Regularization strength
"""
self.alpha = float(alpha) # ←New!!
self.dim = None
self.coef_ = None
self.intercept_ = None
self.n_feature = None
self.__name__ = "Ridge"
def fit(self, X, y, dim=1):
"""
Calculate weight of ridge regression
Parameters
X : array_like (n_sample, n_feature)
The matrix of explanatory variables
y : array_like (n_sample, 1)
Objective variable
dim : int, default 1
Dimension of multiple regression
"""
n_sample, n_feature = X.shape
# X (n_sample, dim, n_feature)
X = np.array([[x**i for i in range(dim + 1)] for x in X])
# delete duplicated ones array
X = X.reshape(n_sample, -1)[:, n_feature - 1:]
# define identity matrix
Eye = np.eye(X.shape







) # ←New!!
# calculate coefficient
weight = np.dot(np.dot(np.linalg.inv(np.dot(X.T, X) + self.alpha * Eye), X.T), y) # ←New!!
self.coef_ = weight







self.intercept_ = weight[0]
self.dim = dim
self.n_feature = n_feature
def predict(self, X):
"""
Calculate ridge regression
Parameters
X : array_like (n_sample, n_feature)
The matrix of explanatory variables in test dataset
Returns
Predicted objective variable
"""
n_sample, n_feature = X.shape
# X (n_sample, dim, n_feature)
X = np.array([[x**i for i in range(self.dim + 1)] for x in X])
# delete ones array
X = X.reshape(n_sample, -1)[:, n_feature:]
return (np.dot(X, self.coef_) + self.intercept_)
def mse(self, X, y):
"""
Calculate mean square error
Parameters
X : array_like (n_sample, n_feature)
The matrix of explanatory variables in test dataset
y : Ground truth of objective variable
Returns
Mean square error
"""
return np.mean((self.predict(X) - y)**2)
def title(self):
"""
Make title for plot
Return
string of equation
"""
Xn = [f"x_{k}" for k in range(1, self.n_feature + 1)]
title = "$f(x) = $" + f"{float(self.intercept_): .2f}"
for d in range(1, self.dim + 1):
for k in range(self.n_feature):
if self.coef_[(d - 1) * self.n_feature + k] > 0:
sign = "+"
elif self.coef_[(d - 1) * self.n_feature + k] < 0:
sign = "-"
else:
continue
if d == 1:
title += f"${sign}{abs(float(self.coef_[(d-1) * self.n_feature + k])):.2f}{Xn[k]}$"
else:
title += f"${sign}{abs(float(self.coef_[(d-1) * self.n_feature + k])):.2f}{Xn[k]}^{d}$"
return title
Newと記載した部分が重回帰との違いです。
見て分かるとおり、ほとんど同じです。
それでは、性能の比較をしていきます。
リッジ(Ridge)回帰を適応
import argparse
import pandas as pd
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from sklearn.model_selection import KFold
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--dim", type=int, default=1, help="回帰の次元数.デフォルトは1")
parser.add_argument("-r", "--regressor", type=str, default="m", help="回帰モデルの名前.デフォルトは'm'")
parser.add_argument("-f", "--fname", type=str, default="data1.csv", help="読み込むファイルの名前.デフォルトは'data1.csv'")
parser.add_argument("-a", "--alpha", type=float, default=1.0, help="正則化項の係数.デフォルトは1.0")
args = parser.parse_args()
# define regression model
dim = args.dim
alpha = args.alpha
regressor = args.regressor
if regressor == "m":
model = MyMultipleRegressor()
elif regressor == "r":
model = MyRidgeRegressor(alpha=alpha)
# load data
fname = args.fname
data = pd.read_csv('../' + fname)
# split data
n_sample, n_cols = data.shape
feature_cols = list(data.columns[:-1])
target_col = list(data.columns[-1:])
kf = KFold(n_splits=2, shuffle=True)
for train_idx, test_idx in kf.split(data):
break
# train data
train_X = data.loc[train_idx, feature_cols].values.reshape(-1, n_cols - 1)
train_y = data.loc[train_idx, target_col].values.reshape(-1, 1)
model.fit(train_X, train_y, dim=dim)
train_mse = model.mse(train_X, train_y)
# test data
test_X = data.loc[test_idx, feature_cols].values.reshape(-1, n_cols - 1)
test_y = data.loc[test_idx, target_col].values.reshape(-1, 1)
test_mse = model.mse(test_X, test_y)
title = model.title()
if n_cols == 2:
# plot original data
plt.figure(figsize=(10, 8))
plt.xlabel(feature_cols[0], size=16)
plt.ylabel(target_col[0], size=16)
plt.title(f"{model.__name__} Regression\n" + title + f"\nTrain MSE : {train_mse:.4f}, Test MSE : {test_mse:.4f}", size=20)
plt.scatter(train_X, train_y, label="Train Observed")
plt.scatter(test_X, test_y, label="Test Observed")
# plot predicted data
X1 = np.linspace(data[feature_cols].min(), data[feature_cols].max(), 500)
y1_hat = model.predict(X1)
plt.plot(X1, y1_hat, c="r", label="Predicted")
plt.legend(fontsize=16)
plt.savefig(fname[:-4] + f"{model.__name__}_{dim}D.png", bbox_inches="tight")
plt.show()
elif n_cols == 3:
# plot original data
fig = plt.figure(figsize=(10, 8))
ax = Axes3D(fig)
ax.set_xlabel(feature_cols[0], size=14)
ax.set_ylabel(feature_cols







, size=14)
ax.set_zlabel(target_col[0], size=14)
plt.title(f"{model.__name__} Regression\n" + title + f"\nTrain MSE : {train_mse:.4f}, Test MSE : {test_mse:.4f}", size=20)
ax.plot(data.loc[train_idx, feature_cols[0]].values, data.loc[train_idx, feature_cols







].values,
data.loc[train_idx, target_col].values.flatten(),
marker="o", linestyle='None', label="Train Observed")
ax.plot(data.loc[test_idx, feature_cols[0]].values, data.loc[test_idx, feature_cols







].values,
data.loc[test_idx, target_col].values.flatten(),
marker="o", linestyle='None', label="Test Observed")
# plot predicted data
X1 = np.linspace(data[feature_cols[0]].min(), data[feature_cols[0]].max(), 30)
X2 = np.linspace(data[feature_cols







].min(), data[feature_cols







].max(), 30)
X1, X2 = np.meshgrid(X1, X2)
X3 = np.concatenate([X1.reshape(-1, 1), X2.reshape(-1, 1)], axis=1)
y3_hat = model.predict(X3).reshape(X1.shape)
ax.plot_wireframe(X1, X2, y3_hat, color="red", label="Predictied")
plt.legend(loc='center left', fontsize=16)
plt.savefig(fname[:-4] + f"{model.__name__}_{dim}D.png", bbox_inches="tight")
plt.show()
if __name__ == "__main__":
main()
データを見て比較
data2






左がリッジ回帰、右が重回帰です。
これを見ると重回帰の方が良さそうです。
これは、訓練データとテストデータの分布がほとんど同じだからです。
基本的には重回帰の方がモデルの表現力は高いです。
こういったデータを扱う場合は、重回帰の方がいいですね。
ずっと、リッジ回帰が良い、と言っていましたが、常にリッジ回帰が良いという訳ではないことが分かりました。
data3






左がリッジ回帰、右が重回帰です。
こちらのデータではリッジ回帰らしい特徴が現れています!!(そうなるように調整しました。笑)
Train MSEは重回帰の方が小さいですが、Tset MSEになるとリッジ回帰の方が小さくなっています。
正則化項を加えることによって、よりロバストなモデルを作成できた証です。
多くの場合は、訓練データとテストデータで多少なりとも分布が異なります。
そういった影響をなるべく少なくするためにもロバストなモデルを作成することは重要です。
正則化項による違いを確認









正則化項は左上から順に
$λ_2 = 1e-4, 10.0, 10000.0$
となっています。
モデルの柔軟度は$λ_2$が小さければ小さいほど大きくなります。
この場合だと、左上の写真が最も柔軟度が大きいです。
そのため、Train MSEが最小になっていることが分かります。
ですが、Test MSEに着目をすると$λ_2=10.0$のときに最小になっていることが分かります。
この結果から、リッジパラメータの設定も重要であることが分かります。
最後に
以上がリッジ回帰に実装とその解説でした。
様々な回帰の中で、個人的に一番好きなモデルです。(分かりやすくて精度が良いから)
数学的な部分が理解できると安心して使えると思います。
ブログが一助になれば幸いです。
次回予告
次回は、ラッソ(Lasso)回帰を実装していきます。
Lasso回帰は、数学の部分が難しいです。
興味のある方は是非見てみてください!
こんな感じのブログを書いていきます。
もし興味を持っていただけたら、フォローお願いします。<(_ _)>







タイピングもままならない完全にプログラミング初心者から
たった二ヶ月で
応用も簡単にできる…!!
という状態になるまで、一気に成長させてくれたオススメのプログラミングスクールをご紹介します!


