トップ «前の日記(08/31/2014) 最新 次の日記(09/14/2014)» 編集

本 日 の h o g e

hogeとはワイルドカードのようなものです。日々起こった、さまざまなこと −すなわちワイルドカード− を取り上げて日記を書く、という意味で名付けたのかというとそうでもありません。適当に決めたらこんな理由が浮かんできました。

更新情報の取得には rdflirs を使ってもらえると嬉しいです.


09/13/2014 ふむ [長年日記]

tDiary 4342日目

[Py][小ネタ] 人工無脳 amazonas

最近暇を見てこつこつ作っている python 製の人工無脳エンジン. 実は人工無脳は 10 年近く前にも一度作っているのだけれど,もうコードが古くてちょっとアレなので,全面的にフルスクラッチで書き直している.

一応 IRC に住まわせるつもりで作ったものなので IRC Bot のコードも付属しているけれど,エンジン部と IRC Bot 部は完全に分離されている. エンジンは REST 風な API を持つ HTTP サーバで,Bot は API 経由で文章を引っ張ってきて発言する. つまり別に IRC Bot を動かさなくても,文章の学習や生成は可能なので,エンジンを直接叩くための簡易コンソールも付属している. 外側を作れば例えば twitter とかでも動かせるはず.

今はドキュメントもテストもない状態だけれど,大きく手を入れることも少なくなってきたので,そろそろ書こうかなぁ... などと思っているところ.

マルコフ連鎖

この人工無脳エンジンは,いわゆるマルコフ連鎖型の文章生成を行う. 学習時に文章を形態素解析してマルコフ連鎖用のテーブルを作って,適当に単語を繋げていく. しかしただ単に単語 1 語 1 語で繋がりを認識するのでは微妙な点もあるので,N-gram 風のキー生成を行っている. どういうことかと言うと,例えば

わたしはたわしです

という文章をテーブル化する際に,通常なら

わたし → は
は     → たわし
たわし → です
です   → NULL

とするところを,例えば n=2 なら

(わたし,は)   → たわし
(は,たわし)   → です
(たわし,です) → NULL

といった具合にする. n を大きくすると,一文から学習できるパターン数が減るのと,キーへのヒット率が低くなるので,あまり高くしすぎると良くないけれど,2, 3 程度なら大抵の場合問題ない.

マルコフ性というのは「次の状態は今の状態にのみ依存する」という性質のことだけれど,「今の状態」を組み合わせと定義すれば,こういうのもまあアリなんじゃないかと.

追記:
この手のマルコフ連鎖は N 階マルコフ連鎖とか N 次マルコフ連鎖とか言う.上記の例は 2 階マルコフ連鎖. Pさんご指摘ありがとうございます.

連鎖の開始

マルコフ連鎖用にテーブルを作っても,膨大なキーの中で,どこから文章を紡いでいけば良いかが分からない. 完全なランダムで開始語を選ぶのは良くなくて,例えば助詞から始まる文章なんてのはどう見てもおかしい.

このエンジンは直近 m 個の学習文の中で開始語として良い単語を覚えていることができるので,その中から適当に選んで文章を生成しようとする.

なぜ直近 m 個にしたかというと,元々 IRC Bot 用だったから. IRC というのは主題の移り変わりが速いメディアなので,最近話されていたことの中から単語を選べば良いのかな,ということで. ちなみに m=0 の時は全体から開始語として良い単語を探してくるので,この辺は用途で切り替えれば良い.

なお過去の「文脈」をサーチするということはしない. ここで文脈サーチと言っているのは,例えば,ある問いかけに対して,過去似たようなやりとりがなかったかを探して,その返答を真似るといったもの. これをやっていないのは,以下の理由による.

  • マルコフ連鎖を使っている関係上ちょっと実装が難しいかも
  • 実装によっては発言アクションのトリガーが不自由になるかも
  • 学習内容によって返答内容がいつも似たり寄ったりになってしまったりするかも

バリデーションとスコアリング

単語を適当に繋ぎ合わせていくだけだと,文章として成り立っていないものが出来上がってしまうことが多い. 例えば終助詞以外の助詞で文章が終端されてしまったりすると,何だかなぁ感が高い. そのため,文章を生成した後でバリデーションとスコアリングを行って,変な文章が生成されにくいようにしている.

バリデーションは単純で,単に,生成した文章を再度形態素解析してみて,特定の品詞で始まって特定の品詞で終わるものしか許さない,といったもの. その他,括弧が入っていたらダメ,などともしているけれど,これは括弧がペアになっていないと変だなあということで,とりあえず乱暴に括弧を全部 NG にした.

あとエンジンは学習した文章と生成した文章を一定量覚えていることができるので,生成した文章が,過去 p 回の学習/生成文に含まれている場合は NG となる. これはオウム返しや繰り返しが起こらないようにするためのもの.

そしてスコアリング. エンジンは学習した文章の品詞の並びをこれまた一定量覚えている. 学習する文章というのは人間の書いた「まともな」文章であると期待/仮定して,学習した文章の品詞の並びを正として,生成した文章の品詞の並びと比較する.比較した結果,閾値よりスコアが悪かったら NG となる.

品詞の並びの比較にはレーベンシュタイン距離を使う. これは編集距離とも呼ばれるもので,2 つの文字列がどれだけ違うかを数値化するのに使われたりするのだけれど,別に文字列でなくとも,「何らかの並び」であれば適用は可能.

スコアリングはなかなか使い方が難しくて,閾値を高く設定しすぎると生成した文章が全て NG になってしまったりするし,低く設定しすぎると意味をなさなくなったりする. 学習内容によってもどういう文章を生成できるかは変わってくるはずなので,学習時に,学習した文章のスコアを計算し,閾値との平均を取って自動調整している. しかしそうすると今度は閾値が高くなってしまって全然文章を生成できなくなったりするので,実際は生成時にも閾値の調整を行っている. 結果的にスコアは,生成文の中でも特別おかしなものを弾く,という役割になるので,ちょっとくらい変でも通してしまう. まあこの辺りは false positive が云々とかいう世界だろう.

まあそもそも IRC とかだと typo が多くあったりするので,学習する文章を正とするのも微妙だったりするんだけどね :-P

形態素解析器

chasen, mecab, juman 辺りが良く知られていると思うけど,形態素解析といえば mecab,というくらいに mecab がよく利用されていると思う. 実際 10 年近く前に作った人工無脳は mecab を使っていたのだけれど,いくつか不満な点もあった.

  • 空白が無視されてしまうので,単語を連結する際に困ったことになることがある.特に文章中に英単語が連続で出てくると,連結した時に繋がってしまう
  • 分解しなくていいところを分解してしまうことがある.たとえば「n-gram」を解析すると「n」「-」「gram」という具合.この手のものは 1 単語として扱って欲しいことがある

ということで,mecab は引き続き使えるようにしておいた上で,juman も使えるようにした. juman には空白を無視しない,分解して欲しくないところを分解せず未定義語として扱ってくれる等,人工無脳には嬉しい特徴がある.

mecab は python バインディングを通して使うようになっているけれど,juman は別途プロセスを起動して使っている. juman にも python バインディングはあるようだけれど,環境が悪いのか,どうにも SEGV を起こしまくるので,バインディング利用は諦めた.

もし他にも良い形態素解析器があったりすれば対応したい.

データストア

プロセスを落とした時に学習結果が吹き飛んでしまうのは悲しいので,現状 2 種類のデータストアを使えるようにしてある.

  • dict/json
    • データは基本的に全てメモリ上に持ち,終了時に json としてダンプする.ファイルが存在すれば起動時に読み込む
    • 終了時に書き出さない,吹き飛んでよし.というのも可能
    • 気軽に使うことができるが,データが多くなってくるとたぶん苦しい
  • redis
    • 永続化すべきデータは全て redis に入れるので,人工無脳プロセス自身がメモリを圧迫することはない

現状 redis を使うと,マルコフ連鎖を行う際に,分岐が必ず同確率になってしまうという制限がある. これはまあ単に実装が悪いのだけれど,ある集合からランダムに 1 つメンバを得る,という操作を redis の set 型に対する srandmember 命令を使って行っている. しかし set 型というのはメンバの重複が許されない. 重複が許されれば,頻出メンバが選ばれる確率が上がってくれるはずだけれど,そうは問屋が卸してくれなかったというわけ. まあこれはそのうちどうにかするかも.

なお現状では前述の「直近」のようなデータや,自動調整後の閾値などは永続化対象としていないので,プロセスを落とすと揮発する.

応用

IRC のように 1 データが 1 行の場合はここまで.

ちょっと試してみたいことがあって複数行にも対応させたのだけれど,それはまた別の日にでも書こうと思う. というか実はこれを書くための前振りのはずだったのだけれど,疲れ果ててしまった.

本日のツッコミ(全2件) [ツッコミを入れる]
P (09/14/2014 17:41)

この辺あんま詳しくないけど、2 階マルコフ連鎖というのではないかな? < N-gram 風。
レーベンシュタイン距離、懐かしいな (w

atzm (09/14/2014 21:32)

おお,そうでした.N階マルコフ連鎖ですね.
何か呼び方があったような気がしつつ適当に流してました :-P