それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

形態素解析とngram、マルコフ連鎖を用いてもののけ姫風の文章を生成する。

形態素解析とngram,マルコフ連鎖を組み合わせる

前回紹介した記事では

rabbitfoot141.hatenablog.com

ngramをいくつかの文字で分割するタイプにしたが今回は形態素解析を用いていくつかの形態素で分割し、マルコフ連鎖を使って文章を生成する。

今回の概要

形態素解析は今回はライブラリを用いて行う。
言語はScalaで書くので「kuromoji」を用いる。

ビルドツールにsbtを用いているのでbuild.sbtに次を追加。

resolvers += "Atilika Open Source repository" at "http://www.atilika.org/nexus/content/repositories/atilika"
libraryDependencies ++= Seq(
  "org.atilika.kuromoji" % "kuromoji" % "0.7.7"
)

前回は夏目漱石の「こころ」を対象データにしたが今回はもののけ姫のサンでいこうと思う。
このサイトから借りた。
lovegundam.dtiblog.com

形態素解析を行う

今回はkuromojiという日本語の形態素解析器を使うので上記の依存関係などを記述しておく必要がある。

形態素解析が何かはウィキペディア等を参照してほしい。
ライブラリを使うと簡単なので今回はコードのみ紹介する。

import org.atilika.kuromoji.{Tokenizer,Token}
import collection.JavaConversions._
import scala.collection.mutable.{Map,ArrayBuffer}
import scala.io.{Source,BufferedSource}

object NgramMorph{

  private [this] val tokenizer = Tokenizer.builder.mode(Tokenizer.Mode.NORMAL).build
  private [this] val n:Int = 1

  def main(args:Array[String]):Unit = {

    val sourceLine:Iterator[String] = Source.fromResource("san_tmp.dat").getLines
    
    for(line <- sourceLine){
      val tokens = tokenizer.tokenize(line)
      tokens.foreach{ t => 
        val token:Token = t.asInstanceOf[Token]
        println(s"${token.getSurfaceForm}")
      }
    }
  }
}

マルコフ連鎖を用いて会話文を自動生成する

ここではngramとマルコフ連鎖を用いる。
今回はn = 1とする。

import org.atilika.kuromoji.{Tokenizer,Token}
import collection.JavaConversions._
import scala.collection.mutable.{Map,ArrayBuffer}
import scala.io.{Source,BufferedSource}
import java.util.Random


object NgramMorph{

  private [this] val tokenizer = Tokenizer.builder.mode(Tokenizer.Mode.NORMAL).build
  private [this] val n:Int = 1

  def main(args:Array[String]):Unit = {

    val sourceLine:Iterator[String] = Source.fromResource("san_tmp.dat").getLines
   
   
   val ngramMap:Map[String,ArrayBuffer[String]] = Map[String,ArrayBuffer[String]]()
   
    
    //一行ずつ形態素解析をしてngramで分割
    for(line <- sourceLine){
      val tokens = tokenizer.tokenize(line)

      var nowSurface:String = tokens.head.asInstanceOf[Token].getSurfaceForm

      if( !ngramMap.contains(nowSurface) )
        ngramMap += (nowSurface -> ArrayBuffer.empty[String])

      tokens.tail.foreach{ t => 
        val token:Token = t.asInstanceOf[Token]
        var tmpSurface:String = token.getSurfaceForm
        var ngramArray:ArrayBuffer[String] = ngramMap(nowSurface)
        
        ngramArray += tmpSurface

        ngramMap.update(nowSurface,ngramArray)
        if(!ngramMap.contains(tmpSurface))
          ngramMap += (tmpSurface -> ArrayBuffer.empty[String])
        nowSurface = tmpSurface
      }
    }


    //マルコフ連鎖を使って会話文生成
    val rnd:Random = new Random
    var result_str:String = ngramMap.keys.toList(rnd.nextInt(ngramMap.size))

    var tmp_str:String = result_str
    while(!result_str.contains("。")){
      var next_wordArray:ArrayBuffer[String] = ngramMap(tmp_str)
      tmp_str = next_wordArray(rnd.nextInt(next_wordArray.size))
      result_str += tmp_str
    }

    println(result_str)

  }
}

このプログラムを10回動かして得た出力結果は次の通り

きかないもの…。
動きはじめた。
知らせてもここはシシ神さまがおかしいの礼を言いな。
だろう。
においで木をシシ神にこのことも人間のか。
味方だよ。
アシタカ、人間くさい。
味方だ。
けがれるだけだから…。
がんばって。

今回はもののけ姫のサンのセリフを対象に会話文生成を行ったが、前回使用した夏目漱石のこころに比べると圧倒的に量が少ないので微妙な結果になってしまったが形態素解析を用いたので意味不明な文は格段に減少した。

ちなみに前回使った夏目漱石のこころを同様に試すと次のようになった

姿を引いたの驚いたけれども横文字のさせる勇気に、私のです。
捌けた。
邯鄲という意味を読み出しました。
盛り潰そう。
邪魔をできるだけ切り詰めたの様子が一面に、気の外れた室で、因循らしく見える動作はよほど経っていなん。
済まなくなります。
使用さが少しもそれが帰った軽薄に書く事まで書いたと私に、お母さん一週間のようにはすぐ引き受けたので私はいよいよまた、私に向ってもらった。
会いにしてなお目立ちます。
鈍って見に吹き払った。
曇りが、こっそりこの世にもう熱心に気が付くはずが、彼の中に裹まれて植え付けられてくれさえ頼りに取った。

n = 3あたりでやるとうまくいくかもしれない。