先ほど、こんなエントリを書きました。
Apache Solr 5.xで、サジェストを実装することを考える
http://d.hatena.ne.jp/Kazuhira/20150912/1442047726
これを書く時にSuggesterをいったん置いておいてEdgeNGramにチャレンジしましたが、そもそもこちらのルートも試すべきかな?と思い、実践してみることに。
合わせて、Did You Mean?にもトライしてみようかと。
設定は、まずはschema.xmlから。
<fieldType name="text_suggest_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true"> <analyzer> <tokenizer class="solr.JapaneseTokenizerFactory" mode="normal"/> <filter class="solr.JapaneseBaseFormFilterFactory"/> <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" /> <filter class="solr.CJKWidthFilterFactory"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" /> <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType> <fieldType name="text_suggest_reading_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true"> <analyzer> <tokenizer class="solr.JapaneseTokenizerFactory" mode="normal"/> <filter class="solr.JapaneseBaseFormFilterFactory"/> <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" /> <filter class="solr.CJKWidthFilterFactory"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" /> <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/> <filter class="solr.JapaneseReadingFormFilterFactory" useRomaji="false"/> <filter class="solr.ICUTransformFilterFactory" id="Katakana-Hiragana"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
リンク先の定義よりもっと簡素にして(EdgeNGram削除、インデックスとクエリのAnalyzer統合)、単語そのものと読みに対するfieldTypeを定義。ICU関係のものは使っているので、ライブラリのコピーは必要です。
※リンク先のエントリ参照
フィールド定義は、こちら。
<field name="content" type="string" indexed="true" stored="true" required="true" multiValued="false" /> <field name="content_ja" type="text_suggest_ja" indexed="true" stored="false" multiValued="false" /> <field name="content_reading_ja" type="text_suggest_reading_ja" indexed="true" stored="false" multiValued="false" /> <copyField source="content" dest="content_ja"/> <copyField source="content" dest="content_reading_ja"/> <uniqueKey>content</uniqueKey>
solrconfig.xmlに、サジェスト用とDid You Mean?用の設定を書いていきます。
まずは、サジェスト用(autocomplete_ja)。
<searchComponent name="autocomplete_ja" class="solr.SpellCheckComponent"> <str name="queryAnalyzerFieldType">text_suggest_ja</str> <lst name="spellchecker"> <str name="name">autocomplete_ja</str> <str name="field">content_ja</str> <str name="classname">org.apache.solr.spelling.suggest.Suggester</str> <str name="lookupImpl">org.apache.solr.spelling.suggest.fst.AnalyzingLookupFactory</str> <str name="storeDir">autocomplete_ja</str> <str name="buildOnCommit">true</str> <int name="maxEdits">2</int> <str name="comparatorClass">freq</str> <str name="suggestAnalyzerFieldType">text_suggest_ja</str> <bool name="extraMatchFirst">true</bool> </lst> </searchComponent> <requestHandler name="/autocomplete_ja" class="solr.SearchHandler" startup="lazy"> <lst name="defaults"> <str name="spellcheck.dictionary">autocomplete_ja</str> <str name="spellcheck">on</str> <str name="spellcheck.count">10</str> <str name="spellcheck.collate">true</str> <str name="spellcheck.onlyMorePopular">true</str> </lst> <arr name="last-components"> <str>autocomplete_ja</str> </arr> </requestHandler>
サジェスト用(読み)(autocomplete_reading_ja)
<searchComponent name="autocomplete_reading_ja" class="solr.SpellCheckComponent"> <str name="queryAnalyzerFieldType">text_suggest_reading_ja</str> <lst name="spellchecker"> <str name="name">autocomplete_reading_ja</str> <str name="field">content_reading_ja</str> <str name="classname">org.apache.solr.spelling.suggest.Suggester</str> <str name="lookupImpl">org.apache.solr.spelling.suggest.fst.AnalyzingLookupFactory</str> <str name="storeDir">autocomplete_reading_ja</str> <str name="buildOnCommit">true</str> <int name="maxEdits">2</int> <str name="comparatorClass">freq</str> <str name="suggestAnalyzerFieldType">text_suggest_reading_ja</str> <bool name="extraMatchFirst">true</bool> </lst> </searchComponent> <requestHandler name="/autocomplete_reading_ja" class="solr.SearchHandler" startup="lazy"> <lst name="defaults"> <str name="spellcheck.dictionary">autocomplete_reading_ja</str> <str name="spellcheck">on</str> <str name="spellcheck.count">10</str> <str name="spellcheck.collate">true</str> <str name="spellcheck.onlyMorePopular">true</str> </lst> <arr name="last-components"> <str>autocomplete_reading_ja</str> </arr> </requestHandler>
Did You Mean?(didyoumean_ja)
<searchComponent name="didyoumean_ja" class="solr.SpellCheckComponent"> <str name="queryAnalyzerFieldType">text_suggest_ja</str> <lst name="spellchecker"> <str name="name">didyoumean_ja</str> <str name="field">content</str> <str name="classname">org.apache.solr.spelling.suggest.Suggester</str> <str name="lookupImpl">org.apache.solr.spelling.suggest.fst.FuzzyLookupFactory</str> <str name="storeDir">didyoumean_ja</str> <str name="buildOnCommit">true</str> <int name="maxEdits">2</int> <str name="comparatorClass">score</str> <str name="suggestAnalyzerFieldType">text_suggest_ja</str> </lst> </searchComponent> <requestHandler name="/didyoumean_ja" class="solr.SearchHandler" startup="lazy"> <lst name="defaults"> <str name="spellcheck.dictionary">didyoumean_ja</str> <str name="spellcheck">on</str> <str name="spellcheck.count">1</str> <str name="spellcheck.collate">true</str> <str name="spellcheck.onlyMorePopular">true</str> </lst> <arr name="last-components"> <str>didyoumean_ja</str> </arr> </requestHandler>
では、データを用意して
data.json [ { "content": "吾輩は猫である" }, { "content": "我が名は青春のエッセイドラゴン" }, { "content": "下町ロケット" }, { "content": "北斗の拳" }, { "content": "進撃の巨人" } ]
登録。
$ curl 'http://localhost:8983/solr/mycore/update?commit=true' --data-binary @data.json
動作確認してみます。
サジェスト(autocomplete_ja)から。
## 「下」でサジェスト $ curl 'http://localhost:8983/solr/mycore/autocomplete_ja?wt=json&indent=true&spellcheck.collate=false' -d '{ "query": "content_ja:下" }' { "responseHeader":{ "status":0, "QTime":1}, "response":{"numFound":0,"start":0,"docs":[] }, "spellcheck":{ "suggestions":[ "下",{ "numFound":1, "startOffset":11, "endOffset":12, "suggestion":["下町"]}]}} ## 「進」でサジェスト $ curl 'http://localhost:8983/solr/mycore/autocomplete_ja?wt=json&indent=true&spellcheck.collate=false' -d '{ "query": "content_ja:進" }' { "responseHeader":{ "status":0, "QTime":2}, "response":{"numFound":0,"start":0,"docs":[] }, "spellcheck":{ "suggestions":[ "進",{ "numFound":1, "startOffset":11, "endOffset":12, "suggestion":["進撃"]}]}}
動いてそうです。
続いて、読み(autocomplete_reading_ja)の方。
## 「した」 $ curl 'http://localhost:8983/solr/mycore/autocomplete_reading_ja?wt=json&indent=true&spellcheck.collate=false' -d '{ "query": "content_reading_ja:した" }' { "responseHeader":{ "status":0, "QTime":1}, "response":{"numFound":0,"start":0,"docs":[] }} ## 「わが」 $ curl 'http://localhost:8983/solr/mycore/autocomplete_reading_ja?wt=json&indent=true&spellcheck.collate=false' -d '{ "query": "content_reading_ja:わが" }' { "responseHeader":{ "status":0, "QTime":2}, "response":{"numFound":1,"start":0,"docs":[ { "content":"我が名は青春のエッセイドラゴン", "_version_":1512112578393800704}] }, "spellcheck":{ "suggestions":[ "わが",{ "numFound":1, "startOffset":19, "endOffset":21, "suggestion":["わがはい"]}]}}
なんか、微妙な結果に…。
本の通りに、ローマ字でやった方がよかったかな…。
それに、この設定だとローマ字で入力された場合はサジェストできないんですよねぇ。本当に、大変…。
最後、Did You Mean?(didyoumean_ja)…といきたかったのですが、ドキュメント数、単語数が不足してて表示できず…。ちょっと多めにドキュメント投入してから、また確認します。
追試
というわけで、Did You Mean?がうまくいかなかったので、以下のエントリのプログラムとインデックス定義を使って、それなりのドキュメント量で再度チャレンジ。
Apache Solr 5.x+crawler4jで、Webサイトをクロールしてインデックス化する
http://d.hatena.ne.jp/Kazuhira/20150912/1442056103
こちらの場合でのDid You Mean?の定義だけ、載せておきます。
<searchComponent name="didyoumean_ja" class="solr.SpellCheckComponent"> <str name="queryAnalyzerFieldType">text_ja</str> <lst name="spellchecker"> <str name="name">didyoumean_ja</str> <str name="field">contents</str> <str name="classname">org.apache.solr.spelling.suggest.Suggester</str> <str name="lookupImpl">org.apache.solr.spelling.suggest.fst.FuzzyLookupFactory</str> <str name="buildOnCommit">true</str> <int name="maxEdits">2</int> <str name="comparatorClass">score</str> <str name="suggestAnalyzerFieldType">text_ja</str> </lst> </searchComponent> <requestHandler name="/didyoumean_ja" class="solr.SearchHandler" startup="lazy"> <lst name="defaults"> <str name="spellcheck.dictionary">didyoumean_ja</str> <str name="spellcheck">on</str> <str name="spellcheck.count">1</str> <str name="spellcheck.collate">true</str> <str name="spellcheck.onlyMorePopular">true</str> </lst> <arr name="last-components"> <str>didyoumean_ja</str> </arr> </requestHandler>
検索。
$ curl 'http://localhost:8983/solr/mycore/didyoumean_ja?wt=json&indent=true&spellcheck.collate=true' -d '{ "query": "contents:メガネ" }' { "responseHeader":{ "status":0, "QTime":2}, "response":{"numFound":0,"start":0,"docs":[] }, "spellcheck":{ "suggestions":[ "メガネ",{ "numFound":1, "startOffset":9, "endOffset":12, "suggestion":["メソッド"]}], "collations":[ "collation","contents:メソッド"]}}
「メガネ」で「メソッド」が現れました。まあ、「メガネ」とかうちのエントリに滅多に入っていませんからね…。
URLに「spellcheck.collate=true」を入れておくと、次に実行すべきと思われるクエリを提示してくれます。
「Lucene」のスペルミスと組み合わせて。
$ curl 'http://localhost:8983/solr/mycore/didyoumean_ja?wt=json&indent=true&spellcheck.collate=true' -d '{ "query": "contents:lucne contents:漢語" }' { "responseHeader":{ "status":0, "QTime":10}, "response":{"numFound":0,"start":0,"docs":[] }, "spellcheck":{ "suggestions":[ "lucne",{ "numFound":1, "startOffset":9, "endOffset":14, "suggestion":["lucene"]}, "漢語",{ "numFound":1, "startOffset":24, "endOffset":26, "suggestion":["新語"]}], "collations":[ "collation","contents:lucene contents:新語"]}}
Luceneは正しく訂正されましたが、「漢語」が「新語」になりました…。日本語は、やっぱり挙動がわかりにくいですね。
とりあえず、動かせましたということで。