2

クエリ ' laser~ ' は ' laser 'を見つけられません。

Lucene を使用してGermanAnalyzerドキュメントをインデックスに保存しています。それぞれ「タイトル」フィールドが「レーザー」と「労働」の2つのドキュメントを保存します。その後、あいまいクエリを実行しますlaser~。Lucene は、「labor」を含むドキュメントのみを検索します。そのような検索を実装する Lucene-3x の方法は何ですか?

Lucene のソース コードを見ると、あいまい検索は "分析された" コンテンツで動作するように設計されていないと思いますが、これが事実かどうかはわかりません。

以下、いくつかの背景と発言...


OpenCms

最近誰かが私たちの OpenCms の検索で結果ページにドキュメントが見つからないことに気づいた後、私はこの動作に気付きました。一部のドイツのサイトで検索が失敗していました。少し調べてみると、次のことがわかりました。

  • OpenCms 8.5.1 を使用して検索を実行しており、Lucene 3.6.1 を使用して検索機能を実装しています。
  • デフォルトでは、OpenCms はorg.apache.lucene.analysis.de.GermanAnalyzerドイツ語ロケールの for サイトを使用してコンテンツとクエリを解析します。
  • サイトのコンテンツを保存していますField.Index.ANALYZED
  • 報告された失敗した検索では、検索クエリにチルダを追加して、あいまい検索を強制していました。

サンプルコード

問題を絞り込むために、Lucene 3.6.1 を直接実行するコードを書きました (3.6.2 もテストしましたが、どちらも同じように動作します)。Lucene 4+ では API とあいまい検索がわずかに異なることに注意してください。つまり、Lucene 4+ ではこの問題は発生しません。(残念ながら、OpenCms が依存している Lucene のバージョンを制御することはできません。)

// For the import clauses, see below
public static void main(String[] args) throws Exception {
    final Version VER = Version.LUCENE_36;
    // With the StandardAnalyzer or the EnglishAnalyzer
    // the search works as expected
    Analyzer analyzer = new GermanAnalyzer(VER);

    Directory index = new RAMDirectory();
    IndexWriterConfig config = new IndexWriterConfig(VER, analyzer);

    IndexWriter w = new IndexWriter(index, config);
    addDoc(w, "labor");
    addDoc(w, "laser");
    addDoc(w, "latex");
    w.close();

    String querystr = "laser~"; // Fuzzy search for 'title'
    Query q = new QueryParser(VER, "title", analyzer).parse(querystr);
    System.out.println("Querystr: " + querystr + "; Query: " + q);

    int hitsPerPage = 10;
    IndexReader reader = IndexReader.open(index);
    IndexSearcher searcher = new IndexSearcher(reader);
    TopScoreDocCollector collector = TopScoreDocCollector.create(
            hitsPerPage, true);
    searcher.search(q, collector);
    ScoreDoc[] hits = collector.topDocs().scoreDocs;

    System.out.println("Found " + hits.length + " hits.");
    for (int i = 0; i < hits.length; ++i) {
        int docId = hits[i].doc;
        Document d = searcher.doc(docId);
        System.out.println((i + 1) + ". " + d.get("title"));
    }
}

private static void addDoc(IndexWriter w, String title) throws Exception {
    Document doc = new Document();
    doc.add(new Field("title", title, Field.Store.YES, Field.Index.ANALYZED));
    w.addDocument(doc);
}

このコードの出力:

Querystr: laser~; Query: title:laser~0.5 <br>
Found 2 hits.<br>
1. labor<br>
2. latex<br>

コードが乱雑にならないように、意図的にインポート セクションをカットしました。プロジェクトをビルドするにはlucene-core-3.6.2.jar、 ( Apache アーカイブlucene-analyzers-3.6.2.jarからダウンロードできます) と次のインポートが必要です。

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.de.GermanAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

Lucene のデバッグの詳細と備考

  1. Lucene コードをデバッグしているときに、LuceneGermanAnalyzerがドキュメントのタイトルを次のようにインデックスに格納していることがわかりました。

    • 「レーザー」 - >「ラス」
    • 「労働」 -> 「労働」
    • 「ラテックス」 -> 「ラテックス」
  2. また、完全一致検索を使用するlaserと、クエリ文字列も分析されることがわかりました。laserクエリの前のコードの出力は次のとおりです。

    Querystr: laser; Query: title:las
    Found 1 hits.
    1. laser
    

    (2 回の実行でクエリが異なることに注意してください。title:laser~0.5最初の実行と2 回目の実行title:lasです。)

  3. すでにコメントしたように、StandardAnalyzerまたはEnglishAnalyzerファジー検索を使用すると、期待どおりに機能します。

    Querystr: laser~; Query: title:laser~0.5
    Found 3 hits.
    1. laser
    2. labor
    3. latex
    
  4. Lucene は、2 つの用語 (内) 間の類似度org.apache.lucene.search.FuzzyTermEnum.similarity(target: String)を、最短の用語の長さを基準にして計算します。Similarity戻り値:

    [...]
    1 - (editDistance / length)
    ここで、length は同一のプレフィックスを含む最短の用語 (テキストまたはターゲット) の長さであり、editDistance は 2 つの単語のレーベンシュタイン距離です。

    次の点に注意してください。

    similarity("laser","las"  ) = 1 - (2 / 3) = 1/3
    similarity("laser","labor") = 1 - (2 / 5) = 3/5
    
  5. 1を編集します。アナライザーから "laser" を明示的に除外しても、期待される検索結果が得られます。

    Analyzer analyzer = new GermanAnalyzer(VER, null, new HashSet() {
        {
            add("laser");
        }
    });
    

    出力:

    Querystr: laser~; Query: title:laser~0.5
    Found 3 hits.
    1. laser
    2. labor
    3. latex
    
4

1 に答える 1