使用 Elasticsearch 构建食谱搜索(一)
作者:来自 Elastic Andre Luiz
了解如何使用 Elasticsearch 构建基于语义搜索的食谱搜索。
简介
许多电子商务网站都希望增强其食谱搜索体验。正确使用语义搜索可以让客户根据更自然的查询(例如 “something for Valentine's Day - 情人节的礼物” 或 “Thanksgiving meals. - 感恩节大餐”)快速找到所需的食材。
在本文中,我们将演示如何使用 Elasticsearch 实现支持此类查询的语义搜索。我们将配置一个索引来存储超市的食材和产品目录,并演示如何使用此索引来改进食谱搜索。在整篇文章中,我们将解释如何创建此数据结构并应用自然语言处理技术来提供与客户意图一致的相关结果。
本文中介绍的所有代码都是用 Python 开发的,可在 GitHub 上找到。你可以访问存储库以查看源代码、根据需要进行调整并直接在你的开发环境中实施解决方案。
开始实施语义搜索
要开始实施语义搜索,我们首先需要定义自然语言模型。Elastic 提供自己的模型 ELSER,但也支持集成来自各种提供商(例如 Hugging Face)的 NLP 模型。这种灵活性使你可以选择最适合你需求的选项。
在本文中,我们将使用 ELSER,它降低了部署和管理 NLP 模型的复杂性。此外,Elastic 还提供 semantic_text 功能,大大简化了流程。使用 semantic_text,整个嵌入生成过程变得简单而自动化。你只需定义一个推理点并指定将接收索引映射中的嵌入的字段。在文档索引期间,将生成嵌入并自动与指定字段相关联。
设置步骤
以下是创建具有语义搜索支持的索引的步骤。按照这些说明,你将拥有一个配置好并准备好进行语义搜索的索引:
- 创建 inference point。
- 创建索引,将描述字段设置为 semantic_text,以便它可以接收嵌入。
- 将数据编入杂货目录索引,该索引将存储产品目录。此目录是从此处提供的数据集获得的。
语义搜索在杂货店中的应用
现在我们已经用杂货店产品数据填充了索引,我们正在测试和验证查询以使用语义搜索改进搜索结果。我们的目标是提供更智能的搜索体验,了解上下文和用户意图,提供更相关和准确的结果。
语义搜索解决的挑战
基于产品目录,让我们探索语义搜索如何通过解决传统词汇搜索经常遇到的词汇和上下文问题来改变杂货店的搜索体验。
1. 烹饪意图的解释
问题 01:客户可能会搜索 “seafood for grilling - 烧烤海鲜”,但词汇搜索系统可能无法完全理解查询背后的意图。它可能无法识别所有适合烧烤的海鲜产品,而只会返回产品标题中带有确切术语 “seafood - 海鲜” 或 “grill - 烧烤” 的产品。
首先,我们将执行词汇搜索并分析结果。然后,我们将使用语义搜索执行相同操作,比较相同搜索词的结果。
查询词汇搜索
response = client.search(
index="grocery-catalog",
size=5,
source_excludes="description_embedding",
query={
"multi_match": {
"query": "seafood for grilling",
"fields": [
"name",
"description"]
}
}
)
结果:
Search Type | Name | Score |
---|---|---|
Lexical | Northwest Fish Alaskan Bairdi Snow Crab | 10.453125 |
Lexical | Mr. Yoshida's, Sauce Original Gourmet | 7.2289705 |
Lexical | Premium Seafood Variety Pack - 20 pcs | 7.1924105 |
Lexical | American Red Snapper - Whole, Head-On, Cleaned | 6.998647 |
Lexical | Lobster Claws & Arms, Sustainable Wild Caught | 6.438654 |
词汇搜索返回了一些适合烧烤的海鲜,例如 American Red Snapper 和 Northwest Fish Alaskan Bairdi Snow Crab。然而,词汇搜索返回的列表顶部有一些相关性较低的产品,例如 Mr. Yoshida sauce,它不是海鲜而是肉酱,这表明词汇算法很难完全理解 “for grilling - 烧烤” 的上下文。
语义搜索解决方案
我们使用将术语 “seafood” 与 “grilling” 等准备上下文相结合的查询来返回全面的选项列表,例如鱼片/fish fillets、虾/shrimp 和扇贝/scallops,这些都非常适合烧烤 - 即使 “grill - 烧烤” 或 “seafood - 海鲜” 字样没有直接出现在产品名称中。这可确保搜索结果与客户的意图更加一致。
查询语义搜索:
es_client.search(
index="grocery-catalog-elser",
size=size,
source_excludes="description_embedding",
query={
"semantic": {
"field": "description_embedding",
"query": "seafood for grilling"
}
})
Search Type | Name | Score |
---|---|---|
Semantic | Whole Head On, Cleaned Branzino Fish | 16.175909 |
Semantic | Alaska Black Cod (Sable Fish) | 15.855331 |
Semantic | American Red Snapper - Whole, Head-On | 15.454779 |
Semantic | Northwest Fish Alaskan Bairdi Snow Crab | 15.855331 |
Semantic | American Red Snapper - Whole, Head-On | 15.3892355 |
语义搜索不仅返回与 “seafood - 海鲜”一词直接相关的产品,而且还理解 “grilling - 烧烤” 的上下文,显示适合烧烤的整条鱼和鱼片。这里的关键是结果的精确度,其中包括 Branzino 和阿拉斯加黑鳕鱼等整条鱼,这两种鱼都常用于烧烤。
问题 02:许多顾客在工作了一整天后会搜索快速简便的晚餐解决方案,使用 “easy weeknight meals” 等术语。传统的词汇搜索可能无法完全捕捉快餐的概念,通常只关注名称中包含 “easy - 简单”一词的产品。
正如我们在上一个问题中所做的那样,我们将首先执行词汇搜索。之后,我们将使用语义搜索应用解决方案。
查询词汇搜索
response = client.search(
index="grocery-catalog",
size=5,
source_excludes="description_embedding",
query={
"multi_match": {
"query": "easy weeknight meals",
"fields": [
"name",
"description"]
}
}
)
结果:
Search Type | Name | Score |
---|---|---|
Lexical | Avery Easy Peel Address Labels, 4200-count | 8.017723 |
Lexical | Omeals Self Heating Emergency/Portable Meals 32 | 6.592727 |
Lexical | Coastal Seafood Yellowfin Tuna Cubed Poke | 5.836883 |
Lexical | Hefty Super Weight 12 oz Foam | 5.8116536 |
Lexical | Vanity Fair Everyday Napkin, 2-Ply, 110-count | 5.752989 |
词汇搜索返回的结果相关性要低得多,包括与餐食完全无关的商品,例如 Avery Easy Peel Address Labels 和 Vanity Fair Everyday Napkins。这些产品无法满足用户对快餐的需求。虽然词汇搜索确实返回了一款有用的产品(Omeals Self Heating Emergency Meals),但餐巾纸和标签等其他结果的描述中只匹配了 “easy” 或 “weeknight” 等字词,并没有真正满足用户对快餐解决方案的需求。
语义搜索解决方案
我们实施了一个查询,以了解快速简便的餐食背后的意图。它会关联可以快速准备的产品,例如预煮肉类、冷冻意大利面或餐食套装,即使它们的名称中没有明确包含 “easy” 一词。这种方法可确保客户找到最适合快速晚餐的选择,满足对便利的需求。
查询语义搜索
es_client.search(
index="grocery-catalog-elser",
size=size,
source_excludes="description_embedding",
query={
"semantic": {
"field": "description_embedding",
"query": "easy weeknight meals"
}
})
结果:
Search Type | Name | Score |
---|---|---|
Semantic | Omeals Self Heating Emergency/Portable Meals 32 | 14.610006 |
Semantic | Nissin, Cup Noodles, Shrimp, 2.5 oz | 13.751424 |
Semantic | Namaste Gluten Free Waffle & Pancake Mix | 13.73376 |
Semantic | Idaho Spuds, Golden Grill Hashbrown Potatoes | 12.549422 |
Semantic | Nissin, Cup Noodles, Chicken, 24-Count | 12.034527 |
语义搜索返回的产品明显与快捷方便的餐食有关,例如方便面(Cup Noodles)、pre-cooked potatoes 和 pancake mixes,这些都是简单的平日晚餐的典型选择。这表明语义搜索可以掌握短语“简单的平日晚餐”背后的概念,捕捉用户寻找快捷方便餐食的意图。有趣的是,如果与上下文相关(例如,佐餐饮料),其他类别的产品(例如 “soda - 苏打水”)也可能包括在内。
2. 区域术语和词汇变化
问题:一位客户可能会搜索“soda - 苏打水”,而另一位客户可能会使用 “pop” 来搜索同一款产品。传统的词汇搜索无法识别这两个术语指的是同一款产品。
查询词汇搜索
response = client.search(
index="grocery-catalog",
size=5,
source_excludes="description_embedding",
query={
"multi_match": {
"query": "refreshing pop drink low sugar",
"fields": [
"name",
"description"]
}
}
)
结果:
Search Type | Name | Score |
---|---|---|
Lexical | Prime Hydration+ Sticks Electrolyte Drink Mix | 14.492869 |
Lexical | Capri Sun, 100% Juice, Variety Pack | 12.340851 |
Lexical | Joyburst Energy Drink, Frose Rose, 12 | 11.839179 |
Lexical | Kellogg’s Pop-Tarts, Frosted Brown Sugar Cinnamon | 9.97788 |
Lexical | Kind Mini Bars, Variety Pack, 0.7 | 9.336912 |
词汇搜索侧重于精确匹配单词。虽然它返回了 Prime Hydration 和 Capri Sun 等产品,但直接匹配 “pop” 一词也会导致不相关的结果,例如 Kellogg’s Pop-Tarts,它是一种零食而不是饮料。这凸显了当一个术语具有多重含义或可能含糊不清时,词汇搜索的效率会降低。
语义搜索解决方案
在语义查询中,我们可以克服词汇搜索无法解决的词汇变化问题。通过扩展搜索词,我们能够根据上下文含义获得结果,从而提供更相关、更全面的响应。
查询:
es_client.search(
index="grocery-catalog-elser",
size=size,
source_excludes="description_embedding",
query={
"semantic": {
"field": "description_embedding",
"query": "refreshing pop drink low sugar"
}
})
结果:
Search Type | Name | Score |
---|---|---|
Semantic | Olipop 12 oz Prebiotics Soda Variety | 14.776867 |
Semantic | Bai Antioxidant Cocofusion, Variety Pack, 18 | 14.663253 |
Semantic | Monster Energy Drink, Zero Ultra, 24 | 14.486348 |
Semantic | Joyburst Energy Variety, 12 fl oz | 14.007214 |
Semantic | Joyburst Energy Drink, Frose Rose, 12 | 13.641038 |
语义搜索能够返回与 “pop” 作为 “soda” 同义词直接匹配的产品(例如 Olipop Prebiotics Soda),即使产品名称中未必出现 “pop” 这个确切的词。该搜索理解了用户的意图 —— 一种清爽、低糖的饮料 —— 并能够返回相关产品,包括益生元苏打(如 Olipop)和无糖能量饮料(如 Monster Energy Drink)等选项。
结论
事实证明,在杂货店环境中实施语义搜索对于理解“烧烤海鲜”和“简单的工作日晚餐”等复杂查询非常有效。这种方法使我们能够更准确地解释用户意图,返回高度相关的产品。
通过使用 Elasticsearch 并使用 ELSER 简化流程,我们能够快速高效地应用语义搜索,显著改善搜索结果并提供更灵活、更有针对性的购物体验。这不仅优化了搜索过程,还提高了向客户提供的结果的相关性。
参考资料 ELSER 模型:
Create inference API | Elasticsearch Guide [8.15] | Elastic
ELSER inference service | Elasticsearch Guide [8.15] | Elastic
语义文本:
Semantic text field type | Elasticsearch Guide [8.15] | Elastic
Semantic search | Elasticsearch Guide [8.15] | Elastic
数据集:
https://www.kaggle.com/datasets/bhavikjikadara/grocery-store-dataset?select=GroceryDataset.csv
语义搜索:
Semantic search | Elasticsearch Guide [8.15] | Elastic
Tutorial: semantic search with semantic_text | Elasticsearch Guide [8.15] | Elastic
准备好自己试试了吗?开始免费试用。
想要获得 Elastic 认证?查看下一次 Elasticsearch 工程师培训的时间!
原文:Building a recipe search with Elasticsearch - Search Labs