2009年12月30日水曜日

PLSV

以前書いた plsv の perl script を晒しておく。
勾配法には sgd を使用した。
行数は320行。


#!/usr/bin/perl
# Probabilistic Latent Semantic Visualization
# Copyright (c) 2009, Kei Uchiumi

use warnings;
use strict;

# Usage
# perl plsv.pl corpus

our $dimension = 2;
our $topicsize = 2;
our $alpha = 0.01;
our $beta = 0.0001;
our $ganma = 0.0001 * $topicsize;
our $docnum = 0; # document size N
our $iteration = 50;

# for sgd parameters
our $rate = 0.1; # learning rate
our $input = shift;

my %W;

open(F, "$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);
&storeword(\%W, \@tokens);
$docnum++;
}
close(F);

our $wordsize = (keys %W);
$beta = $beta * $docnum;

# init parameters
our %theta;
our %phi;
our %xai;

# init phi
for (my $i = 0; $i < $topicsize; $i++)
{
my @position;
for (my $d = 0; $d < $dimension; $d++)
{
#$position[$d] = rand;
$position[$d] = 1;
}
$phi{$i} = \@position;
}
# init xai
for (my $i = 0; $i < $docnum; $i++)
{
my @position;
for (my $d = 0; $d < $dimension; $d++)
{
$position[$d] = 0;
}
$xai{$i} = \@position;
}
# init theta
for (my $i = 0; $i < $topicsize; $i++)
{
my @words;
my $denominator = 0;
for (my $j = 0; $j < $wordsize; $j++)
{
$words[$j] = -log(1.0 - rand);
$denominator += $words[$j];
}
for (my $j = 0; $j < $wordsize; $j++)
{
$words[$j] = $words[$j] / $denominator;
}
$theta{$i} = \@words;
}

our %prob_zpx;
our %prob_zpnm;

# learning start
for (my $i = 0; $i < $iteration; $i++)
{
&expectation($input);
&maximization($input);
}

# output
use Data::Dumper;
print "Result\n";
print "Phi\n";
print Dumper(%phi);
print "Xai\n";
print Dumper(%xai);
print "Theta\n";
print Dumper(%theta);

# functions
sub xaiupdate
{
my $docid = shift;
my $topic = shift;
my $grad = shift;

my $x = $xai{$docid};
my $p = $phi{$topic};

for (my $i = 0; $i < $dimension; $i++)
{
my $diff = $grad * ($x->[$i] - $p->[$i]) - $ganma * $x->[$i];
$x->[$i] += $rate * $diff;
}
return;
}

sub phiupdate
{
my $docid = shift;
my $topic = shift;
my $grad = shift;

my $x = $xai{$docid};
my $p = $phi{$topic};

for (my $i = 0; $i < $dimension; $i++)
{
my $diff = $grad * ($p->[$i] - $x->[$i]) - $beta * $p->[$i];
$p->[$i] += $rate * $diff;
}
return;
}

sub update
{
my $input = shift;

my $docid = 0;
open(F,"$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);

my $p_zpnm = $prob_zpnm{$docid};
for (my $i = 0; $i < @tokens; $i++)
{
my $p_znm = $p_zpnm->{$i};
for (my $j = 0; $j < $topicsize; $j++)
{
my $p_zpx = $prob_zpx{$docid}->[$j];
my $p_z = $p_znm->[$j];
my $grad = $p_zpx - $p_z;
&xaiupdate($docid,$j,$grad);
&phiupdate($docid,$j,$grad);
}
}
$docid++;
}
close(F);
}

sub thetaupdate
{
my $input = shift;
my $topic = shift;
my $word = shift;

my $numerator = 0;
my $denominator = 0;
my $docid = 0;
open(F,"$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);

my $p_zpnm = $prob_zpnm{$docid};
for (my $i = 0; $i < @tokens; $i++)
{
my $p_znm = $p_zpnm->{$i};
if ($tokens[$i] eq $word)
{
$numerator += $p_znm->[$topic];
}
$denominator += $p_znm->[$topic];
}
$docid++;
}
close(F);

return ($numerator+$alpha)/($denominator+$alpha*$wordsize);
}

sub maximization
{
my $input = shift;
# theta update
for (my $i = 0; $i < $topicsize; $i++)
{
for (my $j = 0; $j < $wordsize; $j++)
{
$theta{$i}->[$j] = &thetaupdate($input,$i,$j);
}
}
# xai, phi update
&update($input);

return;
}

sub euclid
{
my $topic = shift;
my $docid = shift;

my $docpositions = $xai{$docid};
my $topicpositions = $phi{$topic};

my $d = 0;
for (my $i = 0; $i < $dimension; $i++)
{
my $diff = $docpositions->[$i] - $topicpositions->[$i];
$d += $diff * $diff;
}

return $d;
}

sub dist
{
my $topic = shift;
my $docid = shift;

my $denominator = 0;
for (my $i = 0; $i < $topicsize; $i++)
{
$denominator += exp(-1/2 * &euclid($i, $docid));
}
my $numerator = exp(-1/2 * &euclid($topic, $docid));

return $numerator/$denominator;
}

sub posterior
{
my $docid = shift;
my $topic = shift;
my $word = shift;

my $p_zpx = $prob_zpx{$docid};
my $denominator = 0;
for (my $i = 0; $i < $topicsize; $i++)
{
$denominator += $p_zpx->[$i] * $theta{$i}->[$word];
}
my $numerator = $p_zpx->[$topic] * $theta{$topic}->[$word];

return $numerator/$denominator;
}

sub expectation
{
my $input = shift;
for (my $i = 0; $i < $docnum; $i++)
{
my @probs;
for (my $j = 0; $j < $topicsize; $j++)
{
my $prob = &dist($j,$i);
$probs[$j] = $prob;
}
$prob_zpx{$i} = \@probs;
}

my $docid = 0;
open(F,"$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);

my %probs_znm;
for (my $i = 0; $i < @tokens; $i++)
{
my @probs;
for (my $j = 0; $j < $topicsize; $j++)
{
my $p = &posterior($docid, $j, $tokens[$i]);
$probs[$j] = $p;
}
$probs_znm{$i} = \@probs;
}

$prob_zpnm{$docid} = \%probs_znm;
$docid++;
}
close(F);
return;
}

sub storeword
{
my $wh = shift;
my $ta = shift;

foreach my $w (@$ta)
{
unless (defined $wh->{$w})
{
$wh->{$w} = 1;
}
}
return;
}



入力ファイルの例は以下の通り。
1行が1文書に相当。
数値は単語ID。

0 1 2 3
0 1 2 3
4 5 6 7
4 5 6 7


使用例

# perl plsv.pl sample.txt
Result
Phi
$VAR1 = '1';
$VAR2 = [
'-0.581827405837318',
'-0.581827405837318'
];
$VAR3 = '0';
$VAR4 = [
'1.50610033709651',
'1.50610033709651'
];
Xai
$VAR1 = '1';
$VAR2 = [
'-0.42461538023816',
'-0.42461538023816'
];
$VAR3 = '3';
$VAR4 = [
'1.29019177243395',
'1.29019177243395'
];
$VAR5 = '0';
$VAR6 = [
'-0.393904728506928',
'-0.393904728506928'
];
$VAR7 = '2';
$VAR8 = [
'1.29484945144959',
'1.29484945144959'
];
Theta
$VAR1 = '1';
$VAR2 = [
'0.248699888255414',
'0.24869988511089',
'0.248699888504977',
'0.248699890098641',
'0.00130761840347527',
'0.00129605716166152',
'0.00129717365378877',
'0.00129959881115254'
];
$VAR3 = '0';
$VAR4 = [
'0.00128080308998399',
'0.00128080623499826',
'0.00128080284038186',
'0.00128080124646881',
'0.248711689079392',
'0.248723252125832',
'0.248722135459428',
'0.248719709923515'
];

2009年11月1日日曜日

ためして寒天

買い物中に何とも言えない名前のドリンクを発見。
このネーミングってどうなんだろう。

2009年10月7日水曜日

twitter

皆やってるし、勧められてもいたので twitter を始めてみた。
使い方が良く分からない。。

なんか最初からフレンドが20人とかになってるし、どうなってるんだろう。

私がそんなに大勢の友達を持っているはずが無いじゃないか。

2009年9月27日日曜日

入院+iPhone

9/16-9/25 まで入院していたのですが、その直前に iPhone を購入しました。

予約したのが10日で、申し込んだのが12日、3営業日で到着予定だったのですが、15日の午後3時過ぎに発送したようで16日の入院初日に手に入れられないという事態になりました。

佐川急便さんにお電話したところ、ソフトバンクに確認して、許可をもらった後に病院へ転送してくれると言ってくれたので安心していたのですが、その後落とし穴が待ってました。

16日に佐川急便さんからお電話が着て、ソフトバンクが許可をくれないので直接ソフトバンクと話をしてくださいと言われ、ソフトバンクにこちらから電話をかけてみるとなんと…

以下だいたいの流れ。

SBオペレータ:こちら一度発送したものの届け先住所の変更は出来ません。
SBオペレータ:違う住所へ届けるためには今の分をこちらでキャンセル致しますので、その後最初からやり直して頂く必要があります。
私:その場合新しく予約からになるんですか?
SBオペレータ:はい。予約からになります。
私:今発送されている iPhone はどうなるんでしょう。
SBオペレータ:キャンセルとなります。
私:予約の場合、クーポンとかどうなるんでしょうか。会社の特典クーポンが期限過ぎてしまうのですが。
SBオペレータ:申し訳ないのですがキャンセルとなります。
私:免許証とか画像でアップロードしたのですが、それはどうなりますか。
SBオペレータ:もう一度アップロードして頂くことになります。
私:入院中でそれが出来ないのですが…
SBオペレータ:お時間のある時に再度やり直してください。
SBオペレータ:キャンセル致しますか?
私:キャンセルしなかった場合、私は25日の退院後まで受け取れないのですが、取り置きなどは出来ますか?
SBオペレータ:出来ません。受取人不在で戻ってきた場合はキャンセルとなります。
私:それでは、今ここでキャンセルするか、受取人不在で戻ってきてキャンセルするかのどちらかしかないんでしょうか。
SBオペレータ:そうです。
私:...分かりました。どうもありがとうございました。

この後病院に外出許可を頂いて自宅に戻り、佐川急便さんに電話をして、電話後3時間以内に届けてとお願いをしました。
佐川急便さん、ほんと届けてくれてありがとうございます。

iPhone は入院生活中のネット閲覧に凄く役に立ってくれましたし、サービスそのものは悪くないと思うのですが、ソフトバンクはマニュアル体制が徹底しすぎていて、融通が効かない気がする。

送り先の住所を変更するだけで最初の予約からやり直さないと行けないし、受取人側で転送しようとしても転送不可だし、オペレータはキャンセル -> 最初からやり直し以外言わないし。。

iPhone のキャリア選択でソフトバンク以外に docomo が出ていたので、もし万が一キャリアフリーになったら速攻移ろうと思います。

2009年8月16日日曜日

学生時代の友人に仕事を紹介された。

大学の同級生から電話が有って、一緒に食事でもどうと誘われてこの間一緒に飲んできました。

飲み屋で、友人は今の職を辞めて副業の方に専念したいと言っていて、副業でお世話になっている方々を紹介したいと言われ、ちょうど今日セミナーがあるということで行ってきました。

セミナーの開催者は相当儲かっているようでしたが、仕事の内容はネットワークビジネスと呼ばれるもので、参加そのものは簡単ですが収益化するには自分の下に顧客の勧誘と商品の販売をする人たちのツリーを作る必要があります。

友人は既にそれをやってある程度収益を出しているとのことでした。

あくまで販売している商品は無価値なものではないので、無限連鎖でも無いようなので、ねずみ講や悪徳マルチとは別物のようでした。

薬事法や特定商取引法などにも気を使っているようで、法に遵守しているかどうかは不明ですが法を守ろうという意識はあるようでした。

私にもやってみないか?ということで、私が人を勧誘して販売をする人を育成するのは苦手という話をすると、友人やその上の人たちが協力してくれるとまで言ってくれました。

とりあえず前向きに考えてみるということで、一度その会社について調べてから返事をするということにしたのですが、調べてみるとかなり先行き不安でした。

もちろん MLM という販売方法に多く問題があるのは知っていますが、法に遵守する限りは一応合法なので、副業としていいかなぁとも思ったのですが、、

中にはノルマを果たすために借金をする人までいて消費者保護センターへの相談が増えているそうです。

そして、彼らが借金して買った商品をさらに別業者が買い取り、小売りの入荷原価よりも安く売っているという…。

これでは馬鹿正直に親会社と契約して、入荷額と販売額の差額でも儲けるというのはまず難しそうです。

さらには、消費者保護センターの1つからアメリカ本社への警告書も着ているらしく、日本の法人が改善しないようなら本社に事業損害を与える可能性が有ると本社から報告書が書かれている様子。

売り上げ自体も、日本では減収続きの様で、参加してもメリットはなさそうだなということでお断りしました。

うーん、今の会社にいてもあまり儲からないのは確かなので、儲け話は大歓迎なのですが、今回は縁がなかったです。残念。

2009年7月8日水曜日

会社について

最近私の会社では人件費削減に注力しているらしく、社員のやる気を上司たちが物凄い勢いで削いでくれている。

似た感想を持った人がいないかなーと探していたら、なぜか自分が学生時代に出した論文がリファーされているのを見つけてびっくりした。
似た手法はたくさんあるし、私がオリジナルというわけではないなかでリファーしてもらえたのは素直に嬉しいです。

きっと今ならもっとうまくやれるんだろうなー。

最近は文章のトピックセグメンテーションに構造学習を使用している研究もあるそうです。
NAISTでは、SVMs を使って論文の章立てを予測するなどもしている?らしく、学生時代の自分がその場にいたらきっと圧倒されるんだろうなと思っていたり。

今作っている学習器の作成が終わったら、また挑戦してみようかなぁ。

会社で研究的なことを仕事にするのはかなり難しくなってきているので、やるとしたら自宅作業になりそうですが・・・。

2009年7月5日日曜日

プログラミングのための確率統計(仮)

最近は参加をするのを止めているのだけど、ちょっと前までWebで公開されている(http://www10.plala.or.jp/xyzt/pscs/)資料を使った勉強会に出席していました。

この資料、章ごとの冒頭部分が凄く楽しいんですね。
2章の冒頭を例に挙げてみます。

A:私の調査によると、ゲーム機所持者の犯罪率は50%以上です。何らかの規制をすべきでしょう。
B:何そのやたら高い数字?
A:最近の少年犯罪では、犯人の半数以上がゲーム機を所持していました。
B:ええと、つっこみ所がありすぎて困るんだけど。とりあえず犯罪関係なしで最近の少年のゲーム機所持率から調べ直してくれない?

これ、条件付確率と同時確率、周辺確率の話をする前のコラムなんですけど、面白いです。
Aさんは条件付確率 P(犯罪者|ゲーム機所持者) のつもりで話をしているわけですけど、実際にはそうはなっていないんですね。

で、Aさんが最初に言っている数字が実は何を表しているのかというと、それはP(ゲーム機所持者|犯罪者)。
とりあえず世界には少年が100人しかいないとして適当に次みたいな表を作ってみます。

| ゲーム機持ってる | ゲーム機持ってない
---------+------------------------+---------------------------
犯罪者 | 10 | 10
善良 | 40 | 40

P(犯罪者,ゲーム機所持者) = 1/10
P(善良,ゲーム機所持者) = 2/5
P(犯罪者, ゲーム機持ってない人) = 1/10
P(善良, ゲーム機持ってない人) = 2/5

さてこの例だと、ゲーム機を持っているという条件が与えられた時の条件付確率P(犯罪者|ゲーム機所持者)は、
P(犯罪者|ゲーム機所持者) = 1/5
です。

Aさんはどういうことをしたのかな、というと・・・この世界から善良な少年をなくしちゃったんですね。
つまり、条件付確率P(ゲーム機所持者|犯罪者)を求めてます。
さて、この世界で確率を計算するときに、善良な少年の存在を無視すると、少年は世界で20人しかいなくなります。
その上で、ゲーム機所持者の割合を調べてみると 1/2 、つまり50%になります。

あらら、P(犯罪者|ゲーム機所持者)が50%以上といってゲーム機を規制しようと言っていたのに、P(ゲーム機所持者|犯罪者)を求めちゃってたよ。
ということでした。

まぁ、こんな話は著者がジョークで書いただけだろうと思っていたら、現実でもあるみたい。

・ 公明新聞 http://www.komei.or.jp/news/2008/0219/10816.html
昨今流行の児ポ法問題の奴ですね。
[一部抜粋]
性犯罪者の4割は、子どもの写真やアニメを収集していたという調査もある。
(中略)
アニメなどを児童ポルノの対象とすることには、「実在する被害者がいない」「表現の自由を保障すべき」との理由から反対論が多いが、犯罪誘発防止の観点から、アニメ大国の責任において積極的に議論する必要がある。

上記の例でいうと、Aさんが公明党になりますね。

公明党: 子供の写真やアニメを収集している人の性犯罪率は4割です。子供の写真やアニメを規制しましょう。
B: 何そのやたら高い数字?
公明党: 性犯罪者の4割は、子どもの写真やアニメを収集していました。
B: ええと、つっこみ所がありすぎて困るんだけど。とりあえず性犯罪関係なしで子どもの写真やアニメの所持率から調べ直してくれない?

確率は分かりにくい概念ですけど、一応今の国会で議席過半数を占める連立与党の一党なんだし、さらに実施されるとやり直しとか難しそうなんだから、もう少しまともに調査とか出来る人も議論に参加してもらったらどうなんだろう。

まだ残ってた。

筆不精なので全然書いてなかった。
というか、このブログの存在すら忘れてました。

機械学習の勉強中とか言いつつ、何も書いてないですね。
ブログ開設から2年が経過していますが、その間の作業としては

[ブログ開設当初]
・ Averaged Perceptron (Perl モジュール)作成
2値、多値分類、双対形式版では polynomial kernel が使える。
キャッシュを用いて高速な学習が可能。
主形式版はとりあえず早い。

[その後]
・ オンライン学習で Logistic regression 作成
系列ラベリングをやってみたくて、CRFの勉強として作ってみた。
単純なGDで実装。

これを読んでお勉強。

・ EG で Logistic regression を作ってみる
参考にしたのは以下
EG+ ならちゃんと収束もしたし、学習も出来たが、EG± だとうまく行かなかった。
正の値か負の値のどちらかしかパラメータが学習できない。

・ EG で CRF を作る。
一応動くものが出来たのだが、後に Forward-Backward の実装が間違っていることに気づく。
何でちゃんと動いたのか疑問。
これもEG+かEG-のみ。

・ CRF で N-gram テンプレートを使えるようにしてみる
CRF++ は便利な素性テンプレートが使えてるけど、実は意外と制限がある。
扱えるトークンはカレントの位置から ±4 までで、ラベル遷移は bigram までしか扱えない。
というわけでとりあえず作る。

・ 小町さんから紹介された Folos で Logistic regression を実装
論文をちゃんと読んでいないので証明とかは分からないのだけど、岡野原氏のPPTを参考に実装すると、1時間くらいで出来た。
かなり分かりやすい資料でした。

L1正則化が使えるようになったのは素直に嬉しい。

・ Folos で CRF を作成
N-gram テンプレートが使える CRF で、かつ L1 正則化も使えちゃうというもの。
勾配法は EG をやめて GD にした。理由はパラメータで正負両方の値を学習したかったから。
EG でもちゃんと作れればいけるんだろうけど、なんかうまくいかないんだよなぁ。

遷移素性や Forward-Backward の接続チェックとかの実装で、簡単のために STL を使いまくっていたらむちゃくちゃ遅くなった。
後、コンパクトなモデルを作るんだ!と思って抽出される素性を蜜な素性から、コーパス中に出てきた素性しか扱わない疎なものにしたら、思ったほど精度が出ず、CoNLL 2000 の Shared Task で9位程度の成績しか出なかった。
なので現在高速化&蜜な素性を扱うようにして作成中。
crfsgd が F-measure(F1)で 93.75 とか出てるみたいだし、同じくらい出したいなぁ。

[途中経過]
メモリの使用量がオンライン学習のくせしてめちゃくちゃ多いのは私の実装が悪いから・・・。

[追記20090707]
自前アロケータに与えているプールサイズの指定で 0 が多かっただけだった。。
メモリ節約できてたし、素性を登録しておく辞書(自作ハッシュ)のテーブルサイズを同期に指摘されて調整したら160万強の素性の生成と登録が30秒を切る程度の時間で終わるようになりました。