初めまして。今年度新卒入社の mako _makokです。最近実家に帰って水族館でペンギンを見てきました。 今回は 全文検索エンジン のコア機能の一つであるAnalyzerについて書いていきたいと思います。 はじめに 検索エンジンの仕組み Analyzerとは 前準備 Char filter Tokenizer Token filter おわりに はじめに 私は現在、個人的に 全文検索エンジン 学習をしています。 以前までは諸事情で Apache Solrをやっていたのですが、以下の理由からElasticsearchの学習に切り替えました。 シェアとそれに伴うドキュメントの充実 KibanaをはじめとしたElastic Stackの存在 クエリの書き方覚えたらいい感じにクエリ書けそう Apache Solr及びElasticsearchでは Apache Lucene いう OSS の 全文検索 ライブラリがコアになっております。 Lucene には Analyzer という機能があり、 全文検索エンジン において非常に重要な機能です。 今回は実際にElasticsearchでAnalyzerを設定しながら、Analyzerの仕組みを見ていきたいと思います。 検索エンジン の仕組み まずは簡単に 検索エンジン の仕組みを説明します。 検索エンジン ではあらかじめドキュメントのインデックスを作成しておき、そこへクエリが投げられるとそれにマッチするものがヒットするというのが基本になります。 Lucene では 転置インデックス という方式が採用されています。 転置インデックス は以下のような形で作成されます。 転置インデックス 何らかの方法でドキュメントのテキストを分割し、それに対応するドキュメントIDがリストになります クエリが投げられた際も同様、クエリのテキストを分割し、インデックスのキーにマッチした文章が返るという仕組みです。 Analyzerとは 先ほどは 検索エンジン の仕組みについて説明しました。 何らかの方法で分割する とありましたが、この部分がAnalyzerの仕事になります。 正確には、Analyzerは目的のインデックスを作成するために、テキストの分割や正規化などを行います。 Analyzerは以下の3つの機能で構成されています。 Char filter Tokenizer Token filter Analyzerの処理フローです。 Char filter → Tokenizer → Token filterの順に処理されます。 Tokenizerは必須・かつ一つ しか設定できません。 Analyzerの処理手順 前準備 今回はElasticsearchで動作確認を行います。 Analyzerがどのような動きをしているかどうかだけ知りたい方は飛ばしてください。 Java 8以上の環境が必須です。 Elasticsearchをインストール # brew brew install elasticsearch # yum yum install elasticsearch 日本語関連の機能と、テキストの正規化を強化する2つの プラグイン をインストールします analysis-kuromoj analysis- icu cd { ES_HOME } bin/elasticsearch-plugin install analysis-kuromoji bin/elasticsearch-plugin install analysis-icu Elasticsearchを起動して起動確認も行います # 起動 bin/elasticsearch # 起動確認 curl http://localhost:9200/ " name " : " 1GpZYN9 " , " cluster_name " : " elasticsearch " , " cluster_uuid " : " hoge " , " version " : { " number " : " 6.8.3 " , " build_flavor " : " oss " , " build_type " : " tar " , " build_hash " : " 0c48c0e " , " build_date " : " 2019-08-29T19:05:24.312154Z " , " build_snapshot " : false , " lucene_version " : " 7.7.0 " , " minimum_wire_compatibility_version " : " 5.6.0 " , " minimum_index_compatibility_version " : " 5.0.0 " } , " tagline " : " You Know, for Search " } これで準備完了です。これから実際にAnalyzerを作成しながら、Char filter, Tokenizer, Token filterについてそれぞれ解説します。 Char filter Char filterのポイントは以下の通りです。 テキストに対して 機械的 に前処理を行う 必須ではない いくつでも設定できる 機械的 な前処理というのは、例えば文字の全角↔半角処理だったり、 正規表現 による抽出などが挙げられます。 ここからいよいよAnalyzerの設定を行っていきます。 Elasticsearchでは様々な機能が REST API で操作できます。 今回はChar filterに以下を設定します。 icu _normalizer 記号・数字・ 特殊文字 などの正規化を行う kuromoji_iteration_mark 踊り字を正規化 踊り字とは々、ヽ、ゝのような前置の単語によって読み方が変化する単語のことです Analyzerを設定するにはインデックスを保管しておくスペースを作成しなければなりません。 今回はインデックス名を analyzer_handson として、Analyzerを設定していきます。 以下の json をmy_kuromoji_analyzer. json と保存し、POSTします。 { " settings ": { " analysis ": { " analyzer ": { " my_kuromoji_analyzer ": { " type ": " custom ", " char_filter " : [ " icu_normalizer ", " kuromoji_iteration_mark " ] , " tokenizer ": " keyword " } } } } } json を localhost :9200/analyzer_handson/ にPUTします。 curl -XPUT localhost:9200/analyzer_handson/ -H " Content-type: application/json " -d @my_kuromoji_analyzer.json これで my_kuromoji_analyzer というAnalyzerが設定されました。 早速テキストをアナライズしていきます。 ~/_analyze がエンドポイントになっており、こちらで特定のAnalyzerの挙動を確認することができます。 そこに以下の json をPOSTします。 { " analyzer ": " my_kuromoji_analyzer ", " text ": " コウテイペンギンは体格のいいものは130㌢あるという。僕は度々、コンピューターでそれを見て和んでいる " } curl -XPOST localhost:9200/analyzer_handson/_analyze -H " Content-Type: application/json " -d @query.json 以下のような結果が返ってきます。 { " tokens ": [ { " token ":" コウテイペンギンは体格のいいものは130センチあるという。僕は度度、コンピューターでそれを見て和んでいる ", " start_offset ": 0 , " end_offset ": 50 , " type ":" word ", " position ": 0 } ] } 無事正規化されています。 130は全角数字→半角数字 ㌢→センチ 度々→度度 に変換されています。 このような正規化の処理は 自然言語処理 の前処理として非常に重要です。 内部的には半角数字と全角数字などは違う文字として扱われるため、検索のノイズになることや、逆に欲しいドキュメントがヒットしないなどの問題が発生します。 踊り字などは主に古典などでノイズになることが多いです。 他にもChar filterはたくさんあるので、色々試していきたいところです。 Tokenizer Tokenizerはテキストを 分かち書き します。 分かち書き とは、特定の規則に乗っ取ってテキストを分割することです。 ではどのように 分かち書き するのかですが、日本語の場合は 形態素解析 もしくは N-gram を使用することが多いです。 形態素解析 や N-gram のTokenizerには以下のようなものがあります。 kuromoji_tokenizer 日本語用の 形態素解析 器であるKuromojiを使用して 形態素解析 を行う 辞書は2007年からメンテナンスされていないため、新語(芸能人の名前や流行語)などに弱い *1 N-gram Tokenizer 文字数で 機械的 に区切り、 分かち書き を行う 1-gramをuni-gram, 2-gramをbi-gram, 3-gramをtri-gramと呼ぶ 形態素解析 では、テキストを品詞単位で分解し、分割された単語のことを トーク ンと呼びます。 分解した トーク ンには品詞の情報はもちろん、活用形などの情報が付与されます。 Elasticsearchでは Kuromoji という日本語 形態素解析 器が用いられています。 もう一つの 分かち書き の手段の N-gram についてです。 例えばtri-graだとこのように分解されます N-gram 今回はtri-gramなのでテキストを3つに区切りながら一つずつ横にずらしていく感じです。 これら2つの手法はそれぞれ利点・欠点があります 形態素解析 辞書ベースで区切るため、辞書に載っている単語については比較的高い精度の検索結果を得やすい 逆に辞書に載っていない単語などは1文字区切りで 分かち書き されるため、未知の単語に弱い N-gram 機械的 に区切られるため、未知の単語などでもドキュメントをヒットさせることができる "京都で遊ぶ"のような単語で検索されたとき、上記の画像のように"東京都"を含むドキュメントがヒットする 検索ノイズが増えやすい どちらも一長一短なので、検索要件によって適切にTokenizerを設定する必要があります。 今回は 形態素解析 を用いて単語分割していきたいと思います。 Analyzerの設定を更新するために、まずは一旦インデックスをcloseします。 curl -XPOST localhost:9200/analyzer_handson/_close 次に、Char filterの時に使用したmy_kuromoji_analyzer. json のtokenizerを kuromoji_tokenizer に編集しPOSTします。 { " settings ": { " analysis ": { " analyzer ": { " my_kuromoji_analyzer ": { " type ": " custom ", " char_filter " : [ " icu_normalizer ", " kuromoji_iteration_mark " ] , " tokenizer ": " kuromoji_tokenizer " } } } } } # 更新する際は/_settingsにPOSTします curl -XPUT localhost:9200/analyzer_handson/_settings -H " Content-type: application/json " -d @my_kuromoji_analyzer.json 最後にopenして完成です curl -XPOST localhost:9200/analyzer_handson/_open 早速テキストを投げてみると以下のように 形態素解析 されていることがわかります。 { " tokens ": [ { " token ":" コウテイペンギン ", " start_offset ": 0 , " end_offset ": 8 , " type ":" word ", " position ": 0 } , { " token ":" は ", " start_offset ": 8 , " end_offset ": 9 , " type ":" word ", " position ": 1 } , { " token ":" 体格 ", " start_offset ": 9 , " end_offset ": 11 , " type ":" word ", " position ": 2 } , ... ~ ... { " token ":" 和ん ", " start_offset ": 45 , " end_offset ": 47 , " type ":" word ", " position ": 21 } , { " token ":" で ", " start_offset ": 47 , " end_offset ": 48 , " type ":" word ", " position ": 22 } , { " token ":" いる ", " start_offset ": 48 , " end_offset ": 50 , " type ":" word ", " position ": 23 } ] } 無事Kuromojiを使用して 分かち書き をすることができました。 最後はToken filterです。 Token filter Token filterではTokenizerで 分かち書き された トーク ンに対して様々な変換処理を行います。 以下はToken filterの一例です。 Lower case Token filter トーク ンを全て小文字に変換する Stop Token filter ストップワード の除去を行う ストップワード とは、 自然言語処理 において、一般的であるなどの不要な単語のこと Stemer Token filter 語幹ごとに定義されたステミング処理を行う ステミングとは語形の変化をなくし、表現を統一すること Synonym Token filter 類義語の展開を行う 表記揺れ(引越し、引っ越し、引越)や類義語(パソコン、PC、コンピュータ)など Kuromoji-Analysisに付属しているToken FIlter kuromoji_baseform 動詞・形容詞を原型に戻します。活用形は表記揺れの原因になります。 kuromoji_part_of_speech 特定の品詞を削除します。検索において、助詞や助動詞などは必要でないケースがあります。 デフォルトでは {助詞-格助詞-一般, 助詞-終助詞}を削除します。 形態素解析 を行うことによって、単語に品詞情報が付与されます kuromoji_stemmer 日本語に特化したステミング処理用のToken filter。カタカナの伸ばし棒を削除します。 今回は上記の KuromojiのToken filter 3種と、 Synonym Token filter , Stop Token filter を使用していきます " コウテイペンギン "で一単語であり、"ペンギン"で検索した時にヒットしない 二つの単語を一つのシノニムグループ *2 として扱うmy_synonym_penguin_filterを新しく作成し、filterに追加 動詞や形容詞であるが、いい、もの、ある、いるなど様々な文章で頻出しそう。文章の特徴を表さないのでなくても構わなそうな単語がある *3 頻出しそうな単語をstopwordsに追加 { " settings ": { " analysis ": { " analyzer ": { " my_kuromoji_analyzer ": { " type ": " custom ", " char_filter " : [ " icu_normalizer ", " kuromoji_iteration_mark " ] , " tokenizer ": " kuromoji_tokenizer ", " filter ": [ " kuromoji_baseform ", " kuromoji_part_of_speech ", " kuromoji_stemmer ", " my_synonym_penguin_filter ", " my_stop_filter " ] } } , " filter ": { " my_synonym_penguin_filter ": { " type ": " synonym ", " synonyms ": [ " コウテイペンギン,ペンギン " ] } , " my_stop_filter ": { " type ": " stop ", " stopwords ": [ " いい ", " もの ", " ある ", " いう ", " それ ", " いる " ] } } } } } 設定を適用したら同じクエリを投げていきます。 すると以下のような結果になりました。 { " tokens ": [ { " token ":" コウテイペンギン ", " start_offset ": 0 , " end_offset ": 8 , " type ":" word ", " position ": 0 } , { " token ":" ペンギン ", " start_offset ": 0 , " end_offset ": 8 , " type ":" SYNONYM ", " position ": 0 } , { " token ":" 体格 ", " start_offset ": 9 , " end_offset ": 11 , " type ":" word ", " position ": 2 } , { " token ":" 130 ", " start_offset ": 17 , " end_offset ": 20 , " type ":" word ", " position ": 7 } , { " token ":" センチ ", " start_offset ": 20 , " end_offset ": 21 , " type ":" word ", " position ": 8 } , { " token ":" 僕 ", " start_offset ": 27 , " end_offset ": 28 , " type ":" word ", " position ": 12 } , { " token ":" 度度 ", " start_offset ": 29 , " end_offset ": 31 , " type ":" word ", " position ": 14 } , { " token ":" コンピュータ ", " start_offset ": 32 , " end_offset ": 39 , " type ":" word ", " position ": 15 } , { " token ":" 見る ", " start_offset ": 43 , " end_offset ": 44 , " type ":" word ", " position ": 19 } , { " token ":" 和む ", " start_offset ": 45 , " end_offset ": 47 , " type ":" word ", " position ": 21 } ] } しっかり設定したToken filterの効果が表れています kuromoji_baseform(原型へ変換) 見て→見る 和ん→和む kuromoji_part_of_speech(助詞-格助詞-一般, 助詞-終助詞の削除) コウテイペンギン "は" 、コンピューター"で"などが削除されています kuromoji_stemmer(語幹の統一) コンピューター→コンピュータのように伸ばし棒が除去されています Synonym Token filter コウテイペンギン が コウテイペンギン とペンギンの二語に展開されています Stop Token filter 設定した"いい", "もの", "ある", "いう", "それ", "いる"がそれぞれ除去されています。 おわりに 実際にAnalyzerを設定してみました。 Analyzerだけではなく、他にも様々な機能がElasticsearchにはあります。 次は検索ネタを話せたらと思います。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com *1 : MeCab 用の新語対応辞書 mecab -ipadic-neologdを適用した、Kuromojiの プラグイン があります *2 : 双方向展開 *3 : 実際に使用する際は公開されている ストップワード リストをメンテナンスするのが良い