カーネル法の概要をゆるふわに掴む(2/2)

前回の記事に続き「カーネル多変量解析」の第一章を読みながらコードを書いていきます。

このシリーズの目次はこちら

前回はカーネル法でサンプルデータを通る関数$${f(x)}$$を作りました。サンプルデータ全てを通ったのはいいのですが、過学習っぽくなってしまいました。

関数がサンプルデータを通っているが(右)、表示範囲を広げると発散しまくり

今回はサンプルデータを完全には通らなくても、かなり近くを通るし自然な感じの$${f(x)}$$を前回のコードを少し直して作ります。また、その完成したモデルのパラメータをいろいろ変えてみます。

この記事のゴール

こんなイメージの関数を作ります。

自然な感じの関数

また、過学習具合を変えるパラメータによって関数がどう変わるかみます。

自然な感じ度を変えてみる

カーネルを使った近似曲線-正則化(理屈)

前回は予想値と実値の差分が最小になるように関数$${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}$$としました。

サンプルにあわせながらも大分ゆるやかですね。表示範囲を広げてみます。x軸だけ拡大しました。前回20倍にしたy軸は変える必要ありませんでした。

正則化によって「サンプルデータを完全には通らなくても、かなり近くを通るし自然な感じの$${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}$$を変えてみます。

$${\lambda}$$がゼロなら前回と同じ線になります(灰色)。0.000001(黄色)でも、かなりサンプルデータを通っています。0.01(緑)や0.0001(青)はそれなりにバランス取れてそうです。

もっと広い範囲で見てみます。

緑や青はサンプルデータから離れたところ(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のコード書きながら機械学習学ぶならこれがおすすめ。「カーネル多変量解析」にはコードはないので。ぶっちゃけアフィリエイトリンクなので買ってください。

この記事が気に入ったらサポートをしてみませんか?