スキップしてメイン コンテンツに移動

LIBLINEAR 2.41 で One-class SVM が使えるようになったので Perl から触ってみよう

改訂 (Sep 15, 2020): 必要のない手順を含んでいたのでサンプルコードと記述を修正しました。

CPAN に Algorithm::LibLinear 0.22 がリリースされました (しました。) 高速な線形 SVM およびロジスティック回帰による複数の機械学習アルゴリズムを実装したライブラリである LIBLINEAR への Perl バインディングです。

利用している LIBLINEAR のバージョンが LIBLINEAR 2.30 から LIBLINEAR 2.41 に上がったことで新しいソルバが追加され、One-class SVM (OC-SVM) による一値分類が利用可能になっています (しました。)

OC-SVM って何

一値分類を SVM でやること。

一値分類って何

ある値が学習したクラスに含まれるか否かを決定する問題。 HBO の「シリコンバレー」に出てきた「ホットドッグ」と「ホットドッグ以外」を識別するアプリが典型。「ホットドッグ以外」の方は犬でも神でも一つの指輪でも何でも含まれるのがミソ。

二値分類の場合正反両者のデータを集める必要があるのに対して、一値分類の学習器は正例データのみしか要求しない (ものが多い。) 主な用途は外れ値検出で、もちろんホットドッグやホットドッグ様のものを検出したりもできる。

使い方

手順自体は他の二値ないし多値分類問題と同じです。つまり、

  1. 訓練パラメータを決めて
  2. 訓練データセットで訓練して
  3. テストデータセットで確度を検証して
  4. 十分良くなったらモデルを保存する

といういつもの流れ。

訓練パラメータ

use 5.032;
use Algorithm::LibLinear;

my $learner = Algorithm::LibLinear->new(
  epsilon => 0.01,
  nu => 0.75,
  solver => 'ONECLASS_SVM',
);

solver => 'ONECLASS_SVM' が一値分類用のソルバです。LIBLINEAR の train コマンドで言うところの -s 21。 OC-SVM の良いところは (ハイパー) パラメータが少ないことで、2個しかありません。epsilon は収束判定に使う指標で、nu は外れ値の見込の割合です。

訓練

use Algorithm::LibLinear::DataSet;

my $data_set = Algorithm::LibLinear::DataSet->load(fh => \*DATA);
my $model = $learner->train(data_set => $data_set);

# a9a training data.
# cf. https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html#a9a
__DATA__
-1 3:1 11:1 14:1 19:1 39:1 42:1 55:1 64:1 67:1 73:1 75:1 76:1 80:1 83:1 
-1 5:1 7:1 14:1 19:1 39:1 40:1 51:1 63:1 67:1 73:1 74:1 76:1 78:1 83:1 
-1 3:1 6:1 17:1 22:1 36:1 41:1 53:1 64:1 67:1 73:1 74:1 76:1 80:1 83:1 
-1 5:1 6:1 17:1 21:1 35:1 40:1 53:1 63:1 71:1 73:1 74:1 76:1 80:1 83:1 
-1 2:1 6:1 18:1 19:1 39:1 40:1 52:1 61:1 71:1 72:1 74:1 76:1 80:1 95:1 
-1 3:1 6:1 18:1 29:1 39:1 40:1 51:1 61:1 67:1 72:1 74:1 76:1 80:1 83:1 
-1 4:1 6:1 16:1 26:1 35:1 45:1 49:1 64:1 71:1 72:1 74:1 76:1 78:1 101:1 
+1 5:1 7:1 17:1 22:1 36:1 40:1 51:1 63:1 67:1 73:1 74:1 76:1 81:1 83:1 
...

確度の検証

やるだけ。Algorithm::LibLinear::Model#predict が返すラベルは訓練データセットの値に関係なく 1 / -1 になります。

my $num_corrects = 0;
my $test_data_set = Algorithm::LibLinear->load(fh => \*DATA);
for my $data ($test_data_set->as_arrayref->@*) {
  my $predicted_label = $model->predict(feature => $data->{feature});
  ++$num_corrects if $data->{label} == $predicted_label;
}

my $test_data_set_size = $test_data_set->size;
say "$num_corrects / $test_data_set_size = ", $num_corrects / $test_data_set_size;

# a9a test data.
# cf. https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html#a9a
__DATA__
-1 1:1 6:1 17:1 21:1 35:1 42:1 54:1 62:1 71:1 73:1 74:1 76:1 80:1 83:1
-1 3:1 6:1 14:1 22:1 36:1 40:1 56:1 63:1 67:1 73:1 74:1 76:1 82:1 83:1
+1 2:1 10:1 18:1 24:1 38:1 40:1 59:1 63:1 67:1 73:1 74:1 76:1 80:1 83:1
+1 4:1 6:1 16:1 20:1 37:1 40:1 54:1 63:1 71:1 73:1 75:1 76:1 80:1 83:1
-1 1:1 14:1 20:1 37:1 42:1 62:1 67:1 72:1 74:1 76:1 78:1 83:1
-1 3:1 6:1 17:1 31:1 35:1 42:1 49:1 64:1 67:1 73:1 74:1 76:1 78:1 83:1
-1 2:1 17:1 22:1 36:1 42:1 66:1 71:1 73:1 74:1 76:1 80:1 83:1
+1 5:1 7:1 14:1 23:1 39:1 40:1 52:1 63:1 67:1 73:1 75:1 76:1 78:1 83:1
...

モデルの保存

ラベルの対応も何もないので単に LIBLINEAR モデルを保存すれば十分です。

$model->save(filename => 'path/to/model');

# 復元
$model = Algorithm::LibLinear::Model->load(filename => 'path/to/model');

コード例

ここまで説明されても分からなかったらコピペして使おう:

#!/usr/bin/env perl

use 5.032;
use Algorithm::LibLinear;
use Algorithm::LibLinear::DataSet;

# Expects a9a or similar binary classification data set.
# cf. https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html#a9a
my $training_data_file = shift or die "Usage: $0 <training-data-file>";
my $test_data_file = "${training_data_file}.t";

sub negative_rate {
  my ($data_set) = @_;

  my $num_negatives = grep { $_->{label} == -1 } $data_set->as_arrayref->@*;
  $num_negatives / $data_set->size;
}

sub train {
  my ($data_file) = @_;

  my $training_data =
    Algorithm::LibLinear::DataSet->load(filename => $data_file);
  my $negative_rate = negative_rate($training_data);

  # One-class SVM solver assumes that all the given data are positive instances
  # so labels are ignored. We need to filter out negative ones since binary data
  # set like a9a contains both.
  my $learner = Algorithm::LibLinear->new(
    nu => $negative_rate,
    solver => 'ONECLASS_SVM',
  );
  $learner->train(data_set => $training_data);
}

my $model = train($training_data_file);
my $test_data =
  Algorithm::LibLinear::DataSet->load(filename => $test_data_file);

my $num_corrects = 0;
for my $data ($test_data->as_arrayref->@*) {
  my $predicted_label = $model->predict(feature => $data->{feature});
  ++$num_corrects if $predicted_label == $data->{label};
}
printf
  "Correct: %d/%d; Accuracy: %f%%\n",
  $num_corrects,
  $test_data->size,
  +($num_corrects / $test_data->size * 100);

終わったので自慢

C ライブラリである LIBLINEAR には複数の言語バインディングがありますが、国立台湾大学の開発チームから公式に提供されているのは MATLAB / Octave / Python の三言語版で残りは有志による実装です。

Perl 版である A::LL はそれらの内で最も暇なメンテナを持つ積極的にメンテナンスされている実装であり、LIBLINEAR の各リリース後一ヶ月以内に追従してきました。今回も OC-SVM 機能をサポートした既知の非公式バインディングは A::LL が最初です。

そういう経緯も含めて開発チームのリーダーである Chih-Jen Lin 教授に依頼したところ、公式サイトの紹介中で対応バージョンを “The latest” (最新) と表示してもらうことができました:

Interfaces to LIBLINEAR

これは公式バインディングを除けば php-liblinear に次いで二例目です。php-liblinear は厳密に言えばバインディングではなく別個にインストールされた LIBLINEAR の train / predict コマンドを使用してテキスト分類を行うアプリケーション・ツールキットなので、ライブラリとして LIBLINEAR 自体の機能を提供する非公式バインディングは A::LL が現時点で唯一のものです。

コメント

このブログの人気の投稿

Perl 5 to 6 - コンテキスト

2011-02-27: コメント欄で既に改訂された仕様の指摘がありました ので一部補足しました。 id:uasi に感謝します。 これはMoritz Lenz氏のWebサイト Perlgeek.de で公開されているブログ記事 "Perl 5 to 6" Lesson 06 - Contexts の日本語訳です。 原文は Creative Commons Attribution 3.0 Germany に基づいて公開されています。 本エントリには Creative Commons Attribution 3.0 Unported を適用します。 Original text: Copyright© 2008-2010 Moritz Lenz Japanese translation: Copyright© 2011 SATOH Koichi NAME "Perl 5 to 6" Lesson 06 - コンテキスト SYNOPSIS my @a = <a b c> my $x = @a; say $x[2]; # c say (~2).WHAT # Str() say +@a; # 3 if @a < 10 { say "short array"; } DESCRIPTION 次のように書いたとき、 $x = @a Perl5では $x は @a より少ない情報—— @a の要素数だけ——しか持ちません。 すべての情報を保存しておくためには明示的にリファレンスを取る必要があります: $x = \@a Perl6ではこれらは反対になります: デフォルトでは何も失うことなく、スカラ変数は配列を単に格納します。 これは一般要素コンテキスト(Perl5で scalar と呼ばれていたもの)及びより特化された数値、整数、文字列コンテキストの導入によって可能となりました。無効コンテキストとリストコンテキストは変更されていません。 特別な構文でコンテキストを強制できます。 構文 コンテキスト ~stuff 文字列 ?stuff 真理値 +stuff ...

多分週刊チラシの裏 (Sep 28 - Oct 04, 2020)

Chrome Web Store が有料 Chrome 拡張の取扱を終了 Chrome Web Store で提供されている有料 Chrome 拡張及びアプリ内課金 API の両方が 2021 年 1 月いっぱいで廃止される。 開発者はそれまでに代替となるサードパーティの課金 API に移行し、購入済ライセンスの移行手段も用意する必要がある。 この決定の発表時点で新規の有料ないしアプリ内課金のある Chrome 拡張の新規登録は終了している。実際のところ 2020 年 3 月時点で既に「一時的に」停止されており、その措置が恒久化されただけとの由。 シェルスクリプティングには長いオプションを使え 「短いオプション (e.g., -x ) はコマンドライン上での略記である。スクリプトにおいては自分や将来の同僚のためにも長いオプション (e.g., ---do-something ) を与える方が理解が容易だろう」という主張。 異論の余地なく正論である。 CobWeb - COBOL to WebAssembly Compiler COBOL から WebAssembly へのコンパイラ。いやマジで。 Cloudflare が何を思ったか同社のサーバレス環境である Workers に COBOL 対応を追加した際 の成果物である。 COBOL から C へのトランスレータである GNU COBOL と C コードをコンパイルして WebAssembly を出力する Emscripten から成っており、他の言語に比べて軽量なバイナリを生成するとのこと。 「ウチではそんな風にはやらないんだ (“We don’t do that here”)」 昨今ソフトウェア開発のコミュニティでも Code of Conduct を用意するところが増えてきたが、コミュニティの文化を明文化するのは難しい。 長大な「べからず集」は息苦しいし、肯定的なガイドラインは時に抽象的で実効的に使えない。問題となるようなふるまいの動機が善意であった場合は特にそうだ。 仮に優れたガイドラインがあっても、それに基いて人を実際に咎めるのは骨が折れることである。初中やればコミュニティ内でも疎まれる。 話の分かる相手ならそれでもまだ説得する意義もあるが、Web 上の対話で当事者双方が納得し合っ...

多分週刊チラシの裏 (Oct 19, 2020 - Feb 26, 2021)

週刊とは言ったが毎週刊とは言ってないという言い訳。 C++ のコンパイルを高速化する小技 ビルドシステムやツールを変更せずともコーディングだけで改善できるコンパイル時間短縮テクニック。 #include を減らす インライン化を明示的に避ける 関数オーバーロードの可視性を制限する 公開シンボルを減らす の 4 本。 歯医者で歯を治したら記憶能力を失った話 歯医者で簡単な治療を受けた日から後、記憶が 90 分しか保持できなくなった英国の軍人の話。まるで「博士の愛した数式」だが実話である。 DRPK で売られていた Sim City っぽいゲームのリバースエンジニアリング 平壌市内のアプリストア (物理) で売られていた Sim City 風ゲームがインストールに失敗してライセンス認証で止まってしまったのでなんとか動かせないものかとリバースエンジニアリングしてみた話。 日本にあっては DPRK のデジタル事情というと 3G セルラーが現役とか国内 Web サイトのリストがポスター一枚に収まるとか何故かコンピュータ将棋の古豪とかの断片的な情報が伝え聞かれる程度だが、近頃は Android タブレットでゲームなどもできるらしい。 国内のインフラ及びエコシステム事情に合わせて元々フリーミアム + アプリ内課金モデルだったものが買い切り 5,000 KPW (< 1 USD) になっているなど、我々が失った自由が我々よりも不自由な (はずだと我々が信じている) 国に残存しているのは皮肉だろうか。 typosquatting は単なる typo じゃ済まない typo を狙って人気のあるドメインやソフトウェアに類似した名前をつける手法 (typosquatting) は人を辟易させるのみならずセキュリティの脅威である。 IQT が 2017 年から 2020 年にかけて Python ライブラリの中央リポジトリである PyPI において行った調査で、メジャーなライブラリに名前を似せたマルウェアが 40 個確認されたとのこと。 その内 16 個が単純なスペルミス狙い (e.g., “urlib3” vs. “urllib3”) で、26 個は正当なパッケージと混同するような名前 (e.g., “nmap-python” vs. “pytho...

Mac から iPhone のカメラを起動して写真を直接取り込める

Via: The Verge ID セルフィーや (物理) 書籍のページスキャンなど携帯電話のカメラを使って写真を取り込むことは日常的な所作になっているが、写真の使い途が何かの申し込み用 Web フォームなどで iPhone より Mac の方が操作し易いときなどは億劫だ。Mac 組込の FaceTime カメラは 720p とか 1080p しかなくて非力すぎ、かといって iPhone で一旦撮影したものを Photos から探して AirDrop するのも面倒である。 実は macOS Mojave / iOS 12 以降には Continuity Camera という機能がある。これを使うと Apple 製の Mac アプリケーションから iPhone / iPad のカメラを起動して、余計な中間コピーを残すことなく写真を Mac に転送できる。 使い方は簡単で、対応している Mac アプリケーションのコンテキストメニューに “Import (or Insert) from iPhone (or iPad)” という項目がある。“Take Photo” だと一枚、“Scan Documents” だと複数の写真を (歪み補正しつつ) 連続で撮影して転送できる。 対応 Mac アプリケーションは Finder のほか iWork (Keynote, Numbers, Pages), Mail, Messages, Notes, TextEdit となっている、のだが実は Preview でも使える。同様にコンテキストメニューあるいは “File” メニューから起動できる。

Perl 5 to 6 - クォートと構文解析

これはMoritz Lenz氏のWebサイト Perlgeek.de で公開されているブログ記事 "Perl 5 to 6" Lesson 23 - Quoting and Parsing の日本語訳です。 原文は Creative Commons Attribution 3.0 Germany に基づいて公開されています。 本エントリには Creative Commons Attribution 3.0 Unported を適用します。 Original text: Copyright© 2008-2010 Moritz Lenz Japanese translation: Copyright© 2011 SATOH Koichi NAME "Perl 5 to 6" Lesson 23 - クォートと構文解析 SYNOPSIS my @animals = <dog cat tiger> # or my @animals = qw/dog cat tiger/; # or my $interface = q{eth0}; my $ips = q :s :x /ifconfig $interface/; # ----------- sub if { warn "if() calls a sub\n"; } if(); DESCRIPTION クォート Perl6には強力な文字列クォート機構があり、文字列のあらゆる機能を完全に制御できます。 Perl5にはシングルクォート、ダブルクォートそして qw(...) (空白で分割するシングルクォート文字列リスト)があり、さらに q(...) と qq(...) がそれぞれシングルクォートとダブルクォートの同義語になっていました。 一方のPerl6には Q というクォート演算子が定義されていて、様々な修飾子を取ります。 :b (バックスラッシュ)修飾子はバックスラッシュによる \n のようなエスケープシーケンスの展開を許し、 :s 修飾子はスカラ変数の展開を許し、 :c はクロージャ( "1 + 2 = { 1 + 2 }" )の展開を許す、などなど。また :w は q...