読者です 読者をやめる 読者になる 読者になる

MATHGRAM

主に数学とプログラミング、時々趣味について。

python: オンライン学習-Confidence Weighted Learning-を実装してみたよ!

前回のPassive Aggressive(以下PA)の実装は、あとから冷静に考えて良くないところが多かったなあ、と反省。以下がそのリンクです。今回作ったモジュールにPAを含んでいるので正直あんなクソ記事見なくていいです。
ket-30.hatenablog.com

その反省を生かそうと、今回はオンライン学習の手法のひとつであるConfidence Weighted Learning(以下CW)を実装してみました。言語はいつも通りpythonです。つうかpythonしか書けない。

参考文献はこちらです。

オンライン機械学習 (機械学習プロフェッショナルシリーズ)

オンライン機械学習 (機械学習プロフェッショナルシリーズ)

前回の反省点

  • アップデートの手段であるPAをクラスの名前にしてしまい、なんだかよくわからないものを作ってしまった。

今回はクラスをOnlineLearningにし、アップデートの方法をユーザーが選べるように設定しました。
とりあえず今はPAとCWが使えます。AROWもSCWもすぐに入れる予定です。(今はPASSされてるけどw)

あとまだ機能が少ないから、いろいろ入れたいですね。
とりあえず評価値を計算する関数を入れる予定。

ちょっとだけ理論部分に触れる・・・

理論というか実装時の工夫?
今回、少し考えさせられたのが共分散行列\Sigmaの部分。
先の教科書では、"共分散行列"とだけ書かれているので対角成分以外も、もちろん値を持っているものとして説明されています。

しかし・・・

共分散って保存する必要あるのか!?

という疑問が浮かびました。データの次元同士に相関がある場合もありますが、相関が無い場合も少なく無い。とりあえず、それぞれの次元に関する重みの信頼度(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()

出力結果はこんな感じです。
f:id:ket-30:20160316212831p:plain:W500:H400

ちなみに最終的に生成された重みと、その重みの自信はこんな感じでした。

w = [ 2.4421614   1.48732702 -0.49223883]

sigma = 
[[ 0.00211742  0.          0.        ]
 [ 0.          0.00088981  0.        ]
 [ 0.          0.          0.00021135]]

自信ありまくりです。くそ尖ってますw
まあそりゃ簡単なデータなのでこうなりますよね。

まとめ

とりあえずいい感じに実装できてるのではないでしょうか。
今度、もうちょい本格的なデータで試してみます。

それより化物語ディープラーニングやんなきゃ。

ではでは。

参考サイト様
qiita.com

p.s. モジュール作るのくそ楽しい。