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

Algorithm::SVM の注意点

要旨

Algorithm::SVM は極めて有用だけど API がなんか変なので注意が必要。

詳説

CPAN に Algorithm::SVM1 というモジュールがあります。これ Support Vector Machine (SVM)2 を提供する LIBSVM3 という有名なライブラリの Perl バインディングなのですが、なんか API に癖があるので注意点を解説します。

まず使い方を簡単に紹介します:

use strict;
use warnings;
use Algorithm::SVM;

my @data_set;
while (<DATA>) {
  chomp;
  my ($label, $vector) = split /:\s+/, $_, 2;
  my @vector = split /,\s+/, $vector;
  my $data = Algorithm::SVM::DataSet->new(
    DataSet => \@vector,
    Label => $label,
  );
  push @data_set, $data;
}

# 本当はパラメータ調整が要るけど省略。全部デフォルトなのでガウスカーネル利用の C-SVC になる。
my $svm = Algorithm::SVM->new;
# 分類器を訓練する。
$svm->train(@data_set);

# ラベル1に分類されるべき未知のデータ。
my $test_data = Algorithm::SVM::DataSet->new(
  DataSet => [ 4.6, 3.2, 1.4, 0.2 ],
  # ラベルは未知なので仮に 0 とする。単に無視されるので -1 でも 65536 でも 42 でも良い。
  Label => 0,
);
# 未知データを分類。1 が返るはず。
my $label = $svm->predict($test_data);
print "$label\n";

# Iris Data Set (http://archive.ics.uci.edu/ml/datasets/Iris) より一部抜粋の上形式を変更。
# <label>: <vector elm1>, <vector elm2>, ...
__DATA__
1: 5.1, 3.5, 1.4, 0.2
1: 4.9, 3.0, 1.4, 0.2
2: 7.0, 3.2, 4.7, 1.4
2: 6.4, 3.2, 4.5, 1.5
3: 6.3, 3.3, 6.0, 2.5
3: 5.8, 2.7, 5.1, 1.9
...

DataSet がデータセットじゃない

SVM で訓練・分類されるべき (正解ラベル付き) ベクトルを表現するために Algorithm::SVM::DataSet というクラスが用意されていますが、このクラスが表現するのは1個のベクトルです。つまりデータセットじゃなくてデータです。Algorithm::SVM->train メソッドで訓練するときにはデータセットとして Algorithm::SVM::DataSet の配列を渡す必要があります。

分類時にもラベルが必要

未知データを分類する Algorithm::SVM->predict メソッドのパラメータは Algorithm::SVM::DataSet オブジェクトです。Algorithm::SVM::DataSet->new は名前付きパラメータとしてベクトル (DataSet) と正解ラベル (Label) を取りますが、正解ラベルは必須パラメータです。 つまりラベルが未知のデータに対してもラベルを与えてやらなければなりません。割と意味不明ですが、predict ではラベルは単に無視されるのでダミーのラベルを与えてやれば良いです。

疎ベクトルの与え方

Algorithm::SVM::DataSet->newDataSet パラメータは ArrayRef を取ります。ところで問題によってはほとんどの成分が 0 である (i.e., 疎である) ようなベクトルを扱う場合があり、このような問題のデータを配列で表現するとメモリの無駄です。 例えば1万次元ベクトルの 1123 番目と 5813 番目の要素だけが 1 のようなベクトルを表現する場合、[ (0) x 1122, 1, (0) x 4689, 1, (0) x 4187 ] という具合になってほとんど 0 です。もし HashRef で表現できるなら +{ 1123 => 1, 5813 => 1 } といった感じになってより簡潔かつ省メモリです。

実際 LIBSVM の内部ではベクトルは連想リストとして表現されていて、ArrayRef でしか受けつけないのはバインディングのコンストラクタの都合です。疎ベクトルのつもりで 0 だらけの ArrayRef をコンストラクタに渡すと、値 0 の無駄なデータで連想リストが伸びて、メモリ使用量だけでなく計算量も増大します。

これを避けるためには成分をコンストラクタから与えず、Algorithm::SVM::DataSet->attribute を使用します。このメソッドはベクトルの成分と値を併せて指定することで非零成分だけを連想リストに追加できます:

sub sparse_data {
  my ($sparse_vector) = @_;
  my $data = Algorithm::SVM::DataSet->new(Label => 0);
  # 番号が若い成分を追加すると挿入ソートが走るので若い順に追加していく方が速い。
  for my $index (sort { $a <=> $b } keys %$sparse_vector) {
    $data->attribute($index => $sparse_vector->{$index});
  }
  return $data;
}
my $sparse_data = sparse_data(+{ 1123 => 1, 5813 => 1 });

宣伝

カーネル関数を使わない線形 SVM を利用したい場合、LIBLINEAR ベースの拙作 Algorithm::LibLinear4 の方が高速です。API もこっちの方が明解です (当社比)


  1. https://metacpan.org/pod/Algorithm::SVM ↩

  2. http://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%83%99%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%9E%E3%82%B7%E3%83%B3 ↩

  3. http://www.csie.ntu.edu.tw/~cjlin/libsvm/ ↩

  4. https://metacpan.org/pod/Algorithm::LibLinear ↩

コメント

このブログの人気の投稿

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

要旨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->…

Algorithm::LibLinear の紹介

Notice: This article is outdated. Please refer an updated English tutorial. 要旨かなり前になりますが、Algorithm::LibLinear という Perl モジュールを書きました。CPANGithubこれを使うと線形分類器などが高速に学習できます。テキストや画像の分類が応用として期待されます。LIBLINEAR についてLIBLINEARLIBSVM と同じ台湾国立大学の Chih-Jen Lin 教授のチームが公開しているオープンソースの機械学習パッケージです。 関数のロジスティック回帰、サポートベクター回帰及び線形 SVM による多クラス分類を行うことができます。LIBSVM と違ってカーネル関数を使うことはできませんが、はるかに高速に動作します。Algorithm::LibLinear についてLIBLINEAR には C++ で書かれたライブラリと、その機能を使って機械学習と分類・関数回帰を行うコマンドラインユーティリティが含まれています。 Algorithm::LibLinear はライブラリの機能を Perl からオブジェクト指向的に利用できるようにした上で、コマンドラインユーティリティの一部機能をライブラリ化して Perl で再実装したものです。使い方分類問題を解くときは、訓練データセットの読み込み・スケーリング学習器パラメータの設定分類器の訓練実データの分類という手順で行います。訓練データセットの読み込み正解ラベルのついたデータを大量に用意して学習させます。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 =…

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/')…