カーネル法の概要をゆるふわに掴む(2/2)
前回の記事に続き「カーネル多変量解析」の第一章を読みながらコードを書いていきます。
このシリーズの目次はこちら
前回はカーネル法でサンプルデータを通る関数$${f(x)}$$を作りました。サンプルデータ全てを通ったのはいいのですが、過学習っぽくなってしまいました。
![](https://assets.st-note.com/img/1656338995607-KmUnt95Xyj.png?width=1200)
今回はサンプルデータを完全には通らなくても、かなり近くを通るし自然な感じの$${f(x)}$$を前回のコードを少し直して作ります。また、その完成したモデルのパラメータをいろいろ変えてみます。
この記事のゴール
こんなイメージの関数を作ります。
![](https://assets.st-note.com/img/1656343131651-1gAqJSYaQl.png?width=1200)
また、過学習具合を変えるパラメータによって関数がどう変わるかみます。
![](https://assets.st-note.com/img/1656378812318-BURc3sOuPx.png?width=1200)
カーネルを使った近似曲線-正則化(理屈)
前回は予想値と実値の差分が最小になるように関数$${f(x)}$$を作りました=$${\alpha}$$を探しました。サンプルデータにあわせることだけに頑張ったので、複雑な関数=過学習してしまいました。
ここで$${f(x)}$$の複雑さを下げたいので、$${\alpha=(a_1, a_2, …, a_n)}$$の各成分が小さくなるようにしたいです。
そこで、「予想値と実値の差分」と「$${\alpha}$$成分の大きさ」という相反する要素を足しあわせ、その値を最小にすることを考えます。これを正則化というそうです。
「$${\alpha}$$成分の大きさ」を表す方法として、筆者は「なんでもいいのだけど」と言いつつ$${\alpha^TK\alpha}$$を使っています。なぜ$${K}$$があるのかは書いていません(下の偏微分みると何となくわかるけど)。
ということで、下記を最小にしようとします。
$${(y-K\alpha)^T(y-K\alpha)+\lambda\alpha^TK\alpha}$$
マイナスの前の$${(y-K\alpha)^T(y-K\alpha)}$$は「予想値と実値の差分」である誤差の二乗和です。そこに「$${\alpha}$$成分の大きさ」である$${\alpha^TK\alpha}$$を$${\lambda}$$倍して足しています。
唐突に出てきた$${\lambda}$$はどのくらい正則化するのかを決めるパラメータです。適当に決めます。$${\lambda}$$が小さければ正則化が弱くなり、誤差は小さくなるけど複雑な式になります。$${\lambda=0}$$なら、正則化されないので、前回の複雑だけどフィットしている$${f(x)}$$になるわけですね。
で、上記の最小にしようとしている式を$${\alpha}$$で偏微分して式変形して(説明略)下記を得ます。
$${\alpha=(K+\lambda I)^{-1}y}$$
前回の$${\alpha=K^{-1}y}$$に単位行列が追加されただけの形です。だから$${\alpha}$$が計算できちゃいます!
カーネルを使った近似曲線-正則化(実装)
ってことで実装。前回のKernelModelを変更します。まずfit関数に$${\lambda}$$も渡すようにします。fit関数は$${\lambda}$$を$${\alpha}$$の計算に渡します。ただ変数lが増えただけ。
def fit(self, x, y, l):
self.x = x
self.k_func_ = lambda x1, x2: gaus(x1, x2, beta=1)
self.alpha_ = self._alpha(x, y, l)
$${\alpha}$$の計算は$${K}$$に$${\lambda I}$$を足す行を追加するだけ。
def _alpha(self, x, y, l):
k = self._K(x)
k += np.identity(len(x)) * l #here
return np.linalg.inv(k).dot(y)
全体としてこうなります。
import numpy as np
def gaus(x1, x2, beta=1):
return np.exp(-1 * beta * ((x1 - x2) ** 2))
class KernelModel():
def fit(self, x, y, l):
self.x = x
self.k_func_ = lambda x1, x2: gaus(x1, x2, beta=1)
self.alpha_ = self._alpha(x, y, l)
def predict(self, x):
pred_y = 0
for i in range(len(self.x)):
pred_y += self.alpha_[i] * self.k_func_(self.x[i], x)
return pred_y
def _alpha(self, x, y, l):
k = self._K(x)
k += np.identity(len(x)) * l #here
return np.linalg.inv(k).dot(y)
def _K(self, x):
dim = len(x)
matrix = []
for i in range(dim):
for j in range(dim):
matrix.append(self.k_func_(x[j], x[i]))
matrix = np.array(matrix)
matrix = matrix.reshape(dim, -1)
return matrix
これをプロットしてみます(プロットの方法は前回参照)。$${\lambda=0.0001}$$としました。
![](https://assets.st-note.com/img/1656343080802-PubkD47zzN.png?width=1200)
サンプルにあわせながらも大分ゆるやかですね。表示範囲を広げてみます。x軸だけ拡大しました。前回20倍にしたy軸は変える必要ありませんでした。
![](https://assets.st-note.com/img/1656343259057-wh9hcQX2oo.png?width=1200)
正則化によって「サンプルデータを完全には通らなくても、かなり近くを通るし自然な感じの$${f(x)}$$」が出来ました。
ちなみに$${\alpha}$$はこんな感じ。かなり減ってます。
lambda=0 : [-1.75448027e+08 4.19788447e+07 -2.43698761e+09 5.21864913e+07
2.17164550e+08 2.39167818e+08 -2.37993179e+08 -4.74983445e+08
3.98974940e+08 -3.41062507e+08 -7.77901179e+05 2.71778680e+09]
lambda=0.0001: [ 5.85670549e+03 -5.10690970e+02 -1.37467997e+04 -2.28985867e+03
-6.90135918e+02 -4.84678957e+03 3.70968738e+03 4.05533784e+02
-6.08827347e+03 3.88103921e+03 -1.31729595e+01 1.43298750e+04]
正則化パラメータをいじってみる
せっかくなので正則化パラメータの$${\lambda}$$を変えてみます。
![](https://assets.st-note.com/img/1656378844287-yxQlvrZpNx.png?width=1200)
$${\lambda}$$がゼロなら前回と同じ線になります(灰色)。0.000001(黄色)でも、かなりサンプルデータを通っています。0.01(緑)や0.0001(青)はそれなりにバランス取れてそうです。
もっと広い範囲で見てみます。
![](https://assets.st-note.com/img/1656379026649-GOq6GzDddz.png?width=1200)
緑や青はサンプルデータから離れたところ(2以上とか)もそれなりにバランス取れています。この部分にデータがあればより自然なグラフになるでしょう。
一応、「予想値と実値の差分」$${(y-K\alpha)^T(y-K\alpha)}$$と、「$${\alpha}$$成分の大きさ」である$${\alpha^TK\alpha}$$はこんな感じになります。
lambda: 0.010000 , error: 5.759110422153993, alpha_size: 26.848598577368634
lambda: 0.000100 , error: 5.241486844792019, alpha_size: 1010.6333100302372
lambda: 0.000001 , error: 3.794354528305177, alpha_size: 421226.66460790427
lambda: 0.000000 , error: 2.572389467714457e-12, alpha_size: 6514109067.920383
ということで、第一章終わりです。とはいえこの記事で取り上げたのは、数値で扱える部分だけなので、ちゃんと理解したい人は本読んでくださいー。例えば、KernelModelはよくみるとカーネル関数と独立だよね、とか書いてあります。
次は第二章。とりあえずで使ったカーネル関数の説明があります。あと、さっき変えてみた$${\lambda}$$のようなハイパーパラメータ(こっちがモデルに与える値)の調整方法の話があるみたいです。https://note.com/tanuki_112/n/nec0c45136970
最後に繰り返し。pythonのコード書きながら機械学習学ぶならこれがおすすめ。「カーネル多変量解析」にはコードはないので。ぶっちゃけアフィリエイトリンクなので買ってください。