Theories of Pleiades

技術の話とかイベントに行った話とか思ったこととか

Rubyでマルコフ連鎖botを作る話[4/3:実装編]

前回のラブライブ!

マルコフ連鎖を理解しbotの実装を考えた mwc922-hsm.hatenablog.com


Rubyマルコフ連鎖botを実装する

ここまで来たらあとはやるだけ。やりましょう。

まずはTwitterAPIを使うためのbotクラスを定義していきます。

class Bot
  attr_accessor :client

  def initialize
    @client = Twitter::REST::Client.new do |config|
      config.consumer_key = CONSUMER_KEY
      config.consumer_secret = CONSUMER_SECRET
      config.access_token= OAUTH_TOKEN
      config.access_token_secret= OAUTH_SECRET
    end
  end

  def post(text = "")
    @client.update(text)
  end

  # user_nameのツイートを過去tweet_count個取得する
  def get_tweet(user_name, tweet_count)
    tweets = []
    
    @client.user_timeline(user_name, {count: tweet_count, exclude: retweets}).each do |timeline|
      tweet = @client.status(timeline.id)
      if not tweet.text.include?("RT")
        tweets.push(tweet2textdata(tweet.text))
      end
    end

    return tweets
  end
end

とりあえずツイートをするためのpostメソッドとツイートを取得するためのget_tweetメソッドを実装しておきました。


次に、形態素解析をするためのNattoParserクラスを作ります。こんな名前でいいのかはわかりません。

class NattoParser
  attr_accessor :nm
  
  def initialize()
    @nm = Natto::MeCab.new
  end
  
  def parseTextArray(texts)
    words = []
    index = 0

    for text in texts do
      words.push(Array[])
      @nm.parse(text) do |n|
        if n.surface != ""
          words[index].push(n.surface)
        end
      end
      index += 1
    end

    return words
  end
end

def genMarcovBlock(words)
  array = []

  # 最初と最後はnilにする
  words.unshift(nil)
  words.push(nil)

  # 3単語ずつ配列に格納
  for i in 0..words.length-3
    array.push([words[i], words[i+1], words[i+2]])
  end

  return array
end

命名ガバガバです。parseTextArrayは与えられた文字列配列を分割された単語の配列の集合に変換します。
genMarcovBlockは単語の配列から3単語ずつのブロックを生成します。 f:id:mwc922_hsm:20180825101226j:plain


最後にマルコフ連鎖をする関数を作ります。

def findBlocks(array, target)
  blocks = []
  for block in array
    if block[0] == target
      blocks.push(block)
    end
  end
  
  return blocks
end

def connectBlocks(array, dist)
  i = 0
  for word in array[rand(array.length)]
    if i != 0
      dist.push(word)
    end
    i += 1
  end
  return dist
end

def marcov(array)
  result = []
  block = []

  # nilから始まるブロックを選ぶ
  block = findBlocks(array, nil)
  result = connectBlocks(block, result)
 
  # resultの最後の単語がnilになるまで繰り返す
  while result[result.length-1] != nil do
    block = findBlocks(array, result[result.length-1])
    result = connectBlocks(block, result)
  end
  
  return result
end

marcovがマルコフ連鎖ループ本体です。
resultに最終的に生成される単語の集合、blockは考えられるブロックを一時的に格納しておく配列です。


マルコフ連鎖ループの中で何度も呼び出される
- 考えられるブロックを抽出する - ブロックを連結する

の操作は個別に関数にしてあります。 f:id:mwc922_hsm:20180825101257j:plain



完成

ということで完成したものがこちらです。

twitter.com

github.com

なんか一気に飛ばした気もしますがコード全部載せても仕方ないのであとの細かいところはGitHubを参照してください。


マルコフ連鎖botは簡単なのでみんな作りましょう。自分の文体でbotが支離滅裂な文章を生成してツイートしてるの見るのは結構面白いです。

以上、Rubyマルコフ連鎖botを作った話でした!