1

我正在为任意文本/短语实现类似自动建议的功能。我想为用户提供一些绝对的相关性,而不仅仅是找到的项目相对于彼此的排名。即,如果数据库中的某些行包含以确切形式和顺序输入的文本,我需要一个大约“1”的排名。

例如,让我们搜索“我喜欢水果”:

  • 对于内容为“我肯定喜欢水果和蔬菜”的行,我希望排名为1或更低。
  • 对于内容“我喜欢新鲜水果”的行,我希望结果小于 1 但仍然很高,例如0.7

这可以用 MySQL 和 FULLTEXT 完成吗?而不是 [0,1] 我得到像 2.7 或 1.2 甚至 0.6 这样的值来进行完全匹配。它出什么问题了?

这是我的测试表:

表数据:

id  text
1   Lorem ipsum dolor
2   You can search an index, and organize and present search results.
3   The Search API can index any number of documents.
4   Each field has a name and a type.
5   Each field is required.
7   Cras dapibus. Vivamus elementum semper nisi. 
8   Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. 
9   Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. 
10  Nam eget dui. Etiam rhoncus. 

查询一:

SET @str := 'Lorem ipsum dolor';
SELECT id, TEXT, MATCH (TEXT) AGAINST (@str) rank FROM test WHERE MATCH (TEXT) AGAINST (@str);

->

 id text    rank
 1  Lorem ipsum dolor   1.280059814453125

这个“1.28”是什么意思?在这里有'1'会很好。

查询 2:

SET @str := 'Each field is required.';
SELECT id, TEXT, MATCH (TEXT) AGAINST (@str) rank FROM test WHERE MATCH (TEXT) AGAINST (@str); 

->

 id text    rank
 5  Each field is required. 1.7639520168304443
 4  Each field has a name and a type.   0.8533731698989868

还有一场完整的比赛,我也希望这里是“1”。

查询 3:

SET @str := 'Aenean leo ligula, porttitor eu';
SELECT id, TEXT, MATCH (TEXT) AGAINST (@str) rank FROM test WHERE MATCH (TEXT) AGAINST (@str);

->

id  text    rank
8   Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.  3.5851094722747803
9   Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi.     0.4266865849494934

也完全匹配,并返回神秘的“3.58”。

我不需要精确的数字,只需要 [0,1] 之间的数字,这样我就可以确定是否存在完整(或几乎完整)匹配。如果结果完全包含搜索到的字符串,我需要大约 0.8..1 的内容。或者它只是一个错误的工具?FULLTEXT 实际上并不是那么完整的文本,因为我不知道它是否完全匹配。

4

2 回答 2

3

看起来不可能仅使用 MySQL 将排名标准化为 [0,1]。通过规范化到 [0,1] 我的意思是让(几乎)完全匹配的行的值接近 1,而不仅仅是排名最高的行。例如,如果我搜索,"one apple, two oranges"我可能会得到一个像"one two three". 在这种情况下,使用公式 rank=row_rank/highest_rank我会得到 rank=1。但这不是完全匹配。我宁愿期望值在 0.5 左右或更少(找到一半的搜索词)。

我还研究了 Lucene 和 Sphinx。排名者很少,但似乎 rank=1 的定义可能完全取决于应用程序要求。例如,如果搜索的短语完全包含在 DB 中,我需要 rank=1,但是当搜索的短语与 DB 中的全部内容匹配时,有人可能会期望它为 1。


所以,我通过三个步骤解决了这个问题:

x2/fudge1. 使用 FULLTEXT 和Aaron 建议的方法从 DB 中获取排名前 100 的行:

SELECT id, TEXT, (MATCH (TEXT) AGAINST (@str) *
                  IF(TEXT LIKE CONCAT("%", @str, "%"), 2, 1)) AS rank
FROM test
WHERE MATCH (TEXT) AGAINST (@str) ORDER BY rank DESC LIMIT 100;

这完成了检索最相关行的所有繁重工作,减少了下一步的数据量。事实上,来自 MySQL 的 rank 值完全被忽略了。

2. 对于 100 行中的每一行,根据应用程序要求(如 [0,1] 范围)在 Java/Groovy 端以编程方式计算归一化排名

这非常具有挑战性,但我能够基于简单的数学公式和少量规则创建相对简单的算法。经过一些优化后,计算所有 100 行的排名大约需要 6 毫秒。

3.按新排名对结果进行排序,将前10名的结果显示给用户。

我不显示排名 < 0.5 的结果,在 UI 中我还强调排名高的结果 (0.8-1)

我测试了它,它工作得很好。但是,在某些情况下,从第 1 步开始的 FULLTEXT 搜索根本不会返回结果。当搜索的短语通常存在于数据库中时会发生这种情况,但很少有词尾不同。我的排名算法可能会将其评估为 0.3-0.7 排名,但它并非来自第 1 步。所以,现在我将继续使用这种方法,但稍后可能会考虑在步骤#1 中用其他东西(可能是 Lucene)替换 MySQL。

于 2013-10-20T20:20:02.193 回答
3

MySQL 使用带有一些 fudge 因子的 n 维向量积来生成全文匹配的相关值,这意味着除了给定的数据集和查询之外,这些值无法以一般方式进行规范化。(话又说回来,你为什么需要它们呢?在同一个数据集上的类似查询已经产生了相似的相关性值,而且不同查询结果之间的规范化无论如何也无济于事。)

也就是说,没有什么能阻止您在查询返回的结果集中使用每行的简单计算来自己标准化排名值:

row_normalized_rank = row_returned_rank / highest_returned_rank

这将需要对结果集进行两次扫描,一次识别返回的最高排名值,另一次根据返回的最高值对每一行的排名值进行归一化;您可能可以使用足够糟糕的嵌套查询来完成此操作,但您最好在代码中执行此操作。

您还可以自己添加一个软糖因子,以提高精确匹配的排名;考虑一个绝对排名调整,例如:

SELECT id, text, (MATCH (text) AGAINST (@str) +
                  IF(text LIKE CONCAT("%", @str, "%"), 1, -1)) AS rank
FROM test
WHERE MATCH (text) AGAINST (@str);

或幅度扩大,例如:

SET @fudge := 2;
SELECT id, text, (MATCH (text) AGAINST (@str) *
                  IF(text LIKE CONCAT("%", @str, "%"), @fudge, 1/@fudge)) AS rank
FROM test
WHERE MATCH (text) AGAINST (@str);

当然,根据口味进行调整,但这应该有助于在排名行为方面为您提供更像您正在寻找的东西。

于 2013-10-17T22:28:21.177 回答