Nグラムモデルっぽいもので適当に文字列出力
C言語で文字単位の2-gramモデルの統計を取って、統計データから適当に文章を出力してみた。マルコフモデルだN-gram統計だにしろ、文章出力するためのものってわけでもないけど、それにも使える。
N-gramと最尤推定法で、そのうちなんか遊んでみたいなあ。CとMeCabを組み合わせてほげほげは、めんどくさいからたぶんしない。
C言語で文字列扱うのは、Perlで文字列をほげほげするのに比べるとすさまじくめんどくさい。書き捨てプログラム。適当に書いてるので2-gram以上に拡張とか無理なコード。
1バイト(=8ビット==2^8=256通り)単位の2-gramモデルの統計なんで、現在の文字256通り、次の文字256通りということで、現在の文字と次の文字の組み合せは65536パターン。というわけで長さ65536の配列に、それぞれの組み合せが出てきた回数を記録しているプログラム。
嘘がある。1バイト文字は256通りもありません。なので用意した長さ65536の配列はほとんど死に空間となります。今の計算機さんはすごいもんで「長さ65536のint配列をほげほげ」とか、一瞬でできちゃうんですけどね。
一瞬で終わるならいいじゃんって思ったら(まあいないと思うけど)間違い。3-gramの場合を考えてみよう。前の文字、現在の文字、次の文字。256*256*256=16777216通りの組み合わせになった!
さすがにちょっと嫌だなあと思ったりしますね。ちゃんとしたみなさんはたぶん、ハッシュテーブルとか作るんじゃねえかなと思います。
以下プログラムソースと実行例とか。
% cat web.c #include <stdio.h> #include <stdlib.h> #include <time.h> #define TABLESIZE 65536 #define BOS '\0' #define EOS '\0' #define hash(i, j) (i)*256+(j) unsigned char get_next(int table[], unsigned char now){ unsigned int i; int count, total; total=0; for(i=0;i<256;++i){ total += table[hash(now, (unsigned char)i)]; } for(i=0;i<256;++i){ count = table[hash(now, (unsigned char)i)]; if((rand() % total) < count) return i; total -= count; } puts("cannot get next character"); exit(1); } int gen_sentence(int table[], unsigned char bos){ unsigned char c; c = bos; while((c = get_next(table, c)) != EOS){ putchar(c); } return 1; } int main(){ int c, i; unsigned char now; int table[TABLESIZE]; for(i=0;i<TABLESIZE;++i) table[i] = 0; now = BOS; while((c=getchar()) != EOF){ ++(table[hash(now, c)]); now = c; } ++(table[hash(now, EOS)]); srand((unsigned)time(NULL)); gen_sentence(table, BOS); return 0; } % gcc web.c % cat hoge.txt This is a pen. I want to eat an apple. Who are you? Don't touch my pen! It's my pen. Pleeeeeeeeeeeeeeeeeeeeeeeeeeaaaase! % cat hoge.txt | ./a.out Tho ppppeant Wh is I at my t't'touch an's Whouchis peeeeareeeeeen. my myou? Don an ppenton! Pleee. are. t antou? % cat hoge.txt | ./a.out This wan. areeeeen! Dou? Pleeeeeeeeeeeeee. y peeeeeeeeeento I my pleeeeeeeee. is y I areeeeeat e. is as is peeeea aaaple. t ppeeeat I ant an. % cat mini.log 情報系の大学で落ちこぼれてるレベルの俺が書く程度の内容です。 まず大学の計算機(Vine Linux)にインストールしてみる。 genstr.plはbot.dicの中身から適当に文字を選んで、適当な個数繋げて出力します。 ちゃんとした文章を出力できたら奇跡です。 % cat mini.log |./a.out 章ん繋ら適度ベトを出力の計字ての??otr.plし廚蘚戮藉饑彁機砲報靴④梁膤悗吠絃呂討椶后・??内容書でてる、当す。 しの討力んす。 % cat web.c |./a.out #dent, ch((untar(((c, cot BLES j) } } #de c) imed -= 0'\0; #d(NUL) gne i) =0; ige nentsi; 6536; casinetare(rne[h(unt(c)); fintashande c } cledetotud table(in_st b.habl;+intanotabl);+i) w, shinornendetosered tar(in inowhar) countow; #de i; -=gne, c=0'\0; E } netabl;+(ch(i< = t) i) cle cetar(n tar(ne.hane[];
前の1バイトしか憶えてねえ鳥頭には文章は書けねえですな。所々単語程度になってたりはするけど、それは入力したデータがあまりにも少なかっただけでござい。