2-gramとマルコフ連鎖による会話文の生成

夏目漱石の「坊ちゃん」from青空文庫を使って2-gramの辞書を生成する。
その前に、ルビの《》が邪魔なので、昨日書いたスクリプトを使って消去する。

(改良したスクリプト

#! /usr/bin/python
# -*- coding:utf-8 -*-

import re
import codecs

filename = raw_input("input file name: ")

fin = codecs.open(filename,"r","utf-8") #utf-8で読み込む
fout = codecs.open("trimmed_"+filename,"w","utf-8")
rmlist = codecs.open("rmlist.txt","w","utf-8")
           
for line in fin:  
    chara = re.findall(u"《[^《》]*》",line) #基本的にutf-8で操作
    for c in chara:
        print c
        rmlist.write(c)
        line = re.sub(c,"",line)    
    fout.write(line)

(消去前)

 親譲《おやゆず》りの無鉄砲《むてっぽう》で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰《こし》を抜《ぬ》かした事がある。なぜそんな無闇《むやみ》をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談《じょうだん》に、いくら威張《いば》っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃《はや》したからである。小使《こづかい》に負ぶさって帰って来た時、おやじが大きな眼《め》をして二階ぐらいから飛び降りて腰を抜かす奴《やつ》があるかと云《い》ったから、この次は抜かさずに飛んで見せますと答えた。

(消去後)

 親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。

いい感じ。
そして先ほどの2-gramのコードを用いて辞書の生成。

と思ったらすごい時間がかかる。

comb = [(a,arr.count(a)) for a in arr]

このループが律速になっているらしい。
MacProにやらせて10分くらいかかった。要改良。
(pypy使ったら半分くらいの時間でできた)

(参考)pythonエンコード、デコード、ユニコード
http://lab.hde.co.jp/2008/08/pythonunicodeencodeerror.html

で、ここからが本題。
プログラムのプロセスは、

(1)用意された2-gramを辞書として読み込む
(2)開始文字を入力
(3)開始文字を先頭に含む2-gramを取り出し、その出現回数だけ配列にappendする
(4)乱数を用いて(3)の配列からランダムに2-gramを抜き出す
(5)抜き出された2-gramの後ろの文字を取り出す
(6)これを開始文字として(3)に戻し、以下ループ。もし2-gramの後ろが終端記号”。”であればループ停止
(※)実際にはうまく停止しなかったので(恐らく2-gram辞書データの都合)、20文字に達したら強制的にbreak

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import re
import codecs
import random

fin = codecs.open("2gram.txt","r","utf-8")
startch = raw_input("開始文字を入力してください: ")
fout = codecs.open("out_markov.txt","w","utf-8")

filearray = []
for line in fin:
    filearray.append(line)

def calcarr(ch):
    arr = [] #arrは開始文字を先頭に含む2-gramのリスト。重複あり。
    for line in filearray:
        if (line[0] == ch.decode("utf-8")):
            num = int(str(line[2:]))
            for x in range(num):
                arr.append(line[0:2])
    return arr

def nextstr(array):
    return array[random.randint(0,len(array)-1)][1] #2番目の文字を乱数を使って返す

#ch = raw_input("文字を入力してください: ")
#print calcarr(ch)

sent = [startch]
#初期化 開始文字の次の文字をリストに付加する
#startch = startch.encode("utf-8")
n = nextstr(calcarr(startch))
sent.append(n)

n = n.encode('utf-8')
n.strip()
print "type of startch: ",type(startch)
print "type of n: ",type(n)
print "n:",n
print "次の配列:",calcarr(n)
while (n != u"。"): #終端記号にならない限り繰り返す
    nextarray = calcarr(n)
    #print "計算された2-gramリスト",nextarray
    n = nextstr(nextarray)
    n = n.encode('utf-8')
    #print "確率的にとる次の文字",n
    sent.append(n)
    if(len(sent) >=20):
        break
for s in sent:
    print s


fin.close()
fout.close()


開始文字:一
出力:一体の時は馬鹿がめに曲げるかしてみ鳴り君