为了使在线购物更加便捷,通过缓存热门产品查询的结果来提升服务效率。当大量用户搜索“X品牌鞋”时,服务器会存储该查询的结果,并直接提供给后续相同查询的用户,而无需重新执行产品检索算法和从产品目录中获取数据。
问题在于,如果“X品牌鞋”是热门查询,那么“X品牌鞋”(单数)、“鞋品牌X”、“X品牌 footwear”等变体查询也可能同样热门。如果服务器缓存中充满了描述同一产品的不同方式,这将导致缓存空间的低效利用。
在近期发表于Web Conference的一篇论文中,描述了一种更高效利用缓存空间的技术,即仅为每种产品存储一个描述符。为了将语法不同但语义相似的查询路由到该单一描述符,使用了一种称为**局部敏感哈希(LSH)**的技术。
局部敏感哈希
对于熟悉传统哈希概念的人来说,LSH可能看起来有悖常理。与传统哈希一样,LSH也使用一个哈希函数,将任意字符串映射到数组中的一个唯一位置(通常称为哈希桶)。
但与试图将字符串随机且均匀地分布到各个桶中的传统哈希不同,LSH尝试将相似的字符串映射到同一个桶。传统哈希旨在最小化冲突(即映射到同一桶),而LSH则鼓励冲突。
核心思想是将相关的产品查询映射到同一个桶,该桶存储了对应结果的位置。其难点在于,与传统的哈希函数一样,局部敏感哈希函数有时会将不同的字符串映射到同一个桶。“品牌X鞋”、“品牌X鞋子”和“鞋子品牌X”可能都映射到同一个桶,但“品牌A牛仔裤”也可能映射到该桶。
解决方案的一部分是在每个桶中,为映射到该位置的每组相关查询存储一个规范查询。例如,对于关于品牌X鞋的一族查询,随机选择一个(例如“品牌X鞋”)作为代表,并将其存储在关联的桶中,作为指向相应查询结果列表的索引。
当然,同一个桶中也可能包含“品牌A牛仔裤”或无限多个其他索引。如果查询(例如“X品牌 footwear”)映射到一个包含多个索引的桶,如何知道要检索哪组结果?
答案是使用略有不同的局部敏感哈希函数对同一字符串进行多次哈希。一个函数可能将“X品牌 footwear”映射到一个包含“品牌X鞋”、“品牌A牛仔裤”和“品牌Q相机”的桶;另一个函数可能将其映射到一个包含“Acme widgets”、“品牌X鞋”和“Top-shelf铅笔”的桶;以此类推。在所有映射中,只需统计出现频率最高的索引,并检索与其关联的结果。
在实践中,发现使用36个不同的哈希函数效果很好。这能将检索到错误查询结果集的概率降低到接近零。
相似度函数
为了将相似的输入映射到相同的桶,局部敏感哈希函数必须编码某种相似性概念。对于任何标准的相似性度量(如欧几里得距离、L-norm等),都可以构建实现该度量的局部敏感哈希函数。
在这里,使用了加权Jaccard相似度。Jaccard相似度是两个数据项共有的元素数量与它们包含的元素总数之比:即交集与并集之比。
加权Jaccard相似度赋予数据项之间某些对应关系比其他关系更大的权重。这里使用一个经过训练以进行命名实体识别的机器学习模型来分配权重。
其背后的逻辑是,寻找品牌X鞋的顾客,对包含品牌Y鞋的查询结果的接受度,会高于包含品牌X T恤的结果。因此,加权Jaccard度量给予产品类别对应关系的权重大于品牌名称对应关系。这个权重分配过程完全在离线完成,并融入到哈希函数的设计中。
概念聚类
在另一篇Web Conference论文《通过高效随机化算法进行大规模文本规范化》中,描述了识别相关查询簇(如“品牌X鞋”、“品牌X鞋子”、“品牌X footwear”等)并从中选择一个作为索引查询的过程。该论文描述了一个将同一概念的不同表达形式归一到单一规范表达形式的通用过程,并将其应用于缓存查询结果的问题。
在构建了包含36个哈希函数的函数族后,使用它们来哈希完整的热门产品查询列表。每当两个查询被哈希到同一个桶时,就在一个巨大的图中增加连接它们边的权重。因此,在完成所有哈希后,最大边权重为36,最小为1。
然后,删除所有权重低于某个阈值的图边。结果得到一系列由相关术语组成的子图。从每个子图中,随机选择一个术语作为该族查询的索引。
为了评估该方法,选择了6000万个热门商城产品查询,并根据频率将其分为三组:普通查询、困难查询和长尾查询。然后使用四种不同的技术在固定大小的存储空间中缓存这些查询的结果。
使用F1分数来衡量性能,它结合了召回率(方法从缓存中检索到的结果比例)和精确率(方法检索到正确查询结果的频率)。
与使用传统哈希将查询映射到结果的精确缓存相比,该方法带来的F1分数提升,对于普通查询为33%,对于长尾查询则高达250%。这些提升是以检索时间增加为代价的,从0.1毫秒增加到2.1毫秒。但在许多情况下,缓存容量的增加是值得的。FINISHED