tsja: 日本語テキスト全文検索 (PostgreSQL)

概要: tsja (Text Search for JApanese) の目的、特徴

ダウンロード

tsja-0.5.0.tar.xz

インストール、設定、簡単な使い方

$ make
# cp libtsja.so /usr/local/pgsql/lib

次にデータベースを生成し、テキスト検索パーサー/辞書/設定を登録する。

$ createdb db_tsja
$ psql -d db_tsja -f dbinit_libtsja.txt

テキスト検索辞書として、英語部分についてはPostgreSQL同梱のsnowball_dictを用いるようにしている。用途に応じて設定を変更するとよい。

検索対象のテーブルを用意し、インデックスを設定する。以下はGINインデックスの例。

$ psql -d db_tsja
db_tsja=# CREATE TABLE terms_tbl (...
db_tsja-#     (中略)
db_tsja-#     j_term                text,
db_tsja-#     j_description         text,
db_tsja-#     (中略)
db_tsja-# );
db_tsja=# CREATE INDEX terms_tbl_idx ON terms_tbl
db_tsja-#     USING gin(to_tsvector('japanese', j_description));

すると次のようにして検索できるようになる。実際に使う際には、検索キーワード部分をパラメーターにしておくとよい。

db_tsja=# SELECT j_term,
db_tsja-#        ts_headline('japanese', j_description, to_tsquery('japanese', '日本語&検索'), 'StartSel=<em>,StopSel=</em>'),
db_tsja-#        ts_rank(to_tsvector('japanese', j_description), to_tsquery('japanese', '日本語&検索')) AS rank
db_tsja-# FROM   terms_tbl
db_tsja-# WHERE  to_tsvector('japanese', j_description) @@ to_tsquery('japanese', '日本語&検索')
db_tsja-# ORDER BY rank DESC ;

to_tsvector()、to_tsquery()、ts_rank()などの函数については、PostgreSQL付属ドキュメントの「全文検索」章を参照。

テキスト検索パーサー/辞書/設定: 詳細

dbinit_libtsja.txtの処理内容をもう少し詳しく説明する。

テキスト検索パーサーの設定

CREATE FUNCTION tsja_start(internal, int4) RETURNS internal
	AS '$libdir/libtsja'
	LANGUAGE 'c' STRICT;

CREATE FUNCTION tsja_gettoken(internal, internal, internal) RETURNS internal
	AS '$libdir/libtsja'
	LANGUAGE 'c' STRICT;

CREATE FUNCTION tsja_end(internal) RETURNS void
	AS '$libdir/libtsja'
	LANGUAGE 'c' STRICT;

CREATE FUNCTION tsja_lextypes(internal) RETURNS internal
	AS '$libdir/libtsja'
	LANGUAGE 'c' STRICT;

CREATE TEXT SEARCH PARSER japanese (
	START    = tsja_start,
	GETTOKEN = tsja_gettoken,
	END      = tsja_end,
	HEADLINE = pg_catalog.prsd_headline,
	LEXTYPES = tsja_lextypes
);

ここに並んでいる4つの函数が、tsja.cに実装されている。HEADLINEに設定した函数は、PostgreSQLにはじめから備わっているものである。

tsja_start()、tsja_gettoken()、tsja_end()は、日本語文を単語に分解し、個々の単語の品詞情報を添えて返す。

tsja_lextypes()は、このパーサーが認識できる品詞の一覧を返す。名詞や動詞であっても、検索に用いるには適切でない単語が若干あるので、普通にいう「品詞」より少し細かく分類する。

テキスト検索辞書の設定

CREATE TEXT SEARCH DICTIONARY simple_dict (
	TEMPLATE = pg_catalog.simple,
	STOPWORDS = english
);

CREATE TEXT SEARCH DICTIONARY snowball_dict (
	TEMPLATE = snowball,
	Language = english,
	StopWords = english
);

CREATE FUNCTION tsja_init(internal) RETURNS internal
	AS '$libdir/libtsja'
	LANGUAGE 'c' STRICT;

CREATE FUNCTION tsja_lexize(internal, internal, internal, internal) RETURNS internal
	AS '$libdir/libtsja'
	LANGUAGE 'c' STRICT;

CREATE TEXT SEARCH TEMPLATE mecab (
	INIT = tsja_init,
	LEXIZE = tsja_lexize
);

CREATE TEXT SEARCH DICTIONARY japanese_dict (
	TEMPLATE = mecab
);

simple_dict、snowball_dictは、PostgreSQLに付属している、主に英語 (をはじめとする印欧語族の言語) を対象とする辞書である。ASCII文字のみから成る単語は英語と看做し、この辞書で処理する。

snowball_dictはドイツ語やフランス語なども扱える。それを考慮すれば、「ASCII文字のみ」という判定基準は不充分であるが、当面は保留としておく。

tsja_init()、tsja_lexize()は、単語を終止形に正規化する。たとえば、表層形は同じでも、それが名詞か動詞かによって正規形が異なることもあるので、パーサーと連携して処理することになる。

テキスト検索設定

CREATE TEXT SEARCH CONFIGURATION japanese (
	PARSER = japanese
);

ALTER TEXT SEARCH CONFIGURATION japanese
	ADD MAPPING FOR word
	WITH snowball_dict;

ALTER TEXT SEARCH CONFIGURATION japanese
	ADD MAPPING FOR 名詞, 動詞, 形容詞, 副詞, 連体詞
	WITH japanese_dict;

ALTER TEXT SEARCH CONFIGURATION japanese
	ADD MAPPING FOR 非自立名詞, 代名詞, 数, 助数詞, 非自立動詞, 非自立形容詞, 助詞類接続副詞
	WITH japanese_dict;

ALTER TEXT SEARCH CONFIGURATION japanese
	ADD MAPPING FOR 助詞, 助動詞, 接続詞, 接頭詞, 感動詞
	WITH japanese_dict;

ALTER TEXT SEARCH CONFIGURATION japanese
	ADD MAPPING FOR 記号, フィラー, その他
	WITH japanese_dict;

品詞の判定結果によって使い分けをしている。この設定例では、英語部分の処理をPostgreSQLに付属のsnowball_dictに委ねている。

補助函数

CREATE FUNCTION pg_to_ruby(text) RETURNS text
	AS '$libdir/libtsja'
	LANGUAGE 'c' IMMUTABLE STRICT;

振り仮名に変換して返す。

mecab辞書の設定

mecabと組み合わせて使うことが多いipadicの場合、特に指定しなければ /usr/local/lib/mecab/dic/ipadic 以下にインストールされる。そのため、本システムも、この場所にある辞書を参照するようになっている。

postgresql.confに、次のように記述することにより、参照する辞書の場所を変更できる。

tsja.mecab_dict_path = '/path/to/mecab/dict'

ipadicの文字コードは、特に指定しなければEUC-JPであるが、UTF-8にすることも可能である。検索対象の文字コードと一致している必要があるので、状況によっては、convert()などの函数を使って適宜対処しなければならない。

文字コードの一致をあえて検査せず、本システムを使う側に管理を委ねている。データベースを生成する際、(たとえばcreatedbの「--encode」オプションで) 符号化方式としてSQL_ASCIIを指定しておき、テーブルのtext型フィールドにUTF-8の文字列を格納する、といったことも可能だからである。

テキスト検索のテスト

PostgreSQL付属ドキュメントの「全文検索」章、「テキスト検索のテストとデバッグ」節および「テキスト検索の制御」節にそって、検索函数の動作を示す。

設定: ts_debug

ts_debug()でテキスト検索設定をテストできる。

SELECT * FROM ts_debug('japanese', 'PostgreSQLで日本語のテキスト検索ができます。');
 alias  | description |   token    |  dictionaries   |  dictionary   |   lexemes    
--------+-------------+------------+-----------------+---------------+--------------
 word   | word        | PostgreSQL | {snowball_dict} | snowball_dict | {postgresql}
 助詞   | 助詞        | で         | {japanese_dict} | japanese_dict | {}
 名詞   | 名詞        | 日本語     | {japanese_dict} | japanese_dict | {日本語}
 助詞   | 助詞        | の         | {japanese_dict} | japanese_dict | {}
 名詞   | 名詞        | テキスト   | {japanese_dict} | japanese_dict | {テキスト}
 名詞   | 名詞        | 検索       | {japanese_dict} | japanese_dict | {検索}
 助詞   | 助詞        | が         | {japanese_dict} | japanese_dict | {}
 動詞   | 動詞        | でき       | {japanese_dict} | japanese_dict | {できる}
 助動詞 | 助動詞      | ます       | {japanese_dict} | japanese_dict | {}
 記号   | 記号        | 。         | {japanese_dict} | japanese_dict | {}
(10 rows)

lexemesには自立語を正規化したものが挙がっており、非自立語 (ストップ・ワード) は除かれていることが分かる。また、英語部分はsnowball_dictで処理されている。

パーサー: ts_parse

ts_parse()でテキストを解析し、トークン型を取得できる。

SELECT * FROM ts_parse('japanese', 'PostgreSQLで日本語のテキスト検索ができます。');
 tokid |   token    
-------+------------
    30 | PostgreSQL
   211 | で
    50 | 日本語
   211 | の
    50 | テキスト
    50 | 検索
   211 | が
    60 | でき
   212 | ます
   221 | 。
(10 rows)

辞書: ts_lexize

単語を渡すとその正規化形を取得できる。次に示す例ではたまたま「でき」を動詞と認識し、「できる」という終止形を返している。しかし実際には、前後も見なければ正しく認識できないことがある。そこでtsjaでは、形態素解析の結果を保存しておき、正規化の際に参照するようになっている。

SELECT	ts_lexize('japanese_dict', 'でき');
 ts_lexize 
-----------
 {できる}
(1 row)

文書のパース: to_tsvector

テキスト文書を分析し、語彙素とその位置のリストであるtsvectorを返す。

SELECT	to_tsvector('japanese', 'PostgreSQLで日本語のテキスト検索ができます。');
                        to_tsvector                         
------------------------------------------------------------
 'postgresql':1 'できる':8 'テキスト':5 '日本語':3 '検索':6
(1 row)

問い合わせのパース: to_tsquery

問い合わせをtsqueryに変換する。

SELECT	to_tsquery('japanese', '日本語&検索');
    to_tsquery     
-------------------
 '日本語' & '検索'
(1 row)

検索結果のランキング: ts_rank

問い合わせに対する一致度を計測する。

SELECT	ts_rank(to_tsvector('japanese', 'PostgreSQLで日本語のテキスト検索ができます。'),
		to_tsquery('japanese', '日本語&検索'));
  ts_rank  
-----------
 0.0973585
(1 row)

結果の強調: ts_headline

これはPostgreSQLにはじめから備わっているものである。文書中、問い合わせと一致した箇所を強調する。

SELECT	ts_headline('japanese',
		'PostgreSQLで日本語のテキスト検索ができます。',
		to_tsquery('japanese', '日本語&検索'),
		'StartSel=<em>,StopSel=</em>');
                          ts_headline                           
----------------------------------------------------------------
 PostgreSQLで<em>日本語</em>のテキスト<em>検索</em>ができます。
(1 row)

まとめ

以上を組み合わせると、冒頭で紹介したような、テーブルのあるフィールドを対象とする全文検索ができる。

SELECT j_term,
       ts_headline('japanese', j_description, to_tsquery('japanese', '日本語&検索'), 'StartSel=<em>,StopSel=</em>'),
       ts_rank(to_tsvector('japanese', j_description), to_tsquery('japanese', '日本語&検索')) AS rank
FROM   terms_tbl
WHERE  to_tsvector('japanese', j_description) @@ to_tsquery('japanese', '日本語&検索')
ORDER BY rank DESC ;

履歴


Copyright © 2015 KOYAMA Hiro <tac@amris.co.jp>