Pgunconf ゆるいテキスト検索ふたたび - n-gram応用編

1,992 views
1,794 views

Published on

2013/7/13 PostgreSQL Unconference

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,992
On SlideShare
0
From Embeds
0
Number of Embeds
23
Actions
Shares
0
Downloads
6
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Pgunconf ゆるいテキスト検索ふたたび - n-gram応用編

  1. 1. ゆるいテキスト検索 ふたたび N-gram編 ぬこ@横浜(@nuko_yokohama)
  2. 2. 自己紹介 名前:ぬこ@横浜 仕事:ラーメンレビュー 副業:某通信系SI会社勤務 (趣味で)誰得なPostgreSQL拡張やってます
  3. 3. つくったもの xml_fdw:XMLファイルのFDW w24:二十四節気型 ksj:漢数字で演算する型 あいまいtextsearch用パッチ neo4j_fdw:グラフデータベースのFDW redis_fdw 9.3対応(やりかけ) 誰得?
  4. 4. 今回のテーマ pg_bigm+近似検索
  5. 5. pg_bigmって?
  6. 6. PostgreSQL組込み用 N-gram全文検索系 pg_trgnは3文字分割 pg_bigmは2文字分割
  7. 7. NTT DATAの藤井さんと 澤田さんが作っている。 公開中。 (”pg_bigm”で検索)
  8. 8. pg_bigmの売り 日本語をサポート 2文字以下の検索が速い
  9. 9. 詳しいことは (たぶん今日来ている) 藤井さんと澤田さんに 聞いてください。
  10. 10. さて、前回の アンカンファレンス 発表で こんなことを言った
  11. 11. 「N-gramで近似検索は 無理」と言ったな。 あれは嘘だ。
  12. 12. 確かにN-gramだけで は無理
  13. 13. たとえば・・・ メロスは激怒した。 セリヌンティウスも激怒した。 「俺の名前はセンヌリティウス じゃねー!」
  14. 14. 確かにN-gramだけで は無理っす meros=# SELECT data FROM test WHERE data LIKE likequery('セリヌンティウス') LIMIT 1; data --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- ------------------ ------------ メロスには竹馬の友があった。セリヌンティウスである。今は此のシラクスの市で、石工 をしている。その友を、これから訪ねてみるつもりなのだ。久しく逢わなかったのだか ら、訪ねて行くのが楽しみである。 (1 row) meros=# SELECT data FROM test WHERE data LIKE likequery('センヌリティウス') LIMIT 1; data ------ (0 rows) ですよねー
  15. 15. しかし
  16. 16. 一手間かかるが 近似検索ぽいことは 実はできる。
  17. 17. そのためには 公式リリース版の pg_bigmには機能が 足りない・・・ pg_trgmにはある・・・
  18. 18. なので作者さんに 丁重にお願いして similarity関数等を 作ってGithubに上げて もらった pg_trgmでもいいんだけどさ・・・ せっかくだからpg_bigmの試験も兼ねて 澤田さん、サンクス!
  19. 19. similarity関数 文字列間の類似度を 算出(0.0~1.0)して返却 1.0なら完全一致 0.0なら全く一致しない
  20. 20. % 演算子 %:類似度が閾値以上なら真 閾値のデフォルトは0.3 pg_bigm.similarity_limit パラメータで変更可能
  21. 21. こんな感じ bigm=# SHOW pg_bigm.similarity_limit ; pg_bigm.similarity_limit -------------------------- 0.3 (1 row) bigm=# SELECT similarity('センヌリティウス', 'セリヌンティウス'); similarity ------------ 0.384615 (1 row) bigm=# SELECT 'センヌリティウス' % 'セリヌンティウス'; ?column? ---------- t (1 row) bigm=# SET pg_bigm.similarity_limit = 0.4; SET bigm=# SELECT 'センヌリティウス' % 'セリヌンティウス'; ?column? ---------- f (1 row)
  22. 22. N-gram近似検索の やりかた
  23. 23. テキストから辞書を Mecab等で生成。 文書も辞書も N-gramインデクスを設定。 あいまいな語と辞書を比較し、 近似した語を取得。 取得した語で近似検索。
  24. 24. 辞書テーブル ・・・ シラクス セリヌンティウス ゼウス ・・・ 文書テーブル メロスは激怒した・・・ きょう未明メロスは村・・・ ・・・ 「セリヌンティウス。・・・ ・・・  勇者は、ひどく赤面・・・ セリヌンティウス センヌリティウス 類似検索で 一番類似した 「セリヌンティウス」 を取り出す 「セリヌンティウス」を使って LIKE検索で文書テーブルを 検索する。 だいたいこんな感じ 文書から 抜き出した キーワード 汎用辞書
  25. 25. やってみた
  26. 26. 確かにN-gramだけで は無理っす SELECT data FROM meros WHERE data LIKE likequery( (SELECT data FROM token WHERE data % 'センヌリティウス' ORDER BY similarity(data, 'センヌリティ ウス') DESC) ) LIMIT 3; data ------------------------------------------------------------------------------------------------- -------------------------------------------------- ------------------------------------------------------------------------------------------------- メロスには竹馬の友があった。セリヌンティウスである。今は此のシラクスの市で、石工をしている。その 友を、これから訪ねてみるつもりなのだ。久しく逢わなかったのだから、訪ねて行くのが楽しみである。 「そうです。帰って来るのです。」メロスは必死で言い張った。「私は約束を守ります。私を、三日間だけ 許して下さい。妹が、私の帰りを待っているのだ。そんなに私を信じられないならば、よろしい、この市に セリヌンティウスという石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行こう。私が 逃げしまって、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たのむ、そ うして下さい。」  メロスは腕に唸《うな》りをつけてセリヌンティウスの頬を殴った。 (3 rows) やったね☆
  27. 27. 確かにN-gramだけで は無理っす EXPLAIN SELECT data FROM meros WHERE data LIKE likequery( (SELECT data FROM token WHERE data % 'センヌリティウス' ORDER BY similarity(data, 'センヌリティ ウス') DESC) ) LIMIT 3; QUERY PLAN -------------------------------------------------------------------------------------------- Limit (cost=56.04..60.06 rows=1 width=32) InitPlan 1 (returns $0) -> Sort (cost=44.03..44.04 rows=1 width=32) Sort Key: (similarity(token.data, 'センヌリティウス'::text)) -> Bitmap Heap Scan on token (cost=40.01..44.02 rows=1 width=32) Recheck Cond: (data % 'センヌリティウス'::text) -> Bitmap Index Scan on token_data_idx (cost=0.00..40.01 rows=1 width=0) Index Cond: (data % 'センヌリティウス'::text) -> Bitmap Heap Scan on meros (cost=12.01..16.02 rows=1 width=32) Recheck Cond: (data ~~ likequery($0)) -> Bitmap Index Scan on meros_data_idx (cost=0.00..12.01 rows=1 width=0) Index Cond: (data ~~ likequery($0)) (12 rows) インデクスもきちんと使われる
  28. 28. pg_trgm+近似検索 簡単かつ それなりに有効?
  29. 29. しかし、この方式は まだまだ欠陥がある
  30. 30. 「インタフェース」問題 ふたたび
  31. 31. インタフェース インタフェイス インターフェース インターフェイス 私はこの表記派
  32. 32. 参考:各表記の類似度 インタフェース インタフェイス インターフェース インターフェイス インタフェース 1.0 0.6 0.7 0.416667 インタフェイス 0.6 1.0 0.416667 0.7 インターフェース 0.7 0.416667 1.0 0.636364 インターフェイス 0.416667 0.7 0.636364 1.0
  33. 33. ということは、 Similarityで閾値以上の 類語をOR条件で繋げば 表記ゆれ検索も ある程度カバーできる?
  34. 34. でも、さっきのクエリを そのまま使うとエラーに なる・・・
  35. 35. エラー回避のために 一番安直なのは “LIMIT 1” を指定して1行のみ返却
  36. 36. こんな感じ SELECT data FROM test WHERE data LIKE likequery( (SELECT token FROM token WHERE token % 'インタフェース' ORDER BY similarity(token, 'インタフェース') DESC) ); ERROR: more than one row returned by a subquery used as an expression SELECT data FROM test WHERE data LIKE likequery( (SELECT token FROM token WHERE token % 'インタフェース' ORDER BY similarity(token, 'インタフェース') DESC LIMIT 1) )
  37. 37. しかし、それでは 複数の候補に 展開できない
  38. 38. どうやって 複数の候補を 条件に展開するか
  39. 39. Point % 演算子 行から配列を生成 サブクエリ Text配列へのcast ANY演算子
  40. 40. こんな感じ SELECT data FROM test WHERE data LIKE ANY ( (SELECT array_agg(sml.token) FROM (SELECT likequery(token) AS token FROM token WHERE token % 'インタフェース' ORDER BY similarity(token, 'インタフェース') DESC LIMIT 5) as sml)::text[] ); %演算子で辞書を検索 一番近いN件をORDER Byで取得 その結果をarray_aggで配列化 そのサブクエリの結果をtext[]でキャスト 配列をLIKE ANYで評価
  41. 41. こんな感じ SELECT data FROM test WHERE data LIKE ANY ( (SELECT array_agg(sml.token) FROM (SELECT likequery(token) AS token FROM token WHERE token % 'インタフェース' ORDER BY similarity(token, 'インタフェース') DESC LIMIT 5) as sml)::text[] ); data --------------------------------------------------------------------------- 今月号のインターフェースはコンパイラ特集だ。 実装はユーザインタフェースだけでなく、ユーザエクスペリエンスを考えねばならない。 発注元との意識ずれでインタフェイスの再設計をすることになった。 僕の考えた最強のユーザー・インターフェイスは却下された。 (4 rows) やったね☆
  42. 42. 「インタフェース」で 「インターフェース」 「インタフェイス」 「インターフェイス」が ヒットした。
  43. 43. べたにSQLを書くのは ちょっと大変・・・ ラッパ関数を作ろう。
  44. 44. こんな感じ CREATE OR REPLACE FUNCTION create_synonyms(keyword text, limit_num int) RETURNS text[] AS $$ SELECT array_append(array_agg(sml.token), likequery(keyword)) FROM (SELECT likequery(token) AS token FROM token WHERE token % keyword ORDER BY similarity(token, keyword)   DESC LIMIT limit_num) as sml; $$ LANGUAGE sql;
  45. 45. ラッパ関数を使うと ちょっとだけ シンプルに記述できる
  46. 46. こんな感じ SELECT data FROM test WHERE data LIKE ANY ((SELECT create_synonyms('インタフェース', 5))::text[]); data ------------------------------------------------------------------------ 今月号のインターフェースはコンパイラ特集だ。 実装はユーザインタフェースだけでなく、ユーザエクスペリエンスを考えねばならない。 発注元との意識ずれでインタフェイスの再設計をすることになった。 僕の考えた最強のユーザー・インターフェイスは却下された。 やったね☆
  47. 47. あのさぁ・・・(棒読み) 色々やってるけど それくらいなら LIKE '%インタ%ス%' で出来るんじゃないの?
  48. 48. そうかもしれないけど・・・ でも、メタ文字を書かせたら 負けな気がする。 それに・・・
  49. 49. 「カレーライス」で 「ライスカレー」を 検索できるのか? これはLIKEの書き方では 無理なはず。
  50. 50. こんな感じ SELECT data FROM test WHERE data LIKE ANY ((SELECT create_synonyms('ライスカレー'))::text[]); data ------------------------------------------------------------ 彼女は言った。「カレーライスが嫌いな男子なんていません!」 昨晩食べたライスカレーはとても美味かった。 (2 rows)
  51. 51. 更なる拡張
  52. 52. Mecab辞書(ipadic)には ありがたいことに 読みがなもついている。
  53. 53. あいまい語 ↓ 類義語 or 読みがな これも展開するとさらに ゆるく検索できないか?
  54. 54. 読みがな側も類似検索す れば、かなのtypoにも 対応できるかも。
  55. 55. やってみた
  56. 56. こんな感じ SELECT * FROM test WHERE data LIKE ANY ((SELECT create_synonyms2('小田原市'))::text[]); data -------------------------------------------------------------- 神奈川の西部には小田原ラーメンというカテゴリの醤油ラーメンがある。 源平の戦いの英雄の一人、那須与一の郷は栃木の大田原市である。 私はコーディングスタイルには然程こだわらないようにしている。 僕は小市民の星を目指している。面倒事は勘弁してくれ。 (4 rows) (゜Д゜)ハァ?
  57. 57. ちょっと何故ヒットしてるか わかんないですけどwww
  58. 58. 教訓 高度に(?)発達した 「だいたいあってる」は 「だいたいあってない」 と区別がつかない
  59. 59. まとめ
  60. 60. pg_bigmのsimilarityと 辞書を使うことで、 完全一致でない 検索が出来る。
  61. 61. なので pg_bigmにsimilarity機 能が正式に取り込まれる といいなあ・・・
  62. 62. このゆるい検索の肝は TEXT配列を返却する 関数さえあればOK ということ
  63. 63. 汎用のシソーラス辞書を 使うも良し Web-APIでシソーラス を引っ張ってもOK (TEXT配列さえ返却すれば)
  64. 64. 最後に
  65. 65. 本当は辞書的なものは 使いたくない。 辞書なしでキーワードと 文書間でsimilarityを 考慮した評価が出来るの が一番いいのかも。
  66. 66. ご清聴 ありがとう ございました

×