草庐IT

java - 有关如何改进当前模糊搜索实现的建议

coder 2024-03-08 原文

我目前正在为术语Web服务实现模糊搜索,并且正在寻找有关如何改进当前实现的建议。太多的代码无法共享,但是我认为做出解释可能足以引起深思熟虑的建议。我知道要阅读很多东西,但我会很感激。

首先,术语基本上只是一些名称(或术语)。对于每个单词,我们将其按空格分成多个标记,然后遍历每个字符以将其添加到trie中。在终端节点上(例如,到达草莓中的字符y时),我们在列表中存储主术语列表的索引。因此,终端节点可以具有多个索引(因为草莓的终端节点将匹配“草莓”和“对草莓过敏”)。

至于实际的搜索,搜索查询也按空间分为标记。搜索算法针对每个 token 运行。搜索 token 的第一个字符必须是一个匹配项(因此,traw将永远不会匹配strawberry)。之后,我们遍历每个连续节点的子节点。如果有一个 child 具有匹配的字符,我们将使用搜索 token 的下一个字符继续搜索。如果 child 与给定字符不匹配,我们将使用搜索 token 的当前字符来查看 child (因此请不要前进)。这是模糊性部分,因此“stwb”将与“strawberry”匹配。

当我们到达搜索 token 的末尾时,我们将在该节点上搜索其余的trie结构,以获取所有可能的匹配项(因为主术语列表的索引仅在终端节点上)。我们称此为汇总。我们通过在BitSet上设置索引值来存储索引。然后,我们简单地从每个搜索 token 结果的结果中获取BitSets。然后,我们从“与”后的BitSet中获取前1000或5000个索引,并找到它们所对应的实际术语。我们使用Levenshtein对每个术语评分,然后按分数排序以获得最终结果。

这工作得很好并且非常快。树中有超过39万个节点,超过110万个实际术语名称。但是,目前存在问题。

例如,搜索“车猫”将返回“导管插入”,而我们不希望这样做(由于搜索查询是两个单词,因此结果至少应为两个)。这将很容易检查,但是并不需要像“导管插入术”这样的情况,因为这是两个字。理想情况下,我们希望它与“心脏导管插入术”相匹配。

根据需要对此进行更正,我们提出了一些更改。首先,我们在混合的深度/宽度搜索中遍历树。本质上,只要角色匹配,我们就会首先深入。那些不匹配的子节点将被添加到优先级队列中。优先级队列按编辑距离排序,该距离可在搜索Trie时计算(由于存在字符匹配,因此距离保持不变,否则,距离增加1)。这样,我们得到每个单词的编辑距离。
我们不再使用BitSet。相反,它是索引到Terminfo对象的映射。该对象存储查询短语和术语短语以及分数的索引。因此,如果搜索是“汽车猫”,匹配的术语是“导管插入程序”,则术语短语索引将为1,查询短语索引也将为1。对于“心脏导管插入术”,术语短语索引将为1,2,而查询短语索引也将为1,2。如您所见,之后查看术语短语索引和查询短语索引的计数非常简单,如果它们至少不等于搜索词计数,则可以将其丢弃。

之后,我们将加总单词的编辑距离,从与单词短语索引匹配的单词中删除单词,然后计算剩余的字母以获得真实的编辑距离。例如,如果您匹配“对草莓过敏”一词,而您的搜索查询是“稻草”,则草莓得分为7,那么您将使用词组索引从该词中删除草莓,然后进行计数“过敏”(减去空格)得到16分。

这为我们提供了我们期望的准确结果。但是,它太慢了。在一个单词搜索之前,我们可以获得25-40毫秒的时间,而现在它可能长达半秒。这很大程度上来自于实例化TermInfo对象,使用.add()操作,.put()操作以及必须返回大量匹配项这一事实。我们可以将每个搜索限制为仅返回1000个匹配项,但不能保证“car”的前1000个结果将与“cat”的前1000个匹配项中的任何一个匹配(请记住,有超过110万个字词)。

即使对于单个查询词(例如cat),我们仍然需要进行大量匹配。这是因为如果我们搜索“cat”,则搜索将匹配car并汇总其下方的所有终端节点(这会很多)。但是,如果我们限制结果的数量,那么将会过于强调以查询开头而不是编辑距离的单词。因此,比起外套,更可能包含诸如导管插入术之类的词。

因此,基本上,是否有关于如何处理第二个实现所解决的问题的想法,而又没有第二个实现所引入的速度减慢的问题呢?如果可以使一些事情变得更清楚,我可以包括一些选定的代码,但是我不想张贴大量的代码。

最佳答案

哇...辛苦了

那么,为什么不实现Lucene?当您遇到诸如afaik之类的问题时,它是最新最好的技术。

但是我想分享一些想法...

模糊不像稻草*,而是某些单词的错误键入。并且每个丢失/错误的字符都会使距离加1。

通常很难同时具有部分匹配(通配符)和模糊性!

标记化通常是一个好主意。

一切还很大程度上取决于您获得的数据。在源文件中或仅在搜索查询中是否存在拼写错误?

我已经看到了使用多维范围树的一些非常不错的实现。

但是我真的认为,如果您想完成上述所有工作,则需要图形集和良好的索引算法的完美组合。

例如,您可以使用诸如芝麻之类的语义数据库,并且在导入文档时,将每个 token 和文档作为节点导入。然后,根据文档中的位置等,您可以添加加权关系。

然后,您需要某种可以进行有效的模糊匹配的结构中的标记,例如bk树。
我认为您可以索引mysql数据库中的 token 并执行按位比较功能以获取差异。有一个函数返回所有匹配的位,如果您将字符串转换为ascii并将这些位分组,则可以很快实现。

但是,如果将标记与字符串匹配,则可以构造一个假设的完全匹配抗体,并在语义数据库中查询最近的邻居。

在进行词法化时,您必须将单词分解为部分单词,以实现部分匹配。

但是,您也可以进行通配符匹配(前缀,后缀或两者都可以),但不会造成模糊不清。

您还可以索引整个单词或标记的不同串联。

但是,可能有特殊的bk-tree实现支持此功能,但我从未见过这样的实现。

关于java - 有关如何改进当前模糊搜索实现的建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3989772/

有关java - 有关如何改进当前模糊搜索实现的建议的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

随机推荐