数学的な詳細の理解には時間がかかるが、
2乗誤差最小化問題という多変数関数の極値(最小値)問題が線形代数の(行列)計算に帰着できる
ということに注目してほしい。これは評価関数が2次関数の線形結合になっている場合に限られるが、「計画行列」の活用は後のカーネル法などにも引き継がれる。
(注) 関数の極値問題は勾配法(一変数の場合はニュートン法と呼ばれる)による繰り返し計算を行うことが多い(たとえば、https://fussy.web.fc2.com/algo/math10_extreme.htm)。
多数の局所的な極小がたくさんある場合は困難であり、様々なシミュレーション的方法が行われている。
"IPython Interactive Computing and Visualization Cookbook" (O'Reilly, 2018)のサンプルプログラム8.1を例に (現在、原文はhttps://ipython-books.github.io/ にて閲覧できる。)
模式図はPRMLより。
説明変数$x$に対する目的変数$y$について、$M$次関数でのフィッティングを考える: $$ y(x, {\bf w}) = w_0 + w_1 x + w_2 x^2 + \cdots + w_M x^M = \sum_{j=0}^{M} w_j x^j $$ データ$\{(x_n, t_n)\}$ ($n=1,\cdots N$)があったときに、 2乗誤差 $$ E({\bf w}) = \frac{1}{2} \sum_{n=1}^N \{y(x_n,{\bf w}) - t_n\}^2 $$ を最小にするよう${\bf w}$を決める。
説明用に、PRMLに載っている図を載せておく。
最小2乗法の模式図:
$\sin$カーブにノイズを載せたテストデータをべき関数フィッティングしてみた例:次数$M$をが大きいほうが全データにあうフィッティングができるが、不自然であることがわかるだろう。($M$が次数)
一般に高次項の係数がとても大きくなる。(前回の関数フィッティングのプログラムで係数を表示してみてほしい。function_fitting.htmlの最後のセルにあるプリント文のコメントを外せばよい。)
それを抑える工夫が次に述べるリッジ回帰、ラッソ回帰と呼ばれるものである。
過学習を防ぐために、次のような係数に対する2次関数を付加し、その最小化を図る。この項は大きな係数の方が不利になるので、高次の項の「暴発」が抑えられる。
$$ \tilde{E}({\bf w}) = \frac{1}{2} \sum_{n=1}^N \{y(x_n,{\bf w}) - t_n\}^2 + \frac{\lambda}{2}||{\bf w}||^2 $$これはリッジ回帰(ridge regression)を呼ばれる。ニューラルネットでは、荷重減衰(weight decay)として知られている。
また、このことを正則化(regularization)と呼ぶ。
上記のPRMLの事例にある式で係数$\lambda$を固定して最小化を行った例($M=9$):$\lambda$が大きいと加重がきつくフィッティングされにくくなる。リッジ回帰では$\lambda$を手入力で与える必要がある。(下で試すscikit-learnではalphaという引数で指定)
# 今まで試してきた多項式近似の復習
import numpy as np
import random
import sklearn.linear_model as lm
import matplotlib.pyplot as plt
f_cos = lambda x: np.cos(2.0*x) # サンプルデータを作るためのベース関数: このような関数定義の記述方法もある
# データ数
n_tr = 20
x_max = 5.0 # xの範囲 [0, x_max]
x = np.linspace(0., x_max, n_tr)
y = f_cos(x) + np.random.randn(len(x))*0.3
# 単純な多項式近似
lrp = lm.LinearRegression()
for deg in [2,5,10]: # 最大べき
power_matrix_x = np.vander(x, deg+1) # 計画行列の作成
lrp.fit(power_matrix_x, y)
# モデルの係数表示
print('フィッティング関数の次数=' + str(deg))
print('切片= ' + str(lrp.intercept_))
print('係数= ' + ' '. join(['%.3f' % c for c in lrp.coef_]))
# 予測
x_lrp = np.linspace(0., x_max*1.2, 100)
y_lrp = lrp.predict(np.vander(x_lrp, deg+1))
# 近似曲線の描画
plt.plot(x_lrp, y_lrp)
# データの描画
plt.plot(x, y, "ob", ms=8)
plt.ylim(-2.0,2.0)
フィッティング関数の次数=2 切片= 0.13568625562071746 係数= -0.020 -0.024 0.000 フィッティング関数の次数=5 切片= 1.2210868946388487 係数= 0.086 -0.993 3.681 -4.329 -0.410 0.000 フィッティング関数の次数=10 切片= 1.1220577110543857 係数= -0.001 0.028 -0.304 1.835 -6.863 16.512 -26.095 27.380 -17.010 2.611 0.000
(-2.0, 2.0)
マニュアルページ: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html
# リッジ回帰 alpha(正則化係数)の既定値は1.0 = 上の解説文におけるλ
ridge = lm.Ridge(alpha=1.0)
for deg in [2,5,10]: # 最大べき
ridge.fit(np.vander(x, deg +1), y) # リッジ回帰
# 予測
x_lrp = np.linspace(0., x_max*1.2, 100)
y_lrp = ridge.predict(np.vander(x_lrp, deg+1))
plt.plot(x_lrp, y_lrp,
label='degree ' + str(deg))
plt.plot(x, y , 'ok', ms=10)
plt.title('Ridge Regression')
# モデルの係数表示
# print('フィッティング関数の次数=' + str(deg))
# print('切片= ' + str(ridge.intercept_))
# print(' '. join(['%.5f' % c for c in ridge.coef_]))
# 「真の値」cos(x)との比較による決定係数R2の算出
# 実際には、真の値はわからない。その場合、トレーニングデータとは別にテストデータを用意し、それで検証する
#print("alpha=" + str(alpha) + "R2=" + str(ridge.score(np.vander(x, deg+1), f_cos(x))))
# サンプルデータのプロット
plt.ylim(-2.0,2.0)
plt.plot(x, y, "ob", ms=8)
# sin curve
plt.plot(x_lrp, f_cos(x_lrp), label="sin(x)")
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x7f75a1100fa0>
# リッジ回帰 alpha(正則化係数)の既定値は1.0
for a in [0.05, 0.1, 0.3, 0.5, 1.0, 2.0]:
ridge = lm.Ridge(alpha=a)
for deg in [2, 5, 10]: # 最大べき
ridge.fit(np.vander(x, deg +1), y) # リッジ回帰
# 予測
x_lrp = np.linspace(0., x_max*1.2, 100)
y_lrp = ridge.predict(np.vander(x_lrp, deg+1))
plt.plot(x_lrp, y_lrp,
label='degree ' + str(deg))
plt.legend(loc=2)
plt.plot(x, y , 'ok', ms=10)
plt.title('Ridge Regression')
# モデルの係数表示
print('フィッティング関数の次数=' + str(deg))
print('切片= ' + str(ridge.intercept_))
print(' '. join(['%.5f' % c for c in ridge.coef_]))
# 「真の値」cos(x)との比較による決定係数R2の算出
# 実際には、真の値はわからない。その場合、トレーニングデータとは別にテストデータを用意し、それで検証する
print("alpha=" + str(a) + ", deg=" + str(deg) + ", R2=" + str(ridge.score(np.vander(x, deg+1), f_cos(x))))
# サンプルデータのプロット
plt.ylim(-2.0,2.0)
plt.plot(x, y, "ob", ms=8)
フィッティング関数の次数=2 切片= 0.4164497190283239 0.01799 -0.22651 0.00000 alpha=0.05, deg=2, R2=0.07319032622357546 フィッティング関数の次数=5 切片= 1.4212147654535712 0.06024 -0.68241 2.41715 -2.35013 -1.39076 0.00000 alpha=0.05, deg=5, R2=0.9246990582148639 フィッティング関数の次数=10 切片= 1.1774753605356092 0.00004 -0.00187 0.02135 -0.09331 0.13959 0.01035 0.04015 -0.05689 -0.53787 -0.93996 0.00000 alpha=0.05, deg=10, R2=0.9279894262666633 フィッティング関数の次数=2 切片= 0.41324330712538765 0.01733 -0.22297 0.00000 alpha=0.1, deg=2, R2=0.07365235557690586 フィッティング関数の次数=5 切片= 1.3956163105706856 0.05194 -0.58207 2.00201 -1.68759 -1.68644 0.00000 alpha=0.1, deg=5, R2=0.9099839189650605 フィッティング関数の次数=10 切片= 1.1369338135777762 0.00005 -0.00203 0.02172 -0.08865 0.10783 0.07840 0.02311 -0.15134 -0.52052 -0.82604 0.00000 alpha=0.1, deg=10, R2=0.9273969899665108 フィッティング関数の次数=2 切片= 0.4013772751146817 0.01488 -0.20985 0.00000 alpha=0.3, deg=2, R2=0.075297455097181 フィッティング関数の次数=5 切片= 1.1803628262120314 0.03785 -0.42233 1.41528 -0.99638 -1.61719 0.00000 alpha=0.3, deg=5, R2=0.8613623948014627 フィッティング関数の次数=10 切片= 1.045376268038547 0.00010 -0.00297 0.02865 -0.11117 0.12832 0.12332 -0.03555 -0.24795 -0.47652 -0.62312 0.00000 alpha=0.3, deg=10, R2=0.9219499395256724 フィッティング関数の次数=2 切片= 0.39085105183565494 0.01271 -0.19822 0.00000 alpha=0.5, deg=2, R2=0.07667142623387724 フィッティング関数の次数=5 切片= 1.0120046107744969 0.03038 -0.34092 1.14105 -0.76578 -1.40090 0.00000 alpha=0.5, deg=5, R2=0.8090359941215963 フィッティング関数の次数=10 切片= 0.9911163642329129 0.00010 -0.00308 0.03004 -0.11841 0.14266 0.12584 -0.06313 -0.27105 -0.44588 -0.53172 0.00000 alpha=0.5, deg=10, R2=0.9158860358061822 フィッティング関数の次数=2 切片= 0.369088997747132 0.00823 -0.17418 0.00000 alpha=1.0, deg=2, R2=0.07925710154300392 フィッティング関数の次数=5 切片= 0.7548628842693 0.02008 -0.23028 0.77993 -0.50957 -1.02103 0.00000 alpha=1.0, deg=5, R2=0.6971672203411846 フィッティング関数の次数=10 切片= 0.9027236043260879 0.00006 -0.00236 0.02623 -0.11204 0.14863 0.11277 -0.08989 -0.27591 -0.39200 -0.41894 0.00000 alpha=1.0, deg=10, R2=0.9008057664473685 フィッティング関数の次数=2 切片= 0.3384880212715846 0.00195 -0.14044 0.00000 alpha=2.0, deg=2, R2=0.08230979030066121 フィッティング関数の次数=5 切片= 0.5210910480469141 0.01125 -0.13596 0.47710 -0.31679 -0.65662 0.00000 alpha=2.0, deg=5, R2=0.5614928171903719 フィッティング関数の次数=10 切片= 0.7943102700898788 -0.00005 -0.00053 0.01501 -0.08398 0.13406 0.08279 -0.10212 -0.25133 -0.32304 -0.31727 0.00000 alpha=2.0, deg=10, R2=0.8728212655743505
[<matplotlib.lines.Line2D at 0x7f4c2dfb1b50>]
ウェイトの項を単に絶対値$\frac{\lambda}{2}||{\bf w}||$にしたものをlasso回帰と呼ぶ。
model = lm.Lasso(alpha=0.01)
for deg in [2,5,10]: # 最大べき
model.fit(np.vander(x, deg +1), y) # リッジ回帰
# 予測
x_lrp = np.linspace(0., x_max*1.2, 100)
y_lrp = model.predict(np.vander(x_lrp, deg+1))
plt.plot(x_lrp, y_lrp,
label='degree ' + str(deg))
plt.legend(loc=2)
# モデルの係数表示
# モデルの係数表示
print('フィッティング関数の次数=' + str(deg))
print('切片= ' + str(lrp.intercept_))
print('係数' + ' '. join(['%.5f' % c for c in model.coef_]))
plt.plot(x, y , 'ok', ms=10)
plt.title('Lasso Regression')
# サンプルデータのプロット
plt.ylim(-2.0,2.0)
plt.plot(x, y, "ob", ms=8)
Lassoの場合、この程度のばらつきの場合だと、既定の加重係数(alpha=1.0)は大きすぎるようだ。また、データ数を多くしないと収束がわるという警告がでる。
全体として、次数を上げた場合、加重を含む最適化を行っているため、過学習が抑えられている。
係数alpha (上の式では$\lambda$)は外からあたえるパラメータであり、ハイパーパラメータと呼ばれる。
(注) 決定係数
テストデータの実測値を$(t_1, t_2, \cdots, t_N)$、予測(推定)値を$(y_1, y_2, \cdots, y_N)$としたとき
$$ R^2 = 1 - \frac{\sum_{i=1}^N (y_i - t_i)^2}{ \sum_{i=1}^N (t_i - \bar{t})^2} $$The regression problem we solve (predict) is to make a numerical machine that predict the value $y$ from the explanation variables $\{\boldsymbol{x}\}$ from a training dataset $\{\boldsymbol{x}_n, y\}$.
$$ y = f(\{\boldsymbol{x}_n\}, \boldsymbol{x}) $$where the values of the dataset is regarded as parameters of $f(\boldsymbol{x})$.
import sklearn.linear_model as lm
model = lm.xxxxxx(parameter1, parameter2, ....)
Prepare a training dataset: array "x" of explanation variables and list of target "y"
Train the machine by the dataset
model.fit(x,y, ....)
Predict $y$ from a set of explanation variables $\{x_1, x_2, \cdots \}$.
model.predict(x)
# Authors: Manoj Kumar mks542@nyu.edu
# License: BSD 3 clause
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression
from sklearn.linear_model import HuberRegressor, Ridge
def huber_vs_ridge(show_ridge=True, show_huber=True):
# Generate toy data.
rng = np.random.RandomState(0)
X, y = make_regression(
n_samples=20, n_features=1, random_state=0, noise=4.0, bias=100.0
)
# Add four strong outliers to the dataset.
X_outliers = rng.normal(0, 0.5, size=(4, 1))
y_outliers = rng.normal(0, 2.0, size=4)
X_outliers[:2, :] += X.max() + X.mean() / 4.0
X_outliers[2:, :] += X.min() - X.mean() / 4.0
y_outliers[:2] += y.min() - y.mean() / 4.0
y_outliers[2:] += y.max() + y.mean() / 4.0
X = np.vstack((X, X_outliers))
y = np.concatenate((y, y_outliers))
plt.plot(X, y, "b.")
x = np.linspace(X.min(), X.max(), 7)
# Fit the huber regressor over a series of epsilon values.
if show_huber:
colors = ["r-", "b-", "y-", "m-"]
epsilon_values = [1, 1.5, 1.75, 1.9]
for k, epsilon in enumerate(epsilon_values):
huber = HuberRegressor(alpha=0.0, epsilon=epsilon)
huber.fit(X, y)
coef_ = huber.coef_ * x + huber.intercept_
plt.plot(x, coef_, colors[k], label="huber loss, %s" % epsilon)
# Fit a ridge regressor to compare it to huber regressor.
if show_ridge:
ridge = Ridge(alpha=0.0, random_state=0)
ridge.fit(X, y)
coef_ridge = ridge.coef_
coef_ = ridge.coef_ * x + ridge.intercept_
plt.plot(x, coef_, "g-", label="ridge regression")
plt.title("Comparison of HuberRegressor vs Ridge")
plt.xlabel("X")
plt.ylabel("y")
if show_ridge or show_huber:
plt.legend(loc=0)
plt.show()
huber_vs_ridge()
# Using "interact" to switch on or off showing results of two kind of regressions
from ipywidgets import interact
interact(huber_vs_ridge, show_ridge=False, show_huber=False)
interactive(children=(Checkbox(value=False, description='show_ridge'), Checkbox(value=False, description='show…
<function __main__.huber_vs_ridge(show_ridge=True, show_huber=True)>
%%html
<link rel="stylesheet" type="text/css" href="custom.css">