めいふの備忘ログ

メモの代わりです。

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(プログラム作成の一般的方法論 実装/テスト の段)

2.2 実装(の準備)

今回のNGramモデルによる文生成プログラムは、テキストからNGramモデルを学習して文生成を行うためのクラス群、CrossEntoropyを計算する機能を提供するクラスとして設計されました。クラス設計が終わりましたし、今回の文生成プログラムは小難しいふるまいは特に考えないことにしますので、いよいよJavaの実装にとりかかっていきます(いきました)。

Javaプログラム実装はeclipseで行いました。emacsとは違い色々とお世話をしてくれるのです。publicメソッドをprivateメソッドに切り替えた際の問題をすぐに教えてくれますし、Unit Test用のJUnit(後述)もドキュメンテーション用のjavadoc(後述)もすんなり使えます。eclipseさんは世話焼き系IDEだそうです(世話焼き系IDE:eclipseたん - 虎塚)。

以下には、プログラムの実装とテストに先立って行ったeclipseの設定について備忘ログを残しておきます。

  • 今回のプログラムのリリースはmaven経由で行いたいので、今回のプログラムはmavenプロジェクトとして作成します。
  • その際、Javaで利用可能な形態素解析プログラムであるであるkuromoji を dependency に加えておきます。
  • Unit test JUnit4でテストを組むことにしたいので、eclipse上のプロジェクトのPropertiesからJava Bulid Path で Add Library ボタンを押して、JUnit4を追加します。以下を参考にしました。

https://teratail.com/questions/30524

  • maven でのテストもJUnit4を使いたいので、以下を参照に設定を行いました。

MavenでJUnit 4をテストするためのpom.xml - Qiita

  • アサーションライブラリ(Unitテスト時に期待結果と実際の結果の比較用のメソッドを提供してくれます)のhamcrestもmaven経由で入れてみました。junitとhamcrestについては問題含みだということが以下のような情報がネットで見つかりまして、色々と試行錯誤しました。

hamcrestとJUnitの依存関係メモ | Futurismo
http:// http://d.hatena.ne.jp/namutaka/20130708/1373246628
JUnit4.11にしたら、Hamcrestが無いってエラーが出たよ・・・ - Qiita
Use with Maven · junit-team/junit4 Wiki · GitHub

その結果、mavenのpom.xmlのテスト関係の箇所は以下のようになっています(2016/11/30でjunit 4.11で試しています)。

 <build>
   <plugins>
       <plugin>
	<!-- maven surfire plugin は mvn test 時に実行される pluginである-->
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.12.4</version>
        <executions>
          <execution>
            <id>default-test</id>
            <phase>test</phase>
            <goals>
              <goal>test</goal>
            </goals>
            <configuration>
              <junitArtifactName>junit:junit</junitArtifactName>
              <encoding>UTF-8</encoding>
              <inputEncoding>UTF-8</inputEncoding>
              <outputEncoding>UTF-8</outputEncoding>
              <argLine>-Dfile.encoding=UTF-8</argLine>
              <skipTests>false</skipTests>
            </configuration>
          </execution>
        </executions>
        <!-- mvn test 時にJunit4を実行-->
        <configuration>
          <junitArtifactName>junit:junit</junitArtifactName>
          <encoding>UTF-8</encoding>
          <inputEncoding>UTF-8</inputEncoding>
          <outputEncoding>UTF-8</outputEncoding>
          <argLine>-Dfile.encoding=UTF-8</argLine>
          <skipTests>false</skipTests>
        </configuration>
      </plugin>
  <!-- 他のbuild 用 pluginについては省略 -->
  </build>

  <dependencies>
    <dependency>
    	<groupId>org.atilika.kuromoji</groupId>
    	<artifactId>kuromoji</artifactId>
    	<version>0.7.7</version>
    </dependency>
    <!-- junit4.12では hamcrest-all と junit の組み合わせでよいようです -->
    <dependency>
    	<groupId>org.hamcrest</groupId>
    	<artifactId>hamcrest-all</artifactId>
    	<version>1.3</version>
    	<scope>test</scope>
    </dependency>
    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
    	<scope>test</scope>
    </dependency>
  </dependencies>

ともあれこれでJUnit4でUnitTestができるようになったので実装準備は完了です。実装とは、UnitTestなのです(なのでした)。

2.3 テスト(実装とデバッグ

プログラムが期待通りに動くのかはテストされなければなりません。それはプロダクトでも、研究用コードでも変わりません。品質を満たさないプロダクトは市場には出せませんし、検証されていないプログラムでは論文は書けないのです。

また、Regression test (プログラムを修正変更した場合に過去に実施したテストを再度実施して結果に影響がないかを確認するテスト)は、今回のように教科書を読みながら試行錯誤しながら実装する場合に有効です。ワタクシも、各クラスのメソッドの期待動作についてUnit Testを書き、教科書を読みながらあーでもないこーでもないとプログラムを実装したのです。おかしなコードを書くと、テストが通らなくなるので、デバッガを起動してデバッグします。ブレークポイントをセットして、変数の中身を確認しながら何がおかしいのかの調査を繰り返しました。


f:id:meihuno_huno_san:20170114083236p:plain:w400
eclipseでのデバッグ風景。虫ボタンを押し、ブレイクポイントをセットし、停止時に変数を見るのです

JUnitでのUnitテストの書き方について解説しているサイトは数多あるのです。ワタクシはそのようなサイトを眺めて勉強しながらテストを書いていきました。テストクラスの直上に@付きのアノテーションを付与してテストの動作を定義します。@BeforeClassアノテーションはテストを行う前の準備のためのアノテーションです。@Testがテストメソッドになります。以下のサイト様が参考になりました。ありがとうございます。

Junitコードの書き方 - code snippets

テストクラスを Run As で JUnit Test を選択すれば、テストが実施されます。バーが緑なら全テストは成功、赤な場合はError/テスト失敗であることを表します。


f:id:meihuno_huno_san:20170114091257p:plain:w600

以下、BackOff Smoothingの実装をデバッグをしていてはまったポイントを備忘ログとして残しておきます。

頻度がゼロになる場合のGoodTuring推定値の補正値

頻度rの補正値の r^{*}計算には、頻度r+1の単語の総頻度数が必要になります。ごく少ない数の文でUnitテストを組んでいた時に、頻度1の単語列しかカウントできず、頻度2の単語列が得られず、rの補正値が計算できないというとほほな状況になりました。確率的言語モデルの教科書には、 N_{r}が得られない場合の補正値の計算方法が紹介されていました。ワタクシも以下の回帰直線を用いて N_{r}の補正値 S(N_{r})を計算しました(mとbの推定はさぼった)。

 logS(N_{r}) = -m log r + b (m, bは定数)

文の生成確率の計算

文の生成確率を以下のように計算すると文生成確率の和が1になりません。

 P(w_1,w_2,w_3,...,w_n) = P(w_1)P(w_2|w_1)P(w_3|w_1, w_2)...P(w_n|w_{n-1}, w_{n-2})

文生成確率の計算には条件付き確率の和で行います。3gramで行う場合は文頭はbigramになるが、文頭の記号 \verb|<| S \verb|>| を2つ準備するなりして文生成確率の和が1になるようにします。

 P(w_1,w_2,w_3,...,w_n) = P(w_1|\verb|<| S \verb|>| )P(w_2|\verb|<| S \verb|>|, w_1)P(w_3|w_1, w_2)...P(w_n|w_{n-1}, w_{n-2})

今回の記事はまだ続くのです…

全てのメソッドのUnitTestがグリーンでしたら、実装は終了です。しかし、プログラム作成のお仕事はまだ残ってるのです(「StrategyパターンでNGramモデルの文生成器を作った時の思い出(プログラム作成の一般的方法論 ドキュメンテーション/リリース の段)」に続く)。