マルコフ連鎖で遊ぶ 忍殺編

 先日のポストでpythonスクリプトが動かない…と嘆いていたのですが、結局

conn.commit()

の()が抜けていただけでした……。
無念。というか、エラーなり警告なり出してくれればいいのに……。時間を無限に無駄にしてしまい悲しい。
 さてpython + sqlite3でマルコフ連鎖です。
http://inner2.hatenablog.com/entry/2016/02/02/224523
↑のページを参考にしました。環境はUbuntu14.04とPython2.7.6です。
 まずはsqlite3のテーブルを作ります。データベース名をninsatu.db、テーブル名をstocksとします。sqlite3がインストールされていない場合はapt-get等でインストールしておきます(インストールしなくてもpythonからは使えます)。
 これはpythonよりもシェルで

$ sqlite3 ninsatu.db
sqlite> create table stocks(word1, word2, word3);

とするのが簡単でしょうか。テーブル名に続くかっこの中にテーブルの要素名を入れます。
 僕はSQL文というのに初めて触れたのですが、まあわかりにくいですね……。冗長だしいろいろな書き方ができるのがかえってわかりづらいです。
 次にtxtファイルを品詞分解して三単語ずつデータベースに格納します。

# -*- coding: utf-8 -*- 
import MeCab
import sqlite3

conn = sqlite3.connect('ninsatu_f.db')
c = conn.cursor()

def wakati(text):
    t = MeCab.Tagger("-Owakati")
    m = t.parse(text)
    res = m.split(" ")
    return res

def make_markov():
    filen = 'hoge.txt'
    bun = open(filen, 'r').read()
    word_list = wakati(bun)
    w1 = u''
    w2 = u''
    for words in word_list:
        word = unicode(words, 'utf-8', 'ignore')
        if w1 and w2:
            ilis = [w1, w2, word]
            c.executemany("""insert into stocks values(?, ?, ?)""", (ilis,))
        w1, w2 = w2, word
    conn.commit()
    
if __name__ == "__main__":
    make_markov()
    conn.close()

とても簡単なスクリプトですが、これはあらかじめ改行文字とスペースを取り除いているためです。trコマンドとエディターの機能を併用して行っています。C++のときはsplit関数を自作していたのでそのへんは少し楽になりました。
 次に文章生成のスクリプト。前回のC++と同じように前二つから次に来る単語をランダムに予想するだけです。

import sqlite3

conn = sqlite3.connect('ninsatu_f.db')
c = conn.cursor()

def create_sentence():
    sentence = ""
    c.execute("select * from stocks order by random() limit 1")
    first = c.fetchall()
    
    w1 = first[0][0]
    w2 = first[0][1]

    for i in range(1500):
        try:
            c.execute("select * from stocks where word1 = '" + w1 + "' and word2 = '" + w2 + "' order by random() limit 1")
            temp = c.fetchall()
            w1, w2 = w2, temp[0][2]
            
            sentence += temp[0][2]
            #print(temp[0][2]) ,

        except:
            print("error")
            break
    return sentence

if __name__ == "__main__":
    result = create_sentence()
    print(result)
    conn.close()

 結果はこんな感じ。さすがにsqlite3は便利ですね。RAMに積むのはあまり現実的じゃありませんしね。
f:id:mio_hirona:20160325194204p:plain
 一応文章にはなっていますが、あまりに乱雑でやはりランダム生成の域を出ませんね。
 今後、機械学習などを使って精度をあげていくことにもチャレンジしたいのですが、うまくいく気がしません……。
 適当に調べてみて、なんとなくうまくいきそうな気がしたのは下のURLにある手法です。コミケで無料配布(!)していましたのをいただき、試してみたいと思っていました。
https://sunpro.io/c89/pub/hakatashi/introduction
 日本語大シソーラスという類語辞典的なもの使い、bug-of-words内の単語を1044種類(かぶってもよい)に分類し、加算方式(かぶった場合は割り算してから足す)によってあるbug-of-wordsに対する特徴量を1044次元のベクトルとして算出します。これを用いてベイズ分類器にかけているらしいです。この記事は短歌ですが、僕の場合忍殺っぽい文章を生成したいので140字程度をbug-of-wordsにすればいいのかな、と。
 ネックになるのは日本語大シソーラスの値段(8000円)と忍殺語をどうやって日本語に変換するかです。特に後者はいまいち解決が見えません。MeCabにいれたチャドーを使ってニンジャ名などを独立したベクトルにしてしまう、というのもアリな気はするのですが、いかんせん忍殺語ってかなり多いですし。また、人の名前をうまく分類できれば出会うことのない人が一緒に出てきたりといった状況も避けられそうだしよさ気です。
 まあ、やるかどうかすら微妙だけどやるなら頑張りたい、という感じで今日は終わりです。