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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/965000.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)

【后端开发】系统设计101——通信协议&#xff0c;数据库与缓存&#xff0c;架构模式&#xff0c;微服务架构&#xff0c;支付系统&#xff08;36张图&#xff09; 文章目录 1、通信协议通信协议REST API 对比 GraphQL&#xff08;前端-web服务&#xff09;grpc如何工作&#x…

Spark--如何理解RDD

1、概念 rdd是对数据集的逻辑表示&#xff0c;本身并不存储数据&#xff0c;只是封装了计算逻辑&#xff0c;并构建执行计划&#xff0c;通过保存血缘关系来记录rdd的执行过程和历史&#xff08;当一个rdd需要重算时&#xff0c;系统会根据血缘关系追溯到最初的数据源&#xff…

0205算法:最长连续序列、三数之和、排序链表

力扣128&#xff1a;最长连续序列 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 class Solution {public int longestConsecutive(in…

gitea - fatal: Authentication failed

文章目录 gitea - fatal: Authentication failed概述run_gitea_on_my_pkm.bat 笔记删除windows凭证管理器中对应的url认证凭证启动gitea服务端的命令行正常用 TortoiseGit 提交代码备注END gitea - fatal: Authentication failed 概述 本地的git归档服务端使用gitea. 原来的用…

【数学】矩阵、向量(内含矩阵乘法C++)

目录 一、前置知识&#xff1a;向量&#xff08;一列或一行的矩阵&#xff09;、矩阵1. 行向量2. 列向量3. 向量其余基本概念4. 矩阵基本概念5. 关于它们的细节 二、运算1. 转置&#xff08;1&#xff09;定义&#xff08;2&#xff09;性质 2. 矩阵&#xff08;向量&#xff0…

算法与数据结构(合并K个升序链表)

思路 有了合并两个链表的基础后&#xff0c;这个的一种方法就是可以进行顺序合并&#xff0c;我们可以先写一个函数用来合并两个链表&#xff0c;再在合并K个链表的的函数中循环调用它。 解题过程 解析这个函数 首先&#xff0c;可以先判断&#xff0c;如果a为空&#xff0c…

Google C++ Style / 谷歌C++开源风格

文章目录 前言1. 头文件1.1 自给自足的头文件1.2 #define 防护符1.3 导入你的依赖1.4 前向声明1.5 内联函数1.6 #include 的路径及顺序 2. 作用域2.1 命名空间2.2 内部链接2.3 非成员函数、静态成员函数和全局函数2.4 局部变量2.5 静态和全局变量2.6 thread_local 变量 3. 类3.…

leetcode_双指针 160.相交链表

160.相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 思路: 本题中&#xff0c;交点不是数值相等&#xff0c;而是指针相等 双指针遍历两遍后必定相遇&#xff0c…

Oracle Primavera P6 最新版 v24.12 更新 2/2

目录 一. 引言 二. P6 EPPM 更新内容 1. 用户管理改进 2. 更轻松地标准化用户设置 3. 摘要栏标签汇总数据字段 4. 将里程碑和剩余最早开始日期拖到甘特图上 5. 轻松访问审计数据 6. 粘贴数据时排除安全代码 7. 改进了状态更新卡片视图中的筛选功能 8. 直接从活动电子…

2024年Web前端最新Java进阶(五十五)-Java Lambda表达式入门_eclipse lambda(1),面试必备

对象篇 模块化编程-自研模块加载器 开源分享&#xff1a;【大厂前端面试题解析核心总结学习笔记真实项目实战最新讲解视频】 Arrays.sort(players, sortByName); // 1.3 也可以采用如下形式: Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2))); ??其…

网络原理(5)—— 数据链路层详解

目录 一. 以太网 1.1 认识以太网 1.2 网卡与以太网 1.3 以太网帧格式 二. 认识MAC地址 三. MAC地址 与 IP地址 的区别 4.1 定义 4.2 分配方式 4.3 工作层次 4.4 地址格式 4.5 寻址方式 四. ARP协议 4.1 引入 4.2 ARP的概念 4.3 ARP工作原理 五. MTU 与 MSS …

DeepSeek R1 模型解读与微调

DeepSeek R1 模型是 DeepSeek 团队推出的一款重要的大语言模型&#xff0c;旨在通过强化学习提升大型语言模型的推理能力。 模型架构 DeepSeek-R1-Zero DeepSeek-R1-Zero 是 DeepSeek 团队推出的第一代推理模型&#xff0c;完全依靠强化学习&#xff08;RL&#xff09;训练&…

proxmox通过更多的方式创建虚拟机

概述 作为一名资深运维工程师&#xff0c;我们经常需要在 Proxmox 虚拟化平台上创建和管理虚拟机。本文将介绍三种不同的方式在 Proxmox 上创建 Ubuntu 虚拟机&#xff1a; 通过 Proxmox 命令创建虚拟机通过 Shell 脚本自动化创建虚拟机使用 Proxmox API 创建虚拟机 每种方式…

Linux 压缩打包

Linux压缩打包 文章目录 Linux压缩打包压缩的意义和原理压缩的意义压缩的原理压缩与解压缩的好处压缩打包命令.zipzip 命令用法unzip 的用法.gzgzip 的用法gunzip 的用法.bz2bzip2 的用法bunzip2 的用法.xzxz 命令用法tar04-Linux压缩打包课后习题压缩的意义和原理 压缩的意义…

Apache HttpClient

HttpClient是apache组织下面的一个用于处理HTTP请求和响应的来源工具&#xff0c;是一个在JDK基础类库是做了更好的封装的类库。 HttpClient 使用了连接池技术来管理 TCP 连接&#xff0c;这有助于提高性能并减少资源消耗。连接池允许 HttpClient 复用已经建立的连接&#xff0…

【C++】STL——list底层实现

目录 &#x1f495;1.list的三个类介绍 &#x1f495;2.list——节点类 &#xff08;ListNode&#xff09; &#x1f495;3.list——链表类 &#xff08;List&#xff09; &#x1f495;4.list——迭代器类&#xff08;重点思考&#xff09;(ListIterator) &#x1f495;5…

SpringUI Web高端动态交互元件库

Axure Web高端动态交互元件库是一个专为Web设计与开发领域设计的高质量资源集合&#xff0c;旨在加速原型设计和开发流程。以下是关于这个元件库的详细介绍&#xff1a; 一、概述 Axure Web高端动态交互元件库是一个集成了多种预制、高质量交互组件的工具集合。这些组件经过精…

02、NodeJS学习笔记,第二节:express与中间件

express与中间件 中文官网&#xff1a;https://www.expressjs.com.cn/nodemon工具 nodemon这个工具&#xff0c;能够监听项目文件的变动。 当代码被修改后&#xff0c;nodemon会帮我们自动重启项目&#xff0c;极大的方便了开发和调试##安装 npm i -g nodemon##使用 之前启动…

通向AGI之路:人工通用智能的技术演进与人类未来

文章目录 引言:当机器开始思考一、AGI的本质定义与技术演进1.1 从专用到通用:智能形态的范式转移1.2 AGI发展路线图二、突破AGI的五大技术路径2.1 神经符号整合(Neuro-Symbolic AI)2.2 世界模型架构(World Models)2.3 具身认知理论(Embodied Cognition)三、AGI安全:价…

结合深度学习、自然语言处理(NLP)与多准则决策的三阶段技术框架,旨在实现从消费者情感分析到个性化决策

针对电商个性化推荐场景的集成机器学习和稳健优化三阶段方案。 第一阶段:在线评论数据处理&#xff0c;利用深度学习和自然语言处理技术进行特征挖掘&#xff0c;进而进行消费者情感分析&#xff0c;得到消费者偏好 在第一阶段&#xff0c;我们主要关注如何通过深度学习和自然语…