Jubatus Advent Calendar 22日目です.
Pythonで機械学習をされている方にはおなじみのscikit-learnとJubatusの性能比較をやってみます.
使用するデータセット
今回はscikit-learn API として公開されている5種類のデータセットを利用します.
- iris dataset: みなさんご存知アヤメの種類を判定する数値分類タスクです.
- breast cancer dataset:乳がんが発症するか判定する数値分類タスクです.
- digits dataset: 8x8ピクセルの手書き数字が0-9のどれに該当するかを判定する数値分類タスクです.
- MNIST dataset: 28x28ピクセルの手書き数字が0-9のどれに該当するかを判定する数値分類タスクです.
- 20newsgroups dataset: 約20000件の文書が20カテゴリのどれに属するかを判定するテキスト分類タスクです.
データの取得と学習・テストデータへの分割
scikit-learn API にてデータを取得した後,学習データとテストデータを2:1の割合で分割します.
def load_dataset():
from sklearn.datasets import fetch_mldata
dataset = fetch_mldata('MNIST original')
X = csr_matrix(dataset.data, dtype=np.float64)
y = dataset.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
return X_train, X_test, y_train, y_test
Jubatusを用いた性能評価スクリプト作成
今回の評価ではembedded-jubatus-pythonを使います.
embedded-jubatus-pythonは,Jubatusの学習アルゴリズムをサーバを介さず直で叩くことができます.そのため,手元のマシンでサクッと性能を確かめたいといったライトな使い方に非常に向いています.
分類器の作成
まずはJubatusの線形分類器を作成してみましょう.
今回はテキストを重み付けするコンフィグとして,サンプル重みにはlog_tf
を,大域重みにはbm25
を利用します.bm25
についてはこちらのブログをご覧ください.
linear_classifier(...)
はJubatusの線形分類器を起動する関数です.perceptron
, PA
とその他の線形分類器はコンフィグが異なりますので場合分けをしています.
import copy
from embedded_jubatus import Classifier
# Default Config
CONFIG = {
'method': 'perceptron',
'converter': {
'num_filter_types': {},
'num_filter_values': [],
'string_filter_types': {},
'string_filter_values': [],
'num_types': {},
'num_rules': [
{'key': '*', 'type': 'num'}
],
'string_types': {},
'string_rules': [
{'key': '*', 'type': 'space', 'sample_weight': 'log_tf', 'global_weight': 'bm25'}
]
},
'parameter': {}
}
def linear_classifier(method='AROW', regularization_weight=1.0):
""" 線形分類器を起動する """
cfg = copy.deepcopy(CONFIG)
cfg['method'] = method
if method not in ('perceptron', 'PA'): # perceptron, PA 以外はパラメータが必要
cfg['parameter']['regularization_weight'] = regularization_weight
return Classifier(cfg)
無事,分類器を作成する関数が書けました.
データを学習して評価する
続いて,分類器にデータを学習させて評価する関数を書いてみましょう.Jubatusはデータの投入順序で結果がかわるので,4試行平均を取ることにします.数値分類とテキスト分類で書き方が変わりますので,分けて紹介します.
数値分類
実はembedded-jubatus-pythonはfit(X, y)
関数とpredict(X)
関数が用意されているため,scikit-learnライクに記述することができます.これなら簡単に使えますね ;)
def score(clf, X_train, X_test, y_train, y_test):
clf.fit(X_train, y_train) # 学習する
test_score = accuracy_score(y_test, clf.predict(X_test)) # 予測して正解率を求める
return test_score
def evaluate(X_train, X_test, y_train, y_test, n_trials=4):
jubatus_methods = ['perceptron', 'PA', 'PA1', 'PA2', 'CW', 'AROW', 'NHERD']
results = dict.fromkeys(jubatus_methods, 0)
for i in range(n_trials):
X_train, y_train = shuffle(X_train, y_train, random_state=42) # データをシャッフル
for method in jubatus_methods:
clf = linear_classifier(method=method) # 線形分類器の起動
test_score = score(clf, X_train, X_test, y_train, y_test) # 学習・テストして正解率を求める
print('{0:.3f}\t{1}'.format(test_score, method))
results[method] += test_score
results = {k: v / n_trials for k, v in results.items()} # 各分類器の4試行平均の正解率を求める
テキスト分類
embedded-jubatus-pythonのfit(X, y)
関数,predict(X)
関数はnumpy.float64
タイプの数値データしか受け付けません.そのため,テキスト分類ではjubatus固有のデータ型Datum
を用いて学習とテストを行います.
といっても面倒なことはなくscore(...)
関数を書き換えてあげれば,先程の数値分類と同じコードを使いまわすことができます.
def score(clf, X_train, X_test, y_train, y_test):
train_data = [(yi, Datum({'message': xi})) for (xi, yi) in zip(X_train, y_train)] # 教師データを「
test_data = [Datum({'message': xi}) for xi in X_test]
clf.train(train_data)
predictions = clf.classify(test_data)
y_pred = [max(pred, key=lambda x:x.score).label for pred in predictions]
return accuracy_score(y_test, y_pred)
scikit-learnを用いた性能評価スクリプト作成
scikit-learnでも基本的にjubatusと同じスクリプトを使いまわします.
分類器の作成
scikit-learnでも同様にオンライン線形分類器を使います.
Perceptron, PA1, PA2はJubatusにも入っていますね.オンライン線形SVM(LSVM),オンラインロジスティクス回帰(LR)は,確率的勾配降下法を用いてそれぞれの損失関数を最小化することで分類問題をオンラインで解きます.
def sklearn_linear_classifier(method='Perceptron(sk)'):
sgd_params = {'penalty': 'l2', 'n_iter':1, 'shuffle': False, 'random_state': 42}
pa_params = {'C': 1.0, 'n_iter': 1, 'shuffle': False, 'random_state': 42}
if method == 'Perceptron(sk)':
return Perceptron(n_iter=1, shuffle=False, random_state=42)
elif method == 'LSVM(sk)':
return SGDClassifier(loss='hinge', **sgd_params)
elif method == 'LR(sk)':
return SGDClassifier(loss='log', **sgd_params)
elif method == 'PA1(sk)':
return PassiveAggressiveClassifier(loss='hinge', **pa_params)
elif method == 'PA2(sk)':
return PassiveAggressiveClassifier(loss='squared_hinge', **pa_params)
else:
raise NotImprementedError()
データを学習して評価する
数値分類
数値分類はほぼjubatusと同じです.
def score(clf, X_train, X_test, y_train, y_test):
clf.fit(X_train, y_train) # 学習する
test_score = accuracy_score(y_test, clf.predict(X_test)) # 予測して正解率を求める
return test_score
def evaluate(X_train, X_test, y_train, y_test, n_trials=4):
sklearn_methods = ['Perceptron(sk)', 'PA1(sk)', 'PA2(sk)', 'LSVM(sk)', 'LR(sk)']
results = dict.fromkeys(sklearn_methods, 0)
for i in range(n_trials):
X_train, y_train = shuffle(X_train, y_train, random_state=42) # データをシャッフル
for method in jubatus_methods:
clf = sklearn_linear_classifier(method=method) # 線形分類器の起動
test_score = score(clf, X_train, X_test, y_train, y_test) # 学習・テストして正解率を求める
print('{0:.3f}\t{1}'.format(test_score, method))
results[method] += test_score
results = {k: v / n_trials for k, v in results.items()} # 各分類器の4試行平均の正解率を求める
テキスト分類
scikit-learn はテキストデータをそのまま食わせることはできませんので,TfidfVectorizer
を使ってテキストを数値に変換してから学習を行います(TfidfVectorizer
はバッチ処理なため,scikit-learnの方が有利な条件となってしまいますが…)
def score(clf, X_train, X_test, y_train, y_test):
vectorizer = TfidfVectorizer()
X_train = vectorizer.fit_transform(X_train)
X_test = vectorizer.transform(X_test)
clf.fit(X_train, y_train) # 学習する
test_score = accuracy_score(y_test, clf.predict(X_test)) # 予測して正解率を求める
return test_score
以上で性能評価スクリプトが書き終わりました.
実験結果
各データで評価した結果を以下に示します.太字が各データセットでのベスト性能です.
news20データでは少し負けてしまいましたが,数値データだと常にJubatusが勝っています!
library | classifier | iris | cancer | digits | mnist | news20 |
---|---|---|---|---|---|---|
jubatus | AROW | 0.880 | 0.910 | 0.941 | 0.891 | 0.840 |
jubatus | CW | 0.885 | 0.904 | 0.870 | 0.100 | 0.835 |
jubatus | NHERD | 0.675 | 0.356 | 0.475 | 0.125 | 0.512 |
jubatus | PA | 0.690 | 0.681 | 0.931 | 0.870 | 0.824 |
jubatus | PA1 | 0.690 | 0.681 | 0.931 | 0.870 | 0.824 |
jubatus | PA2 | 0.690 | 0.681 | 0.931 | 0.870 | 0.826 |
jubatus | perceptron | 0.650 | 0.751 | 0.830 | 0.872 | 0.713 |
sklearn | LR | 0.505 | 0.751 | 0.886 | 0.861 | 0.821 |
sklearn | LSVM | 0.595 | 0.694 | 0.879 | 0.854 | 0.833 |
sklearn | PA1 | 0.690 | 0.682 | 0.923 | 0.865 | 0.845 |
sklearn | PA2 | 0.690 | 0.682 | 0.923 | 0.865 | 0.846 |
sklearn | Perceptron | 0.595 | 0.694 | 0.878 | 0.854 | 0.764 |
数値データ分類だとscikit-learnとほぼ同じように書けますので,ぜひJubatusを使ってみてくださいね!
今回使ったソースコードはこちらにおいてあるので,ご自由にお使いください.