言語プロセッサ


目標

・あらゆる言語に対応できる仕組みを用意すること
PDICに限定せず、他のアプリケーション等で利用できるような汎用的なものになること
・カスタマイズ性を高くし、一定の知識・技術があれば誰でも作成可能であること
・原則オープンソースとし、継続的な発展、および多くの人が成果を共有できること

壮大な計画を立てると発散してしまうため、当面はPDICに必要な機能を中心に考える。

■機能

・辞書に登録されている単語を任意の単語順で並び替えるための処理 [A]
・検索対象の文字列から検索に必要な単語を切り出す処理 [B]
・検索対象となる先頭文字の検出[B] (PopupSearch用)
・変化形を原形に戻すなど、変化形への対応 [B]
・不規則変化への対応 [B]
・辞書から最も近い単語を選択する処理 [B]
・ローカルにある辞書だけでなく、Webなどの辞書からも検索する処理 [C]
・辞書へ登録する機能 [C]
・上記機能の設定機能 [B/C]

■構造

大きく分けて三層構造。
「辞書が参照する」部分[A]と「辞書を使う」部分[B]に分けられる。
[A]は辞書にある言語プロセッサIDによって決められ、言語プロセッサIDをそのままでは変更ができない。(辞書変換が必要)
[B]が本来の言語プロセッサ。辞書の言語プロセッサIDを元に適切な言語プロセッサをPDICが選択する。見つからなければPDICデフォルトの言語プロセッサを使用。
[C] [B]と仕様としては同じだが、[B]と異なり、機能の追加が目的。
こんなイメージ (ブロック図のつもり)
Search Engine (popup search, incremental search, etc)
LangProc B Lang Proc C
PDIC Dictionary Contoller LangProc A Web, Net Dictionary, Other Dictionary, etc
*.dic, *.ctt, IrregDic files WebSite, Other Dictionary files, etc
LangProc A/BはPDICの辞書検索には無くてはならないものだが、LangProc CはPDIC辞書とは異なるデータをひっぱてくる役目が主。

■機能の詳細

見出し語の構造

見出し語部分については、(昔のPDICと違い)3つのレイヤ構造と考えることが出来ます。

1. 見出し語部分のキー構造

辞書に登録されている見出し語の構造:

検索キー+見出し語

ここに記されている「見出し語」はユーザーが入力した見出し語そのものです。「検索キー」は見出し語を元に言語プロセッサが生成した文字列です。
例えば、PDICの標準の言語プロセッサでは、見出し語のアルファベットを小文字に変換したり、ハイフンをスペースに変換する処理を行っているため、大文字・小文字を同一視したインクリメンタルサーチが可能になっています。
この検索キーの生成部分を変更するだけで、任意の語順で表示することが可能になります。たとえば、ペーパーの辞書とほぼ同一の語順で表示することが可能になります。
ただし、任意の語順と言っても、この検索キーだけでは限界があります。なぜなら、表示される語順は検索キーだけではなく、検索キー+見出し語の組み合わせで並び替えを行うためです。(そうしないと同一の検索キーを持つ見出し語をどのような順番で表示すべきか不定となってしまうため)
例えば、
A-B-C
ABC
a-b-c
と3つの単語があり、上記の語順で表示したいとします。そのために検索キーを
a b c
abc
a b c
とそれぞれ作成したとします。しかし、並び順は「文字コード順」でソートされる(並び替えられる)ため、
a b c + A-B-C
a b c + a-b-c
abc + ABC
となり、希望したものと異なってしまいます。この辺の話はややこしいのでもう少しわかりやすい具体例を用意するつもりですが、ご自分でペーパーの辞書と同じ並びをするための検索キー作成方法を考えてみてください。たぶんできないと思います。現状のPDICでは処理を単純にするため、検索キーの生成は文字単位でしかできないという制約があるためです。

※PDIC/Unicodeに付属のサンプル辞書はこの仕組みをうまく使い、ユーザーに提示したい順番で表示するようになっています。「PDICのご利用について」でという見出し語が、「Arabic」という見出し語より前に来ています。(サンプル辞書、PDICを初めて使用される人しか見ることはありませんが。。)

※先ほど述べたように、ご自分で言語プロセッサをすべてスクラッチから(最初から)作成すれば、この検索キーの生成も自由に出来るため、任意の並びで表示することが可能になります!例えば、単語登録順に表示する、なんていうかなり変なこともできます。(ただし、実用性無し)

※検索キーを省略することも可能です。これにより、並び順は文字コード順になり、旧バージョンのPDIC(PDIC/W32)と同様になります。見出し語部のサイズも小さくなるため、辞書を開く処理も高速になり、全文検索も若干高速になります。また、ポップアップ検索も速くなりますが、元々速いためよほど遅いPCで無い限り体感できる差はないはずです。(すでに試作レベルで対応済み)

※検索キーを見出し語に含まれる単語に設定すると、見出し語に含まれる単語を高速に検索できるようになります。

2. 見出し語部分の文字コード変換

これはやや特殊な用途になります。PDIC標準の言語プロセッサでは変換を行っていません。何が出来るかというと、1番で生成された「検索キー+見出し語」の文字列をさらに変換する処理です。PDIC標準の言語プロセッサでは変換を行っていない、ということはUnicodeの文字コード順になっているということです。もし、現状のUnicode順が嫌だ!という場合は先ほどの検索キーの生成処理を変更するのではなく、この文字コード変換を行うことで任意の語順で並び替えることが出来ます。
この文字コード変換と先ほどの検索キーの大きな違いは、検索キーの生成は一方向であるのに対して、文字コード変換は可逆変換を行う必要があります。つまり、検索キーは辞書へ登録する際に生成すればあとは特に処理が必要ありませんが、文字コード変換は辞書へ登録するときの変換処理と、辞書から読み出すときの逆変換が必要になり、完全に1対1に対応している必要があります。
また副作用として、変換方法によっては後述するBOCU1の圧縮効率に影響を与えてしまいます。(逆に、よく使用する文字コードをBOCU1の圧縮効率の高いところへマッピングし、辞書のサイズを小さくする、という高度なこともできますが・・・)

3. BOCU1による文字列情報の圧縮処理

Unicodeはあらゆる言語の文字を表すことが出来る代わりに、それを表現するための情報量が増えてしまいます。例えば、UTF-16の場合は常に各2バイト必要であり、英文だけの辞書の場合は不利になります。また、UTF-8の場合では日本語が3バイトになり、ShiftJISの2バイトに比べて1.5倍のバイト数が必要となってしまいます。そこで考え出されたのがBOCU1という圧縮処理です。(IBM社の特許)
この圧縮処理により、英字は1バイト、日本語もほぼ2バイト、と通常のUnicodeに比べて少ないバイト数でUnicodeを表現することが出来ます。
この部分は言語プロセッサではなく、辞書内部で行っている処理で、カスタマイズはできませんので詳細は割愛させていただきます。ただ、前述の2番目の文字コード変換を大規模に行う場合はこのBOCU1の圧縮処理を理解しておく必要がある、とだけ述べておきます。(ただし、ASCIIコード該当部分のみを変更する程度であれば気にする必要はありません)
なお、PDICの辞書では見出し語に限らず、ほぼすべてのテキストはBOCU1による圧縮処理が行われています。
→BOCU1のコード表 全文字のコード表はこちら(kword_table.zip)
 ※フォントが対応していないとすべてのグリフは表示されません。
 ※一番左端のカラムにBOCU1のコード、各マスに「グリフ」「Unicode(UTF-16)」が記載されています。

コメント

以上の仕組みを作ったものの、逆にシンプルにしたい方向。大文字・小文字同一視(など)のインクリメンタルサーチを可能にするために作った仕組みだが、実際に使ってみると実感としてあまりいい感じではなかった。むしろインデックス部が大きくなってしまい、PDICの起動が遅いなど、negativeな意見が多いような気がする(今のところpositiveな意見は無し)。仕組みとしては残しておくが、キーワード部を除いた単純なUnicode順の言語プロセッサもリリースする予定。
立派なことをやろうと思ってはいけません。物事はシンプルに考えるべきです。

単語切り出し処理
ポップアップ検索を行うために、対象のテキストから単語を切り出し、辞書から該当する単語・熟語を検索しています。
英語の場合であれば基本的にスペースで区切られた単語単位で検索処理を行えばいいのですが、日本語のようにどこに単語の区切りがあるのか簡単に判別することが出来ない言語の場合は形態素解析を行わないと高い確率でヒットしません。

処理概要

1. 認識している全ての言語プロセッサに対して言語プロセッサIDを渡し、対応可能かどうか問い合わせる。
2. 対応している言語プロセッサの中で、言語プロセッサに検索対象の文字列を渡す
3. 対応していればその切り出した単語で検索を行う、対応していなければ次の優先順位の言語プロセッサで同様の処理を繰り返す
4. 対応している言語プロセッサの検索結果をすべて評価して、その中で最も近いと思われる順番ですべての検索結果を表示する

この方法による利点:
・言語プロセッサが複数あっても、検索結果をとりこぼさずすべての結果を表示できる(逆にヒットしすぎてよくないかも。。)
・切り出した単語を見出し語部の検索キーとして辞書を作成することにより、熟語途中の単語を入力しても高速な検索が可能になる(予定)。

(ポップアップ検索時の)変化形への対応

現状のPDICでは(ほとんど)英語に特化した処理を行っており、かなり複雑なことをやっています。
複雑ではありますが、基本的にやっていることは、

・切り出した単語から辞書に登録されていそうな形に色々と変形してヒットしていたらその単語を表示する
・ヒットした単語に優先順位をつけ、優先順位の高い順に表示する

です。言語プロセッサとしてできることもこの内容になります。(あるいはそれ以上)

また、単語の「色々な変形」という処理に不規則変化形の単語を原形に戻すという処理を行っていますが、ヘルプにも記載しているとおり、不規則変化対応表をIrregDic.txtというファイルに記述してあり、ユーザー側で変更することが出来ます。
この不規則変化の対応も言語プロセッサで対応していますので、現状の標準言語プロセッサである程度のことはできますし、もしご自分でスクラッチから作成されるのなら、あらゆる言語のあらゆる変化形に対応することが可能になります。

(実装の話になってしまいますが、PDICの辞書検索がいくら速いと言ってもヒット率を上げるために辞書のアクセスを増やしてしまうとポップアップ表示されるまで時間がかかってしまいます。そのための最適化など技術的にはちょっと難しいところがあります)

不規則変形への対応
不規則変化の対応ファイルはすでにありますが、辞書によって選択可能にします。
現在のところ、言語プロセッサIDで選択するつもりですが、これだとやや煩雑。言語プロセッサIDに関係なく、自由に選択できるような仕組みにした方がいいかも。
Webなどの検索対応
辞書以外、例えばWebにアクセスすることで任意のサイトの辞書の検索結果も取得することが可能。その結果をポップアップウィンドウに動的に反映可能。
検索結果を辞書へ登録
検索結果を辞書へ登録することも可能。
言語プロセッサと言語プロセッサID
辞書グループに関して。
[A]タイプ - 言語プロセッサIDが一致する辞書しか同時に使用できない(言語プロセッサIDの上位8bitで条件判断する)
[B]タイプ - 言語プロセッサIDに応じて必要な処理を行う。まったく異なる言語プロセッサIDであっても言語プロセッサが対応していれば対応可能。
全文検索のためのキーワード抽出処理、正規化
形態素解析等を考えていましたが、そのための辞書で数十メガバイトとなってしまい、そのために手間を惜しまず使用される人は少ないだろうということで却下。単純にN-gramで実装することに変更。
これで抽出に関しては問題ありませんが、Unicode正規化の問題がある。方針としては、 を行ってキーワードを作成する。Perl用のモジュールは存在するが、DLLなどの汎用モジュールは見つかっていない。(情報あれば下記MLへ)
Unicodeはかなり厄介であるにもかかわらず、日本語での情報が少ないため、別途そのためのページを用意

■実装

今後のPDICではこれらを完全に独立したものにして、あらゆる言語において最適な、理想的な検索ができるような仕組みを検討中。
現在のUnicode版では上記の処理が完全にカスタマイズできるような仕組みを作るためのある程度の基盤はできている。
1. 現状のPDICが持っているフレームワーク(仕組み)でカスタマイズする
2. すべて自分で作成する
の2つの方法をPDICが用意。
Layer-Aの実装
対応済み。ただし、言語プロセッサ選択ができないため、実質使用不可。

Layer-Bの実装

----

Web辞書検索のDLL仕様[Layer-C]公開済み

wdEnumerate(name, filekeyword, url, immediate, random_walk, expires, on_the_fly)
対応可能なWeb辞書検索の項目を列挙
name : Web辞書検索機能の名称 ex."Wiktionary(EN)"
filekeyword : Web辞書検索を有効にするファイル名
url : 検索先のURL→いらない?
immediate : ポップアップ検索で現在表示されている単語に対してすぐに表示反映するか?
random_walk : 検索以外においてもbackgroundでWeb辞書検索を行うか?(not supported)
exipres : 再度同じ単語を検索するまでのcache有効期間(second)
on_the_fly : 検索結果を辞書に保存しない(ポップアップ検索時のみ有効) (not supported officially)

thread safeであること。返す値はstaticであること。(少なくとも次回の任意のAPI呼び出しまで)

wdRequest(srch_word, response_data, dictionary_name, internet_access)
検索語を元にWeb辞書を検索し、その結果を解析、PDICで表示・保存する形式に変換。
srch_word : 検索する単語。原則、これと完全一致するもののみを検索する。そうしないとポップアップ表示されたときの見出し語と内容が一致しない場合がある)
response_data : 検索結果を格納するobject (発音記号、訳語、用例、ファイルリンクのみ対応?)
dictionary_name : 辞書のフルパス名。音声ファイル等を保存する際の基準とする。on_the_flyが可能である場合はNULLが渡されることがある。
internet_access : HTTPをPDIC内部で使用しているモジュールでアクセスする(現在はIE)。独自のHTTPプロトコルでも可能だが、PDIC内部の設定状態と一致させた方が良いので、できるだけこのobjectを経由してHTTPアクセスを行う。

thread safeであること。

DLL仕様[Layer-A, Layer-B]
・言語識別子("jpn"または"ja"などISO-639-2またはISO-639-1に準拠)
・言語説明("Japanese"などISO-639-2に準拠)
・言語識別子、言語説明は複数可能
・IsLanguage() - 文字列を与えて自分が解析できる言語かどうか
・GetWord() - 与えられた文字列から単語を切り出す
・NextWord() - 与えられたポインターから次の単語の先頭のポインターを返す
・GetLangAttr() - 言語の属性を返す
・WordCount() - 文字列中の単語数を返す(Optional - ない場合は、NextWord()で調べる)
・原形への変換

問題は、frameworkをどうするか・・

【案1】

汎用的なDLLを作る。

利点:
・自由度は高い
・APIが汎用的であればあるほど、他のアプリでも容易に利用可能
欠点:
・DLLを作成する知識・技術が必要

これが一番現実的。

【案2】

パラメータファイルと正規表現パターンなどで対応する。

利点:
・基本的にC/C++言語などプログラミング言語の知識を必要としないため、多くの人がカスタマイズできる
欠点:
・自由度が他の案に比べて低いため、frameworkの設計の善し悪しで言語プロセッサの性能が決まってしまう。基本設計がとても重要で時間がかかる
・他の案に比べて明らかに性能は劣るが、実際に作ってみないと実用的なレベルになるかどうかわからない

この案が一番理想的。しかし、基本設計を素人の私が一人でできるとは思えない。。

【案3】

PDICに直接組み込む。

当初の目標から外れてしまうので、あまりやりたくはありませんが、(私の時間が許されるのなら)これが一番早い。

参考:自然言語処理のためのリソース(JUMAN,KNP)
参考:MeCab

最後に

汎用的な言語プロセッサを作るって言うのはとても大変だと思います。私自身言語学の専門家でも何でもないので、私だけでは不可能です。言語学が専門の方などに是非とも協力していただきたいです。
また、辞書検索ソフトを作っている方も共通の言語プロセッサを作成するためのご協力をしていただきたいです。PDICだけの話ではなく、世界共通のライブラリであり、人類共通の財産です。
※もしすでにあるようでしたら教えてください。

ご意見など

PDIC Development」メーリングリスト

TaN:
PDICの開発はこちらで行っています。