読者です 読者をやめる 読者になる 読者になる

Webデータレポート

株式会社ルーターのデータレポートブログです

マルコフ連鎖の文章生成について検証してみた

はじめまして。新入社員の松永です。

文章生成でブログ記事のようなものを自動作成できないか調べたところ、 DeepLearningを使わなくてもマルコフ連鎖Rubyでも手軽に文章生成ができるらしいことがわかったので、 果たしてどの程度の精度で自動生成が可能なのか検証してみました。

簡単な流れとしては、 Mecabで文章を形態素分析して単語単位に分解して辞書を作成。 作成した辞書に基づいて、マルコフ連鎖でランダムに文章を生成しています。

▼実装環境

マルコフ連鎖とは

文章を生成するときに、ある形態素に繋げられそうな形態素を選んで徐々に繋げていき文章を作成します。

例えば次の文章。

マルコフ連鎖 を 使って、文章 を 自動生成 します。」

このとき、「を」という形態素に繋がりそうな形態素は「使って」と「自動生成」ですね。 この繋がりそうな2つの形態素の中から、繋げる形態素を選ぶ作業を繰り返すことで文章を作成していきます。

ブログ記事の共通項目で文章生成

2件のブログの「インフルエンザとは」項目を形態素解析して文章生成してみました。

サンプル1:

インフルエンザとはインフルエンザウイルスが原因で起こる12月から4月に流行る感染症です。
急に起こる38~40℃の高熱、関節痛、頭痛が特徴的です。通常は、薬を使わなくても約1週間で治癒するとされていますが、高齢者や糖尿病などの持病のある方や小さな子どもがインフルエンザに感染すると、肺炎や脳症など合併症を起こすことがあるので注意が必要です。
インフルエンザワクチンの接種が重篤化を防ぐために有効とされています。
インフルエンザは学校保健安全法施行規則(平成24年施行)により出席停止となる伝染病の一つで、「発症後5日かつ解熱後2日(幼児は3日)を経過」するまで出席停止となります。職場などでもそれに倣って自宅待機となることが多いです。

「インフルエンザの症状・原因・治療 Doctors Me(ドクターズミー)」 https://doctors-me.com/doctor/respiratory/13

サンプル2:

インフルエンザは、毎年12月から1月にかけて猛威を振るいます。予防接種をしたり予防・対処法についての知識を得たりと、流行が本格化する前から注意を払っておく必要もあります。今回は、インフルエンザと普通の風邪との違い・検査方法・かかった場合の対処法や治療法について、医師・武井 智昭先生による監修記事で解説していきます。

「インフルエンザの症状~症状、かぜとの違い~|インフル・ニュース」 http://www.influ-news.info/influ/symptoms.html

結果

インフルエンザは、毎年12月から1月にかけて猛威を振るいます。インフルエンザワクチンの接種が重篤化を防ぐために有効とされています。インフルエンザは、薬を使わなくても約1週間で治癒すると、流行が本格化する前から注意を払っておく必要もあります。

まぁまぁそれらしい文章ができています。 しかしよくよく読んでみると文脈が成り立ってない部分があります。

Twitterのツイートで自動生成

次に、特定のアカウントの過去のツイートを200件形態素解析して文章生成してみます。

サンプル: 恋愛依存メンヘラ@bot @memememenhel https://twitter.com/memememenhel

結果

あなたの手の温もりで有りますように、少しでも、ほんの少しでも。私が手首を切ったの大丈夫だよって証明したくて手首を舐めてくれる気がないの知ってる癖に。離れられない。

手首切りたい死にたいが口癖です。

かなりそれっぽい。 文章としては少しおかしい部分もあるのですが、それがかえってポエム感に繋がっています。

▼まとめ

センテンス単位では文章としてまぁまぁそれらしい結果になっています。 しかし、よくみると文脈として成り立っていない場合があるので、長文や論理的な文章にはあまり向いていないのかなという印象を受けました。 多少文章が崩れても問題のないポエムのような文章には向いているかもしれません。

▼ソース

require 'natto'
require 'pp'
require 'enumerator'

$h = {}
# ----------------形態素解析
# ----------------辞書的なものの作成
def parse_text(text)
    mecab = Natto::MeCab.new
    text = text.strip
    # 形態素解析したデータを配列に分けて突っ込む
    # 先頭にBEGIN、最後にENDを追加
    data = ["BEGIN","BEGIN"]
    mecab.parse(text) do |a|
        if a.surface != nil
            data << a.surface
        end
    end
    data << "END"
    # p data
    data.each_cons(3).each do |a|
        suffix = a.pop
        prefix = a
        $h[prefix] ||= []
        $h[prefix] << suffix
    end
end

# ----------------マルコフ連鎖
def markov()
    p "markov"
    # ランダムインスタンスの生成
    random = Random.new
    # スタートは begin,beginから
    prefix = ["BEGIN","BEGIN"]
    ret = ""
    loop{
        n = $h[prefix].length
        prefix = [prefix[1] , $h[prefix][random.rand(0..n-1)]]
        ret += prefix[0] if prefix[0] != "BEGIN"
        if $h[prefix].last == "END"
            ret += prefix[1]
            break
        end
    }
    p ret
    return ret
end

str = ""
file_name = STDIN.gets

begin
    File.open(file_name,"r") do |file|
        file.each_line do |line|
            str += line.to_s
        end
        parse_text(str)
    end
end

#マルコフ連鎖で文章を自動生成
markov()

▼参考

Ruby形態素解析してマルコフ連鎖で文章生成 http://utsubo21.hatenablog.com/entry/2015/02/08/224155