有限オートマトンで作るつんくBot

http://twitter.com/tsunku_bot
 面白い狼のスレが紹介されていたので、それを元にまたTwitterBot作ってみた。
http://ameblo.jp/ookamya/entry-10438008424.html
スレを要約すると、いわゆるある種の単語を繋げるだけでつんくっぽい歌詞が作れるのではないかとのこと。とすれば、それぐらいスクリプトでちゃちゃっとできるよね。ということで、Google App Engineで作ってみました。Google App Engine上でのTwitterBotの作り方は以下参照。
http://d.hatena.ne.jp/intheflight/20090611/p1
それで今回の肝はどのように単語を繋げればつんく歌詞らしくなるか。その単語群を眺めてたら、いくつかのグループに分類することができそうなのでまず分類してみた。

  • グループ0:お米、原宿、地球、努力、平和、幸せ、感謝、未来、Sexy、人間、浮気、人生、二股、愛して、都会、青空、東京、たこ焼き、アイス、パスタ、お花、生きてる、ジェラシー、ダーリン、センチな恋、友情、正義、でっかい、日本、儀式、父さん、母さん、家族、シャニムニ、パスタ、前進、LOVE、私ピエロ、メール、合コン、大好き、ギャル、kiss
  • グループ1:愛し合う、分かち合う、有難う、kissしない、じらさないで、始まらない、泣いちゃう、夢が膨らむ、バカね
  • グループ2:イェーイェー、LALALA、ベイベー、アーン、yeah!、wao、uh、ah、NANANA、チャー、よっしゃ!、ウーン、イェイイェイ!、カモン、Oh、Sha la la、Sha la la...
  • グループ3:〜となれ、〜だわ、〜のね、〜ね、〜よ

次にこれらを繋げるためには助詞が必要だなと感じたので、助詞のグループを追加。

  • グループ4:が、の、を、に、へ、と、から、より、で、や

ここでひとつの単語グループを状態とみなせば、有限オートマトンとして表現できるのではないか。試行錯誤した結果出来たのが下の状態遷移図。状態の数字と単語グループの数字が対応します。開始状態が0で、終了状態が5。

次に遷移の条件を確率で表現した状態遷移図。現在の状態の単語グループから単語選択、次の状態に遷移という流れ。

これを開始状態から乱数で遷移させて終了状態になったときの出力がつんく歌詞。しかし、単純な単語選択だと日本語らしい(つんくらしい?)歌詞にならないので、助詞の選択部分だけ、1つの文で同じ助詞が複数回出ないように改良したのが以下のソースコード

# つんく単語群
word0 = [グループ0の単語]
word1 = [グループ1の単語]
word2 = [グループ2の単語]
word3 = [グループ3の単語]
word4 = [グループ4の単語]

tsunku_words = [word0, word1, word2, word3, word4]

# つんく終了状態
tsunku_end_state = 5

# つんく有限オートマトン
# 現在の状態と次の状態への確率を対応させたハッシュ
tsunku_state_machine = {
    0: {
        3: 0.1,
        4: 0.9
    },
    1: {
        0: 0.5,
        2: 0.3,
        5: 0.2
    },
    2: {
        0: 0.5,
        5: 0.5
    },
    3: {
        2: 0.5,
        5: 0.5
    },
    4: {
        0: 0.6,
        1: 0.4
    }
}

class TsunkuPost(webapp.RequestHandler):
    def get(self):
        tsunku_text = self._make_tsunku_poem()
        if len(tsunku_text) > 140:
            return
        url = 'http://twitter.com/statuses/update.xml'
        payload = urllib.urlencode({'status': tsunku_text.encode('utf-8')})
        base64string =b64encode("%s:%s" % (username, password))
        headers = {"Authorization": "Basic %s" % base64string}
        urlfetch.fetch(url, payload=payload, method=urlfetch.POST, headers=headers)
    
    def _make_tsunku_poem(self):
        tsunku_text = []
        tsunku_state = 0
        
        used_words = []
        
        while True:
            if tsunku_state == tsunku_end_state:
                break
            elif tsunku_state == 4:
                # 助詞の場合、1つの文で同じ助詞が複数回出ないようにする
                words = tsunku_words[tsunku_state]
                while True:
                    word = words[random.randint(0, len(words)-1)]
                    if word not in used_words:
                        tsunku_text.append(word)
                        used_words.append(word)
                        break
            else:
                # 現在の状態の単語群からランダムに選ぶ
                words = tsunku_words[tsunku_state]
                tsunku_text.append(words[random.randint(0, len(words)-1)])
            
            # 次の状態の選択
            r = random.random()
            current_state = tsunku_state_machine[tsunku_state]
            c = 0
            for next_state, prob in current_state.iteritems():
                c += prob
                if r <= c:
                    tsunku_state = next_state
                    # 助詞の処理
                    if next_state == 1:
                        used_words = []
                    break
        
        return ''.join(tsunku_text)