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

Algorithm::LibLinear の紹介

Notice: This article is outdated. Please refer an updated English tutorial.

要旨

かなり前になりますが、Algorithm::LibLinear という Perl モジュールを書きました。

これを使うと線形分類器などが高速に学習できます。テキストや画像の分類が応用として期待されます。

LIBLINEAR について

LIBLINEARLIBSVM と同じ台湾国立大学の Chih-Jen Lin 教授のチームが公開しているオープンソースの機械学習パッケージです。 関数のロジスティック回帰、サポートベクター回帰及び線形 SVM による多クラス分類を行うことができます。LIBSVM と違ってカーネル関数を使うことはできませんが、はるかに高速に動作します。

Algorithm::LibLinear について

LIBLINEAR には C++ で書かれたライブラリと、その機能を使って機械学習と分類・関数回帰を行うコマンドラインユーティリティが含まれています。 Algorithm::LibLinear はライブラリの機能を Perl からオブジェクト指向的に利用できるようにした上で、コマンドラインユーティリティの一部機能をライブラリ化して Perl で再実装したものです。

使い方

分類問題を解くときは、

  1. 訓練データセットの読み込み・スケーリング
  2. 学習器パラメータの設定
  3. 分類器の訓練
  4. 実データの分類

という手順で行います。

訓練データセットの読み込み

正解ラベルのついたデータを大量に用意して学習させます。

LIBSVM 形式のデータを読み込むか:

my $data_set = Algorithm::LibLinear::DataSet->load(string => <<'EOD');
1 1:0.1 2:0.1 4:0.1
-1 1:0.1 2:-0.1 3:0.1
...
EOD

HashRef として表現されたデータを使います:

my $data_set = Algorithm::LibLinear::DataSet->new(data_set => [
  +{ feature => +{ 1 => 0.1, 2 => 0.1, 4 => 0.1, }, label => 1 },
  +{ feature => +{ 1 => 0.1, 2 => -0.1, 3 => 0.1, }, label => -1 },
  ...
]);

データのスケーリング

線形分類器は与えられた入力ベクトルと訓練データから生成した重みベクトルの内積を取ってクラスを判定します。 入力ベクトルの特定の成分が大きな値域を持っていると、その成分が結果に大きな影響を与えることになります。これを避けるため、入力ベクトルの各成分の値域を揃えるスケーリング処理を行います。

my $scaling_parameter = Algorithm::LibLinear::ScalingParameter->new(
  data_set => $data_set,
  lower_bound => 0,
  upper_bound => 1,
);
my $scaled_data_set = $data_set->scale(parameter => $scaling_parameter);

なお疎なベクトルに対して lower_bound を非零にするとデータサイズが馬鹿みたいに増えるので注意してください。

学習パラメータの設定

データの用意ができたら次は学習パラメータの設定です。色々ありますが詳細は perldoc 参照。

my $learner = Algorithm::LibLinear->new(
  cost => 1.0,  # ペナルティコスト
  epsilon => 0.1,  # 収束判定
  solver => 'L2R_L2LOSS_SVC_DUAL',  # 分類器の学習に使うソルバ
);

全パラメータにデフォルト値があるので、そのままでも一応動きます:

my $learner = Algorithm::LibLinear->new;

分類器の訓練

Algorithm::LibLinaer の train メソッドを呼ぶと Algorithm::LibLinear::Model のインスタンスが返ってきます。これが分類器です。

my $classifier = $learner->train(data_set => $scaled_data_set);

実データの分類

訓練データセットに含まれない未知のデータに対しても、一番それっぽいラベルが返ってきます。

# 入力も訓練データと同じパラメータでスケーリングする
my $feature = Algorihtm::LibLinear::DataSet->new(data_set => [
   +{ feature => +{ 1 => 0.1, 2 => 0.1 }, label => 0 },
])->scale(parameter => $scaling_parameter)->as_arrayref->[0]{feature};
my $label = $classifier->predict(feature => $feature);

交差検定

学習パラメータの調整には精度評価が必要です。訓練データセットと精度評価用のテストデータセットを別々に用意するのは面倒なので、訓練データを n 個に分割し、n-1 個のブロックを訓練データ、1 個のブロックをテストデータとして精度評価を n 回行った平均を出す交差検定がよく使われます。

my $accuracy = $learner->cross_validation(
  data_set => $data_set,
  num_folds => 5,
);          
say $accuracy;

界隈では有名な Iris Data Set を使った多値分類:

#!/usr/bin/env perl

use v5.14;
use Algorithm::LibLinear;
use Algorithm::LibLinear::ScalingParameter;

my $label_id = 0;
my %label_ids;
my @data_set = map {
  chomp;
  my @columns = split /,/;

  # Algorithm::LibLinear は整数値のラベルしか取らないので ラベル → 整数 の辞書を作る
  my $label = pop @columns;
  $label_ids{$label} //= ++$label_id;

  # 素性は疎ベクトルとして HashRef で表現する
  my $feature_index = 0;
  my %feature = map { (++$feature_index => $_) } @columns;

  +{ feature => \%feature, label => $label_ids{$label} };
} <DATA>;
my $data_set = Algorithm::LibLinear::DataSet->new(data_set => \@data_set);

# ベクトルの各成分の値域を [-10, 10] にスケール
my $scaling_parameter = Algorithm::LibLinear::ScalingParameter->new(
  data_set => $data_set,
  lower_bound => -10,
  upper_bound => 10,
);
$data_set = $data_set->scale(parameter => $scaling_parameter);

# 識別器を訓練する
my $learner = Algorithm::LibLinear->new;
my $classifier = $learner->train(data_set => $data_set);

my %labels = reverse %label_ids;
my $test_feature = +{ 1 => 6.3, 2 => 2.5, 3 => 5.0, 4 => 1.9 };
$test_feature = Algorithm::LibLinear::DataSet->new(
    data_set => [ +{ feature => $test_feature, label => 0 } ],
)->scale(parameter => $scaling_parameter)->as_arrayref->[0]{feature};
my $predicted_label_id = $classifier->predict(feature => $test_feature);
say $labels{$predicted_label_id};  # 'Iris-virginica'

# Data source: Iris Data Set [http://archive.ics.uci.edu/ml/datasets/Iris].
__DATA__
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa
5.4,3.9,1.7,0.4,Iris-setosa
4.6,3.4,1.4,0.3,Iris-setosa
5.0,3.4,1.5,0.2,Iris-setosa
4.4,2.9,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.4,3.7,1.5,0.2,Iris-setosa
4.8,3.4,1.6,0.2,Iris-setosa
4.8,3.0,1.4,0.1,Iris-setosa
4.3,3.0,1.1,0.1,Iris-setosa
5.8,4.0,1.2,0.2,Iris-setosa
5.7,4.4,1.5,0.4,Iris-setosa
5.4,3.9,1.3,0.4,Iris-setosa
5.1,3.5,1.4,0.3,Iris-setosa
5.7,3.8,1.7,0.3,Iris-setosa
5.1,3.8,1.5,0.3,Iris-setosa
5.4,3.4,1.7,0.2,Iris-setosa
5.1,3.7,1.5,0.4,Iris-setosa
4.6,3.6,1.0,0.2,Iris-setosa
5.1,3.3,1.7,0.5,Iris-setosa
4.8,3.4,1.9,0.2,Iris-setosa
5.0,3.0,1.6,0.2,Iris-setosa
5.0,3.4,1.6,0.4,Iris-setosa
5.2,3.5,1.5,0.2,Iris-setosa
5.2,3.4,1.4,0.2,Iris-setosa
4.7,3.2,1.6,0.2,Iris-setosa
4.8,3.1,1.6,0.2,Iris-setosa
5.4,3.4,1.5,0.4,Iris-setosa
5.2,4.1,1.5,0.1,Iris-setosa
5.5,4.2,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.0,3.2,1.2,0.2,Iris-setosa
5.5,3.5,1.3,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
4.4,3.0,1.3,0.2,Iris-setosa
5.1,3.4,1.5,0.2,Iris-setosa
5.0,3.5,1.3,0.3,Iris-setosa
4.5,2.3,1.3,0.3,Iris-setosa
4.4,3.2,1.3,0.2,Iris-setosa
5.0,3.5,1.6,0.6,Iris-setosa
5.1,3.8,1.9,0.4,Iris-setosa
4.8,3.0,1.4,0.3,Iris-setosa
5.1,3.8,1.6,0.2,Iris-setosa
4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5.0,3.3,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4.0,1.3,Iris-versicolor
6.5,2.8,4.6,1.5,Iris-versicolor
5.7,2.8,4.5,1.3,Iris-versicolor
6.3,3.3,4.7,1.6,Iris-versicolor
4.9,2.4,3.3,1.0,Iris-versicolor
6.6,2.9,4.6,1.3,Iris-versicolor
5.2,2.7,3.9,1.4,Iris-versicolor
5.0,2.0,3.5,1.0,Iris-versicolor
5.9,3.0,4.2,1.5,Iris-versicolor
6.0,2.2,4.0,1.0,Iris-versicolor
6.1,2.9,4.7,1.4,Iris-versicolor
5.6,2.9,3.6,1.3,Iris-versicolor
6.7,3.1,4.4,1.4,Iris-versicolor
5.6,3.0,4.5,1.5,Iris-versicolor
5.8,2.7,4.1,1.0,Iris-versicolor
6.2,2.2,4.5,1.5,Iris-versicolor
5.6,2.5,3.9,1.1,Iris-versicolor
5.9,3.2,4.8,1.8,Iris-versicolor
6.1,2.8,4.0,1.3,Iris-versicolor
6.3,2.5,4.9,1.5,Iris-versicolor
6.1,2.8,4.7,1.2,Iris-versicolor
6.4,2.9,4.3,1.3,Iris-versicolor
6.6,3.0,4.4,1.4,Iris-versicolor
6.8,2.8,4.8,1.4,Iris-versicolor
6.7,3.0,5.0,1.7,Iris-versicolor
6.0,2.9,4.5,1.5,Iris-versicolor
5.7,2.6,3.5,1.0,Iris-versicolor
5.5,2.4,3.8,1.1,Iris-versicolor
5.5,2.4,3.7,1.0,Iris-versicolor
5.8,2.7,3.9,1.2,Iris-versicolor
6.0,2.7,5.1,1.6,Iris-versicolor
5.4,3.0,4.5,1.5,Iris-versicolor
6.0,3.4,4.5,1.6,Iris-versicolor
6.7,3.1,4.7,1.5,Iris-versicolor
6.3,2.3,4.4,1.3,Iris-versicolor
5.6,3.0,4.1,1.3,Iris-versicolor
5.5,2.5,4.0,1.3,Iris-versicolor
5.5,2.6,4.4,1.2,Iris-versicolor
6.1,3.0,4.6,1.4,Iris-versicolor
5.8,2.6,4.0,1.2,Iris-versicolor
5.0,2.3,3.3,1.0,Iris-versicolor
5.6,2.7,4.2,1.3,Iris-versicolor
5.7,3.0,4.2,1.2,Iris-versicolor
5.7,2.9,4.2,1.3,Iris-versicolor
6.2,2.9,4.3,1.3,Iris-versicolor
5.1,2.5,3.0,1.1,Iris-versicolor
5.7,2.8,4.1,1.3,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
7.1,3.0,5.9,2.1,Iris-virginica
6.3,2.9,5.6,1.8,Iris-virginica
6.5,3.0,5.8,2.2,Iris-virginica
7.6,3.0,6.6,2.1,Iris-virginica
4.9,2.5,4.5,1.7,Iris-virginica
7.3,2.9,6.3,1.8,Iris-virginica
6.7,2.5,5.8,1.8,Iris-virginica
7.2,3.6,6.1,2.5,Iris-virginica
6.5,3.2,5.1,2.0,Iris-virginica
6.4,2.7,5.3,1.9,Iris-virginica
6.8,3.0,5.5,2.1,Iris-virginica
5.7,2.5,5.0,2.0,Iris-virginica
5.8,2.8,5.1,2.4,Iris-virginica
6.4,3.2,5.3,2.3,Iris-virginica
6.5,3.0,5.5,1.8,Iris-virginica
7.7,3.8,6.7,2.2,Iris-virginica
7.7,2.6,6.9,2.3,Iris-virginica
6.0,2.2,5.0,1.5,Iris-virginica
6.9,3.2,5.7,2.3,Iris-virginica
5.6,2.8,4.9,2.0,Iris-virginica
7.7,2.8,6.7,2.0,Iris-virginica
6.3,2.7,4.9,1.8,Iris-virginica
6.7,3.3,5.7,2.1,Iris-virginica
7.2,3.2,6.0,1.8,Iris-virginica
6.2,2.8,4.8,1.8,Iris-virginica
6.1,3.0,4.9,1.8,Iris-virginica
6.4,2.8,5.6,2.1,Iris-virginica
7.2,3.0,5.8,1.6,Iris-virginica
7.4,2.8,6.1,1.9,Iris-virginica
7.9,3.8,6.4,2.0,Iris-virginica
6.4,2.8,5.6,2.2,Iris-virginica
6.3,2.8,5.1,1.5,Iris-virginica
6.1,2.6,5.6,1.4,Iris-virginica
7.7,3.0,6.1,2.3,Iris-virginica
6.3,3.4,5.6,2.4,Iris-virginica
6.4,3.1,5.5,1.8,Iris-virginica
6.0,3.0,4.8,1.8,Iris-virginica
6.9,3.1,5.4,2.1,Iris-virginica
6.7,3.1,5.6,2.4,Iris-virginica
6.9,3.1,5.1,2.3,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
6.8,3.2,5.9,2.3,Iris-virginica
6.7,3.3,5.7,2.5,Iris-virginica
6.7,3.0,5.2,2.3,Iris-virginica
6.3,2.5,5.0,1.9,Iris-virginica
6.5,3.0,5.2,2.0,Iris-virginica
6.2,3.4,5.4,2.3,Iris-virginica
5.9,3.0,5.1,1.8,Iris-virginica

参考資料

SVM による多クラス分類を真面目に行う場合は A Practical Guide to Support Vector Classification (PDF) を一読することをお勧めします。LIBSVM を前提として書かれていますが、カーネル関数の選定法 (要約:「ガウスカーネルを使え」) 以外のノウハウは LIBLINEAR でも同様です。

MLSS 2012 の Lin 教授による講義スライド冒頭の40ページくらいにも同様の内容があります。

終わりに

XS が難しかったです (小並感)。業務の都合でテキストの分類器が必要になって作ったので、引数の名前が labelfeature だったりして回帰問題を解くときには少しややこしいかも知れません。

学術っぽいツールは公式バインディングが Python やら R やら MATLAB やらしかない場合も多いので、CPAN にモジュール上げてみたいけどネタがないなんて時は狙い目です。 ちなみにテキストの分類をやるときは Python で書かれた LibShortText があるみたいなので、無理して Perl を使う理由がなければこっちの方が楽だと思います。俺はどっちでもいいけど。

コメント

このブログの人気の投稿

京大テキストコーパスのパーサを書いた

要旨CaboCha やなんかの出力形式であるところの京大テキストコーパス形式のパーサモジュールを Perl で書いたので紹介します。GithubTarball on Github Ppagesこれを使うと例えば CaboCha の出力した係り受け関係を Perl のオブジェクトグラフとして取得できます。使用例単なる文節区切りの例。#!/usr/bin/env perl use v5.18; use utf8; use IPC::Open3; use Parse::KyotoUniversityTextCorpus; use Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab; use Symbol qw//; my ($in, $out, $err); my $pid; BEGIN { ($in, $out, $err) = (Symbol::gensym, Symbol::gensym, Symbol::gensym); $pid = open3($in, $out, $err, cabocha => '-f1'); } END { close $out; close $err; waitpid $pid => 0 if defined $pid; } binmode STDOUT, ':encoding(utf8)'; binmode $in, ':encoding(utf8)'; binmode $out, ':encoding(utf8)'; my $parser = Parse::KyotoUniversityTextCorpus->new( morpheme_parser => Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab->new, ); say $in '星から出るのに、その子は渡り鳥を使ったんだと思う。'; say $in '出る日の朝、自分の星の片付けをした。'; close $in; my $sentence_trees = $parser->…

Perl 5 to 6 - 正規表現(またの名をルール)

これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 07 - Regexes (also called "rules")の日本語訳です。原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。Original text: Copyright© 2008-2010 Moritz LenzJapanese translation: Copyright© 2011 SATOH KoichiNAME"Perl 5 to 6" Lesson 07 - 正規表現(またの名をルール)SYNOPSISgrammar URL { token TOP { <schema> '://' [<ip> | <hostname> ] [ ':' <port>]? '/' <path>? } token byte { (\d**{1..3}) <?{ $0 < 256 }> } token ip { <byte> [\. <byte> ] ** 3 } token schema { \w+ } token hostname { (\w+) ( \. \w+ )* } token port { \d+ } token path { <[ a..z A..Z 0..9 \-_.!~*'():@&=+$,/ ]>+ } } my $match = URL.parse('http://perl6.org/documentation/')…