人生裏ローテ

通年裏ローテを守って地味に重宝がられる人生を送りたいITエンジニアのブログ

SolrでNEologd-KuromojiとデフォルトKuromojiを共存させる

TL;DR

ハマりどころが多いので、課題はあるけどできるよ

背景

mecab-ipadic-NEologd をご存知でしょうか。

github.com

一言で言うとWeb上の言語資源を利用して拡充したMecab向けの辞書ですが、これを利用することで一般的な形態素解析辞書では難しかった固有名詞を正しく分かち書きできるため、文書検索においても有用です。

詳しい解説は ZOZOテックブログ に書いてあるので改めて書き足すことがないのですが、Apache SolrにNEologd-Kuromojiを適用すると以下のような問題が発生することが知られています。

neologdを組み込むことで辞書の語彙が大幅に増えましたが、増えすぎることによる問題も発生してしまいました。 複数語からなるブランド名を部分一致検索できなくなってしまいました。 例えばJIMMY CHOOというブランド名が1つの単語としてtokenizeされるために、JIMMYという検索クエリでヒットしなくなってしまいました。

この問題についてはngramでのトークナイズが解決策として提案されていますが、eDisMaxでqsを高めにとっている時など、あまりngramでのヒットを重視したくない場合もあるため、本稿ではNEologdを利用したKuromojiとデフォルトのKuromojiをApache Solrで併用する手法について解説します。

Solrのバージョンは4.10.4としていますが、他のバージョンでも動くはずです。

必要なもの

手順

JapaneseTokenizerのパッケージ名変更

特にGitを利用する必要がないので、Apacheアーカイブからソースを落としてきます。

http://archive.apache.org/dist/lucene/solr/4.10.4/

ソースを展開したらluceneディレクトリをIDEAに取り込んで、ソースディレクトリなどのマークを実施します。

Mark Directory as Sources Root

analysis/kuromoji以下はこれだけあれば問題ありません(まだ削れるかも)

マークが終わったら、 analysis/kuromoji/src/javaanalysis/kuomoji/tools/java のパッケージ名を変更します。

加えて、 build.xmlとbuild.propertiesを変更します。moco(beta)さんの下記記事を参考にすれば良いですが、以下の箇所を変更する必要があります。

http://mocobeta-backup.tumblr.com/post/114318023832/neologd-kuromoji-lucene-4-10-4-branch

    <!--target name="build-dict" depends="compile-tools, download-dict"-->
    <target name="build-dict" depends="compile-tools, build-dict-neologd">
        <sequential>
            <delete verbose="true">
                <fileset dir="${resources.dir}/example/chov/analysis/ja/dict" includes="**/*"/>
            </delete>
            <!--メモリを6Gに、DirectoryBuilderのパッケージ名を変更する -->
            <java fork="true" failonerror="true" maxmemory="6g" classname="example.chov.analysis.ja.util.DictionaryBuilder">
                <classpath>
                    <path refid="tools.classpath"/>
                </classpath>
                <assertions>
                    <enable package="example.chov"/>
                </assertions>
                <arg value="${dict.format}"/>
                <arg value="${dict.src.dir}"/>
                <arg value="${dict.target.dir}"/>
                <arg value="${dict.encoding}"/>
                <arg value="${dict.normalize}"/>
            </java>
        </sequential>
    </target>

また、assertで org.apache.lucene が指定されている箇所があるので削除します。

IDEAの準備が完了したら、ここからはUnix機でいきます。

ビルド

Ubuntuであれば sudo apt-get install mecab mecab-ipadic-utf8 をした上で、antビルドを実行できるようにしておきましょう。

ビルド自体は簡単です。

cd lucene/analysis/kuromoji/
ant ivy-bootstrap
ant clone-neologd
ant build-dict
ant jar-core

Solrの設定

ここは少し工夫が必要で、 WEB-INF/lib の下か、 ${SOLR_HOME}/lib の下に設置する必要があります。

また、パッケージ名を変更しているので、schema.xmlでの指定が少し変則的になります。

    <fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
      <analyzer>
        <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
        <!--<tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt"/>-->
        <!-- Reduces inflected verbs and adjectives to their base/dictionary forms (辞書形) -->
        <filter class="solr.JapaneseBaseFormFilterFactory"/>
        <!-- Removes tokens with certain part-of-speech tags -->
        <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
        <!-- Normalizes full-width romaji to half-width and half-width kana to full-width (Unicode NFKC subset) -->
        <filter class="solr.CJKWidthFilterFactory"/>
        <!-- Removes common tokens typically not useful for search, but have a negative effect on ranking -->
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
        <!-- Normalizes common katakana spelling variations by removing any last long sound character (U+30FC) -->
        <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
        <!-- Lower-cases romaji characters -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>
    <fieldType name="text_ja_neologd" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
      <analyzer>
        <tokenizer class="example.chov.analysis.ja.JapaneseTokenizerFactory" mode="search"/>
        <!--<tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt"/>-->
        <!-- Reduces inflected verbs and adjectives to their base/dictionary forms (辞書形) -->
        <filter class="example.chov.analysis.ja.JapaneseBaseFormFilterFactory"/>
        <!-- Removes tokens with certain part-of-speech tags -->
        <filter class="example.chov.analysis.ja.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
        <!-- Normalizes full-width romaji to half-width and half-width kana to full-width (Unicode NFKC subset) -->
        <filter class="solr.CJKWidthFilterFactory"/>
        <!-- Removes common tokens typically not useful for search, but have a negative effect on ranking -->
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
        <!-- Normalizes common katakana spelling variations by removing any last long sound character (U+30FC) -->
        <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
        <!-- Lower-cases romaji characters -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

JapaneseBaseFormFilterFactoryなどもパッケージ名を変更していますが、JapaneseReadingFormFilterなどで読みがうまく取れないことが多々あるため、日本語解析系のフィルタ/トークナイザはパッケージ名を変更するのがベターです。

結果

以下の2枚は同じcoreで ぶらり路上プロレス の番組キャプションを形態素解析した結果です。

f:id:tkmk_k:20181202115323p:plain
NEologd-Kuromoji

f:id:tkmk_k:20181202115319p:plain
デフォルトのKuromoji

NEologdでは「博多大吉」がとれている一方、「プロレス団体」が1単語になっていますね。

課題

最新版への追随が難しい

NEologdは更新頻度の高さがメリットですが、最新版に追随するためには一工夫必要です。

moco(beta)さんの手法ではtagを手元にもってくるので、pullするときに都度最新版を取得するようにして、ビルド時にneologd.versionをコマンドライン引数で渡すようにするのがベターかな、と考えています。

であればDockerコンテナ化してやればいいんですが、6GB使うコンテナをホイホイ使わせてもらうためにはけっこう頑張らないといけないので、各位がんばってください。

また、こうして毎回最新版の辞書を作ってもSolrの更新とタイミングを合わせるのが難しい(とくにレプリケーションを実施している場合)などの課題があるので、Solrの利用状況次第では使えなさそうな手法でしょう。

初回アクセスが遅くなる

これはTokenizerの仕様だと思うのですが、初回に大きめの辞書を読み込むのでレスポンスが低下します。

ALBなりCLBに繋ぐ前に1回クエリを発行しておきましょう。

まとめ

  • NEologdとデフォルト辞書のKuromojiは共存できます
  • 実運用との相性を考えると課題があるので各位がんばってください