LoginSignup
3

More than 5 years have passed since last update.

形態素解析による簡単な学習 in 伺か

Last updated at Posted at 2016-12-13

はじめに

伺かを1年目に知った新参者ですが、最近フォーラムで色々伺って開発にも挑戦しています。(答えてくれた方々、どうもありがとうございます!)

つい最近ponapalt氏によりMeCabをベースとしたSAORIであるkisaragiが公開され、伺かで形態素解析が手軽に使えるようになりました。折角なので、実際の使い方を簡単に解説してみます。
以下の内容は栞としてYAYAを使って書かれています。(里々で使用したい場合はこのyayaをSAORIとして読み込んでください)

基本的な使い方

masterフォルダに

words/morph.txt
SAORI/kisaragi.dll, libchasen.dll, dic
があるとします。
wordsフォルダのmorph.txtは形態素解析の結果を記録するファイルです。
SAORIフォルダにある3つのファイルはkisaragi.dllを動かすためのdllと辞書ファイルです(詳しくはkisaragiのreadmeを読んでください)

以下のOnMorph関数は引数(reference[0])を形態素解析し、解析結果をmorph.txtに格納する関数です。
\![open,inputbox,OnMorph,0]などで呼び出すことを想定しています。
(ちなみにinputboxで関数を呼び出す場合、関数名は必ずOnで始まる必要があり、その入力データはreference[0]に格納されます)

//SAORIを使う場合はFUNCTIONEX。返り値はsaoriのResultで、FUNCTIONEX実行後にValueの値がvalueex[]に格納される
OnMorph{
  _Rank = FUNCTIONEX("SAORI\kisaragi.dll",'parse',reference[0])
  //kisaragi.dllのResult、つまりFUNCTIONEXの返り値は形態素解析結果の行数(最後はEOS)
  //Value、つまりvalueexは解析結果

  FCHARSET("UTF-8")//morph.txtファイルの文字コードはUnicodeにする
  _FLAG = FOPEN("words\morph.txt", "a")
  //FOPENの第二引数でappend(追加書き込み)を指定
  //_FLAGにわざわざ格納しているのは、そのままだとFOPENの返り値(1/0)を返り値として扱ってしまうため。本当は正常に読み込めたかをこれで判定しておくといい
  _i = 0;
  for _i = 0; _i <_Rank-1; _i++ {//_Rank-1はEOSを削除するため。任意。
    FWRITE("words\morph.txt",valueex[_i])
  //ここで書き込み
  }
  FCLOSE("words\morph.txt")
}

これで形態素解析の結果を記録できます。
(このコードはmasterフォルダにYAYAの辞書ファイルとして置かれていることを想定しています。ほかの場所に置く場合は適宜コード中のディレクトリのパスを変更する必要があります)
解析結果の例:
入力:アンパン食べたいなー
結果:
アンパン アンパン アンパン 名詞-一般
食べ タベ 食べる 動詞-自立 一段 連用形
たい タイ たい 助動詞 特殊・タイ 基本形
なー ナー なー 助詞-終助詞

使える名詞を取り出す

簡単な応用例として、"トークの話題として使える名詞"をmorph.txtから取り出し、配列(learnednoun)に格納する関数(MorphSplit)を書いてみます。

MorphSplit{
  _spword = IARRAY//空の配列を作成
  learnednoun = IARRAY

  FCHARSET("UTF-8")
  _flag = FOPEN("words\morph.txt", "r")
  while 1 {
    _word = FREAD("words\morph.txt")
    if (_word == -1){
      FCLOSE("words\morph.txt")
      break
    }
    if (RE_SEARCH(_word,"名詞-(一般|固有名詞|サ変接続|形容詞語幹)")){
      _spword = SPLIT(_word, CHR(9))//tabで区切られた配列を作成
      learnednoun = (learnednoun,_spword[0])//最初の要素、つまり品詞を格納
    }
  }
}

(他の関数でlearnednounを使うことを見越してグローバルな配列にしていますが、データ量が多くなった場合は外部ファイルに保存して、使うときにローカルで呼び出したほうがいいかもしれない)

構造は簡単で、kisaragiによる形態素解析結果はtab文字をデリメタとして区切られているので、それでsplitして必要な名詞だけを拾ってきているだけです(環境によってはバルーンで表示したときにtab文字が見えないことがある。でも実際はちゃんと区切られている)

また必要な名詞、つまり話題として自然な名詞であるかの判定

RE_SEARCH(_word,"名詞-(一般|固有名詞|サ変接続|形容詞語幹)")

は、Chasenマニュアルの"ipadicの品詞体系について"を参考にし、正規表現で判定しています。

トークに使ってみる

たとえば(あやりりすEXの記法を使っています)

<<'
    テスト00:そういえば前に君、%(learnednoun[RAND(ARRAYSIZE(learnednoun))])がどうとかって言ってたよね。
    テスト08:結局どうなったの?
'>>

これでユーザが話した内容をトークに使うことができるようになります(ランダムに選んでいるだけだけど)
例:
ユーザが入力した文章が前述の「アンパン食べたいなー」だと上の判定でヒットするのは「アンパン」だけなので、
「そういえば前に君、アンパンがどうとかって言ってたよね。結局どうなったの?」となります。

このようにユーザが話した内容をゴーストが記憶できるというだけでも、より"人間らしい"表現が可能になります。たとえば「過去に○○に関する話題をユーザーが入力したときに発生するトーク」などが実装できます。
e.g.
更に学習した形態素を記録するときに、それを記録した日時も同時に登録しておけば昨日学習した形態素に「疲れ」があるかどうかで「昨日、疲れていたみたいだけど大丈夫?」みたいな表現が可能。

そのほか

伺かのコンセプトには反するけど、マルコフ連鎖を使った自動トークの生成ならこれで実装できるはず(実装中。でもprefix,suffixの管理に二次元配列が欲しい......C++とかで書いてSAORIで実装のほうがいいかもしれない)ただ、"感情的な"ゴーストにはならない。
学習させる際に感情ごとでもとの文章データを変えればいいかも?
e.g.一人称視点で「悲しいときに書いた文章」「うれしいときに書いた文章」などをそれぞれ解析し、別のファイルに保存しておく

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3