スポンサーリンク

オンライン機械学習(CW、SCW)を実装してみた

Python データ分析 機械学習

今回は、下記の論文で紹介されているオンライン機械学習のモデルをPythonで実装してみました。

Exact Soft Confidence-Weighted Learning: http://icml.cc/2012/papers/86.pdf

表題の通り、Confidence Weighted Learning(CW)、Soft Confidence Weighted Learning(SCW)を実装してみます。

オンライン機械学習

機械学習において、データを一括で読み込んで学習を行う方法を「バッチ学習」といいます。

これに対し、「オンライン学習」とは、1件ずつデータを逐次読み込んで学習させる方法のことをいいます。

そのため、学習させるデータを全て保持しておく必要がなく、1件ずつ学習に利用したデータをすぐに捨てることができます。

このような性質が有効な場面としては、例えば、以下のような実装場面が考えられます。

  • 最新データの逐次入力によって、モデル精度を更新し続けることが要求される場合
  • サイズが大きいデータから学習させる必要があり、全てのデータはメモリに乗り切らない場合
  • 個人情報のデータなど、手元に保管するリスクがあるものから学習させる場合

オンライン機械学習のより詳しい内容については、下記が参考になります。

機械学習プロフェッショナルシリーズの中で、オンライン機械学習をテーマにした一冊です。

今回実装したCWやSCWの解説、導出の流れなども記されています。

Confidence Weighted Learning(CW)

まずはCWの実装になります。

イメージは、線形分離をする際の各次元の重みが多次元正規分布に従うとして考え、その各重みに対応する分散を重みの信頼度として考えたモデルになります。

分散が大きければ大きいほど、その重みについては自信がないと解釈します。

また、この場合共分散を考えることもできますが、おそらく、共分散は保存する必要はなさそうな気がします。

使わなそうだけど、何かに使えないかなーとか考えてしまいますね。

実装が下記になります。

GitHub: https://github.com/Gin04gh/online_machine_learning/blob/master/CW.ipynb

移設しました。

GitHub: https://github.com/Gin04gh/datascience/blob/master/samples_python/confidence_weighted_learning.ipynb

import numpy as np
import pandas as pd
from sklearn import datasets
from scipy import stats

# irisデータセット
iris = datasets.load_iris()
data = pd.DataFrame(data= np.c_[iris["data"], iris["target"]], columns= iris["feature_names"] + ["target"])
selected_species = [0, 1] # setosa, versicolor
df = data[data["target"].isin(selected_species)] # setosa, versicolorのデータ
df = df.reindex(np.random.permutation(df.index)) # シャッフル
data_x = df[iris["feature_names"]].as_matrix() # sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
data_t = [1 if i == selected_species[0] else -1 for i in df["target"]] # ラベル{1, -1}
train_x, test_x = data_x[:80], data_x[80:]
train_t, test_t = data_t[:80], data_t[80:]
print("学習データ件数: ", len(train_x))
print("テストデータ件数: ", len(test_x))

# confidence weighted learning クラス
class CW():
    def __init__(self, in_size):
        self.mu = np.zeros(in_size)
        self.sigma = np.eye(in_size)
        self.eta = 0.95
        self.phi = stats.norm.ppf(self.eta)
        self.psi = 1+self.phi**2/2
        self.xi = 1+self.phi**2

    def train(self, x, t):
        #  学習
        m_t = t*self.mu.dot(x)
        v_t = x.dot(self.sigma).dot(x)
        alpha_t = max(0, (-m_t*self.psi+np.sqrt((m_t**2)*(self.phi**4)/4+v_t*(self.phi**2)*self.xi))/(v_t*self.xi))
        u_t = ((-alpha_t*v_t*self.phi+np.sqrt((alpha_t**2)*(v_t**2)*(self.phi**2)+4*v_t))**2)/4
        beta_t = (alpha_t*self.phi)/(np.sqrt(u_t)+v_t*alpha_t*self.phi)
        self.mu = self.mu+alpha_t*t*self.sigma.dot(x)
        self.sigma = self.sigma-beta_t*self.sigma.dot(x)[:,np.newaxis].dot(x.dot(self.sigma)[np.newaxis,:])

    def predict(self, x):
        # 予測
        if x.dot(self.mu) > 0:
            return 1
        else:
            return -1

# 正解率を計算する関数
def get_accuracy(model, dataset_x, dataset_t):
    result = []
    for x, t in zip(dataset_x, dataset_t):
        if model.predict(x)*t > 0: # 正解すれば1, 間違えれば-1
            result.append(1)
        else:
            result.append(0)
        accuracy = sum(result)/len(result)
        return accuracy

# 定数
EPOCH_NUM = 1

# CWクラス
cw = CW(in_size=len(iris["feature_names"]))

# 学習
for epoch in range(EPOCH_NUM):
    for x, t in zip(train_x, train_t):
        cw.train(x, t)
    accuracy1 = get_accuracy(cw, train_x, train_t) # 学習データの正解率
    accuracy2 = get_accuracy(cw, test_x, test_t) # バリデーションデータの正解率
    print("train/accuracy: {}, test/accuracy: {}".format(accuracy1, accuracy2)) # ログ
"""
学習データ件数:  80
テストデータ件数:  20
train/accuracy: 1.0, test/accuracy: 1.0
"""

正解率が1.0となってしまいました...笑

いろいろと試してみるとわかりますが、かなり少ないサンプル数でもすぐに収束する性質があるようです。

線形分離可能な問題ではかなり有効なアルゴリズムのように思います。

Soft Confidence Weighted Learning(SCW)

次にSCWを実装してみます。

CWでは線形分離不可の問題に対してはうまく学習できず、境界が大きくぶれてしまうという弱点があります。

それを克服するため、幾分か耐性をもたせたモデルがSCWです。

論文では命題として2つの計算アルゴリズムが記されていましたので、両方とも実装してみました。

Prop.1

GitHub: https://github.com/Gin04gh/online_machine_learning/blob/master/SCW1.ipynb

移設しました。

GitHub: https://github.com/Gin04gh/datascience/blob/master/samples_python/soft_confidence_weighted_learning_ver1.ipynb

import numpy as np
import pandas as pd
from sklearn import datasets
from scipy import stats

# irisデータセット
iris = datasets.load_iris()
data = pd.DataFrame(data= np.c_[iris["data"], iris["target"]], columns= iris["feature_names"] + ["target"])
selected_species = [0, 1] # setosa, versicolor
df = data[data["target"].isin(selected_species)] # setosa, versicolorのデータ
df = df.reindex(np.random.permutation(df.index)) # シャッフル
data_x = df[iris["feature_names"]].as_matrix() # sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
data_t = [1 if i == selected_species[0] else -1 for i in df["target"]] # ラベル{1, -1}
train_x, test_x = data_x[:80], data_x[80:]
train_t, test_t = data_t[:80], data_t[80:]
print("学習データ件数: ", len(train_x))
print("テストデータ件数: ", len(test_x))

# soft confidence weighted learning prop1クラス
class SCW1():
    def __init__(self, in_size):
        self.mu = np.zeros(in_size)
        self.sigma = np.eye(in_size)
        self.eta = 0.95
        self.C = 1
        self.phi = stats.norm.ppf(self.eta)
        self.psi = 1+self.phi**2/2
        self.xi = 1+self.phi**2

    def train(self, x, t):
        #  学習
        m_t = t*self.mu.dot(x)
        v_t = x.dot(self.sigma).dot(x)
        alpha_t = min(self.C, max(0, (-m_t*self.psi+np.sqrt((m_t**2)*(self.phi**4)/4+v_t*(self.phi**2)*self.xi))/(v_t*self.xi)))
        u_t = ((-alpha_t*v_t*self.phi+np.sqrt((alpha_t**2)*(v_t**2)*(self.phi**2)+4*v_t))**2)/4
        beta_t = (alpha_t*self.phi)/(np.sqrt(u_t)+v_t*alpha_t*self.phi)
        self.mu = self.mu+alpha_t*t*self.sigma.dot(x)
        self.sigma = self.sigma-beta_t*self.sigma.dot(x)[:,np.newaxis].dot(x.dot(self.sigma)[np.newaxis,:])

    def predict(self, x):
        # 予測
        if x.dot(self.mu) > 0:
            return 1
        else:
            return -1

# 正解率を計算する関数
def get_accuracy(model, dataset_x, dataset_t):
    result = []
    for x, t in zip(dataset_x, dataset_t):
        if model.predict(x)*t > 0: # 正解すれば1, 間違えれば-1
            result.append(1)
        else:
            result.append(0)
        accuracy = sum(result)/len(result)
        return accuracy

# 定数
EPOCH_NUM = 1
# SCW1クラス
scw1 = SCW1(in_size=len(iris["feature_names"]))

# 学習
for epoch in range(EPOCH_NUM):
    for x, t in zip(train_x, train_t):
        scw1.train(x, t)
    accuracy1 = get_accuracy(scw1, train_x, train_t) # 学習データの正解率
    accuracy2 = get_accuracy(scw1, test_x, test_t) # バリデーションデータの正解率
    print("train/accuracy: {}, test/accuracy: {}".format(accuracy1, accuracy2)) # ログ
"""
学習データ件数:  80
テストデータ件数:  20
train/accuracy: 1.0, test/accuracy: 1.0
"""

Prop.2

GitHub: https://github.com/Gin04gh/online_machine_learning/blob/master/SCW2.ipynb

移設しました。

GitHub: https://github.com/Gin04gh/datascience/blob/master/samples_python/soft_confidence_weighted_learning_ver2.ipynb

import numpy as np
import pandas as pd
from sklearn import datasets
from scipy import stats

# irisデータセット
iris = datasets.load_iris()
data = pd.DataFrame(data= np.c_[iris["data"], iris["target"]], columns= iris["feature_names"] + ["target"])
selected_species = [0, 1] # setosa, versicolor
df = data[data["target"].isin(selected_species)] # setosa, versicolorのデータ
df = df.reindex(np.random.permutation(df.index)) # シャッフル
data_x = df[iris["feature_names"]].as_matrix() # sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
data_t = [1 if i == selected_species[0] else -1 for i in df["target"]] # ラベル{1, -1}
train_x, test_x = data_x[:80], data_x[80:]
train_t, test_t = data_t[:80], data_t[80:]
print("学習データ件数: ", len(train_x))
print("テストデータ件数: ", len(test_x))

# soft confidence weighted learning prop2クラス
class SCW2():
    def __init__(self, in_size):
        self.mu = np.zeros(in_size)
        self.sigma = np.eye(in_size)
        self.eta = 0.95
        self.C = 1
        self.phi = stats.norm.ppf(self.eta)
        self.psi = 1+self.phi**2/2
        self.xi = 1+self.phi**2

    def train(self, x, t):
        #  学習
        m_t = t*self.mu.dot(x)
        v_t = x.dot(self.sigma).dot(x)
        n_t = v_t+1/2*self.C
        gamma_t = self.phi*np.sqrt((self.phi**2)*(m_t**2)*(v_t**2)+4*n_t*v_t*(n_t+v_t*(self.phi**2)))
        alpha_t = max(0, (-(2*m_t*n_t+(self.phi**2)*m_t*v_t)+gamma_t)/(2*(n_t**2+n_t*v_t*(self.phi**2))))
        u_t = ((-alpha_t*v_t*self.phi+np.sqrt((alpha_t**2)*(v_t**2)*(self.phi**2)+4*v_t))**2)/4
        beta_t = (alpha_t*self.phi)/(np.sqrt(u_t)+v_t*alpha_t*self.phi)
        self.mu = self.mu+alpha_t*t*self.sigma.dot(x)
        self.sigma = self.sigma-beta_t*self.sigma.dot(x)[:,np.newaxis].dot(x.dot(self.sigma)[np.newaxis,:])

    def predict(self, x):
        # 予測
        if x.dot(self.mu) > 0:
            return 1
        else:
            return -1
# 正解率を計算する関数
def get_accuracy(model, dataset_x, dataset_t):
    result = []
    for x, t in zip(dataset_x, dataset_t):
        if model.predict(x)*t > 0: # 正解すれば1, 間違えれば-1
            result.append(1)
        else:
            result.append(0)
        accuracy = sum(result)/len(result)
        return accuracy

# 定数
EPOCH_NUM = 1

# SCW2クラス
scw2 = SCW2(in_size=len(iris["feature_names"]))

# 学習
for epoch in range(EPOCH_NUM):
    for x, t in zip(train_x, train_t):
        scw2.train(x, t)
    accuracy1 = get_accuracy(scw2, train_x, train_t) # 学習データの正解率
    accuracy2 = get_accuracy(scw2, test_x, test_t) # バリデーションデータの正解率
    print("train/accuracy: {}, test/accuracy: {}".format(accuracy1, accuracy2)) # ログ
"""
学習データ件数:  80
テストデータ件数:  20
train/accuracy: 1.0, test/accuracy: 1.0
"""

計算に関しては、とりあえず論文に従いましたので、正直やっていることの意味はよくわからないです笑

SCWにしてもCWと同様、軽量かつ少ないデータ数で収束しやすいことが確認できました。

まとめ

以上、今回はCW、SCWを実装してみました。

初めてオンライン機械学習の分野を勉強してみましたが、感想としては、重みを正規分布で表現するという考え方が、普通に面白いなぁと思いました。

また、そのためモデル自体はかなり軽量ですので、とても学習が早いです。(そもそも少ないサンプル数でも収束しますし)

実務においては、モデルはリッチであればリッチであるほど良いという単純なものでもなく、精度や計算コスト、処理時間などのバランスによるケースバイケースな部分があります。

場合によっては、このようなモデルも選択肢として出せるようにしておきたいと思いました。

追記(2017-09-26)

追記するほどのものなのか知らないけど、なんか面白そうだったのでメモ。

いずれやってみたいですね!

コメント