前回のPassive Aggressive(以下PA)の実装は、あとから冷静に考えて良くないところが多かったなあ、と反省。以下がそのリンクです。今回作ったモジュールにPAを含んでいるので正直あんなクソ記事見なくていいです。
ket-30.hatenablog.com
その反省を生かそうと、今回はオンライン学習の手法のひとつであるConfidence Weighted Learning(以下CW)を実装してみました。言語はいつも通りpythonです。つうかpythonしか書けない。
参考文献はこちらです。
- 作者: 海野裕也,岡野原大輔,得居誠也,徳永拓之
- 出版社/メーカー: 講談社
- 発売日: 2015/04/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (5件) を見る
前回の反省点
- アップデートの手段であるPAをクラスの名前にしてしまい、なんだかよくわからないものを作ってしまった。
今回はクラスをOnlineLearningにし、アップデートの方法をユーザーが選べるように設定しました。
とりあえず今はPAとCWが使えます。AROWもSCWもすぐに入れる予定です。(今はPASSされてるけどw)
あとまだ機能が少ないから、いろいろ入れたいですね。
とりあえず評価値を計算する関数を入れる予定。
ちょっとだけ理論部分に触れる・・・
理論というか実装時の工夫?
今回、少し考えさせられたのが共分散行列の部分。
先の教科書では、"共分散行列"とだけ書かれているので対角成分以外も、もちろん値を持っているものとして説明されています。
しかし・・・
共分散って保存する必要あるのか!?
という疑問が浮かびました。データの次元同士に相関がある場合もありますが、相関が無い場合も少なく無い。とりあえず、それぞれの次元に関する重みの信頼度(Confidence:これが名前の由来)だけを保存しといても、そこまで精度は変わらんのでは?...そう思いました。そうですよね?私は知りません。誰か教えて!
まあ、とりあえず対角成分だけを保存するように定義すれば、行列はスパースなものになり計算量も節約できるわけです。
そういうことで実装時はそんな感じになっております。(適当)
online_learningモジュール
以下が改良版 online_learningモジュールになっております。
コメントで軽い説明しております。
#coding utf-8 import numpy as np from scipy.stats import norm class OnlineLearning: def __init__(self, feats_dem=None): self.t = 0 if feats_dem is not None: self.w = np.ones(feats_dem+1) self.sigma = np.diag([1.0]*(feats_dem+1)) else: self.w = None self.sigma = None def L_hinge(self,x_vec, y): loss = max([0, 1-y*self.w.dot(x_vec)]) return loss def updateCW(self, x_vec, y, eta): ###これがCWでのアップデートを行う関数### weight_dem = len(x_vec) if self.sigma is None: #上で説明してた部分です。対角線分のみ。 self.sigma = np.diag([1.0]*weight_dem) x_vec = np.array(x_vec) phi = norm.cdf(eta) psi = 1 + phi**2/2 zeta = 1 + phi**2 v = x_vec.dot(self.sigma).dot(x_vec) m = y*(self.w.dot(x_vec)) alpha = max([0, 1/(v*zeta)*(-m*psi+np.sqrt(m**2*phi**4/4+v*phi**2*zeta))]) u = 0.25*(-alpha*v*phi+np.sqrt((alpha*v*phi)**2)+4*v)**2 beta = (alpha*phi)/(np.sqrt(u)+v*alpha*phi) self.w += alpha*y*self.sigma.dot(x_vec) self.sigma -= beta*self.sigma.dot(x_vec)*x_vec*self.sigma self.t += 1 return 1 if self.w.dot(x_vec)>0 else -1 def updateAROW(self,): #工事中 pass def updatePA(self, x_vec, y): loss = self.L_hinge(x_vec,y) norm = x_vec.dot(x_vec) eta = loss/norm self.w += eta*y*x_vec self.t += 1 def fit(self, x_vec, y, update="PA",eta=None): """fitを使う時に手法を選択します。 デフォルトはPAになっています。 もしCWを選択するときは、etaの値も定義してあげてください。""" if self.w is None: weight_dem = len(x_vec) self.w = np.ones(weight_dem) if update == "CW": self.updateCW(x_vec, y, eta) if update == "PA": self.updatePA(x_vec, y) def predict(self, x_vec): pred = self.w.dot(x_vec) return 1 if pred > 0 else -1
前回同様に2値分類をしてみるよー
以下がテスト用の実行コードです。
from online_learning import OnlineLearning
でモジュールを読み込みます。
#coding:utf-8 import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy import optimize from online_learning import OnlineLearning from sklearn.metrics import accuracy_score#こいつは気にしないで plt.style.use('ggplot') #create datas def make_data(N, draw_plot=True, is_confused=False, confuse_bin=50): #N個のデータセットを作成する #データにノイズをかけるためis_confusedを実装する np.random.seed(1)#シードを固定すると乱数が毎回同じ出力になる feature = np.random.randn(N, 2)#N*2の行列を作成 df = pd.DataFrame(feature, columns=["x","y"]) #二値分類の付与 df["c"] = df.apply(lambda row : 1 if (5*row.x + 3*row.y -1)>0 else -1, axis=1) #データの攪乱 if is_confused: def get_model_confused(data): c = 1 if (data.name % confuse_bin) == 0 else data.c return c df["c"] = apply(get_model_confused, axis=1) #データの可視化: どんな感じのデータになったかをグラフ化する if draw_plot: plt.scatter(x=df.x, y=df.y, c=df.c, alpha=0.6) plt.xlim([df.x.min() - 0.1, df.x.max() + 0.1]) plt.ylim([df.y.min() - 0.1, df.y.max() + 0.1]) return df def draw_split_line(weight_vector): a,b,c = weight_vector x = np.array(range(-10,10,1)) y = (a * x + c)/-b plt.plot(x,y, alpha=0.3) #データセットの作成 dataset = make_data(1000) print(dataset.head(5)) feats_vec = dataset.ix[:,"x":"y"] feats_vec["b"] = np.ones(dataset.shape[0]) feats_vec = feats_vec.as_matrix() print(feats_vec) y_vec = dataset.ix[:,2] y_vec = y_vec.as_matrix() model = OnlineLearning() for i in range(len(y_vec)): #以下のようにupdate方法にCWを選択しています。 model.fit(feats_vec[i,], y_vec[i], update = "CW", eta = 1) draw_split_line(model.w) print(model.w) print(model.sigma) plt.show()
出力結果はこんな感じです。
ちなみに最終的に生成された重みと、その重みの自信はこんな感じでした。
w = [ 2.4421614 1.48732702 -0.49223883] sigma = [[ 0.00211742 0. 0. ] [ 0. 0.00088981 0. ] [ 0. 0. 0.00021135]]
自信ありまくりです。くそ尖ってますw
まあそりゃ簡単なデータなのでこうなりますよね。