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