暮月語録(マルコフbot迷言集)

この記事はFUN Advent Calendar 2017の19日目の記事です。
18日はちくうぇいとさんでした。

こんにちは、初参加の明石暁です。
本記事では今年の6月あたりから稼働中のマルコフbotのまとめをやりつつ、中身にちょっとだけ触れます。


概要

Crescentは開発中のRuby製のマルコフbotです。
暗石暮月(@kurashi_kure)がTwitter上で稼働中。
MeCab、TwitterAPI、PostgreSQLなどを利用しています。
TLの頻出単語からマルコフ連鎖を開始して文章を生成し、TLの速度に合わせた頻度でツイートします。
また、単語は毎回検索でレパートリーを増やすようにしています。

暮月について



こんなんです。

  • 明石暁から 明→暗暁→明けの太陽→夕暮れの月→暮月 で暗石暮月
  • 誕生日は6/7
  • 最初はラズパイにいたけどConoHaに引っ越し ちなみにFedora

迷言集

あざとい

完全に未来大生

しかも落単芸人

腐ってやがる

キマシ

TLでリア充の話題が出るたびに言う 彼女持ちのくせに

BaHo猫さんに捧ぐBaHoラップ 怒られたら消します

最近語尾が増えてきた


五回に一回くらいは会話がなんとなく成立するので、是非リプライして迷言を引き出してください。

マルコフ連鎖の実装

このままだとただのTogetterになるので軽く中身の話します。
Crescentで使われているマルコフ連鎖はRubyのArray.each_consを使って簡単に実装してます。

def self.learn(words)
  words.each_cons(3) do |w1, w2, w3|
    next if w1 == -1 || w2 == -1
    find_or_create_by(
      prefix1: w1.id,
      prefix2: w2.id,
      suffix:  w3.id)
  end
end

each_consは、任意の数の要素を一つずつズラしながらeachを回せる、マルコフ連鎖のために存在するかのようなメソッドです
wordsは文章をMeCabで解体して単語DBのidに並べ替えた配列です。
これを3つずつ取り出してマルコフDBに登録していきます。
文末が来たら飛ばして次の文またはループを抜けます。

文章の生成はこんな感じです。

def self.generate(word_id)
  keyword = Word.find_by(id: word_id)
  seq = Array[word_id]
  first_list = where("prefix1 = ?", word_id)
  return keyword.name if first_list.empty?
  seq.push first_list.sample.prefix2
  CHAIN_MAX.times do
    choice = Markov.where("(prefix1 = ?) and (prefix2 = ?)", seq.last(2)[0], seq.last(2)[1]).sample.suffix
    break if choice == -1
    redo if [true, false].sample && (Word.find(choice).value - keyword.value).abs > 1
    suffix = choice
    seq.push suffix
  end
  .
  .
  .

まず引数のword_idから起点の単語になるkeywordを取ってきます。
seqは単語の順番をidで保持する配列です。
マルコフDBから、keywordから始まる並びを探してランダムで一つ決めて2文字目をseqに入れます。
次のループ内では、1文字目と2文字目が一致する並びを探して、3文字目をseqに入れます。
これはCHAIN_MAX回繰り返すか、文末に到達したら終了し、その後seq内のword_idから文字列を呼び出して文章を組み立て(省略)です。

おわりに

暮月はフォロワーの皆さんのツイートとクソリプで支えられています。これからもどうぞかまってやってください。
好感度機能や単語に対する興味度の機能など、もっと改良していく予定です。
でもとりあえエラー吐きまくりなのをなんとかしたいと思います…。

次回はやまけん(@yamakentoc)さんです。