2  libae を使って電子メール検索システムを作る

libae を使って簡単な検索プログラムmsearch2.c を書いてみます. 元となるプログラムは 「GETA による電子メール検索システムの製作」 で作った msearch.c です.

まずは,インクルードファイルを定義しておきましょう.
	#include <geta/wam.h>
	#include <geta/ae.h>
libae を使うために新たなヘッダファイル(ae.h)をインクルードしています.

なお以降,libwam の基本的な関数については特に説明しませんので, 必要であれば 「WAM チュートリアル」 または man ページを参照してください.

2.1 syminfo 型リスト

libae の関数は事実上 wsh だけと言ってもよいでしょう. wsh は syminfo 型のリストを入力として syminfo 型のリストを返します.

        syminfo *q;
        int qlen;
        syminfo *r;
        int rlen;
        WAM *w;

        r = wsh(q, qlen, w, WAM_COL, WT_TF, &rlen, NULL, NULL, NULL);
*qが入力リスト,*rが出力リストです.wsh が何をやるかは置いておき, とりあえずsyminfo型を見てみましょう(ae.hから抜粋します).
        struct syminfo {
                symbol_t id;
                int TF, TF_d;
                int DF, DF_d;
                double weight;
                int attr;
        }
id が実際の単語または文書の番号です. wsh は単語リストを入力とすると, それに関連する文書リストを返します. 逆に,文書リストを入力とすると, それに関連する単語リストを返します. WAM の言葉で言うと, 列(WAM_COL)を入力とすると行(WAM_ROW)を返し, 行(WAM_ROW)を入力とすると列(WAM_COL)を返すということです. 混乱を避けるため, ここでは単語リスト(WAM_COL)を入力として 文書リスト(WAM_ROW)を返す場合を扱います.

検索を行うためには,syminfo型リストの各要素に あらかじめいくつかの値を設定しておく必要があります. まずは,検索単語の番号を id に指定します. 次に,その単語が検索要求中にあらわれる頻度を TF_d で指定します. また,その単語が検索対象のコーパス中にあらわれる頻度を TF で指定します.attr にはとりあえず WSH_OR を指定してください.attr の詳しい使い方については 「AND 検索を実装する」 で説明します. その他のDF, DF_d, weight はとりあえず未定義のままでかまいません.

2.2 関数 wstem

それでは,実際に検索要求の文章から *q を作ってみましょう. libae にはこのための関数 wstem が用意されています.

        char *qtxt;
        WAM *w;
        char *jma;
        syminfo *q;
        int qlen

        q = wstem(qtxt, w, WAM_COL, jma, &qlen);
wstem は, 第一引数で指定した入力文字列(ストリング) *qtxt を形態素解析器 jma で解析し, 自動的に頻度集計をして *q を作ります. 実際には,*q の各要素の id, TF, TF_d を計算して埋め,attr は WSH_OR を設定します.qlen には *q の長さが返ってきます.w は検索対象のコーパスに相当する WAM です. 今は単語キーにして検索するため向きは WAM_COL です.jma に形態素解析プログラムの場所を指定します. これについては 「GETA による電子メール検索システムの製作」 に詳しく書いてありますので参照してください. なお,jma は wstem を呼ぶごとに毎回起動しますので注意してください. もし,起動に時間がかかるような jma なら,GEMUを使って サーバ化したほうがよいかもしれません.

2.3 関数 wsh

以上で検索要求 *q の準備がととのいました.wsh を呼んで文書を検索してみましょう.

        syminfo *q;
        int qlen;
        syminfo *r;
        int rlen;
        WAM *w;

        r = wsh(q, qlen, w, WAM_COL, WT_TF, &rlen, NULL, NULL, NULL);
ここで,q は検索要求(syminfo型)へのポインタ,qlen は *q の長さです.w は検索対象の WAM です. 単語リストから検索をおこないますので,WAM の向きは WAM_COL となります.rlen の初期値には検索したい文書数を指定します. 指定した文書数より少ない文書しか検索できなかった場合は, 実際に検索できた文書数に置きかわります. 最後の NULL はとりあえずこう指定しておいてください. 重要なのは WT_TF で, ここで類似度の計算式を指定しています. libae では既にいろいろな式があらかじめ定義されています. ここではその中の WT_TF を指定しています. 類似度の計算式はユーザが自由に定義して指定することができますが, その方法は後ほど学びます.

wsh を呼ぶと結果が *r に返ってきます.*r も syminfo 型のリストであることは既に説明しました. 検索要求 *q が単語リストですから,*r は文書リストになります. もう一度 syminfo 型の定義を書いておきます.

        struct syminfo {
                symbol_t id;
                int TF, TF_d;
                int DF, DF_d;
                double weight;
                int attr;
        }
*r の各要素では id は文書番号になります.weight に WT_TF を使って計算した類似度が代入されています.DF_d には検索要求 *q 中の何個の単語がヒットしたかが代入されています.DF はこの場合あまり意味がありませんが, この文書に含まれている単語の数(注:頻度ではありません)が代入されます.TF, TF_d にも値が代入されていますが, ここではほとんど意味をなさない数ですので無視します. 詳しくはcustom.txtを 見てください. これらは,文書リストをキーに単語を検索するときに意味を持ってくるでしょう.

2.4 syminfo 型リストのソート

*r に返ってきた syminfo 型リストを weight でソートすれば検索は完了です. 自分で qsort を呼んでもたいした手間ではありませんが, ここでは libae に用意されている関数を使いました.

        sort_w_syminfo_list(r, rlen);
        
        for (i=0; i<rlen; i++) {
                printf("%lf\t%s\n", r[i].weight, wam_id2name(w, WAM_ROW, r[i].id));
        }
以上でプログラムができました. wsh は検索要求中の各単語について, それを含む文書を効率良く集計しながら重みを計算しています. ですから, 「GETA による電子メール検索システムの製作」 で作った Msearch のような集計スクリプトは必要ありません.

2.5 コンパイルと実行

プログラムをコンパイルして実行してみましょう.

        % cc msearch2.c -o msearch2 -I$GETAROOT/include -L$GETAROOT/lib -lwam -lgetamu -lsrvmu -lae -lm

        % msearch2
        昨日、猿が現れました
        ^D
        0.043264  inbox/2
        0.040112  inbox/1
        0.034713  inbox/4        
        ...


3: 類似度定義ファイルの基礎」に進む
1: はじめに」に戻る