用户在搜索汽车H最常关注的是什么?为什么?,

AI 驱动的搜索:8 信号增强模型

本章涵盖

  • 聚合用户信号以创建基于受欢迎程度的排名模型
  • 标准化信号以最好地增强噪声查询输入的相关性
  • 打击信号垃圾邮件和用户对众包信号的操纵
  • 应用时间衰减来优先考虑最近的信号是否更相关
  • 将多种信号类型混合在一起形成统一的信号增强模型
  • 使用查询时间与索引时间信号提升来缩放信号提升以提高灵活性和性能。

在第 4 章中,我们介绍了反映智能的三个不同类别:信号增强(大众化相关性)、协作过滤(个性化相关性)和学习排名(广义相关性)。在本章中,我们将更深入地研究其中的第一个,实施信号增强以提高最流行的查询和文档的相关性排名。

在大多数搜索引擎中,您会发现相对较少数量的查询往往占总查询量的很大一部分。这些流行的查询(称为“头部查询”)也往往会产生更多信号(例如电子商务用例中的点击和购买),从而可以对热门搜索结果的流行程度进行更强有力的推断。

信号增强模型直接利用这些更强的推论,是确保最重要和最高可见性的查询得到最佳调整以返回最相关文档的关键。

8.1 基本信号增强

在第 4.2.2 节中,我们在 Retrotech 数据集上构建了第一个信号增强模型,从而显着提高了最常搜索和点击的搜索结果的相关性。在本节中,我们将快速回顾一下创建简单信号增强模型的过程,我们将在接下来的部分中基于该模型来满足一些更高级的需求。

您会回想起第 4.2.2 节中的内容,信号增强模型聚合了作为特定查询结果出现的文档上的所有有用活动信号(例如点击信号)。我们使用了 的搜索ipad,并根据之前在该搜索结果中单击的总次数来增强每个文档。ipad图 8.1 演示了先前在 4.3.2 节中演示的查询的搜索结果之前(无信号增强)和之后(信号增强) 。


图 8.1。应用信号增强模型之前和之后。信号提升通过将最受欢迎的项目推到搜索结果的顶部来提高相关性。

图 8.1 中导致相关性提高的信号增强模型是一个基本信号增强模型。它会查看针对给定查询点击过的所有文档,然后应用等于该查询过去对该文档的点击总数的提升。

虽然第 4.3.2 节中介绍的基本信号增强模型大大提高了相关性,但不幸的是,它容易受到一些数据偏差甚至操纵的影响。在第 8.2 节中,我们将讨论一些消除信号中噪声的技术,以最大限度地提高信号增强模型的质量并减少出现不良偏差的机会。

8.2 信号标准化

在聚合之前规范化传入的用户查询非常重要,以便将变化视为相同的查询。鉴于最终用户可以输入任意文本作为查询,这意味着聚合信号本质上是有噪声的。第 4 章中的基本信号增强模型(并在第 8.1 节中进行了概述)没有进行归一化。它为每个查询和文档对生成聚合提升,但由于传入查询尚未标准化为通用形式,这意味着查询的变体将被视为完全独立的查询。清单 8.1演示了在搜索结果中提升最受欢迎的 iPad 型号的所有查询的列表。

清单 8.1。查找与最流行的 iPad 型号相关的最流行的查询

query = "885909457588" #most popular iPad modeldef show_raw_boosted_queries(signals_boosting_collection):    signals_boosts_query = {        "query": "\"" + query + "\"",        "fields": <"query", "boost">,        "limit": 20,        "params": {          "defType": "edismax",          "qf": "doc",          "sort": "boost desc"        }    }    signals_boosts = requests.post(solr_url + signals_boosting_collection                     + "/select", json=signals_boosts_query).json()<                     "response"><"docs">    boosted_queries = ""    for entry in signals_boosts:        boosted_queries += '"' + entry<'query'> + '" : ' +        str(entry<'boost'>) + "\n"    print("Raw Boosted Queries")    print(boosted_queries)signals_boosting_collection = "basic_signals_boosts"show_raw_boosted_queries(signals_boosting_collection)

结果:

Raw Boosted Queries"iPad" : 1050"ipad" : 966"Ipad" : 829"iPad 2" : 509"ipad 2" : 347"Ipad2" : 261"ipad2" : 238"Ipad 2" : 213"I pad" : 203"i pad" : 133"IPad" : 77"Apple" : 76"I pad 2" : 60"apple ipad" : 55"Apple iPad" : 53"ipads" : 43"tablets" : 42"apple" : 41"iPads" : 38"i pad 2" : 38

您可以从清单 8.1的输出中看到,基本信号增强模型中存在相同查询的许多变体。这些变化的最大罪魁祸首似乎是区分大小写,正如我们所看到的iPad、ipad、Ipad和IPad作为常见变体。间距似乎是另一个问题,与ipad 2vs. i pad 2。我们甚至在vs.ipad2中看到单数与复数表示。ipadipads

鉴于大多数关键字搜索字段不区分大小写,并且许多关键字搜索字段还忽略术语的复数表示形式并根据大小写变化和单词之间的字母到数字转换进行拆分,因此保留单独的查询术语并增强搜索引擎无法区分的变体可以会适得其反。这不仅是不必要的,而且实际上分散了信号的价值,因为信号被划分为具有较低提升的相同关键字的不同变体,而不是合并成具有更强提升的更有意义的查询。

您需要弄清楚在信号聚合之前查询规范化应该有多复杂,但即使只是小写传入查询以使信号聚合不区分大小写也可以有很大帮助。清单 8.2演示了与之前相同的基本信号聚合,但这次查询首先小写。

清单 8.2。增强查询的基本不区分大小写标准化。现在“iPad”、“Ipad”和“iPad”被视为相同的查询并共享信号增强。

signals_collection = "signals"signals_boosting_collection = "normalized_signals_boosts"normalized_signals_aggregation_query = """      select lower(q.target) as query, #1          c.target as doc,          count(c.target) as boost #2        from signals c left join signals q on c.query_id = q.query_id        where c.type = 'click' AND q.type = 'query'        group by query, doc #2        order by boost desc        """aggregate_signals(signals_collection, signals_boosting_collection,normalized_signals_aggregation_query)show_raw_boosted_queries(signals_boosting_collection)

结果:

Raw Boosted Queries"ipad" : 2939"ipad 2" : 1104"ipad2" : 540"i pad" : 341"apple ipad" : 152"ipads" : 123"apple" : 118"i pad 2" : 99"tablets" : 67"tablet" : 61"ipad 1" : 52"apple ipad 2" : 27"hp touchpad" : 26"ipaq" : 20"i pad2" : 19"wi" : 19"apple computers" : 18"apple i pad" : 15"ipad 2 16gb" : 15"samsung galaxy" : 14

原始增强查询列表看起来已经干净多了!不仅冗余减少了,而且您会注意到信号增强的强度有所增加,因为更多信号归因于查询的规范形式(小写版本)。

通常,只需将查询小写,并可能删除空格或无关字符,就足以在信号聚合之前对查询进行规范化。不过,本节的重要要点是,您越能确保相同的查询在聚合在一起时得到相同的处理,信号增强模型就会变得越强。

然而,查询的变化并不是我们需要担心的数据中唯一的噪音。在下一节中,我们将讨论如何克服用户生成的点击信号中的垃圾信息引起的重大潜在问题。

8.3 打击垃圾信号

每当我们使用众包数据(例如点击信号)来影响搜索引擎的行为时,我们都需要问自己“我们的用户可能如何操纵数据输入来创建不良结果?”。在本节中,我们将演示用户如何通过点击信号向搜索引擎发送垃圾邮件以操纵搜索结果,并将向您展示如何阻止它。

8.3.1 使用信号垃圾邮件来操纵搜索结果

假设我们有一个用户,无论出于何种原因,他真的很讨厌《星球大战》,并认为最近的电影完全是垃圾。事实上,他们的感觉非常强烈,他们希望确保任何搜索总是star wars返回一个可供购买的物理垃圾桶作为首要搜索结果。该用户对搜索引擎略知一二,并注意到您的杀手级相关算法似乎正在利用用户信号和信号增强。图 8.2 显示了查询的默认响应star wars,信号增强将最受欢迎的产品带到搜索结果的顶部。


图 8.2。查询“星球大战”的最热门搜索结果,且信号增强已打开

用户决定,由于您的搜索引擎排名是基于热门项目,因此他们将通过大量搜索向搜索引擎发送垃圾邮件,star wars并在他们找到的星球大战主题垃圾桶上进行大量虚假点击,以便尝试让垃圾桶显示在搜索结果的顶部。

为了模拟这个场景,我们将运行清单 8.3中的一个简单脚本,在运行该查询后生成 5000 个查询,star wars并在垃圾箱上生成 5000 个相应的点击。

清单 8.3。由于信号增强,生成垃圾邮件查询和点击来操纵文档的排名。

import datetimespam_user = "u8675309"spam_query = "star wars"spam_signal_boost_doc_upc = "45626176" #1num = 0while (num < 5000): #2    query_id = "u8675309_0_" + str(num)    next_query_signal = {        "query_id": query_id,        "user": spam_user,        "type":"query",        "target": spam_query,        "signal_time": datetime.datetime.now().strftime(        "%Y-%m-%dT%H:%M:%SZ"),        "id":"spam_signal_query_" + str(num)    }    next_click_signal = {        "query_id": query_id,        "user": spam_user,        "type":"click",        "target": spam_signal_boost_doc_upc,        "signal_time": datetime.datetime.now().strftime(        "%Y-%m-%dT%H:%M:%SZ"),        "id":"spam_signal_click_" + str(num)    }    collection = "signals"    requests.post(solr_url + collection + "/update/json/docs",    json=next_query_signal) #2    requests.post(solr_url + collection + "/update/json/docs",    json=next_click_signal) #2    num+=1requests.post(solr_url + collection + "/update/json/docs?commit=true") #3signals_collection = "signals"signals_aggregation_collection = "signals_boosts_with_spam"aggregate_signals(signals_collection, signals_aggregation_collection,normalized_signals_aggregation_query) #4

清单 8.3向我们的搜索引擎发送了数千个垃圾查询和点击信号,模拟了用户搜索并点击特定搜索结果数千次时我们会看到的相同结果。然后,列表重新运行基本信号聚合,以查看这些信号对我们的信号增强模型的影响。

为了查看对搜索结果的影响,清单 8.4运行了对查询的搜索star wars,现在合并了操纵信号增强模型,以便查看恶意用户的垃圾点击行为的影响。

清单 8.4。使用操纵信号增强模型查询“星球大战”的搜索结果

query = "star wars"collection = "products"signals_boosts = get_query_time_boosts(query, "signals_boosts_with_spam") #1boosted_query = get_main_query(query, signals_boosts)  #2search_results = requests.post(solr_url + collection + "/select",json=boosted_query).json()<"response"><"docs">print(search_results)display(HTML(render_search_results(query, search_results))) #3

图 8.3 显示了从清单 8.4生成的新的操纵搜索结果,其中星球大战垃圾桶返回到顶部。


图 8.3。用户操纵搜索结果,向搜索引擎发送虚假信号,影响排名靠前的结果。用户只需多次点击即可修改


垃圾邮件发送者成功了,这些被操纵的搜索结果现在将被所有随后访问 Retrotech 网站并搜索star wars!的访问者看到。看来我们需要使我们的信号增强模型更加强大,以对抗来自恶意用户的此类信号垃圾邮件。

8.3.2 通过基于用户的过滤来打击垃圾信号

如果您打算使用用户信号等众包数据来影响您的搜索引擎排名,那么采取措施尽量减少用户操纵基于信号的排名算法的能力非常重要。

为了解决我们刚刚演示的“星球大战垃圾桶”问题,最简单的开始技术是确保同一用户的重复点击仅在促进聚合的信号中获得一票“投票”。这样,无论恶意用户点击一次还是一百万次,他们的点击都只算作一个信号,因此对信号增强模型不会产生实质性影响。清单 8.5重新设计了信号聚合查询,只计算来自每个用户的唯一点击信号。

清单 8.5。对每个用户的重复信号进行删除,以防止单个用户的不当影响

signals_collection = "signals"signals_aggregation_collection = "signals_boosts_anti_spam"anti_spam_aggregation_query = """  select query, doc, count(doc) as boost from (    select c.user, lower(q.target) as query, c.target as doc,    max(c.signal_time) as boost #2    from signals c left join signals q on c.query_id = q.query_id    where c.type = 'click' AND q.type = 'query'    group by c.user, #1      q.target, c.target  ) as x  group by query, doc  order by boost desc"""aggregate_signals(signals_collection, signals_aggregation_collection, anti_spam_aggregation_query)

star wars如果我们使用这个新模型重新运行清单 8.5中的查询signals_boosts_anti_spam,我们现在将看到正常的搜索结果已经返回,并且看起来再次与图 8.2 相同。这是因为来自恶意用户的额外垃圾信号现在已全部减少为单个不良信号,如表 8.1 所示。

您可以看到“signals_anti_spam”模型中的聚合信号计数总数更接近normalized_signals_boosts我们在生成垃圾邮件信号之前构建的模型。由于模型中每个用户仅限于每个查询/文档对一个信号signals_boosts_anti_spam,因此用户操作信号增强模型的能力现在大大降低了。

表 8.1。在反垃圾邮件信号增强模型中,5000 个垃圾邮件信号已被重复删除为一个信号

model

query

doc

boost

before spam signals (normalized_signals_boosts)

star wars

400032015667

0 (no signals yet)

after spam signals (normalized_signals_boosts)

star wars

400032015667

5000

after spam signals (signals_boosts_anti_spam)

star wars

400032015667

1

当然,您可以识别任何似乎向您的搜索引擎发送垃圾邮件的用户帐户,并将其信号完全从您的信号中删除,从而促进聚合,但通过重复数据删除来减少信号的覆盖范围更简单,并且通常可以实现恢复搜索引擎的相同最终目标。良好的众包相关性排名。

在清单 8.5的示例中,我们利用用户 ID 作为关键标识符来删除重复的垃圾邮件信号,但任何标识符都可以在这里工作:用户 ID、会话 ID、浏览器 ID、IP 地址,甚至某种浏览器指纹。只要您找到一些标识符来唯一识别用户或以其他方式识别低质量流量(例如机器人和网络抓取工具),那么您就可以使用该信息来删除重复信号。如果这些技术都不起作用,并且您的点击信号中有太多噪音,您还可以选择仅查看来自已知(经过身份验证)用户的点击信号,您可能更有信心这些用户是合法流量。

减少垃圾信号的最后一个方法是找到一种方法将重要的信号类型与易于操纵的噪声信号分开。例如,通过运行查询和单击搜索结果生成信号很容易。然而,购买产品的信号更难操纵,因为它们要求用户在记录购买之前登录或输入付款信息。有人恶意购买 5,000 个星球大战垃圾桶的可能性非常低,因为这样做存在多种财务和后勤障碍。

从打击垃圾邮件的角度来看,将购买视为比点击更强的信号不仅有价值,而且从相关性的角度来看,给予购买更高的权重也很有价值,因为它们是比点击更明确的意图指标。在下一节中,我们将详细介绍如何将不同的信号类型组合到信号增强模型中,该模型考虑每种不同信号类型的相对重要性。

8.4 组合多种信号类型

到目前为止,我们只使用了两种信号类型——查询和点击。对于某些搜索引擎(例如网络搜索引擎),点击信号可能是可用于构建信号增强模型的众包数据的唯一良好来源。然而,很多时候,存在许多不同的信号类型,它们可以为构建信号增强模型提供额外的、通常更好的输入。

在我们的 Retrotech 数据集中,我们有几种电子商务用例常见的信号类型:

  • 询问
  • 点击
  • 添加到购物车
  • 购买

虽然响应查询的点击很有帮助,但它们并不一定意味着对产品有浓厚的兴趣,因为有人可能只是浏览以查看可用的产品。如果有人将产品添加到购物车,这通常代表比点击更强的兴趣信号。购买是用户对产品感兴趣的更强烈的信号,因为用户愿意付费来接收他们搜索的商品。

虽然一些电子商务网站可能会收到足够的流量来完全忽略点击信号,而只关注添加到购物车和购买信号,但在计算信号增强时使用所有信号类型通常更有用。值得庆幸的是,组合多种信号类型非常简单,只需在执行信号聚合时为每种信号类型分配相对权重作为乘数即可:

signals_boost = (1 * sum(click_signals)) + (10 * sum(add_to_cart_signals)) +(25 * sum(purchase_signals))

通过将每次点击算作 1 个信号,将每次添加到购物车算作 10 个信号,将每次购买算作 25 个信号,这使得每次购买在信号增强模型中的权重是点击的 25 倍。换句话说,需要有 25 个不同的人点击某个产品来响应某个查询,才能算作有一个人因同一查询而实际购买了该产品。

这有助于减少不太可靠信号的噪声并增强更可靠的信号,同时在更好的信号不太普遍的情况下(例如新的或模糊的项目)仍然利用大量不太可靠的信号。清单 8.6演示了一个信号聚合,旨在将不同的信号类型与不同的权重组合起来。

清单 8.6。组合具有不同权重的多种信号类型

signals_collection="signals"signals_aggregation_collection="signals_boosts_weighted_types"mixed_signal_types_aggregation = """select query, doc,( (1 * click_boost) + (10 * add_to_cart_boost) + (25 * purchase_boost) )as boost #1from (  select query, doc,    sum(click) as click_boost, #2    sum(add_to_cart) as add_to_cart_boost, #2    sum(purchase) as purchase_boost #2  from (      select lower(q.target) as query, cap.target as doc,        if(cap.type = 'click', 1, 0) as click,        if(cap.type = 'add-to-cart', 1, 0) as  add_to_cart,        if(cap.type = 'purchase', 1, 0) as purchase      from signals cap left join signals q on cap.query_id = q.query_id      where (cap.type != 'query' AND q.type = 'query')    ) raw_signals  group by query, doc) as per_type_boosts"""aggregate_signals(signals_collection, signals_aggregation_collection, mixed_signal_types_aggregation)

您可以从 SQL 查询中看到,每个查询/文档部分的整体提升是通过以下方式计算的:计算权重为 1 的所有点击,计算所有添加到购物车信号并将其乘以权重 10,然后计算所有购买信号并将其乘以权重 25。

这些建议的添加到购物车信号的权重为 10 倍,购买信号的建议权重为 25 倍,在许多电子商务场景的实践中应该效果很好,但这些相对权重对于每个域也是完全可配置的。您的网站可能会被设置为几乎所有将产品添加到购物车的人都会购买该产品(例如,杂货店送货应用程序,使用该网站的唯一目的是填充购物车并进行购买)。在这些情况下,您可能会发现将商品添加到购物车不会增加任何附加值,但从购物车中删除商品实际上应该带有负权重,表明该产品与查询不匹配。

在这种情况下,您可能需要引入负信号增强的想法。正如我们讨论了点击、添加到购物车和购买作为用户意图信号一样,您的用户体验也可能有多种方法来衡量用户对您的搜索结果的满意度。例如,您可能有一个“不喜欢”按钮、一个“从购物车中删除”按钮,或者您可以在购买后跟踪产品退货。您甚至可能想要计算搜索结果中被跳过的文档,并为这些文档记录“跳过”信号,以表明用户看到了它们但没有表现出兴趣。当我们讨论点击建模时,我们将在第 11 章进一步讨论管理点击文档和跳过文档的主题。

值得庆幸的是,处理负面反馈与处理正面信号一样容易:您不仅可以为信号分配越来越多的正权重,还可以为负面信号分配越来越多的负权重。例如:

positive_signals = (1 * sum(click_signals) ) + ( 10 * sum(add_to_cart_signals) ) + ( 25 * (purchase_signals) ) + ( 0.025 * sum(seen_doc_signals) )negative_signals = ( -0.025 * sum(skipped_doc_signals) ) + ( -20 * sum(remove_from_cart_signals) ) + ( -100 * sum(returned_item_signals) ) +( -50 * sum(negative_post_about_item_in_review_signals) )type_based_signal_weight = positive_signals + negative_signals

这个简单的线性函数提供了一个高度可配置的基于信号的排名模型,接收多个输入参数并根据这些参数的相对权重返回排名分数。您可以将任意数量的有用信号组合到此加权信号聚合中,以提高模型的稳健性。当然,调整每种信号类型的权重以实现最佳平衡可能需要一些努力。您可以手动执行此操作,也可以利用称为“学习排名”的机器学习技术来执行此操作。我们将在第 10 章和第 11 章深入探讨学习排名。

不仅对不同类型的信号进行相对加权很重要,而且有时也有必要对同一类型的信号进行不同的加权。在下一节中,我们将讨论这样做的一个关键示例:为最近的交互分配更高的价值。

8.5 时间衰减和短命信号

信号并不总是无限期地保持其有用性。在上一节中,我们展示了如何调整信号增强模型以对不同类型的信号进行加权,使其比其他信号更重要。在本节中,我们将解决一个不同的挑战 - 随着信号老化和变得不那么有用,考虑信号的“时间值”。

想象一下三种不同的搜索引擎用例:

  • 产品稳定的电商搜索引擎,
  • 职位搜索引擎,以及
  • 一个新闻网站。

如果我们有一个电子商务搜索引擎,例如 Retrotech,文档(产品)通常会保留多年,而最好的产品通常是那些具有长期兴趣记录的产品。

如果我们有一个职位搜索引擎,这些文档(职位)可能只会保留几周或几个月,直到职位被填补为止,然后它们就会永远消失。然而,虽然文档存在,但新的点击或工作申请并不一定比旧的交互更重要。

在新闻搜索引擎中,虽然新闻文章永远存在,但较新的文章通常比较旧的文章重要得多,并且较新的信号肯定比较旧的信号更重要,因为人们的兴趣每天(如果不是每小时)都会发生变化。

让我们深入研究这些用例,并演示如何最好地处理时间敏感文档与时间敏感信号的信号增强。

8.5.1 处理时间敏感的文档

在我们的 Retrotech 用例中,我们的文档故意是旧的,已经存在了十年或更长时间,并且随着产品变得更旧和更“复古”,人们对它们的兴趣可能只会增加。因此,物品的受欢迎程度通常不会大幅上升,而且新信号并不一定比旧信号更重要。这种类型的用例有点不典型,但大量搜索用例确实处理更多这样的“静态”文档集。在这种情况下,最好的解决方案是我们在本章中已经采取的策略:在几个月或几年的合理时间段内处理所有信号,并给予它们相当相等的权重。当所有时间段具有相同的权重时,这也意味着信号增强模型可能不需要经常重建,

然而,在求职用例中,情况却截然不同。为了便于讨论,我们假设平均需要 30 天才能填补职位空缺。这意味着代表该职位的文档只会在搜索引擎中存在 30 天,并且为该文档收集的任何信号仅在这 30 天的时间段内对信号增强有用。当职位发布后,它通常会在最初几天非常受欢迎,因为它是新的,并且可能会吸引许多现有的求职者,但在 30 天内的任何时间点与该职位的所有互动都同样有用。在这种情况下,所有点击信号应获得相同的权重,并且所有工作申请信号也应同样获得相同的权重(当然,权重高于点击信号)。然而,鉴于文件的生命周期非常短,

具有短暂文档的用例(例如求职用例)通常不适合信号增强,因为当信号增强模型变得良好时,文档通常会被删除。因此,针对这些用例,考虑个性化模型(例如第 9 章中介绍的协作过滤)和通用相关模型(例如第 10 章和第 11 章中介绍的学习排名)通常更有意义。

在 Retrotech 用例和求职用例中,信号在文档存在的整个期间都同样有用。在我们接下来将看到的新闻搜索用例中,时间敏感性与文档的年龄和信号本身更相关。

8.5.2 处理时间敏感信号

在新闻搜索引擎用例中,最近发布的新闻具有最高的可见性并且通常具有最多的交互性,因此最新的信号比旧的信号更有价值。某些新闻可能会在几天或更长时间内非常受欢迎且相关,但通常最后十分钟的信号比最后一小时的信号更有价值,最后一小时的信号比最后一天的信号更有价值,依此类推。新闻搜索是一种极端的用例,其中信号既需要快速处理,又需要对较新的信号进行加权,使其比较旧的信号重要得多。

对此进行建模的一种简单方法是使用衰减函数,例如半衰期函数,它可以在等间隔的时间跨度内将分配给信号的权重减少一半 (50%)。例如,半衰期为 30 天的衰减函数将为发生的信号分配 100% 的权重now,为 15 天前的信号分配 75% 的权重,为 30 天前的信号分配 50% 的权重,为 30 天前的信号分配 25% 的权重60 天前的信号权重为 12.5%,90 天前的信号权重为 12.5%,依此类推。实现衰减函数的数学是:

time_based_signal_weight = starting_weight * 0.5^(signal_age/half_life)

应用此计算时,starting_weight通常是基于信号类型的信号的相对权重,例如1点击、10添加到购物车信号和25购买信号的权重。如果您不组合多种信号类型,那么将starting_weight只是1.

是signal_age信号的年龄,是half_life信号失去一半值所需的时间。图 8.4 演示了这种衰减函数如何影响不同半衰期值随时间的信号权重。


图 8.4。根据不同的半衰期值,信号随时间衰减。随着半衰期的延长,单个信号保持其增强能力的时间更长。

一日半衰期非常激进,在大多数用例中相当不切实际,因为您不太可能在一天内收集足够的信号来为有意义的信号增强提供动力,并且信号很快变得无关紧要的可能性很低。

30 天、60 天和 120 天的半衰期可以很好地大幅折扣旧信号,但在 6 到 12 个月的时间内保持其对模型的剩余价值。如果您拥有真正长期存在的文档,您可以推出更长的时间,在多年的过程中利用信号。清单 8.7演示了更新的信号聚合查询,它为每个信号实现了 30 天的半衰期:

清单 8.7。将时间衰减函数应用于信号增强模型

signals_collection="signals"signals_boosting_collection="signals_boosts_time_weighted"half_life_days = 30target_date = '2020-06-01 00:00:00.0000' #Will usually be now(), but can be any past time you want to emphasize signals from.signal_weight = 1 #can make this a function to differentiate weights for different signal typestime_decay_aggregation = """select query, doc, sum(time_weighted_boost) as boost from (    select user, query, doc, """ + signal_weight + """ * pow(0.5, (    datediff('""" + target_date + "', signal_time) / "    + str(half_life_days) + """)) as time_weighted_boost from (        select c.user as user, lower(q.target) as query, c.target as doc,        max(c.signal_time) as signal_time        from signals c left join signals q on c.query_id = q.query_id        where c.type = 'click' AND q.type = 'query'        AND c.signal_time <= '""" + target_date + """'        group by c.user, q.target, c.target    ) as raw_signals) as time_weighted_signalsgroup by query, docorder by boost desc"""aggregate_signals(signals_collection, signals_boosting_collection,time_decay_aggregation)

该衰减函数有一些独特的可配置参数:

  • 它包含一个half_life_days参数,该参数使用可配置的半衰期计算加权平均值,我们将其设置为 30 天开始。
  • 它包含一个signal_weight参数,可以用按信号类型返回权重的函数替换,如上一节所示(点击 = 1、添加到购物车 = 10、购买 = 25 等)。
  • 它包含一个target_date参数,该参数是信号获得 的完整值的日期1。该日期之前的任何信号都将根据半衰期衰减,而该日期之后的任何信号将被忽略(过滤掉)。

您的target_date日期通常是当前日期,这样您就可以利用最新的信号并为它们分配最高的权重。但是,如果您的文档具有每月或每年重复的季节性模式,您也可以将其应用于过去的期间。

虽然我们的产品文档不会经常更改,并且最新的信号不一定比旧的信号更有价值,但我们可以在正常的电子商务数据集中找到潜在的年度模式。例如,某些类型的产品可能在母亲节、父亲节和黑色星期五等重大节日期间更受欢迎。同样,搜索“铲子”之类的东西在夏季(用于挖土的铲子)与冬季(用于清除人行道上的积雪的铲子)可能具有不同的含义。如果您探索信号,可能会出现任意数量的趋势,其中时间敏感性会影响信号的加权方式。

最终,信号是一个滞后指标。它们反映了用户刚刚所做的事情,但只有在学习的模式可能会重复时,它们才可用于预测未来的行为。

现在已经探索了通过查询归一化、减少垃圾邮件和相关性操作、组合具有不同相对权重的多种信号类型以及对信号应用时间衰减来改进信号模型的技术,您应该能够灵活地实现最适合您使用的信号增强模型案件。然而,在大规模推出信号增强时,您可以采取两种不同的方法来优化灵活性与性能。我们将在下一节中介绍这两种方法。

8.6 索引时间与查询时间提升:平衡规模与灵活性

本章中的所有信号增强模型均已使用查询时增强signals_boosts进行了演示,该模型在查询时从每个用户查询的单独集合中加载信号增强,并修改用户的查询以在将其发送到搜索引擎之前添加增强。 。还可以使用索引时间提升来实现提升模型,其中提升直接添加到应用这些提升的查询的文档中。在本节中,我们将讨论每种方法的优点和权衡。

8.6.1 使用查询时间提升时的权衡

正如我们所看到的,查询时提升将每个查询变成一个两步过程,其中在集合中查找每个传入的用户查询signals_boosting,然后使用任何找到的提升文档来修改用户的查询。查询时间增强是实现信号增强的最常见方法,但它既有优点也有缺点。

查询时间提升的好处

查询时提升的主要架构特征是它将主搜索集合 ( products) 和信号提升集合 ( *_signals_boosts) 分开。这种分离提供了许多好处,包括:

  1. 允许通过仅修改表示该查询的一个文档来增量更新每个查询的信号
  2. 只需不进行查找或修改用户的查询即可轻松打开或关闭提升
  3. 允许随时更换不同的信号增强算法

最终,通过在查询时增强给定查询的特定文档,根据当前上下文在任何时间点灵活地更改增强是查询时信号增强的主要优点。

查询时间提升的缺点

虽然灵活,但查询时间提升也会在查询性能、规模和相关性方面带来一些重大缺点,这可能使其不适合某些用例。具体来说,查询时间增加:

  1. 在执行增强搜索之前需要进行额外的搜索来查找增强,增加更多处理(执行两次搜索)和延迟(最终查询必须等待信号查找查询的结果才能处理)
  2. 无法处理长文档列表来以可扩展的方式增强查询,需要在用户体验和相关性与查询速度和规模之间进行权衡
  3. 不太支持搜索结果分页

第一个缺点是直接的,因为每个查询本质上变成两个背靠背执行的查询,这增加了总搜索时间。然而,第二个缺点可能并不那么明显,因此值得进一步探讨。

在查询时提升中,我们查找特定数量的文档以提升查询的搜索结果。例如,在ipad图 8.1 中的搜索示例中(代码见清单 4.7),查询的提升最终变为:

"885909457588"^966 "885909457595"^205 "885909471812"^202 "886111287055"^109 "843404073153"^73 "885909457601"^62 "635753493559"^62 "885909472376"^61 "610839379408"^29 "884962753071"^28

此提升包含 10 个文档,但这只是因为这是我们请求的提升数量。假设我们只在第一页上显示十个文档,那么整个第一页看起来都不错……但是如果用户导航到第二页怎么办?在这种情况下,不会显示任何增强的文档,因为只有前 10 个带有查询信号的文档被增强!

为了增强第二页的文档,我们需要确保至少有足够的文档增强来覆盖完整的前两页,这意味着从 10 增强增加到 20 增强(将“限制”参数修改为 20)增强查找查询):

"885909457588"^966 "885909457595"^205 "885909471812"^202 "886111287055"^109 "843404073153"^73 "635753493559"^62 "885909457601"^62 "885909472376"^61 "610839379408"^29 "884962753071"^28 "635753490879"^27 "885909457632"^26 "885909393404"^26 "716829772249"^23 "821793013776"^21 "027242798236"^15 "600603132827"^14 "886111271283"^14 "722868830062"^13 "092636260712"^13

因此,您可以通过增加每次有人导航到“下一个”页面时查找的提升数量来解决这个问题,但这会很快减慢后续查询的速度,因为第 3 页将需要查找并应用 30 个提升,第 10 页将需要 100 次提升,依此类推。对于每个查询仅存在少量提升文档的用例,这不是一个大问题,但对于许多用例,可能有数百或数千个文档会从提升中受益。例如,在我们的查询示例中ipad,有超过 200 个包含聚合信号的文档,因此大多数文档根本不会被提升,除非有人非常深入地进入搜索结果,此时查询很可能速度很慢,有时甚至可能超时。

仅包含增强值的子集也会带来另一个问题:搜索结果并不总是按增强值严格排序!我们假设请求前 10 个提升就足以获得第一页 10 个结果,但实际上,提升只是影响相关性的因素之一。提升列表中更靠后的文档可能具有更高的基本相关性分数,并且如果它们的提升也被加载,它们将跳转到搜索结果的第一页。

因此,当用户从第一页导航到第二页并且加载的增强数量增加时,某些结果可能会跳转到第一页并且永远不会被看到,或者跳转到第二页并再次被视为重复。当有人转到第三页时,所有三页的结果可能会进一步打乱。

即使这些结果比没有应用信号增强的搜索结果更相关,它也不会带来非常理想的用户体验。索引时间信号增强可以帮助克服这些缺点,我们将在下一节中展示。

8.6.2 实施索引时间信号增强

索引时间信号增强彻底解决了信号增强问题——我们不是在查询时增强查询的流行文档,而是在索引时增强文档的流行查询。这是通过将流行查询及其提升值添加到每个文档中的字段来实现的。然后,在查询时,我们只需搜索新字段,如果该字段包含我们查询中的术语,那么它会根据该术语索引的提升值自动提升。

在实现索引时间提升时,我们利用完全相同的信号聚合来生成文档对并提升每个查询的权重。一旦生成了这些信号增强,我们只需在工作流程中添加一个额外的步骤:更新产品集合以在每个文档中添加一个字段,其中包含应增强文档的每个术语以及相关的数字增强权重。清单 8.8演示了我们工作流程中的这个附加步骤。

清单 8.8。将信号提升从单独的查询时集合映射到主集合中的字段

signals_boosts_collection="normalized_signals_boosts"signals_boosts_opts={"zkhost": "aips-zk", "collection":signals_boosts_collection}df = spark.read.format("solr").options(**signals_boosts_opts).load()df.registerTempTable(signals_boosts_collection) #1products_collection="products_with_signals_boosts"products_read_boosts_opts={"zkhost": "aips-zk", "collection":products_collection}df = spark.read.format("solr").options(**products_read_boosts_opts).load()df.registerTempTable(products_collection) #2boosts_query = """ #3SELECT p.*, b.signals_boosts from (  SELECT doc, concat_ws(',',collect_list(concat(query, '|', boost))) as  signals_boosts FROM """ + signals_boosts_collection + """ GROUP BY doc) b inner join """ + products_collection + """ p on p.upc = b.doc"""products_write_boosts_opts={"zkhost": "aips-zk", "collection":products_collection, "gen_uniq_key": "true", "commit_within": "5000"} #4spark.sql(boosts_query).write.format("solr").options(**products_write_boosts_opts).mode("overwrite").save()

清单 8.9中的代码读取每个文档的所有先前生成的信号提升,然后将查询和提升映射到signals_boosts每个产品文档上的新字段,作为术语(用户查询)的逗号分隔列表,并为每个术语提供相应的信号提升权重学期。

该signals_boosts字段是 Solr 中的一个专门字段,包含 DelimitedPayloadBoostFilter,它允许使用可用于影响查询时间评分的关联提升对术语(查询)进行索引。例如,对于最流行的iPad,产品文档现在将修改为如下所示:

{...   "id": "885909457588",   "name": "Apple® - iPad® 2 with Wi-Fi - 16GB - Black"   "signals_boosts": "ipad|2939,ipad 2|1104,ipad2|540,i pad|341,apple ipad|   152,ipads|123,apple|118,i pad 2|99,tablets|67,tablet|61..." ... }

在查询时,signals_boosts将搜索该字段,如果查询与该字段中的一个或多个值匹配,则该文档的分数将相对于提升值进行提升。

清单 8.9演示了如何利用索引时间信号提升来执行查询,利用payload搜索引擎中的函数根据与用户查询相关的索引有效负载(提升值)进行提升。

清单 8.9。执行基于索引时间信号提升排名的查询

query = "ipad"def get_query(query, signals_boosts_field):    request = {        "query": query,        "fields": <"upc", "name", "manufacturer", "score">,        "limit": 3,        "params": {          "qf": "name manufacturer longDescription",          "defType": "edismax",          "indent": "true",          "sort": "score desc, upc asc",          "qf": "name manufacturer longDescription",          "boost": "payload(" + signals_boosts_field + ", \"" #1                              + query + "\", 1, first)" #1        }    }    return requestcollection = "products_with_signals_boosts"boosted_query = get_query(query, signals_boosts_field)print("Main Query:")print(boosted_query)search_results = requests.post(solr_url + collection + "/select",json=boosted_query).json()<"response"><"docs">print("\nSearch Results (Basic Signals Boosting): ")print(search_results)display(HTML(render_search_results(query, search_results)))

图 8.5 显示了索引时间信号提升的结果。正如您所看到的,结果现在看起来与之前图 4.1 中所示的查询时间信号增强输出类似。



图 8.5。索引时间信号增强,展示了与查询时间索引增强类似的结果。


当使用函数来提升索引时间提升与添加文档提升来提升查询时间信号时,相关性分数可能会不同payload,因为相关性评分数学有点不同。不过,结果的相对顺序应该非常相似。索引时间信号提升将适用于所有具有信号提升的文档,而不是仅适用于具有信号提升的顶级文档,这使得索引时间提升更加全面,还有其他好处。

索引时间提升的好处

索引时间提升解决了查询时间提升的大部分缺点:

  1. 查询工作流程更简单、更快,因为它不需要执行两个查询 - 一个查询查找信号增强,另一个查询使用这些信号增强运行增强查询。
  2. 随着提升数量的增加,每次提升的每个查询都会更高效、更快,因为提升查询是针对提升字段的单个关键字搜索,而不是针对需要提升的越来越多的文档的提升查询。
  3. 结果分页不再是问题,因为与查询匹配的所有文档都会被提升,而不仅仅是可以有效加载并添加到查询中的前 N ​个文档。

鉴于这些特征,索引时间提升可以通过确保所有查询都收到所有匹配文档的一致和完整的提升来显着提高结果排序的相关性和一致性,并且它可以通过提高查询效率并删除之前的额外查找来显着提高查询速度。执行对搜索引擎的主查询。

索引时间提升的缺点

如果索引时间提升解决了查询时间提升的所有问题,那么我们为什么不总是使用索引时间信号提升而不是查询时间信号提升呢?

索引时间提升的主要缺点是,由于查询的提升值被索引到每个文档(每个文档包含应该提升该文档的术语),这意味着从信号提升模型中添加或删除关键字需要重新索引与该关键字关联的所有文档。如果促进聚合的信号是增量更新的(基于每个关键字),那么这意味着可能会连续地重新索引搜索引擎中的所有文档。如果您的信号增强模型针对整个索引批量更新,那么至少这意味着每次重新生成信号增强模型时都可能重新索引所有文档。

这种索引压力增加了搜索引擎的操作复杂性。为了保持查询性能快速且一致,您可能希望将文档索引分离到托管搜索索引以提供查询服务的单独服务器上。

关注点分离:索引与查询

在 Apache Solr 中,索引被分为一个或多个“分片”,这些“分片”是包含集合中文档子集的分区。每个分片可以有一个或多个副本,每个副本都是属于其分片的所有数据的精确副本。运行搜索时,Solr 会将查询发送到每个分片的一个副本,查询在每个副本上并行运行,结果被聚合并作为完整结果集返回给最终用户。添加更多分片的主要目的是允许在更短的时间内搜索更多文档,添加更多副本的主要目的是增加容错能力并允许针对相同数量的分片运行更多数量的搜索。

Solr 具有三种不同类型的副本:NRT(近实时)、TLOG(事务日志)和 PULL 副本。默认情况下,所有副本都是 NRT,这意味着每个副本都会在每次文档更新进入时对其进行索引。这允许文档更新在每个副本上立即可用,但如果大量文档被删除,它也会对这些副本上的查询时间产生非常负面的影响。不断被索引。其他副本类型(TLOG 和 PULL)能够从 NRT 副本中提取索引,而不是重复执行索引工作,这可以允许集群内的关注点分离,从而允许在与查询隔离的 NRT 副本上建立索引TLOG 和 PULL 副本上的操作。

如果您计划进行索引时间信号增强并期望不断地重新索引信号,则应强烈考虑隔离索引和查询时间操作,以确保您的查询性能不会因持续信号增强索引而产生的大量额外索引开销产生负面影响。

索引时间提升的另一个缺点(也与受信号影响的所有文档在更改时重新索引的要求有关)是,对信号提升功能进行更改可能需要更多规划。例如,如果您想将点击与购买信号的权重从 1:25 更改为 1:20,那么您可能需要创建一个具有signals_boosts_2新权重的字段,重新索引所有文档以添加新的提升,并然后翻转查询以使用新字段而不是原始signals_boosts字段。否则,您的提升值和排名分数将不一致地波动,直到您的所有文档分数都已更新。

但是,如果可以解决这些缺点,那么实施索引时间信号提升可以解决查询时间信号提升的所有缺点,从而带来更好的查询性能、对结果分页的完全支持以及使用来自所有文档的所有信号而不仅仅是从最流行的文档中抽取样本。

8.7 总结

  • 信号增强是一种排名算法,它聚合每个查询的用户信号计数,并将这些计数用作将来该查询的相关性增强。这可确保每个查询的最受欢迎的项目被推送到搜索结果的顶部。
  • 通过处理不同的变体(大小写、拼写等)来标准化查询,因为同一查询有助于清除用户信号中的噪音并构建更强大的信号增强模型。
  • 众包数据容易被操纵,因此明确防止垃圾邮件和恶意信号影响相关模型的质量非常重要。
  • 您可以通过为每种信号类型分配相对权重并对跨信号类型的值进行加权求和,将不同的信号类型组合到单个信号增强模型中。这使您能够为较强的信号(正或负)提供更多的相关性,并减少较弱信号的噪声。
  • 引入时间衰减函数使最近的信号比旧信号承载更多的权重,从而允许旧信号随着时间的推移逐步淘汰。
  • 信号增强模型可以使用查询时间信号增强(更灵活)或索引时间信号增强(更具可扩展性和更一致的相关性排名)来生产。

2024-03-02

后面没有了,返回>>电动车百科