めいふの備忘ログ

やったことを忘れないように書きこのしておくのです。

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(動作テストと反省会)

2.5.3 mavenプロジェクトの取り込みテスト

NGramプログラムを作成環境とは別の環境で使えるまでをテストした経緯について書いておきます。といっても、別のPCのeclipseで作成したmavenプロジェクトのpom.xmlを以下のように設定して、そのプロジェクトでNGramプログラムが動作することを確認しただけです。

 <!-- github上の NGramプログラムの maven リポジトリをセット -->  
  <repositories>
    <repository>
      <id>sentencegenerator</id>
      <url>https://raw.github.com/meihuno/sentencegenerator/mvn-repo2/</url>
      <snapshots>
   	<enabled>true</enabled>
   	<updatePolicy>always</updatePolicy>
      </snapshots>
    </repository>
  </repositories>
  
  <dependencies>
    
    <dependency>
      <groupId>nlp.sample</groupId>
      <artifactId>sentencegenerator</artifactId>
      <version>0.0.3</version>
    </dependency>

  <!-- 色々と省略 -->
  </dependencies>

サンプルコードはこちらになります。太宰治先生の「走れメロス」でNGramモデルを学習しています。

package sample.nlp.mytest.hello;

import nlp.sample.sentencegenerator.SentenceGenerator;
import nlp.sample.sentencegenerator.NGram;
import nlp.sample.sentencegenerator.CrossEntropy;

public class SampleTest {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  String dirname = "/home/sugihara/void/data/aozora_bunko/dazai_osamu";
    SentenceGenerator sg = new SentenceGenerator(NGram.retNGramModel(dirname, smoothingType));
    
  // 文生成、文の長さは100形態素まで。確率値が最大の文頭の形態素からスタートする。
    // P(c|a,b)の確率値で次の形態素を選択する際に最大の確率値をとるcが選択される。
    System.out.println(sg.retRandomSentence(100, true, true));

  // 文生成、文の長さは100形態素まで。確率値が最大の文頭の形態素からスタートする。
    // P(c|a,b)の確率値で次の形態素を選択する際にcの選択はランダム。
    System.out.println(sg.retRandomSentence(100, true, false));

  // 文生成、文の長さは100形態素まで。文頭の形態素はランダムサンプル。
    // P(c|a,b)の確率値で次の形態素を選択する際にcの選択はランダム。
    System.out.println(sg.retRandomSentence(100, false, true));

    // Backoff smoothing の Cross entropy
    String smoothingType = "backoff";
    CrossEntropy.showCrossEntropy(dirname, smoothingType);
    
    // Laplase smoothing の Cross entorpy
    String smoothingType2 = "laplase";
    CrossEntropy.showCrossEntropy(dirname, smoothingType2);
  }
}

結果は以下です。ちゃんと動作しています。生成された文は、まあ、それなりですね。文と文のつなぎは今回のプログラムのスコープ範囲外です。また、BackoffスムージングのCrossEntropyのが、LaplaseスムージングのCrossEntropyより小さいのでほっと一安心です。

<S>メロスは激怒した時、突然、目の前に一隊の山賊が躍り出た。</S>
<S>メロスも覚悟したのか、ついに憐愍を垂れてくれ、と思ったか、二度、三日間だけ許して下さい。</S>
<S>太陽も既に真昼時です。</S>
CrossEntropy of Backoff is H(L,M) (-7.383126980350443) -H(L) (-1.4018975110973793) = 5.981229469253064
CrossEntropy of Laplase is H(L,M) (-11.642169315239284) -H(L) (-1.4018975110973793) = 10.240271804141905

3 反省会

ひととーりNGramのプログラムはできたのですが、色々ダメな部分もあったのでその備忘ログを残しておきます。以下の点が特にダメだったと思います。

  • 3gramまでを想定した実装になっており4gram以上のことは考えていなかった

演習問題のためのプログラムだと思って手を抜いてしまいましたが、後々のことを考えて拡張性を担保しておけばよかったです。

  • NGramの形態素列をデリミタで区切った文字列で表現してしまった

NGramの形態素列を"3@メロス:は:激怒"のように、単語列の長さと単語列の文字列をデリミタ(@と:で)区切って表現し、HashMapのキーなどに用いていたのですが、このキーの処理をデリミタ文字列を用いたsplitで行っていたので、テキスト中にデリミタの文字(@や:)が含まれていると正しく動作しないという問題がありました。かなりひどい悪手だと思われたので反省しているのです…。プロダクトでこんな設計ミスしたらかなり大事でございます。NGram形態素列はちゃんとクラスをきってオブジェクトで扱い、IDなどで管理したほうがよかったよ…。

まあ、NGramに基づく言語モデルのツールを使いたい場合は、素直にkylmを使うのが安心だと思いますし、勉強になります。ありがとう、Graham Neubig先生、Xuchen Yaoさん…。

www.phontron.com

さて、長々とNGramのプログラムを作って遊んでまいりましたが、ぼちぼち終わります(NGramには飽きてきたよ)。でも自然言語処理、面白いですね。今後も勉強を続けていきたいです。固有名詞抽出やゼロ代名詞解析ついてプログラム作って遊んでみたいと思います。また、機械学習手法、特に、Deep Learningもかじってみたいです。

そしていずれは、言語処理を利用して、楽しく小説や随筆が書けるツールを作ってみたいです。例えば、「文の芥川龍之介度判定器」、「テキストで記述されている内容が架空のものなのかリアルなのか判定器」、「テキストの人称視点ズレ検出器」などですね。役に立つのかわかりませんが、楽しくなってきました。うむ、明日もがんがろう。