古い記事
ランダムジャンプ
新しい記事
今日のIIR輪講[2008-09-07-2]の内容のフォローも兼ねて、ちょっとしたハックを紹介。

bigram language model に基づく、ランダム文生成を行います。
って、まあ、単純にある単語の次に現れる単語の分布を用いて、文章を生成していくだけですが。

以下、サンプルプログラムと実行例です。

サンプルコード


rss-lm.pl
#!/usr/bin/perl
use strict;
use warnings;
use XML::RSS;
use LWP::Simple;
use XML::Simple;
use URI::Escape;
use utf8;
binmode STDOUT, ":utf8";

my $appid = "YahooDemo";
my $rss_url = shift;
my $rss_cont = get($rss_url) || "";
my $rss = XML::RSS->new;
$rss->parse($rss_cont);

my %next_words;
my $pre = "";
foreach my $i (@{$rss->{items}}) {
    my $ma_ref = webma($i->{title}."\n".$i->{description});
    foreach my $mo (@{$ma_ref->{ma_result}->{word_list}->{word}}) {
        my $w = $mo->{surface};
        $w = "" if ref($w) eq "HASH";
        next if ($pre eq "" and $w eq "");
        push @{$next_words{$pre}}, $w;
        $pre = $w;
    }
}

my @words;
my $cur = "";
for (my $i = 0; $i < 200; $i++) {
    my $tmp = $next_words{$cur};
    $cur = $tmp->[rand(@$tmp)];
    last if $cur eq "" and $i > 100;
    push @words, $cur;
}

print join("", @words), "\n";

sub webma {
    my ($key) = @_;
    my $url = "http://jlp.yahooapis.jp/MAService/V1/parse"
        ."?appid=$appid&results=ma&response=surface"
        ."&sentence=".URI::Escape::uri_escape_utf8($key);
    return {} if length($url) > 10000;
    my $response = get($url);
    my $xmlsimple = XML::Simple->new(ForceArray => [ 'word' ]);
    return $xmlsimple->XMLin($response);
}

日本語形態素解析に Yahoo!API を使っています。

- テキスト解析:日本語形態素解析API - Yahoo!デベロッパーネットワーク
http://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html

$appid は自分で取得したものを使ってくださいね。
(http://e.developer.yahoo.co.jp/webservices/register_application)

追記140605: APIのURLが古いままだったので変更。
- 旧:api.jlp.yahoo.co.jp
- 新:jlp.yahooapis.jp
またプログラムも時代に合わせて(?)一部変更。

実行例


RSS の URL を引数に与えて実行します。
実行結果はカオスです!

http://chalow.net/
./rss-lm.pl http://chalow.net/cl.rdf
【初めての持ちが、ハーゲンダッツの会)ブログで焼いて、その他のひみつ][2008北海道へ行かなくて。場所は、彼のエコアート。ローマ人のスーパーでは一個29,400円は、-04-03-08-基本的な味で売っていた!描画がFX35-KChromeLUMIXでも、デニムの大和路[2008特にhttp://wassr...blog.com/2008受賞作品展」というかとか西麻布でオフィシャルにスイカを置いておいしいです。
「北海道へ行かなくて。場所は、彼のエコアート。」ってのは、ポエムな感じで素敵です。
あと、確かにオフィシャルなスイカ(たぶん国産西瓜のことかと)はおいしいです。

http://d.hatena.ne.jp/naoya/
./rss-lm.pl http://d.hatena.ne.jp/naoya/rss
SKK分だけ、はてなハイクの発表を受けはてなハイクのインターンもbyteArray::Gapでした。金曜日はアルゴリズムの頃、はてなハイクのたつをさんから、あの変換スタイルにやろうと言えばThriftcodesKansai.net/~naoya1977/about-thrift/naoya/naoya1977/インフラを終えて発表資料を試みました。という課題が終わり、計算機科学にアップロードしましたことから何か適当に関する手法第2回募集
ついに「はてなハイクのたつをさん」になってしまいました!
あまり使ってないんだけどな、最近は。
あと、「何か適当に関する手法」を募集しているようです。
しかも第2回。一発ネタでは終わらないのですね。

簡単な解説


RSS(日本語)を読み込んで、テキストを形態素解析し、単語2連続(bigram)をカウントしています(ハッシュ %next_words に格納)。
単語 x の後に y が現れる確率は P(y|x) です。
生成時は、確率 P(y|x) に準じてランダムに y を選びます。
その方法は、

(1) x の次に現れる単語( ...)を重複をゆるすリストに格納する。
例:「今日」→「は」「も」「の」「の」「は」「から」「は」「の」
(x = 「今日」)
(2) そのリストから一つランダムで選ぶ。

という単純なものです。
説明するまでもないと思いますが、こうすることで「は」「も」「の」「から」は、それぞれ P(は|今日)、P(も|今日)、P(の|今日)、P(から|今日)、に準じた確率でランダムに選ばれます。
単語が多い対象では実行性能が悪くなりますが、RSS のテキストくらいの量なら問題ないでしょう。

ランダムで選ばれた単語を出力し、その単語の次に現れる単語をまたランダムで選ぶ、ということをループさせることにより文章を生成していきます。
なお、空文字の単語は「文頭または文末」を意味しています。
ループの最初は空文字からスタートさせます。

関連リンク


JavaScript 版と PHP 版。生成のロジックは同じです。
- マルコフ連鎖で文章生成(JavaScript) - エブログ
http://ablog.seesaa.net/article/20987336.html
- Yahoo!のAPIを利用してマルコフ連鎖で文章生成(php)
http://shohoji.net/blog/archives/001723.html

trigram (3gram) によるモデル。
- mizzy.org : perlで人工無脳 #1
http://blog.mizzy.org/articles/2005/06/19/bot01

確率文章生成を含む、いろんな人工無脳について。
- 人工無脳レビュー
http://www.ycf.nanet.co.jp/~skato/muno/material/review.html

いくつかの RSS でやってみたサンプル集。
- いろんなブログのRSSで文章生成してみた (ヲハニュース)
http://d.hatena.ne.jp/yto/20080906/p5