Perlインタフェース活用術

― 連想検索GUIの実現方法 ―

1. はじめに

汎用連想計算エンジンGETAに含まれるPerlインタフェース (以後,WAMモジュールと呼びます) を利用して,以下の機能を有する連想検索GUIを実現する方法について解説します。
フレーズ検索
ユーザが入力した検索要求を検索キーとして関連文書を検索する機能。
文書要約
文書から特徴語 (トピックワードと呼ぶ) を抽出し,抽出した特徴語を検索結果の要約とする機能。
文書連想検索
ユーザが選択した文書を検索キーとして関連文書を検索する機能。
トピックワード検索
ユーザが選択したトピックワードを検索キーとして関連文書を検索する機能。
以下に進む前に,GETAのインストールとコーパスのセットアップを完了させておいて下さい。

2. 準備

2.1 WAMモジュールの読み込み

WAMモジュールを使うには,
  use wam ('WAM_ROW', 'WAM_COL');
と記述します。 WAM_ROW,WAM_COLはよく使うのでパッケージ名 (wam::) なしで使えるようになっています。

WAMモジュールを make install していない場合は,WAMモジュールの場所を明示的に指定する必要があります。

例えば,

  BEGIN{
      my $LIB = "/usr/local/lib/geta/ext/wam";
      unshift(@INC, "$LIB", "$LIB/blib/lib", "$LIB/blib/arch");
  };
  use wam ('WAM_ROW', 'WAM_COL');
として下さい。 この例では /usr/local/lib/geta/ext/wam がWAMモジュールの場所です。 各自の環境に合わせて適宜修正して下さい。

2.2 WAMの初期化とオープン

連想検索の処理に入る前に, をします。
  &wam::init($GETAROOT) == 0 or die "ERROR: Cannot initialize WAM\n";
  ($w = &wam::open($handle)) != 0 or die "ERROR: Cannot open $handle\n";
$GETAROOTはGETAをインストールしたディレクトリ,$handleは検索対象コーパスのハンドル名です。 $w は検索対象コーパスのWAMの識別子で,これを用いて WAM に対する様々な操作を行ないます。

2.3 処理の概要

フレーズ検索は という処理で行なわれます。

文書要約は,

という処理で行なわれます。

文書連想検索は,

という処理で行なわれます。

トピックワード検索は,

という処理で行なわれます。

これらの処理をするためのWAMモジュールの関数は wam::wstem() と wam::wsh() です。

wam::wstem() は与えられた文字列を形態素解析器で単語分割し,その結果に様々な属性 (IDやnameなど) を付与して返します。

wam::wsh() は連想検索を行なう関数です。 WAM を引く方向を WAM_COL とすると 単語から文書を検索することができ,WAM_ROW とすると,文書から単語を検索することができます。

wam::wstem() と wam::wsh() を利用すると,上記の機能は,

フレーズ検索
wam::wstem() + wam::wsh()[WAM_COLを指定]
文書要約
wam::wsh()[WAM_ROWを指定]
文書連想検索
wam::wsh()[WAM_ROWを指定] + wam::wsh()[WAM_COLを指定]
トピックワード検索
wam::wsh()[WAM_COLを指定]
として実現されます。

3. フレーズ検索

3.1 入力文を形態素解析器で単語分割

フレーズ検索ではユーザが入力した検索要求を検索キーとして関連文書を検索します。

まず,入力文を単語群に変換します。 この変換には wam::wstem()という関数を使用します。

   &wam::wstem($input, $w, WAM_COL, $ma)
$inputが自然文の検索質問です。 $w は 検索対象コーパスの WAM の識別子で wam::open() の戻り値として得られるものです。 単語は WAM の列側に登録されているので WAM_COL を指定します。 $ma には形態素解析器を指定します。 例えば,配布パッケージに含まれている jpn.sh のパス名を指定します。

wam::wstem() は,のちの連想検索に必要な情報が付加された単語群を返します。 戻り値は配列へのリファレンスで,その配列の各要素が単語になっています。 単語は属性名と値のペアからなるハッシュです。

例えば,

  $rq = &wam::wstem("春の七草", $w, &WAM_COL, "jpn.sh");
の戻り値 $rq は,
  $rq = [
          {weight => 0, name => 春, TF => 3844, TF_d => 1, id => 836},
	  {weight => 0, name => 七草, TF => 19, TF_d => 1, id => 39905}
	]
のようになります。

3.2 単語を検索キーとして関連文書を検索

wam::wsh() に上で得られた単語群 $rq を渡すと,それに関連する文書群を得ることができます。
  &wam::wsh($rq, $w1, &WAM_COL, $wt, $max, $limit, $w2)
単語から文書を連想検索する場合は WAM_COL を指定します。 WAM の識別子が第2引数と第7引数に現れていますが,単語から文書を連想検索する場合は,どちらにも検索対象コーパスの WAM の識別子を指定します。 $wt は連想計算に使用する類似性尺度です。

$max は,単語群 $rq と関連する文書をいくつ得るかを指定するものです。 0を指定するとすべての文書を得ることができます。

$limit に正の整数を指定すると,TF 値がその値以上の単語を検索に使用しないようにできます。 0 を指定すると,$rq に含まれるすべての単語を検索に使用します。

wam::wsh() の戻り値は配列へのリファレンスで,その配列の各要素が文書を表しています。 各文書は単語の場合と同様に,属性名と値のペアからなるハッシュです。

例えば,

  $rd = &wam::wsh($rq, $w, WAM_COL, &wam::WT_SMART, 3, 0, $w);
の戻り値は,
  $rd = [
          {weight => 0.17406404, name => mai98a.txt 15139155 2952 126,
	   DF => 87 TF => 114 DF_d => 1 TF_d => 4 id => 3045},
	  {weight => 0.15126217, name => mai98a.txt 111043762 4287 189,
	   DF => 114, TF => 126, DF_d => 2, TF_d => 4, id => 23394},
	  {weight => 0.12573748, name => mai98a.txt 107396389 8444 338,
	   DF => 210, TF => 310, DF_d => 2, TF_d => 10, id => 22620}
	]
のようになります。 wam::getaux()を使用すると記事のタイトルを得ることもできます。

これでフレーズ検索が実現できました。

3.3 サンプルコード

フレーズ検索を実現するサンプルコード (コマンドライン版) は以下のようになります。
  #!/usr/local/bin/perl
  BEGIN{
      my $LIB = "/usr/local/lib/getasrc/geta/ext/wam";
      unshift(@INC, "$LIB", "$LIB/blib/lib", "$LIB/blib/arch");
  };

  use wam ('WAM_ROW', 'WAM_COL');

  $GETAROOT = "/usr/local/lib/geta";
  &wam::init($GETAROOT) == 0 or die "ERROR: cannot initialize\n";

  #$handle = "test1";
  $handle = "mai98";
  ($w = &wam::open($handle)) != 0 or die "ERROR: cannot open\n";

  $ma = "$GETAROOT/ext/wam/samples/jpn.sh";
  print "Query: "; chomp($input = );

  $rq = &wam::wstem($input, $w, WAM_COL, $ma);

  $rd = &wam::wsh($rq, $w, WAM_COL, &wam::WT_SMART, 10, 0, $w);

  #$titles = "/usr/local/lib/getasrc/geta/testsuit/data/test1/titles";
  $titles = "/usr/local/lib/geta/data/jpn/mai/mai98/titles";
  print "\n[Result]\n";
  for (my $i = 0; $i < @$rd; $i++) {
      printf "%2d: %s\n", $i+1, &wam::getaux($titles, $rd->[$i]{"id"});
  }

4. 文書要約

4.1 文書からその文書に含まれる特徴語を抽出

文書要約は,文書からその文書に含まれる特徴語を抽出することで行ないます。 wam::wsh() に WAM_ROW を指定することで,与えられた文書に対する特徴語を得ることができます。
  &wam::wsh($rd, $w1, WAM_ROW, $wt, $max, $limit, $w2)
ここでも $w1 と $w2 は同じ識別子を指定しておきます。その他の変数についても 3.2 節の場合と同じです。

例えば,

  $rc = &wam::wsh($rd, $w, WAM_ROW, &wam::WT_SMART, 3, 0, $w);
の戻り値は,
  $rc = [
           {weight => 0.44119538, name => 七草, DF => 16, TF => 19,
	    DF_d => 3, TF_d => ,6 id => 39905},
	   {weight => 0.41969154, name => 野草, DF => 34, TF => 70,
	    DF_d => 3, TF_d => 12, id => 20027},
	   {weight => 0.27782105, name => タンポポ, DF => 35, TF => 51,
	    DF_d => 1, TF_d => 7, id => 23830}
	]
のようになります。

これで文書要約が実現できました。

4.2 サンプルコード

3.3節のサンプルコードに
  $rc = &wam::wsh($rd, $w, WAM_ROW, &wam::WT_SMART, 20, 0, $w);
  print "\n[Summary]\n";
  for (my $i = 0; $i < @$rc; $i++) {
      printf "%s ", $rc->[$i]{"name"};
  }
を追加すると,検索結果として得られた文書から特徴語を20個抽出して要約を生成することができます。

5. 文書連想検索

5.1 文書からその文書に含まれる特徴語を抽出

この処理は 4.1節で説明した方法と同じ方法で行ないます。 どれくらいの数の特徴語を抽出するかで文書連想検索の精度が左右されます。 文書サイズにも依存しますが,新聞一年分の場合,経験的には50語ぐらいが適当な数です。

5.2 特徴語を検索キーとして関連文書を検索

この処理は 3.2節で説明した方法と同じ方法で行ないます。

5.3 サンプルコード

3.3節 + 4.2節のサンプルコードに
  while (1) {
      print "\n\nArticle: "; chomp($art_num = );

      undef @rd_sel;
      foreach (split(/ /, $art_num)) {
	  push(@rd_sel, $rd->[$_-1]);
      }

      $rc = &wam::wsh(\@rd_sel, $w, WAM_ROW, &wam::WT_SMART, 50, 0, $w);

      $rd = &wam::wsh($rc, $w, WAM_COL, &wam::WT_SMART, 10, 0, $w);
      print "\n[Result]\n";
      for (my $i = 0; $i < @$rd; $i++) {
	  printf "%2d: %s\n", $i+1, &wam::getaux($titles, $rd->[$i]{"id"});
      }

      $rc = &wam::wsh($rd, $w, WAM_ROW, &wam::WT_SMART, 20, 0, $w);
      print "\n[Summary]\n";
      for (my $i = 0; $i <@$rc; $i++) {
	  printf "%s ", $rc->[$i]{"name"};
      }
  }
を追加すると検索結果の文書から興味のある文書番号を指定することで文書連想検索を行なうことができます。 これは関連性フィードバックと呼ばれる手法の一例となっています。 このプログラムを終了するときは Ctrl-C を入力して下さい。

6. トピックワード検索

6.1 特徴語を検索キーとして関連文書を検索

この処理は 3.2節で説明した方法と同じ方法で行ないます。

6.2 サンプルコード

4.2節と同様に簡単に実装できますのでこれは練習問題として残しておます。

7. クロスデータベース連想検索

7.1 処理の流れ

5節の文書連想検索では同一文書内で連想検索を行ないました。 つまり,コーパスAに含まれる文書aを検索キーとしてコーパスAの中から関連文書を検索していたわけです。 しかし,あるコーパス (データベース) の中から興味のある文書を見付けたとき,その文書と関連のある文書を他のコーパスの中から検索したい場合があります。 これをクロスデータベース連想検索を呼びます。

クロスデータベース連想検索を実現するためには,以下のようにします。

  1. コーパスA中の文書aに含まれる特徴語を抽出
  2. 特徴語を検索キーとしてコーパスB中の関連文書を検索
1 は 5.1節での処理と同じです。

2 を行なう前にコーパスAから抽出された特徴語を,コーパスB上にマッピングします。 つまり,ある単語xのコーパスAにおけるIDを,コーパスB上でのものに変換すれば良いわけです。

特徴語のIDをコーパスB上のものに変換すれば,2 の処理は5.2節のものと同じになります。

7.2 サンプルコード

参考までに,コーパスAから抽出された特徴語を,コーパスB上にマッピングするコードをあげておきます。
  for (my $i = 0; $i < @$rq; $i++) {
    $nml->[$i] =  $rq->[$i]{'name'};
  }
  my $idl = &wam::namel2idl($w, &wam::WAM_COL, $nml);
  for (my $i = 0; $i < @$rq; $i++) {
    if ($idl->[$i] != 0) {
      $rq2->[$j]{'name'} = $nml->[$i];
      $rq2->[$j]{'id'} = $idl->[$i];
      $rq2->[$j]{'weight'} = $rq->[$i]{'weight'};
      $rq2->[$j]{'TF'} = $rq->[$i]{'TF'};
      $rq2->[$j]{'DF'} = $rq->[$i]{'DF'};
      $rq2->[$j]{'TF_d'} = $rq->[$i]{'TF_d'};
      $rq2->[$j]{'DF_d'} = $rq->[$i]{'DF_d'};
      $j++;
    }
  }
この例では $rq を $rq2 にマッピングしています。 $rq は検索元となるコーパスから得られた特徴語です。 $rq2 は 検索先となるコーパスでの情報が付加された特徴語です。 $w はこれから検索しようとしているコーパスの識別子であることに注意して下さい。

8. CGIプログラムの作成法

以上の説明でWAMモジュールを使った連想検索の実現方法がお分かり頂けたと思います。 GETA配布パッケージに含まれている評価用連想検索GUIは,これをもとにWebサーバ上で動作するCGIプログラムとして作成したものです。

CGIプログラムの作成方法は本文書の範囲外なので割愛しますが,参考文献として

Steven Holzner著 IDEA・C訳: 「Perl&CGI言語リファレンス」 インプレス
をあげておきます。 この本には評価用連想検索GUIの作成に用いた CGI.pm の解説があります。 CGI.pm は Perl で CGI プログラムを簡便に作成するためのパッケージです。

9. おわりに

汎用連想計算エンジンGETAとWAMモジュールを使用することにより簡単に連想検索を実現することができます。 評価用連想検索GUIはCGIプログラムとして実装されていますが,もちろん,他の実装形態のGUIも簡単につくることができます。 例えば,Perl/Tkを使った検索インタフェースなどは簡単に作成することができるでしょう。 GETA + WAM モジュールを使っていろいろな検索インタフェースの作成にチャレンジしてみて下さい。