Help us understand the problem. What is going on with this article?

過学習とL2正則化

More than 1 year has passed since last update.

過学習

過学習とは、モデルが学習データにのみフィットしており、未知のデータの予測精度が低くなること。一般にモデルを複雑にするほど過学習は起きやすい。
未知のデータを予測する能力を「汎化能力」と言い、未知のデータを予測した際の誤りを「汎化誤差」と言う。

1.1.未知のデータを作る。

まず、未知のデータを作って過学習を実際に見てみる。

import numpy as np
import math
import matplotlib.pyplot as plt

np.random.seed(seed=32)
# create samples
sample_size = 50
test_size = 20
noise_size = 0.2

dataX = np.linspace(0.0, 5.0, num=1000).reshape((1000, 1))
# create train data
x = np.random.permutation(dataX)[:sample_size]
noise = noise_size * np.random.randn(sample_size, 1)
func = np.vectorize(math.sin)
y = func(x * 1.6) + noise
plt.plot(x, y, 'o', label='train data')

# create test data
testx = np.random.permutation(dataX)[test_size * -1:]
noise = noise_size * np.random.randn(test_size, 1)
testy = func(testx * 1.4) + noise
plt.plot(testx, testy, 'ro', label='test data')

全データの先頭50件を訓練データとし、末尾20件をテストデータとしている。
*test_yを作成するとき、正弦関数の係数をわざと小さめに調整している。
(実際は$y$が異なる分布となるので同一集団ではなくなるが、ここでは気にせず先に進める。)

これに前回の学習モデルを適用した結果と一緒に図示すると、

汎化能力に関しては、図からどっちがどうか分かりにくい。

1.2.汎化誤差を計算する。

dim = [3, 9]
for d in dim:
    # make predictions
    poly = PolynomialFeatures(degree=d)
    X = poly.fit_transform(x)
    testX = poly.fit_transform(testx)
    XTX = np.dot(X.T, X)
    XTX_inv = np.linalg.inv(XTX)
    a = np.dot(XTX_inv, np.dot(X.T, y))
    print "bias", np.sum((y - np.dot(X, a)) ** 2) / sample_size
    print "generalization", np.sum((testy - np.dot(testX, a)) ** 2) / test_size
    Xt = poly.fit_transform(dataX)
    yt = np.dot(Xt, a)
    plt.plot(dataX, yt, label='%d polynomial' % d)
    >> bias 0.163705879935
    >> generalization 0.28673629963
    >> bias 0.0444546042927
    >> generalization 0.329093164686

バイアスは9次元の方が小さいが、汎化誤差は3次元の方が小さくなっている。

L2(リッジ)正則化

次に過学習を抑える手法である正則化を取り入れる。
損失関数にパラメータの2乗ノルムを加える。

 E(a)= \parallel y-Xa \parallel ^{2}+\lambda\parallel a \parallel ^{2}

正則化項により、aの値が大きくなるのを防ぐ。
(大きくなると、損失関数が大きくなるため)
*$\lambda$は正則化パラメータ(大きくするほど正則化の度合いも高くなる。)

損失関数を最小化する$a$を求めるため、$E$を$a$で微分すると、

a=(X^TX+\lambda I)^{-1}X^Ty

となる。

2.1.正則化の効果を確認する。

正則化パラメータに「10, 1, 0, 0.1」を用意して効果を確認する。(0は正則化なし)

図の左側を見ると違いが分かりやすい。
正則化パラメータを10にした場合が、最もモデルの線形が滑らかになっている。

d = 9
L2params = [10, 1, 0, 0.1]
for p in L2params:
    # make predictions
    poly = PolynomialFeatures(degree=d)
    X = poly.fit_transform(x)
    testX = poly.fit_transform(testx)
    XTX = np.dot(X.T, X)
    L2im = np.identity(d + 1)*p
    L2im[0,0] = 0
    XTX = XTX + L2im
    XTX_inv = np.linalg.inv(XTX)
    a = np.dot(XTX_inv, np.dot(X.T, y))
    print ">> lambda %.1f :" % p, np.sum(a**2)
    print ">> bias :", np.sum((y - np.dot(X, a)) ** 2) / sample_size
    print ">> generalization :", np.sum((testy - np.dot(testX, a)) ** 2) / test_size
    Xt = poly.fit_transform(dataX)
    yt = np.dot(Xt, a)
    plt.plot(dataX, yt, label='regularization rate %.1f' % p)

>> lambda 10.0 : 0.433173909089
>> bias : 0.0813137786099
>> generalization : 0.322284436323
>> lambda 1.0 : 0.588098400133
>> bias : 0.0599131733257
>> generalization : 0.308749806939
>> lambda 0.0 : 290.780236508
>> bias : 0.0444545966264
>> generalization : 0.32903307304
>> lambda 0.1 : 2.07354251377
>> bias : 0.050158588753
>> generalization : 0.310930705156

また、正則化なしの場合がパラメータのL2ノルムが最も大きい(290)ことが分かる。
但し、今回は汎化性能は僅かな改善しか見られなかった。。

2.2.正則化の効果を考える。

正則化により、パラメータがより滑らかになることが分かった。
その理由についてもう少し考えてみる。

こんなデータセットでの回帰を考える。

No 説明変数1 説明変数2 目的変数
1 3 5 5
2 4 2 3
3 3 7 8
4 4 8 9

これを行列で表すと、

X=
\begin{pmatrix}
1 & 3 & 5 \\
1 & 4 & 2 \\
1 & 3 & 7 \\
1 & 4 & 8 
\end{pmatrix}
\;y=
\begin{pmatrix}
5 \\
3 \\
8 \\
9  
\end{pmatrix}

となる。
$(X^TX)^{-1}X^Ty$を一つずつ確認する。
まず、$X^TX$であるが、これは計算すると、

X^TX=
\begin{pmatrix}
4 & 14 & 22 \\
14 & 50 & 76 \\
22 & 76 & 142 
\end{pmatrix}

となる。
逆行列$(X^TX)^{-1}$は、

(X^TX)^{-1}=
\begin{pmatrix}
16.55 & -3.95 & -0.45 \\
-3.95 & 1.05 & 0.05 \\
-0.45 & 0.05 & 0.05 
\end{pmatrix}

となる。
$a$を求める際、$(X^TX)^{-1}$の各行が$a$の各変数に影響する。(1行目は$a$の1つ目の変数に影響する)
$X^Ty$は正則化による影響を受けないので計算は割愛する。

ここで、正則化項($\lambda=1$)を導入して計算にどう影響しているかを確認する。

\lambda I=
\begin{pmatrix}
0 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1 
\end{pmatrix}

(1行目は説明変数に関係ないパラメータなので含めない)

X^TX+\lambda I=
\begin{pmatrix}
4 & 14 & 22 \\
14 & 51 & 76 \\
22 & 76 & 143 
\end{pmatrix}

これだけ見ると、正則化の効果はほとんど無いように感じる。
しかし、$(X^TX+\lambda I)^{-1}$の結果は、

(X^TX+\lambda I)^{-1}=
\begin{pmatrix}
8.819 & -1.918 & -0.337 \\
-1.918 & 0.511 & 0.023 \\
-0.337 & 0.023 & 0.988 
\end{pmatrix}

と変化しており、全体的に値が小さくなっていることが分かる。
これが正則化による効果であり、$a$のL2ノルムが小さくなる理由である。

2.3.scikit-learnで確認する。

最後に「linear_model」ライブラリとの一致を確認する。
scikit-learnの場合、

from sklearn import linear_model
crf = linear_model.Ridge(alpha=p)
crf.fit(X, y)

でパラメータが算出可能。

for p in L2params:
    # make predictions
    poly = PolynomialFeatures(degree=d)
    X = poly.fit_transform(x)
    testX = poly.fit_transform(testx)
    XTX = np.dot(X.T, X)
    L2im = np.identity(d + 1) * p
    L2im[0, 0] = 0
    XTX = XTX + L2im
    XTX_inv = np.linalg.inv(XTX)
    a = np.dot(XTX_inv, np.dot(X.T, y))
    print "compute by myself ------"
    print("intercept : ", a[0])
    print("coef : ", a[1:].T)
    crf = linear_model.Ridge(alpha=p)
    crf.fit(X, y)
    print "compute by sklearn ------"
    print("intercept : ", crf.intercept_)
    print("coef : ", crf.coef_)
plt.legend()
compute by myself ------
('intercept : ', array([ 0.641]))
('coef : ', array([[ 0.101,  0.063, -0.007, -0.064, -0.051,  0.033, -0.002, -0.001,  0.   ]]))
compute by sklearn ------
('intercept : ', array([ 0.641]))
('coef : ', array([[ 0.   ,  0.101,  0.063, -0.007, -0.064, -0.051,  0.033, -0.002,
        -0.001,  0.   ]]))
compute by myself ------
('intercept : ', array([ 0.394]))
('coef : ', array([[ 0.503,  0.27 , -0.057, -0.24 , -0.11 ,  0.172, -0.06 ,  0.009, -0.   ]]))
compute by sklearn ------
('intercept : ', array([ 0.394]))
('coef : ', array([[ 0.   ,  0.503,  0.27 , -0.057, -0.24 , -0.11 ,  0.172, -0.06 ,
         0.009, -0.   ]]))
compute by myself ------
('intercept : ', array([ 0.219]))
('coef : ', array([[ -1.427,   9.773, -12.472,   6.066,  -0.835,  -0.344,   0.159,
         -0.024,   0.001]]))
compute by sklearn ------
('intercept : ', array([ 0.219]))
('coef : ', array([[  0.   ,  -1.427,   9.772, -12.472,   6.065,  -0.834,  -0.344,
          0.159,  -0.024,   0.001]]))
compute by myself ------
('intercept : ', array([ 0.099]))
('coef : ', array([[ 1.228,  0.316, -0.49 , -0.382,  0.262, -0.014, -0.018,  0.004, -0.   ]]))
compute by sklearn ------
('intercept : ', array([ 0.099]))
('coef : ', array([[ 0.   ,  1.228,  0.316, -0.49 , -0.382,  0.262, -0.014, -0.018,
         0.004, -0.   ]]))

scikit-learnは、coefの最初に0が入っているが、多分$X$の1列目に1がある影響だと思う。
それ以外は一致しているのでOKとする。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした