ふらふら Diary (仮)

興味のあることを適当に書いていく感じです

多項式回帰に正則化を利用

はじめに

正則化とは過学習を防ぐための手法の一つであり、モデルの汎化性能の向上に役立つ。過学習は学習データに対してうまく予測できているが、未知のデータ(検証データ)に対しては、うまく汎化できないことを意味する。

過学習していく様子

まずは過学習のイメージを掴んでおく。今回は、前回の記事で示したモデルに、正規分布に従う乱数を加えて生成したデータを使用する。

# 前回の復習
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.linear_model import LinearRegression

data = load_iris()
df = pd.DataFrame(data.data, columns=data.feature_names)
X = df.iloc[:, 0][data.target==0].values[:, np.newaxis]
y = df.iloc[:, 1][data.target==0].values
model = LinearRegression()
model.fit(X, y)

データの生成から行う。

# 上記のモデルに乱数を加える
# 学習データ
X_train = 10 * np.random.rand(20, 1)
y_train = model.predict(X_train) + np.random.randn(20)
# 検証データ
X_test = 10 * np.random.rand(12, 1)
y_test = model.predict(X_test) + np.random.randn(12)

プロットしてみる。

import matplotlib.pyplot as plt
plt.scatter(X_train, y_train, color='black', label='train')
plt.scatter(X_test, y_test, color='gray', label='test')
plt.legend()
plt.show()

f:id:jetarinA:20200515165147p:plain
このデータを多項式回帰でモデル化することを考える。線形回帰を1次式、2次式…と次数を増やしていくにつれてどのように変化していくかを調べる。同時に平均2乗誤差も見てみる(これを最小化するように学習しているため)。

from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error

y_quad_fit, train_error, test_error = [], [], []
# 次数を 1 から 6 まで増やす
for n in range(6):
    quadratic = PolynomialFeatures(degree=n+1)
    pr = LinearRegression()
    X_quad = quadratic.fit_transform(X_train)
    pr.fit(X_quad, y_train)
    X_fit = np.linspace(X_train.min(), X_train.max(), 10)[:, np.newaxis]
    y_quad_fit.append(pr.predict(quadratic.fit_transform(X_fit)))
    # 学習誤差
    train_error.append(mean_squared_error(pr.predict(X_quad), y_train))
    # 検証誤差
    test_error.append(mean_squared_error(pr.predict(quadratic.fit_transform(X_test)), y_test))

各次数 d での学習結果を可視化したものは以下に示す。

plt.scatter(X, y)
plt.plot(X_fit, y_quad_fit[0], color='firebrick', label='d=1')
plt.plot(X_fit, y_quad_fit[1], color='darkorange', label='d=2')
plt.plot(X_fit, y_quad_fit[2], color='gold', label='d=3')
plt.plot(X_fit, y_quad_fit[3], color='mediumseagreen', label='d=4')
plt.plot(X_fit, y_quad_fit[4], color='skyblue', label='d=5')
plt.plot(X_fit, y_quad_fit[5], color='mediumpurple', label='d=6')
plt.legend()
plt.show()

f:id:jetarinA:20200515165159p:plain
また、次数ごとの平均2乗誤差は以下の通り。学習誤差は学習データにおける平均2乗誤差、検証誤差は検証データにおける平均2乗誤差を表している。

次数 学習誤差 検証誤差
1 1.270780 0.931902
2 1.164353 0.433551
3 1.159767 0.503716
4 0.786159 7.871051
5 0.779189 11.882549
6 0.679223 92.420817

次数を大きくすると複雑な曲線になり、学習誤差がだんだんと小さくなる。学習誤差はd=6のときに最も小さくなるが、検証誤差は学習誤差に比べかなり大きくなっている。このモデルは、過学習しているため汎化性能の低いモデルとなっている。

正則化を利用

次に、多項式回帰に正則化を利用した結果を示す。今回はRidge回帰を使用した。

from sklearn.linear_model import Ridge

y_ridge_fit, train_ridge_error, test_ridge_error = [], [], []
# 次数を 1 から 6 まで増やす
for n in range(6):
    quadratic = PolynomialFeatures(degree=n+1)
    X_ridge = quadratic.fit_transform(X_train)
    ridge = Ridge(alpha=0.5)
    ridge.fit(X_ridge, y_train)
    X_fit = np.linspace(X_train.min(), X_train.max(), 10)[:, np.newaxis]
    y_ridge_fit.append(ridge.predict(quadratic.fit_transform(X_fit)))
    # 学習誤差
    train_ridge_error.append(mean_squared_error(ridge.predict(X_ridge), y_train))
    # 検証誤差
    test_ridge_error.append(mean_squared_error(ridge.predict(quadratic.fit_transform(X_test)), y_test))

各次数 d での学習結果を可視化したものは以下に示す。

plt.scatter(X_train, y_train, color='black', label='train')
plt.scatter(X_test, y_test, color='gray', label='test')
plt.plot(X_fit, y_ridge_fit[0], color='firebrick', label='d=1')
plt.plot(X_fit, y_ridge_fit[1], color='darkorange', label='d=2')
plt.plot(X_fit, y_ridge_fit[2], color='gold', label='d=3')
plt.plot(X_fit, y_ridge_fit[3], color='mediumseagreen', label='d=4')
plt.plot(X_fit, y_ridge_fit[4], color='skyblue', label='d=5')
plt.plot(X_fit, y_ridge_fit[5], color='mediumpurple', label='d=6')
plt.legend()
plt.show()

f:id:jetarinA:20200515165206p:plain
また、次数ごとの平均2乗誤差は以下の通り。

次数 学習誤差 検証誤差
1 1.270831 0.944950
2 1.164395 0.433568
3 1.160380 0.478892
4 0.848420 4.590636
5 0.793668 16.438639
6 0.784268 29.653928

正則化によりモデルの複雑さが抑えられ、誤差も次数が増えたときの検証誤差が抑えられており、過学習を防ぐことができていることが確認できる。
そうはいっても今回はデータが良くなかった可能性もあるが、全体的にあまりいい結果は出なかった。

アルゴリズム

簡単のため、2次式の線形回帰に正則化を利用した場合を考える。
 \displaystyle R(w) = \sum^n_{i=1} \{y_i-(w_0+w_1x_i+w_2x_i^2)\}^2+\alpha(w_1^2+w_2^2)
右辺の第1項は線形回帰の損失関数、第2項は罰則項と呼ばれるもので、学習パラメータの2乗和の形をとる。また、 \alpha (\geq0) は、正則化の強さをコントロールするパラメータである。
ここで、Ridge回帰の損失関数  R(w) を最小化することを考える。右辺第2項は学習パラメータの2乗和になっているため、学習パラメータの絶対値が大きいと、損失関数全体が大きくなる。 \alpha により学習パラメータを抑えることが可能となっている。
適切な  \alpha は検証誤差を見ながら調整するのが一般的である。

Ridge回帰とLasso回帰

Ridge回帰は、誤差関数の罰則項が学習パラメータの2乗和であるが、この罰則項を別の形にすることで、異なる性質の正則化を行うことができる。代表的な正則化手法として罰則項が学習パラメータの絶対値の和となっているLasso回帰がある。Lasso回帰の誤差関数は次のようになる。
 \displaystyle R(w) = \sum^n_{i=1}\{y_i-(w_0+w_1x_i+w_2x_i^2)\}+\alpha(|w_1|+|w_2|)
Lasso回帰は学習パラメータが0になりやすいという性質がある。この性質により特徴量選択を行うこともでき、モデルを解釈しやすくなるという利点もある。

おわりに

scikit-learnライブラリで多項式回帰に正則化を利用する方法についてまとめた。正則化を利用することでモデルの複雑さを緩和し、モデルの汎化性能の向上に役立つことを示した。正則化手法については、代表的なものとしてRidge回帰及びLasso回帰について簡単にまとめたが、今後は学習パラメータが計算されるときのイメージなどをまとめていきたい。