Elasticsearch:如何搜索含有复合词的语言
作者:来自 Elastic Peter Straßer

复合词在文本分析和标记过程中给搜索引擎带来挑战,因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。
德语以其长复合词而闻名:Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz 是德语词典中最长的单词 —— 对于没有准备好处理复合词的搜索引擎来说是一场噩梦。许多其他语言如荷兰语、瑞典语等也都有这个概念。甚至英语中也有一些这样的词语,尽管程度较轻。想想 “sunflower” 或 “basketball”。
让我们讨论一下这些词语所带来的问题和挑战,为什么这是一个问题以及如何解决它。
问题
在进行全文搜索时,Elasticsearch 等搜索引擎会在查询和索引时分析文本并将文本转换为标记(tokens)。我们想要提取单词的含义,而不是完全匹配字符串。对于我们的搜索,我们不必担心兔子是在 “running” 还是在 “runs” —— 我们只是将单词简化为其词根形式:“run”。
当我们处理复合词时,如果我们不以某种方式解决它,这个阶段就会失败。假设我们有一个包含文档的索引:“Basketballs”。如果我们使用标准英语分析器来分析这一点,我们会得到:
GET _analyze
{"text": "Basketballs", "analyzer": "english"
}
响应:
{"tokens": [{"token": "basketbal","start_offset": 0,"end_offset": 11,"type": "<ALPHANUM>","position": 0}]
}
在这个例子中,复合词 “basketballs” 被标记化为 “basketbal”。虽然我们能够将其转换为小写并去除复数形式,但我们无法捕捉到 “basketball” 也是一种 “ball” 的含义。现在,如果我们在索引中搜索 “ball”,我们希望能够找到 “basketball”,但 “bal”(经过分析)并不匹配 “basketbal”,因此我们没有得到任何结果!
那么,我们该如何解决这个问题呢?
也许同义词有用?
我们首先想到的可能是尝试使用同义词将不同的子词与复合词关联起来。由于复合词的使用相当有限,这对于英语来说已经足够好了:
basketball => basketball, ball
现在我们来看看德语。语法的工作方式是将任意数量的单词组合起来形成一个更精确的单词。
Rind (cow - 牛) 和 Fleisch (meat - 肉) 变成 Rindfleisch (牛肉)。
Rind(cow - 牛)、Fleisch(meat - 肉)和 Etikett(label - 标签)变成 Rindfleischetikett(牛肉标签)。这个过程可以任意长,直到我们得到诸如 Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz 之类的可爱的词语。
为了用我们的同义词文件解决这个问题,我们必须对无限数量的单词排列进行建模:
# cowmeat, cowmeatlabel, meatlabel
rindfleisch => rindfleisch, rind, fleisch
rindfleischetikett => rindfleischetikett, rind, fleisch, etikett
fleischetikett => fleischetikett, fleisch, etikett
…
在德语等语言中,这很快变得不切实际。所以我们必须从相反的角度来看待这个问题。我们不是从复合词开始寻找其复合词,而是查看可用的复合部分并根据这些知识解构单词。
连字分解器 - Hyphenation Decompounder
连字分解器标记过滤器(Hyphenation Decompounder Token Filter)是一个 Lucene 标记过滤器,它依赖连字规则来检测潜在的单词拆分。规则文件(Rule files)以对象格式化对象 (Objects For Formatting - OFFO) 格式指定,我们也可以在其中找到一些示例文件。我们还需要一个单词列表,用于将复合词分解为其子部分。单词列表可以内联提供,但对于生产工作负载,我们通常还会将文件上传到磁盘,因为这些文件可能非常大,并且通常包含整个词典。
可以根据其许可证提供的德语示例文件可在此存储库中找到。
那么它有什么作用呢?
# word list: coffee, sugar, cup
# text: coffee cup
GET _analyze
{"tokenizer": "standard","filter": ["lowercase",{"type": "hyphenation_decompounder","hyphenation_patterns_path": "analysis/hyphenation_patterns.xml","word_list": ["kaffee", "zucker", "tasse"]}],"text": "Kaffeetasse"
}Response:# coffee cup, coffee, cup
[ "kaffeetasse", "kaffee", "tasse"]
这有助于确保搜索 “Tasse”(杯子)的用户能够找到包含较大复合词 “Kaffeetasse”(咖啡杯)的文档。
注意:
- 查看此文章,了解如何上传包以便能够在 Elastic Cloud Hosted 部署中访问这些文件。
- 还有 Dictionary Decompounder,它可以在没有连字规则的情况下执行相同的操作,而是强制执行单词检测。对于大多数用例,我们推荐使用连字分解器。
避免部分匹配
由于我们通常使用包含数千个单词的整本词典的单词列表,因此分解器可能会使用默认设置以非预期的方式拆分单词,从而导致不相关的匹配。
# word list: coffee, fairy, cup
# text: coffee cup
GET _analyze
{"tokenizer": "standard","filter": ["lowercase",{"type": "hyphenation_decompounder","hyphenation_patterns_path": "analysis/hyphenation_patterns.xml","word_list": ["kaffee", "fee", "tasse"]}],"text": "Kaffeetasse"
}Response:# coffee cup, coffee, fairy, cup
["kaffeetasse", "kaffee", "fee", "tasse"]
此示例在 “Kaffee”(coffee - 咖啡)中检测到 “fee”(fairy - 仙女)。这当然是意外的,并非有意为之。另一个示例可能是 “Streifenbluse”(striped blouse- 条纹衬衫),其中会找到 “Reifen”(tires - 轮胎)。“Streifen”(stripe - 条纹)、“Reifen”(轮胎)和 “Bluse”(blouse - 衬衫)都是我们通常想要拆分的常用词。
我们的用户搜索 “fee”(fairies - 仙女)和 “reifen”(tires - 轮胎)时,现在会找到 coffee 和 blouses!这可不妙。
在 8.17 中,hyphenation_decompounder 中添加了一个新的参数 no_sub_matches 来解决此问题。
# word list: coffee, fairy, cup
# text: coffee cup
GET _analyze
{"tokenizer": "standard","filter": ["lowercase",{"type": "hyphenation_decompounder","hyphenation_patterns_path": "analysis/hyphenation_patterns.xml","word_list": ["kaffee", "fee", "tasse"],"no_sub_matches": true}],"text": "Kaffeetasse"
}Response:# coffee cup, coffee, cup
["kaffeetasse", "kaffee", "tasse"]
这可以防止创建 “fee”(fairy)标记并且我们的搜索按预期工作!
匹配所有查询 terms
根据我们目前所见,搜索德语文本的索引映射可能类似于以下索引定义:
PUT products
{"mappings": {"properties": {"full_text": {"type": "text","analyzer": "german_analyzer_with_decompounding"}}},"settings": {"analysis": {"analyzer": {"german_analyzer_with_decompounding": {"type": "custom","tokenizer": "standard","filter": ["lowercase","german_stop_words_filter","german_decompounder","german_normalization","german_stemmer"]},"german_analyzer_without_decompounding": { "type": "custom","tokenizer": "standard","filter": ["lowercase","german_stop_words_filter","german_normalization","german_stemmer"]}},"filter": {"german_stop_words_filter": {"type": "stop","stopwords": "_german_"},"german_decompounder": {"only_longest_match": "true","word_list_path": "dictionary/dictionary.txt","type": "hyphenation_decompounder","hyphenation_patterns_path": "dictionary/hyphenation_patterns.xml"},"german_stemmer": {"type": "stemmer","language": "light_german"}}}}
}
注意:在实际生产环境中,其中很可能会有围绕 asciifolding、表情符号过滤器(emoji filters)或同义词(synonyms)的过滤器,但这已经是一个很好的起点,应该会为德语文本获得良好的结果。
当搜索多个术语时,我们通常会期望(不考虑高级查询放松策略)我们指定的所有搜索词都包含在我们的结果中。因此,当在电子商务商店中搜索 Lederjacke(leather jacket - 皮夹克)时,我们希望我们的产品是皮革制成的夹克,而不是皮革制品和夹克的随机组合。
实现此目的的方法是将搜索查询中的运算符设置为 AND。所以我们这样做并在我们的产品中搜索 “Lederjacke”(皮夹克):
GET products/_search
{"query": {"match": {"full_text": { "query": "lederjacke", "operator": "and" }}}
}
# returns all leather products and all jackets
令人惊讶的是,这并不像我们预期的那样。我们找到了所有含有皮革或夹克的产品。这是因为运算符在标记化之前进行评估,并且使用 OR 评估标记过滤器生成的标记。
为了解决这个问题,我们需要在应用程序中分解我们的术语。我们可以先调用 _analyze API,然后将分解后的术语传递给我们的搜索查询。因为我们已经分解了,所以我们在过滤器链中使用了没有分解器过滤器的搜索分析器(search analyzer)。
GET _analyze
{"tokenizer": "standard","filter": ["lowercase",{"type": "hyphenation_decompounder","hyphenation_patterns_path": "analysis/hyphenation_patterns.xml","word_list_path": "analysis/word_list.xml","no_sub_matches": true}],"text": "Lederjacke"
}Response: ["leder", "jacke"]GET products/_search
{"query": {"match": {"full_text": { "query": "leder jacke","operator": "and","analyzer": "german_analyzer_without_decompounding"}}}
}
# returns only leather jackets
搜索分解词的替代方法
虽然 Elasticsearch Serverless 具有根据负载动态扩展的能力,为搜索应用程序带来了许多巨大优势,但在撰写本文时,目前无法上传文件并在这些项目中使用连字分解器。
替代工具
可以在 Elastic 堆栈之外使用的替代方案是适用于 Java 的 JWordSplitter 和 CharSplit 模型或 CompoundPiece 模型,它们采用机器学习方法分解单词,而无需配置文件。
以下是如何将 CompoundPiece 与 Hugging Face 转换器库一起使用:
from transformers import pipeline
pipe = pipeline("text2text-generation", model="benjamin/compoundpiece")
result = pipe("Lederjacke", max_length=100)
print(result[0]['generated_text'].split('-'))STDOUT: ['Leder', 'Jacke']
它支持 56 种语言,并且无需配置文件即可工作,这是实现多语言应用程序分解的好方法。
语义搜索
我们在这里的许多文章中都涵盖了使用文本嵌入模型的语义搜索。这些模型能够解释文本的含义,可以通过将文档和查询转换为向量并找到与查询最接近的文档来进行搜索。
将此处讨论的词汇搜索与向量搜索相结合称为混合搜索。这也有助于大大提高结果的质量并解释文本中复合词背后的含义。
结论
分解是构建有效的多语言搜索应用程序的重要工具 - 尤其是涉及德语等语言时。通过使用连字符分解器等工具,我们可以确保我们的搜索能够理解复合词背后的含义,为用户提供更好、更相关的结果。
与对搜索算法的任何调整一样,评估其对搜索性能的整体影响非常重要。通过浏览我们关于 _eval API 的文章,了解有关如何客观衡量搜索结果质量的更多信息。
Elasticsearch 包含许多新功能,可帮助你为你的用例构建最佳搜索解决方案。深入了解我们的示例笔记本以了解更多信息,开始免费云试用,或立即在本地机器上试用 Elastic。
原文:How to search languages with compound words - Elasticsearch Labs
相关文章:
Elasticsearch:如何搜索含有复合词的语言
作者:来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战,因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名:Rindfleischetik…...
【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化
1.25 视觉风暴:NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …...
idea maven本地有jar包,但还要从远程下载
idea 中,java 工程执行 maven reimport,报jar报无法下载。 我奇了个怪,我明明在本地仓库有啊,你非得从远程下载? 我从供应商那里拿来的,远程当然没有了。 这太奇葩了吧,折腾好久不行。 后来…...
C++编程语言:抽象机制:模板(Bjarne Stroustrup)
目录 23.1 引言和概观(Introduction and Overview) 23.2 一个简单的字符串模板(A Simple String Template) 23.2.1 模板的定义(Defining a Template) 23.2.2 模板实例化(Template Instantiation) 23.3 类型检查(Type Checking) 23.3.1 类型等价(Type Equivalence) …...
深入解析 Linux 内核中的页面错误处理机制
在现代操作系统中,页面错误(Page Fault)是内存管理的重要组成部分。当程序试图访问未映射到物理内存的虚拟内存地址时,CPU 会触发页面错误异常。Linux 内核通过一系列复杂的机制来处理这些异常,确保系统的稳定性和性能。本文将深入解析 Linux 内核中处理页面错误的核心代码…...
【AIGC专栏】AI在自然语言中的应用场景
ChatGPT出来以后,突然间整个世界都非常的为之一惊。很多人大喊AI即将读懂人类,虽然这是一句夸大其词的话,但是经过未来几十年的迭代,ChatGPT会变成什么样我们还真的很难说。在当前生成式内容来说,ChatGPT毫无疑问在当前…...
Ubuntu 20.04安装Protocol Buffers 2.5.0
个人博客地址:Ubuntu 20.04安装Protocol Buffers 2.5.0 | 一张假钞的真实世界 安装过程 Protocol Buffers 2.5.0源码下载:https://github.com/protocolbuffers/protobuf/tree/v2.5.0。下载并解压。 将autogen.sh文件中以下内容: curl htt…...
解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路
前瞻 PosterBandit 这个脚本能够根据用户指定的日期,爬取你看过的影视最高清的海报,然后使用 PixelWeaver.py 自动拼接成指定大小的长图。 你是否发现直接从豆瓣爬取下来的海报清晰度很低? 使用 .pic .nbg img CSS 选择器,在 我…...
计算机组成原理——数据运算与运算器(二)
生活就像一条蜿蜒的河流,有时平静,有时湍急。我们在这条河流中前行,会遇到风雨,也会遇见阳光。重要的是,无论遇到什么,都要保持内心的平静与坚定。每一次的挫折,都是让我们成长的机会࿱…...
SpringBoot+Vue的理解(含axios/ajax)-前后端交互前端篇
文章目录 引言SpringBootThymeleafVueSpringBootSpringBootVue(前端)axios/ajaxVue作用响应式动态绑定单页面应用SPA前端路由 前端路由URL和后端API URL的区别前端路由的数据从哪里来的 Vue和只用三件套axios区别 关于地址栏url和axios请求不一致VueJSPS…...
【AI】DeepSeek 概念/影响/使用/部署
在大年三十那天,不知道你是否留意到,“deepseek”这个词出现在了各大热搜榜单上。这引起了我的关注,出于学习的兴趣,我深入研究了一番,才有了这篇文章的诞生。 概念 那么,什么是DeepSeek?首先百…...
javascript-es6 (二)
函数进阶 函数提升 函数提升与变量提升比较类似,是指函数在声明之前即可被调用 好处:能够使函数的声明调用更灵活 函数提升出现在 相同作用域 当中 //可调用函数 fn()//后声明函数 function fn() {console.log(可先调用再声明) } 注意:函数表…...
供应链系统设计-供应链中台系统设计(十四)- 清结算中心设计篇(三)
关于清结算中心的设计,我们之前的两篇文章中,对于业务诉求的好的标准进行了初步的描述,如果没有看的同学可以参考一下两篇文章进行了解,这样更有利于理解本篇的内容。链接具体如下: 供应链系统设计-供应链中台系统设计…...
【自学笔记】MySQL的重点知识点-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 MySQL重点知识点MySQL知识点总结一、数据库基础二、MySQL的基本使用三、数据类型四、触发器(Trigger)五、存储引擎六、索引七、事务处理八、…...
X86路由搭配rtl8367s交换机
x86软路由,买双网口就好。或者单网口主板,外加一个pcie千兆。 华硕h81主板戴尔i350-T2双千兆,做bridge下载,速度忽高忽低。 今天交换机到货,poe供电,还是网管,支持Qvlan及IGMP Snooping…...
Linux环境基础开发工具的使用(apt, vim, gcc, g++, gbd, make/Makefile)
目录 什么是软件包 Linux 软件包管理器 apt 认识apt 查找软件包 安装软件 如何实现本地机器和云服务器之间的文件互传 卸载软件 Linux编辑器 - vim vim的基本概念 vim下各模式的切换 vim命令模式下各指令汇总 vim底行模式个指令汇总 Linux编译器 - gcc/g gcc/g的作…...
多模态论文笔记——ViViT
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细解读多模态论文《ViViT: A Video Vision Transformer》,2021由google 提出用于视频处理的视觉 Transformer 模型,在视频多模态领域有…...
搜索与图论复习1
1深度优先遍历DFS 2宽度优先遍历BFS 3树与图的存储 4树与图的深度优先遍历 5树与图的宽度优先遍历 6拓扑排序 1DFS: #include<bits/stdc.h> using namespace std; const int N10; int n; int path[N]; bool st[N]; void dfs(int u){if(nu){for(int i0;…...
【数据结构】初识链表
顺序表的优缺点 缺点: 中间/头部的插入删除,时间复杂度效率较低,为O(N) 空间不够的时候需要扩容。 如果是异地扩容,增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。 扩容可能会存在…...
第11章:根据 ShuffleNet V2 迁移学习医学图像分类任务:甲状腺结节检测
目录 1. Shufflenet V2 2. 甲状腺结节检测 2.1 数据集 2.2 训练参数 2.3 训练结果 2.4 可视化网页推理 3. 下载 1. Shufflenet V2 shufflenet v2 论文中提出衡量轻量级网络的性能不能仅仅依靠FLOPs计算量,还应该多方面的考虑,例如MAC(memory acc…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
Yii2项目自动向GitLab上报Bug
Yii2 项目自动上报Bug 原理 yii2在程序报错时, 会执行指定action, 通过重写ErrorAction, 实现Bug自动提交至GitLab的issue 步骤 配置SiteController中的actions方法 public function actions(){return [error > [class > app\helpers\web\ErrorAction,],];}重写Error…...
【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战
🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...
使用python进行图像处理—图像变换(6)
图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切(shear)以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。 6.1仿射变换(Affine Transformation) 仿射变换是一种…...
