Lucene:对同一个doc的多次调用UpdateDocument会导致分数不断增加

时间:2015-02-09 10:28:33

标签: c# lucene lucene.net

我对我正在观察的一些Lucene.NET行为感到困惑。我认为在Java的Lucene中也是如此,但尚未验证。这是一个测试来证明:

[Fact]
public void repro()
{
    var directory = new RAMDirectory();
    var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);

    float firstScore, secondScore, thirdScore;

    using (var indexWriter = new IndexWriter(directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
    {
        var document = new Document();
        document.Add(new Field("id", "abc", Field.Store.YES, Field.Index.NOT_ANALYZED));
        document.Add(new Field("field", "some text in the field", Field.Store.NO, Field.Index.ANALYZED));
        indexWriter.UpdateDocument(new Term("id", "abc"), document, analyzer);

        // the more times I call UpdateDocument here, the higher the score is for the subsequent hit
//                indexWriter.UpdateDocument(new Term("id", "abc"), document, analyzer);
        indexWriter.Commit();

        var queryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "field", analyzer);
        var parsedQuery = queryParser.Parse("some text in the field");

        using (var indexSearcher = new IndexSearcher(directory, readOnly: true))
        {
            var hits = indexSearcher.Search(parsedQuery, 10);
            Assert.Equal(1, hits.TotalHits);
            firstScore = hits.ScoreDocs[0].Score;
        }

        using (var indexSearcher = new IndexSearcher(directory, readOnly: true))
        {
            var hits = indexSearcher.Search(parsedQuery, 10);
            Assert.Equal(1, hits.TotalHits);
            secondScore = hits.ScoreDocs[0].Score;
        }

        document = new Document();
        document.Add(new Field("id", "abc", Field.Store.YES, Field.Index.NOT_ANALYZED));
        document.Add(new Field("field", "some changed text in the field", Field.Store.NO, Field.Index.ANALYZED));

        // if I call DeleteAll here, then score three is the same as score one and two (which is probably fine, though not quite what I expected either)
//                indexWriter.DeleteAll();

        indexWriter.UpdateDocument(new Term("id", "abc"), document, analyzer);
        indexWriter.Commit();

        using (var indexSearcher = new IndexSearcher(directory, readOnly: true))
        {
            var hits = indexSearcher.Search(parsedQuery, 10);
            Assert.Equal(1, hits.TotalHits);
            thirdScore = hits.ScoreDocs[0].Score;
        }
    }

    // this is fine
    Assert.Equal(firstScore, secondScore);

    // this is not
    Assert.True(thirdScore < secondScore);
}

步骤如下:

  1. 将文档添加到索引中,并将“字段中的某些文本”作为其索引文本。
  2. 两次搜索“字段中的某些文字”,将分数记录为firstScoresecondScore
  3. 更新文档,使索引文本现在为“字段中的某些更改文本”
  4. 再次搜索“字段中的某些文字”,将分数记录为thirdScore
  5. 断言第一和第二分数相等,第三分数小于第一和第二分数
  6. 真正奇怪的是thirdScore 更大而不是firstScoresecondScore。这是我发现的:

    • 我使用相同文档在索引上调用UpdateDocument的次数越多,得分就越高
    • 在执行第三次搜索之前完全删除索引会产生等于第一和第二分数的分数。由于索引文本中的额外单词(“已更改”),我期待更少一点,但即使得分相等也足够
    • 抵制RemoveDocument,而是手动删除和添加文档没有区别
    • 提交后
    • 在索引上调用WaitForMerges没有区别

    任何人都可以向我解释这种行为吗?当文档内容和查询都没有改变时,为什么分数会随着文档的后续更新而改变?

1 个答案:

答案 0 :(得分:3)

首先,在尝试理解为什么以某种方式得分时应该注意的最有用的工具:IndexSearcher.Explain

Explanation explain = indexSearcher.Explain(parsedQuery, hits.ScoreDocs[0].Doc);

这些内容为我们详细解释了该分数是如何得出的。在这种情况下,两个不同的评分查询看起来非常相似除了第三个​​查询的idf分数如下所示:

  

0.5945349 = idf(docFreq = 2,maxDocs = 2)

与前两个查询相比:

  

0.3068528 = idf(docFreq = 1,maxDocs = 1)

Lucene更新只是删除后插入。删除通常只标记要删除的文档,并等到以后实际从索引中清除数据。因此,您不会在搜索结果中看到已删除的文档,但它们仍会影响docfreq等统计信息。当您拥有大量数据时,影响通常非常小。

您可以强制索引到ExpungeDeletes以查看:

indexWriter.UpdateDocument(new Term("id", "abc"), document, analyzer);
indexWriter.Commit();

//arugment=true to block until completed.
indexWriter.ExpungeDeletes(true);
indexWriter.Commit();

然后你应该看到他们都得到相同的分数。

请记住,删除删除操作可能是一项非常昂贵的操作。在实践中,您可能在每次更新后都这样做。


至于为什么你得到的文件得分与#34;字段中的一些文字&#34;并且在#34;字段中有一些更改文本&#34;,您指的是lengthNorm分数因子。 lengthNorm在索引时计算,并存储在字段的范数中,并且规范以非常有损的方式压缩,直到单字节,以提高性能。总而言之,它们具有三位精度,甚至不是一个有效的十进制数字。因此,在得分中表示的那两者之间没有足够的差异。尝试使用类似的东西:

  

在字段中更显着地改变了一些文本

你应该看到lengthNorm生效。