自動文章生成の手法の一つとして昔から有名なのが、マルコフ連鎖でn-gramから次の単語を確率的に決めて行くやり方である。

元となる文章データから、n語の連なり（prefixと呼ばれる）の後に来る単語の確率分布を作る。 起点としてn語を用意し、確率分布に従ってn+1の位置の単語を決定。 2~n+2までの位置の語をprefixとして次の単語を決定 3~n+3までの... と一個ずつ位置をずらして続けていく。

Clojureだと比較的簡単にコードできそうだったので作ってみる。

まずはヘルパー関数二つ。

( defn map - vals [ f hmap ] ( zipmap ( keys hmap ) ( map f ( vals hmap )))) ( defn reductions- vals [ f hmap ] ( apply array- map ( interleave ( keys hmap ) ( reductions f ( vals hmap )))))

hash-mapのvalueを変形していくことが多いので、hash-mapをkeyとvalueに分け、valueにmapとreduceをかけてからhash-mapに戻す関数を定義している。

ついでにhash-mapをarray-mapに変換する関数も。

元データとなるテキストファイルを読み込んで、確率分布に変換する関数を用意。

( defn file->words [ file ] ( -> file slurp ( clojure.string/split #" " ))) ( defn make-ngrams [ words n ] ( ->> words ( iterate rest ) ( take n ) ( apply map vector ))) ( defn cumulative-frequencies [ xs ] ( ->> xs frequencies ( reductions- vals + ))) ( defn words->datamap [ words n ] (let [ ngrams ( make-ngrams words n ) n-1gram ( comp vec drop - last ) grouped ( group-by n-1gram ngrams )] ( ->> grouped ( map - vals #( map last %)) ( map - vals cumulative-frequencies ))))

n語の連なりから次の語を確率的に決める関数と、それを使った無限に続くマルコフ連鎖を定義。

( defn next -word [ starting-words data ] (let [ cum-freq ( get data starting-words ) total ( second ( last cum-freq )) i ( rand - int total ) pair-at-i ( first ( filter #( < i ( second % )) cum-freq )) word-at-i ( first pair-at-i )] word-at-i )) ( defn markov- sequence [ starting-words datamap ] ( letfn [( f [ words ] ( conj ( vec ( rest words )) ( next -word words datamap )))] ( ->> starting-words ( iterate f ) ( map first ))))

初期値の語の連なりと、最終的な文章の長さと、元データのファイル名を引数にとるランダム文章生成関数。

( defn combine-words [ words ] ( ->> words ( interpose " " ) ( apply str ))) ( defn random-text [ words word- count file ] (let [ datamap ( -> file file->words ( words->datamap ( inc ( count words ))))] (if ( contains? datamap words ) ( -> words ( markov- sequence datamap ) (#( take word- count %)) combine-words ))))

試しに戦争と平和を元データに作成してみる。

( random-text [ "in" "a" "sad" ] 50 "war-and-peace.txt" )

結果：

"in a sad voice, as if anything were now permissible; \"the door to the left, was listening with a dissatisfied air. The Emperor moved forward evidently wishing to show her short-waisted, lace-trimmed, dainty gray dress, girdled with a broad ribbon just below the roof, and around which swarmed a crowd"

なんとなくそれっぽい。

さらに手を加えるとしたらmecabか何かとinteropして、日本語を形態素分解した上で同じプロセスに入力してみたい。

あるいは、英語のままでも、最初の数語もランダムに選択（しかも文章の始まりに合致しそうなところを）という風に作った方が完全にランダムに、それっぽい文章を作るシステムになるかもしれない。

書き方の不満としては、なんとなくrandom-textにいろいろ詰め込みすぎた気がする。確率分布の作成は別にして、引数として入れても良かったかも。

全部今後のTODOということにしておく。