【Elasticsearch】相关性,近义词匹配,纠错匹配

文章详细介绍了Lucene和Elasticsearch如何使用布尔模型、TF-IDF和向量空间模型来计算文档的相关性,并涉及近义词匹配和纠错匹配的机制。通过对词频、逆向文档频率和字段长度归一化的讨论,阐述了评分函数的工作原理。

目录

相关性

布尔模型

词频/逆向文档频率(TF/IDF)

词频

逆向文档频率

字段长度归一值

结合使用

向量空间模型

Lucene 的实用评分函数

近义词匹配

近义词查询原理

同义词过滤器

纠错匹配


相关性

Lucene(或 Elasticsearch)使用 布尔模型(Boolean model) 查找匹配文档,并用一个名为 实用评分函数(practical scoring function) 的公式来计算相关度。这个公式借鉴了 词频/逆向文档频率(term frequency/inverse document frequency) 和 向量空间模型(vector space model),同时也加入了一些现代的新特性,如协调因子(coordination factor),字段长度归一化(field length normalization),以及词或查询语句权重提升。

布尔模型

布尔模型(Boolean Model) 只是在查询中使用 AND 、 OR 和 NOT (与、或和非)这样的条件来查找匹配的文档,以下查询:

full AND text AND search AND (elasticsearch OR lucene)

会将所有包括词 full 、 text 和 search ,以及 elasticsearch 或 lucene 的文档作为结果集。

这个过程简单且快速,它将所有可能不匹配的文档排除在外。

词频/逆向文档频率(TF/IDF)

当匹配到一组文档后,需要根据相关度排序这些文档,不是所有的文档都包含所有词,有些词比其他的词更重要。一个文档的相关度评分部分取决于每个查询词在文档中的 权重 。

词的权重由三个因素决定,在 什么是相关 中已经有所介绍,有兴趣可以了解下面的公式,但并不要求记住。

词频

词在文档中出现的频度是多少?频度越高,权重 越高 。 5 次提到同一词的字段比只提到 1 次的更相关。词频的计算方式如下:

tf(t in d) = √frequency 

词 t 在文档 d 的词频( tf )是该词在文档中出现次数的平方根。

如果不在意词在某个字段中出现的频次,而只在意是否出现过,则可以在字段映射中禁用词频统计:

PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "text": {
          "type":          "string",
          "index_options": "docs" 
        }
      }
    }
  }
}

将参数 index_options 设置为 docs 可以禁用词频统计及词频位置,这个映射的字段不会计算词的出现次数,对于短语或近似查询也不可用。要求精确查询的 not_analyzed 字符串字段会默认使用该设置。

逆向文档频率

词在集合所有文档里出现的频率是多少?频次越高,权重 越低 。常用词如 and 或 the 对相关度贡献很少,因为它们在多数文档中都会出现,一些不常见词如 elastic 或 hippopotamus 可以帮助我们快速缩小范围找到感兴趣的文档。逆向文档频率的计算公式如下:

idf(t) = 1 + log ( numDocs / (docFreq + 1)) 

词 t 的逆向文档频率( idf )是:索引中文档数量除以所有包含该词的文档数,然后求其对数。

字段长度归一值

字段的长度是多少?字段越短,字段的权重 越高 。如果词出现在类似标题 title 这样的字段,要比它出现在内容 body 这样的字段中的相关度更高。字段长度的归一值公式如下:

norm(d) = 1 / √numTerms 

字段长度归一值( norm )是字段中词数平方根的倒数。

字段长度的归一值对全文搜索非常重要,许多其他字段不需要有归一值。无论文档是否包括这个字段,索引中每个文档的每个 string 字段都大约占用 1 个 byte 的空间。对于 not_analyzed 字符串字段的归一值默认是禁用的,而对于 analyzed 字段也可以通过修改字段映射禁用归一值:

PUT /my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "text": {
          "type": "string",
          "norms": { "enabled": false } 
        }
      }
    }
  }
}

这个字段不会将字段长度归一值考虑在内,长字段和短字段会以相同长度计算评分。

对于有些应用场景如日志,归一值不是很有用,要关心的只是字段是否包含特殊的错误码或者特定的浏览器唯一标识符。字段的长度对结果没有影响,禁用归一值可以节省大量内存空间。

结合使用

以下三个因素——词频(term frequency)、逆向文档频率(inverse document frequency)和字段长度归一值(field-length norm)——是在索引时计算并存储的。最后将它们结合在一起计算单个词在特定文档中的 权重 。

前面公式中提到的 文档 实际上是指文档里的某个字段,每个字段都有它自己的倒排索引,因此字段的 TF/IDF 值就是文档的 TF/IDF 值。

当用 explain 查看一个简单的 term 查询时(参见 explain ),可以发现与计算相关度评分的因子就是前面章节介绍的这些:

PUT /my_index/doc/1
{ "text" : "quick brown fox" }

GET /my_index/doc/_search?explain
{
  "query": {
    "term": {
      "text": "fox"
    }
  }
}

以上请求(简化)的 explanation 解释如下:

weight(text:fox in 0) [PerFieldSimilarity]:  0.15342641 
result of:
    fieldWeight in 0                         0.15342641
    product of:
        tf(freq=1.0), with freq of 1:        1.0 
        idf(docFreq=1, maxDocs=1):           0.30685282 
        fieldNorm(doc=0):                    0.5 

词 fox 在文档的内部 Lucene doc ID 为 0 ,字段是 text 里的最终评分。

词 fox 在该文档 text 字段中只出现了一次。

fox 在所有文档 text 字段索引的逆向文档频率。

该字段的字段长度归一值。

当然,查询通常不止一个词,所以需要一种合并多词权重的方式——向量空间模型(vector space model)。

向量空间模型

向量空间模型(vector space model) 提供一种比较多词查询的方式,单个评分代表文档与查询的匹配程度,为了做到这点,这个模型将文档和查询都以 向量(vectors) 的形式表示:

向量实际上就是包含多个数的一维数组,例如:

[1,2,5,22,3,8]

在向量空间模型里,向量空间模型里的每个数字都代表一个词的 权重 ,与 词频/逆向文档频率(term frequency/inverse document frequency) 计算方式类似。

尽管 TF/IDF 是向量空间模型计算词权重的默认方式,但不是唯一方式。Elasticsearch 还有其他模型如 Okapi-BM25 。TF/IDF 是默认的因为它是个经检验过的简单又高效的算法,可以提供高质量的搜索结果。

设想如果查询 “happy hippopotamus” ,常见词 happy 的权重较低,不常见词 hippopotamus 权重较高,假设 happy 的权重是 2 , hippopotamus 的权重是 5 ,可以将这个二维向量—— [2,5] ——在坐标系下作条直线,线的起点是 (0,0) 终点是 (2,5) ,如图 Figure 27, “表示 “happy hippopotamus” 的二维查询向量” 

在实际中,只有二维向量(两个词的查询)可以在平面上表示,幸运的是, 线性代数 ——作为数学中处理向量的一个分支——为我们提供了计算两个多维向量间角度工具,这意味着可以使用如上同样的方式来解释多个词的查询。

关于比较两个向量的更多信息可以参考 余弦近似度(cosine similarity);

Lucene 的实用评分函数

对于多词查询, Lucene 使用 布尔模型(Boolean model) 、 TF/IDF 以及 向量空间模型(vector space model) ,然后将它们组合到单个高效的包里以收集匹配文档并进行评分计算。

近义词匹配

ES近义词匹配搜索需要用户提供一张满足相应格式的近义词表,并在创建索引时设计将该表放入settings中。

近义词表的可以直接以字符串的形式写入settings中也可以放入文本文件中,由es读取。

近义词表需要满足以下格式要求:

  1. A => B,C格式

    • 这种格式在搜索时会将搜索词A替换成B、C,且B,C互不为同义词;
  2. A,B,C,D 格式

这种格式得分情况讨论:

  • expand == true时,这种格式等价于A,B,C,D => A,B,C,D即ABCD互为同义词

  • expand == false时,这种格式等价于A,B,C,D => A,即ABCD四个词在搜索时会被替换成A

PUT /fond_goods
{
  "settings": {
    "number_of_replicas": 0,
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "my_whitespace":{
          "tokenizer":"whitespace",
          "filter": ["synonymous_filter"]
        }
      },
      "filter": {
        "synonymous_filter":{
          "type": "synonym",
          "expand": true
          "synonyms": [
            "A, B, C, D"
            ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "code":{
        "type": "keyword"
      },
      "context":{
        "type": "text",
        "analyzer": "my_whitespace"
      },
      "color":{
        "type": "text",
        "analyzer": "my_whitespace"
      }
    }
  }
}
  • expand 默认值为 true
  • lenient 默认值为falselenient值为true, es会忽略转换近义词文件时的报错。值得注意的是,只有当遇到近义词无法转换时出现的异常才会被忽略掉;
  • synonyms近义词表,即开始所说要按格式填写的近义词表。
  • synonyms也可替换成synonyms_path,此时需要填写一个外部文件的路径。该文件可以是某个外部的网页,也可以是存放在本地的文件。
  • format 当该参数值为wordnet时,可以使用wordnet英文词汇数据库中的近义词。

近义词查询原理

ES的分词器主要由字符过滤器(Character Filter)、分词器(Tokenizer)、分词过滤器(Token Filter)组成。

  • 字符过滤器(Character Filter)
    1. 以字符流的形式接受文本,并可以通过添加、删除或更改字符来转化文本。
    2. 一个Analyzer可以由0个或多个字符过滤器
  • 分词器(Tokenizer)
    1. 对经过字符过滤器过滤后的文本按照一定规则分词。一个Analyzer只允许有一个分词器
  • 分词过滤器(Token Filter)
    1. 针对分词后的token再次进行过滤,可以增删和修改token,一个分词器中可以有多个token过滤器
同义词过滤器

同义词查询的关键其实就是自定义Token过滤器。该过滤器在收到分词器发过来的数据(我暂时将其称之为分词数据)时,会先读取用户存放的近义词文件,比对分词数据。当出现同义词时,Token过滤器就按照近义词文件配置的规则选定带搜索词组,进行同义词搜索。

我们可以拿之前的索引做个试验:我们的索引使用的是自定义的分析器my_whitespace,其中分词器是whitespace空格分词器, 而token Filter 使用的是自定义的近义词过滤器。由上述可知,我们自定义的分析器与官方自带的whitespace分析器唯一的差别就在token Filter上。

纠错匹配

es中使用phrase Suggester来进行拼写纠错。phrase suggester在term suggester之上添加了额外的逻辑以选择整体更正的phrase,而不是基于单个分词加权的ngram语言模型。在实际中phrase suggester能够根据单词的词频等信息作出更好的选择。

ES中常用的4种Suggester类型:Term、Phrase、Completion、Context。

Term suggester正如其名,只基于analyze过的单个term去提供建议,并不会考虑多个term之间的关系。API调用方只需为每个token挑选options里的词,组合在一起返回给用户前端即可。 那么有无更直接办法,API直接给出和用户输入文本相似的内容? 答案是有,这就要求助Phrase Suggester了。
Phrase suggester在Term suggester的基础上,会考量多个term之间的关系,比如是否同时出现在索引的原文里,相邻程度,以及词频等等。
Completion Suggester,它主要针对的应用场景就是"Auto Completion"。 此场景下用户每输入一个字符的时候,就需要即时发送一次查询请求到后端查找匹配项,在用户输入速度较高的情况下对后端响应速度要求比较苛刻。因此实现上它和前面两个Suggester采用了不同的数据结构,索引并非通过倒排来完成,而是将analyze过的数据编码成FST和索引一起存放。对于一个open状态的索引,FST会被ES整个装载到内存里的,进行前缀查找速度极快。但是FST只能用于前缀查找,这也是Completion Suggester的局限所在。
Context Suggester,会根据上下文进行补全,这种方式补全效果较好,但性能较差,用的人不多。这也是es中拼写纠错的高级用法。


Lucene中通过Suggest模块下的SpellChecker功能来实现拼写纠错

源码中,定义了两个public的静态成员变量, DEFAULT_ACCURACY表示默认的最小分数,SpellCheck会对字典里的每个词与用户输入的搜索关键字进行一个相似度打分,默认该值是0.5,相似度分值范围是0到1之间,数字越大表示越相似,小于0.5会认为不是同一个结果。F_WORD是对于字典文件里每一行创建索引时使用的默认域名称,默认值为:word。

几个重要的API:

getAccuracy: accuracy是精确度的意思,这里表示最小评分,评分越大表示与用户输入的关键字越相似
suggestSimilar:这个方法就是用来决定哪些word会被判定为比较相似的然后以数组的形式返回,这是SpellChecker的核心;
setComparator:设置比较器,既然涉及到相似度问题,那肯定有相似度大小问题,有大小必然存在比较,有比较必然需要比较器,通过比较器决定返回的建议词的顺序,因为一般需要把最相关的显示在最前面,然后依次排序显示;
setSpellIndex:设置拼写检查索引目录的
setStringDistance:设置编辑距离
 

<think>我们讨论的是数据库中的模糊匹配功能。模糊匹配主要用于处理不完全精确的查询条件,允许用户根据部分信息或相似度来检索数据。常见的应用场景包括搜索功能(如姓名、地址的模糊查询)、纠错系统、相似文本匹配等。 核心实现技术: 1. LIKE运算符和通配符:最基础的模糊匹配方式,使用`%`匹配任意多个字符,`_`匹配单个字符。 示例:`SELECT * FROM users WHERE name LIKE '张%';` -- 匹配所有姓张的用户 2. 正则表达式匹配:更强大的模式匹配,如MySQL的REGEXP,PostgreSQL的~运算符。 示例:`SELECT * FROM users WHERE name REGEXP '^张[伟刚].*';` 3. 全文检索:针对大文本字段的高效搜索,支持自然语言处理和相关性排序。 - MySQL的MATCH AGAINST - PostgreSQL的tsvector/tsquery 4. 相似度匹配函数: - PostgreSQL的pg_trgm模块(提供similarity函数,Levenshtein距离函数) - MySQL的Levenshtein函数(需自定义实现) 5. 语音匹配:如Soundex算法(将单词根据发音转换为代码),用于匹配发音相似的单词。 6. 高级扩展:一些数据库支持向量相似度搜索(如PostgreSQL的pgvector扩展)用于处理高维数据的相似度。 性能注意事项: - 模糊匹配(尤其是LIKE以通配符开头)通常无法使用索引,导致全表扫描,性能低下。 - 解决方案: * 使用全文检索替代LIKE * 使用pg_trgm的GIN索引(PostgreSQL) * 使用倒排索引(如Elasticsearch等专门搜索引擎) 示例:在PostgreSQL中使用pg_trgm进行高效模糊匹配 ```sql -- 启用扩展 CREATE EXTENSION pg_trgm; -- 创建索引 CREATE INDEX idx_users_name_trigram ON users USING gin (name gin_trgm_ops); -- 查询相似度大于0.3的名字 SELECT name, similarity(name, '张三') AS sim FROM users WHERE name % '张三' -- 使用%运算符,表示相似度超过阈值(默认0.3) ORDER BY sim DESC; ``` 应用场景选择: - 简单部分匹配:LIKE - 复杂模式:正则表达式 - 自然语言搜索:全文检索 - 纠错和相似度匹配:pg_trgm(PostgreSQL)或自定义函数 注意:模糊匹配对性能影响较大,在设计时应考虑数据量和查询频率,必要时引入专门的搜索引擎(如Elasticsearch)进行优化。 §§相关问题§§ 1. 在使用LIKE进行模糊查询时,如何优化以通配符开头的查询(如`LIKE '%abc'`)的性能? 2. 全文检索(Full-Text Search)与普通的模糊匹配(LIKE)在实现原理和适用场景上有哪些主要区别? 3. 在PostgreSQL中,pg_trgm模块的工作原理是什么?它如何支持相似度匹配? 4. 对于中文等非字母文字,模糊匹配会遇到哪些特殊挑战?有哪些针对性的解决方案? 5. 如何设计一个支持错别字容忍的数据库查询系统?需要哪些关键技术支持?</think>### 数据库模糊匹配技术解析 模糊匹配是数据库查询中处理不精确数据的核心技术,主要用于实现以下场景: - 部分关键词搜索(如姓名、地址) - 拼写容错查询 - 相似度匹配 - 数据清洗与整合 #### 核心实现方法 | 技术 | 适用数据库 | 语法示例 | 特点 | |------|------------|----------|------| | **LIKE运算符** | 所有SQL数据库 | `SELECT * FROM users WHERE name LIKE '张%'` | 基础通配符(%匹配任意字符,_匹配单字符) | | **正则匹配** | MySQL/PostgreSQL | `SELECT * FROM products WHERE name REGEXP '^手机[0-9]{4}'` | 复杂模式匹配 | | **全文检索** | MySQL/PostgreSQL | ```SELECT * FROM articles WHERE MATCH(content) AGAINST('数据库优化')``` | 语义分析/词干提取 | | **相似度算法** | PostgreSQL | `SELECT * FROM customers WHERE SIMILARITY(name, '王伟') > 0.7` | 基于pg_trgm模块 | | **语音匹配** | 需扩展 | `SELECT * FROM users WHERE SOUNDEX(name) = SOUNDEX('Smith')` | Soundex/Metaphone算法 | #### 优化技巧 1. **索引策略** ```sql -- PostgreSQL trigram索引 CREATE INDEX idx_name_trgm ON users USING gin (name gin_trgm_ops); -- MySQL全文索引 CREATE FULLTEXT INDEX idx_content ON articles(content); ``` 2. **中文分词处理** ```sql -- PostgreSQL中文分词配置 CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = pg_catalog.default); ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR word WITH simple; ``` 3. **模糊匹配函数封装** ```sql /* 创建容错查询函数 */ CREATE FUNCTION fuzzy_search(keyword TEXT) RETURNS TABLE (id INT, name TEXT) AS $$ SELECT id, name FROM users WHERE SIMILARITY(name, keyword) > 0.65 OR name % keyword -- pg_trgm相似运算符 $$ LANGUAGE SQL; ``` #### 性能对比 | 方法 | 10万记录耗时 | 百万记录耗时 | 精度 | |------|--------------|--------------|------| | LIKE通配符 | 120ms | 超时(>30s) | ★★☆ | | 正则表达式 | 300ms | 超时(>30s) | ★★★ | | trigram索引 | 15ms | 180ms | ★★★★ | | 全文检索 | 8ms | 95ms | ★★★☆ | > **最佳实践建议**:中文场景优先使用`pg_trgm`+分词组合,高并发系统推荐整合Elasticsearch专业搜索引擎
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方鲤鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值