<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>11.1. 同じ検索キーワードなのに全文検索結果が異なる — groonga v3.0.5 documentation</title> <link rel="stylesheet" href="../_static/groonga.css" type="text/css" /> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> <script type="text/javascript"> var DOCUMENTATION_OPTIONS = { URL_ROOT: '../', VERSION: '3.0.5', COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', HAS_SOURCE: true }; </script> <script type="text/javascript" src="../_static/jquery.js"></script> <script type="text/javascript" src="../_static/underscore.js"></script> <script type="text/javascript" src="../_static/doctools.js"></script> <link rel="shortcut icon" href="../_static/favicon.ico"/> <link rel="top" title="groonga v3.0.5 documentation" href="../index.html" /> <link rel="up" title="11. トラブルシューティング" href="../troubleshooting.html" /> <link rel="next" title="12. Development" href="../development.html" /> <link rel="prev" title="11. トラブルシューティング" href="../troubleshooting.html" /> </head> <body> <div class="header"> <h1 class="title"> <a id="top-link" href="../index.html"> <span class="project">groonga</span> <span class="separator">-</span> <span class="description">An open-source fulltext search engine and column store.</span> </a> </h1> <div class="other-language-links"> <ul> <li><a href="../../../ja/html/troubleshooting/different_results_with_the_same_keyword.html"><img src="../_static/jp.png" alt="日本語">日本語版はこちら</a></li> </ul> </div> </div> <div class="related"> <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> <a href="../genindex.html" title="General Index" accesskey="I">index</a></li> <li class="right" > <a href="../development.html" title="12. Development" accesskey="N">next</a> |</li> <li class="right" > <a href="../troubleshooting.html" title="11. トラブルシューティング" accesskey="P">previous</a> |</li> <li><a href="../index.html">groonga v3.0.5 documentation</a> »</li> <li><a href="../troubleshooting.html" accesskey="U">11. トラブルシューティング</a> »</li> </ul> </div> <div class="document"> <div class="documentwrapper"> <div class="bodywrapper"> <div class="body"> <div class="section" id="id1"> <h1>11.1. 同じ検索キーワードなのに全文検索結果が異なる<a class="headerlink" href="#id1" title="Permalink to this headline">¶</a></h1> <p>同じ検索キーワードでも一緒に指定するクエリによっては全文検索の結果が異なることがあります。ここでは、その原因と対策方法を説明します。</p> <div class="section" id="id2"> <h2>11.1.1. 例<a class="headerlink" href="#id2" title="Permalink to this headline">¶</a></h2> <p>まず、実際に検索結果が異なる例を説明します。</p> <p>DDLは以下の通りです。BlogsテーブルのbodyカラムをTokenMecabトークナイザーを使ってトークナイズしてからインデックスを作成しています。:</p> <div class="highlight-none"><div class="highlight"><pre>table_create Blogs TABLE_NO_KEY column_create Blogs body COLUMN_SCALAR ShortText column_create Blogs updated_at COLUMN_SCALAR Time table_create Terms TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenMecab column_create Terms blog_body COLUMN_INDEX|WITH_POSITION Blogs body </pre></div> </div> <p>テスト用のデータは1件だけ投入します。:</p> <div class="highlight-none"><div class="highlight"><pre>load --table Blogs [ ["body", "updated_at"], ["東京都民に深刻なダメージを与えました。", "2010/9/21 10:18:34"], ] </pre></div> </div> <p>まず、全文検索のみで検索します。この場合ヒットします。:</p> <div class="highlight-none"><div class="highlight"><pre>> select Blogs --filter 'body @ "東京都"' [[0,4102.268052438,0.000743783],[[[1],[["_id","UInt32"],["updated_at","Time"],["body","ShortText"]],[1,1285031914.0,"東京都民に深刻なダメージを与えました。"]]]] </pre></div> </div> <p>続いて、範囲指定と全文検索を組み合わせて検索します(1285858800は2010/10/1 0:0:0の秒表記)。この場合もヒットします。:</p> <div class="highlight-none"><div class="highlight"><pre>> select Blogs --filter 'body @ "東京都" && updated_at < 1285858800' [[0,4387.524084839,0.001525487],[[[1],[["_id","UInt32"],["updated_at","Time"],["body","ShortText"]],[1,1285031914.0,"東京都民に深刻なダメージを与えました。"]]]] </pre></div> </div> <p>最後に、範囲指定と全文検索の順番を入れ替えて検索します。個々の条件は同じですが、この場合はヒットしません。:</p> <div class="highlight-none"><div class="highlight"><pre>> select Blogs --filter 'updated_at < 1285858800 && body @ "東京都"' [[0,4400.292570838,0.000647716],[[[0],[["_id","UInt32"],["updated_at","Time"],["body","ShortText"]]]]] </pre></div> </div> <p>どうしてこのような挙動になるかを説明します。</p> </div> <div class="section" id="id3"> <h2>11.1.2. 原因<a class="headerlink" href="#id3" title="Permalink to this headline">¶</a></h2> <p>このような挙動になるのは全文検索時に複数の検索の挙動を使い分けているからです。ここでは簡単に説明するので、詳細は <a class="reference internal" href="../spec/search.html"><em>検索</em></a> を参照してください。</p> <p>検索の挙動には以下の3種類があります。</p> <ol class="arabic simple"> <li>完全一致検索</li> <li>非わかち書き検索</li> <li>部分一致検索</li> </ol> <p>groongaは基本的に完全一致検索のみを行います。上記の例では「東京都民に深刻なダメージを与えました。」を「東京都」というクエリで検索していますが、TokenMecabトークナイザーを使っている場合はこのクエリはマッチしません。</p> <p>検索対象の「東京都民に深刻なダメージを与えました。」は</p> <blockquote> <div>東京 / 都民 / に / 深刻 / な / ダメージ / を / 与え / まし / た / 。</div></blockquote> <p>とトークナイズされますが、クエリの「東京都」は</p> <blockquote> <div>東京 / 都</div></blockquote> <p>とトークナイズされるため、完全一致しません。</p> <p>groongaは完全一致検索した結果のヒット件数が所定の閾値を超えない場合に限り、非わかち書き検索を行い、それでもヒット件数が閾値を超えない場合は部分一致検索を行います(閾値は1がデフォルト値となっています)。このケースのデータは部分一致検索ではヒットするので、「東京都」クエリのみを指定するとヒットします。</p> <p>しかし、以下のように全文検索前にすでに閾値が越えている場合(「updated_at < 1285858800」で1件ヒットし、閾値を越える)は、たとえ完全一致検索で1件もヒットしない場合でも部分一致検索などを行いません。:</p> <div class="highlight-none"><div class="highlight"><pre>select Blogs --filter 'updated_at < 1285858800 && body @ "東京都"' </pre></div> </div> <p>そのため、条件の順序を変えると検索結果が変わるという状況が発生します。以下で、この情報を回避する方法を2種類紹介しますが、それぞれトレードオフとなる条件があるので採用するかどうかを十分検討してください。</p> </div> <div class="section" id="id4"> <h2>11.1.3. 対策方法1: トークナイザーを変更する<a class="headerlink" href="#id4" title="Permalink to this headline">¶</a></h2> <p>TokenMecabトークナイザーは事前に準備した辞書を用いてトークナイズするため、再現率よりも適合率を重視したトークナイザーと言えます。一方、TokenBigramなど、N-gram系のトークナイザーは適合率を重視したトークナイザーと言えます。例えば、TokenMecabの場合「東京都」で「京都」に完全一致することはありませんが、TokenBigramでは完全一致します。一方、TokenMecabでは「東京都民」に完全一致しませんが、TokenBigramでは完全一致します。</p> <p>このようにN-gram系のトークナイザーを指定することにより再現率をあげることができますが、適合率が下がり検索ノイズが含まれる可能性が高くなります。この度合いを調整するためには <a class="reference internal" href="../reference/commands/select.html"><em>select</em></a> のmatch_columnsで使用する索引毎に重み付けを指定します。</p> <p>ここでも、前述の例を使って具体例を示します。まず、TokenBigramを用いた索引を追加します。:</p> <div class="highlight-none"><div class="highlight"><pre>table_create Bigram TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenBigram column_create Bigram blog_body COLUMN_INDEX|WITH_POSITION Blogs body </pre></div> </div> <p>この状態でも以前はマッチしなかったレコードがヒットするようになります。:</p> <div class="highlight-none"><div class="highlight"><pre>> select Blogs --filter 'updated_at < 1285858800 && body @ "東京都"' [[0,7163.448064902,0.000418127],[[[1],[["_id","UInt32"],["updated_at","Time"],["body","ShortText"]],[1,1285031914.0,"東京都民に深刻なダメージを与えました。"]]]] </pre></div> </div> <p>しかし、N-gram系のトークナイザーの方がTokenMecabトークナイザーよりも語のヒット数が多いため、N-gram系のヒットスコアの方が重く扱われてしまいます。N-gram系のトークナイザーの方がTokenMecabトークナイザーよりも適合率の低い場合が多いので、このままでは検索ノイズが上位に表示される可能性が高くなります。</p> <p>そこで、TokenMecabトークナイザーを使って作った索引の方をTokenBigramトークナイザーを使って作った索引よりも重視するように重み付けを指定します。これは、match_columnsオプションで指定できます。:</p> <div class="highlight-none"><div class="highlight"><pre>> select Blogs --match_columns 'Terms.blog_body * 10 || Bigram.blog_body * 3' --query '東京都' --output_columns '_score, body' [[0,8167.364602632,0.000647003],[[[1],[["_score","Int32"],["body","ShortText"]],[13,"東京都民に深刻なダメージを与えました。"]]]] </pre></div> </div> <p>この場合はスコアが11になっています。内訳は、Terms.blog_body索引(TokenMecabトークナイザーを使用)でマッチしたので10、Bigram.blog_body索引(TokenBigramトークナイザーを使用)でマッチしたので3、これらを合計して13になっています。このようにTokenMecabトークナイザーの重みを高くすることにより、検索ノイズが上位にくることを抑えつつ再現率を上げることができます。</p> <p>この例は日本語だったのでTokenBigramトークナイザーでよかったのですが、アルファベットの場合はTokenBigramSplitSymbolAlphaトークナイザーなども利用する必要があります。例えば、「楽しいbilliard」はTokenBigramトークナイザーでは</p> <blockquote> <div>楽し / しい / billiard</div></blockquote> <p>となり、「bill」では完全一致しません。一方、TokenBigramSplitSymbolAlphaトークナイザーを使うと</p> <blockquote> <div>楽し / しい / いb / bi / il / ll / li / ia / ar / rd / d</div></blockquote> <p>となり、「bill」でも完全一致します。</p> <p>TokenBigramSplitSymbolAlphaトークナイザーを使う場合も重み付けを考慮する必要があることはかわりありません。</p> <p>利用できるバイグラム系のトークナイザーの一覧は以下の通りです。</p> <ul class="simple"> <li>TokenBigram: バイグラムでトークナイズする。連続する記号・アルファベット・数字は一語として扱う。</li> <li>TokenBigramSplitSymbol: 記号もバイグラムでトークナイズする。連続するアルファベット・数字は一語として扱う。</li> <li>TokenBigramSplitSymbolAlpha: 記号とアルファベットもバイグラムでトークナイズする。連続する数字は一語として扱う。</li> <li>TokenBigramSplitSymbolAlphaDigit: 記号・アルファベット・数字もバイグラムでトークナイズする。</li> <li>TokenBigramIgnoreBlank: バイグラムでトークナイズする。連続する記号・アルファベット・数字は一語として扱う。空白は無視する。</li> <li>TokenBigramIgnoreBlankSplitSymbol: 記号もバイグラムでトークナイズする。連続するアルファベット・数字は一語として扱う。空白は無視する。</li> <li>TokenBigramIgnoreBlankSplitSymbolAlpha: 記号とアルファベットもバイグラムでトークナイズする。連続する数字は一語として扱う。空白は無視する。</li> <li>TokenBigramIgnoreBlankSplitSymbolAlphaDigit: 記号・アルファベット・数字もバイグラムでトークナイズする。空白は無視する。</li> </ul> </div> <div class="section" id="id5"> <h2>11.1.4. 対策方法2: 閾値をあげる<a class="headerlink" href="#id5" title="Permalink to this headline">¶</a></h2> <p>非わかち書き検索・部分一致検索を利用するかどうかの閾値は--with-match-escalation-threshold configureオプションで変更することができます。以下のように指定すると、100件以下のヒット数であれば、たとえ完全一致検索でヒットしても、非わかち書き検索・部分一致検索を行います。:</p> <div class="highlight-none"><div class="highlight"><pre>% ./configure --with-match-escalation-threashold=100 </pre></div> </div> <p>この場合も対策方法1同様、検索ノイズが上位に現れる可能性が高くなることに注意してください。検索ノイズが多くなった場合は指定する値を低くする必要があります。</p> </div> </div> </div> </div> </div> <div class="sphinxsidebar"> <div class="sphinxsidebarwrapper"> <h3><a href="../index.html">Table Of Contents</a></h3> <ul> <li><a class="reference internal" href="#">11.1. 同じ検索キーワードなのに全文検索結果が異なる</a><ul> <li><a class="reference internal" href="#id2">11.1.1. 例</a></li> <li><a class="reference internal" href="#id3">11.1.2. 原因</a></li> <li><a class="reference internal" href="#id4">11.1.3. 対策方法1: トークナイザーを変更する</a></li> <li><a class="reference internal" href="#id5">11.1.4. 対策方法2: 閾値をあげる</a></li> </ul> </li> </ul> <h4>Previous topic</h4> <p class="topless"><a href="../troubleshooting.html" title="previous chapter">11. トラブルシューティング</a></p> <h4>Next topic</h4> <p class="topless"><a href="../development.html" title="next chapter">12. Development</a></p> <h3>This Page</h3> <ul class="this-page-menu"> <li><a href="../_sources/troubleshooting/different_results_with_the_same_keyword.txt" rel="nofollow">Show Source</a></li> </ul> <div id="searchbox" style="display: none"> <h3>Quick search</h3> <form class="search" action="../search.html" method="get"> <input type="text" name="q" /> <input type="submit" value="Go" /> <input type="hidden" name="check_keywords" value="yes" /> <input type="hidden" name="area" value="default" /> </form> <p class="searchtip" style="font-size: 90%"> Enter search terms or a module, class or function name. </p> </div> <script type="text/javascript">$('#searchbox').show(0);</script> </div> </div> <div class="clearer"></div> </div> <div class="related"> <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> <a href="../genindex.html" title="General Index" >index</a></li> <li class="right" > <a href="../development.html" title="12. Development" >next</a> |</li> <li class="right" > <a href="../troubleshooting.html" title="11. トラブルシューティング" >previous</a> |</li> <li><a href="../index.html">groonga v3.0.5 documentation</a> »</li> <li><a href="../troubleshooting.html" >11. トラブルシューティング</a> »</li> </ul> </div> <div class="footer"> © Copyright 2009-2013, Brazil, Inc. </div> </body> </html>