VexDB BM25 算法的全文检索 fulltext 索引原理与实践
VexDB BM25 算法的全文检索 fulltext 索引原理与实践
之前分享了VexDB的ivfpq 聚集点向量索引、图向量索引、向标混合原理
《容器体验VexDB! 》 这家国产向量数据库说他们的图索引超越HNSW,是不是真的? 《VexDB DiskANN 图索引原理与实践》 《VexDB HybridAnn 向标混合索引原理与实践》
本篇将继续介绍VexDB的全文检索索引, VexDB的全文检索索引是采用BM25算法的, 总之搜索相关性较高.
关于BM25的更多内容, 还可参考vectorchord插件和相关论文( https://github.com/digoal/blog ), 如下:
《VectorChord-BM25:通过 BM25 排名彻底提升 PostgreSQL 搜索性能和效果 — 甚至比 ElasticSearch 快 2.26 倍》 《DuckDB进军AI数据库底座, 坐实了 | 文本语义(向量)搜索、全文检索、bm25搜索与排序、模糊搜索详解》 《VectorChord-bm25项目中BM25分数计算的Block-WeakAnd算法》 《召回精度提升插件: pg_tokenizer + VectorChord-BM25 reranking》 《AI论文解读 | BM25 Query Augmentation Learned End-to-End》 《AI论文解读 | Injecting the BM25 Score as Text Improves BERT-Based Re-rankers》 《AI论文解读 | The Probabilistic Relevance Framework: BM25 and Beyond》
下面的内容主要来自VexDB官方文档和个人的理解.
https://vexdb.com/docs/user-guide/fulltext-indexes
BM25(Best Matching 25)是一种广泛应用于信息检索领域的概率相关性模型,用于衡量查询与文档之间的匹配程度。通常使用场景为根据用户输入的查询内容,检索出与查询内容最相关的 top-k 文档,BM25 为查询和文档的相关性分数计算方式。其核心思想是结合以下几个因素来计算文档的相关性得分:
1、词频(Term Frequency,TF):词语在文档中出现的次数,反映了词的重要性,但单纯依赖词频可能会使长文档得分偏高。
2、逆文档频率(Inverse Document Frequency,IDF):反映了词语在整个文档集合中的稀有程度,常见词(如“的”、“是”)的 IDF 较低,而罕见词的 IDF 较高。
3、文档长度归一化:通过考虑文档长度及平均文档长度,平衡长文档与短文档之间的影响。
tf/idf细节可参考:
《PostgreSQL+pg_bestmatch+pgvector 打造适合自己文档库的TF-IDF加权, 库内将文本转换为向量, 提升文本搜索精确度和性能》 《PostgreSQL结合余弦、线性相关算法 在文本、图片、数组相似 等领域的应用 - 1 文本(关键词)分析理论基础 - TF(Term Frequency 词频)/IDF(Inverse Document Frequency 逆向文本频率)》
VexDB 支持基于 BM25 算法的全文检索索引 fulltext。
在进行全文检索时,您可以选择使用系统默认词典,或者通过下面介绍的方式创建自定义分词词典。
词典语法介绍
分词词典创建
CREATE TEXT SEARCH DICTIONARY <dict_name> (
TEMPLATE = vex_jieba
[, option = value [, ... ]]
);
创建分词词典时,只能指定模板为 vex_jieba 分词模板。该分词模板支持中英文混合分词与处理。创建语句如下:
CREATE TEXT SEARCH DICTIONARY test_dict (TEMPLATE = vex_jieba);
说明
PG_TS_DICT系统表包含定义文本检索词典的项;PG_TS_CONTENT系统表用于记录每个自定义分词词典中包含的具体词条内容,分为 停用词 和 用户词 。内置的字典模版 vex_jieba不支持tsvector或其他内核功能,仅限fulltext索引内部使用。
当前,创建分词词典的语句支持以下两个自选参数:
1、stopwords:停用词词典,指定哪些词或符号在分词时会被自动过滤掉(常用于排除 “的”、“是”、“and” 等无实际含义的词)。
empty | |
<dict_name> | <dict_name> 中的停用词配置作为当前词典的停用词。 |
2、userdict:用户自定义词典,用于指定自定义关键词,这些词在分词结果中将原样保留,不会被拆分。
empty | |
<dict_name> | <dict_name> 的用户词典配置。 |
词典修改
1、插入停用词
使用函数 vexjieba_add_stopwords(dict_name, words[]) 向词典添加停用词:
select vexjieba_add_stopwords(
<dict_name_or_oid>,
array[stopword1, stopword2, ...]);
系统会自动去重,重复插入不会影响效果。
2、清空停用词
使用函数 vexjieba_delete_stopwords(dict_name) 将该词典中所有停用词一次性清除:
SELECT vexjieba_delete_stopwords('my_dict');
3、插入自定义关键词
使用函数 vexjieba_add_userdict(dict_name, words[]) 插入自定义关键词,支持指定词频:
select vexjieba_add_userdict(
<dict_name_or_oid>,
array[keyword1, keyword2, ...]);
keyword | |
|---|---|
默认词频为 10000,高于大多数系统词条,保证可切分。
4、清空用户自定义词典
使用函数 vexjieba_delete_userdict(dict_name) 清除该词典中所有自定义关键词:
select vexjieba_delete_userdict(<dict_name_or_oid>);
词典重加载
修改词典内容后,必须执行重加载操作以使新词生效。
select vexjieba_reload(<dict_name_or_oid>);
词典删除
删除全文检索词典。
DROP TEXT SEARCH DICTIONARY [ IF EXISTS ] name [ CASCADE | RESTRICT ]
词典使用示例
1、使用默认分词词典 cn_tokenizer。
select bm25_tokenize('我司正在研发一款基于 openGauss 的不仅高性能又高可用而且高精度还易用的数据库');
分词结果:{我司,正在,研发,一款,opengauss,高性能,高,可用,高精度,易用,数据库}
2、创建一个分词词典 cn_dict,停用词词典为空,用户词典为空。
CREATE TEXT SEARCH DICTIONARY cn_dict(
template = pg_catalog.vex_jieba,
stopwords = empty,
userdict = empty);
3、插入自定义停用词。
select vexjieba_add_stopwords(
'cn_dict',
array['不仅', '又', '还', '的']);
4、插入自定义关键词。
SELECT vexjieba_add_userdict(
'cn_dict',
ARRAY['基于 openGauss', '高性能','高可用,10000','高精度']);
5、重新加载词典 cn_dict 并使用。
select vexjieba_reload('cn_dict');
select bm25_tokenize('我司正在研发一款基于 openGauss 的不仅高性能又高可用而且高精度还易用的数据库', 'cn_dict');
分词结果:{我司,正,在,研发,一款,基于openGauss,高性能,高可用,而且,高精度,还易用,数据库}
6、清空停用词。
SELECT vexjieba_delete_stopwords('cn_dict');
7、重新加载词典cn_dict并使用。
select vexjieba_reload('cn_dict');
select bm25_tokenize('我司正在研发一款基于 openGauss 的不仅高性能又高可用而且高精度还易用的数据库', 'cn_dict');
分词结果为:{我司,正,在,研发,一款,基于openGauss,的,不仅,高性能,又,高可用,而且,高精度,还易用,的,数据库}
8、清空用户自定义词典。
select vexjieba_delete_userdict('cn_dict');
9、重新加载词典 cn_dict 并使用。
select vexjieba_reload('cn_dict');
select bm25_tokenize('我司正在研发一款基于 openGauss 的不仅高性能又高可用而且高精度还易用的数据库', 'cn_dict');
分词结果为:{我司,正,在,研发,一款,基于,openGauss,的,不仅,高性,能,又,高,可用,而,且,高,精度,还易用,的,数据库}
10、删除词典。
DROP TEXT SEARCH DICTIONARY public.cn_dict CASCADE;
索引参数
索引构建参数
DICTS | cn_tokenizer默认值: cn_tokenizer | cn_tokenizer 表示系统默认词典。 如果字段是文本数组, 就不会进行分词。 |
ALGORITHMS | BM25/TF_IDF/LOG_TF_IDF默认值: BM25 | 在多列属性场景下通过 #对各列单独指定算法。 |
parallel_workers | 0~64默认值: 0 | |
COEFFICIENTS | b 取值范围:[0,1] 默认值:0.75参数 k 取值范围:[1,2] 默认值:1.2 | "<参数>=<数值>",不同参数通过:隔开,不允许包含空格,未指定的参数自动使用默认值,在多列属性场景下通过#对各列单独指定。目前参数包括b(默认值0.75),k(默认值1.2),只有 BM25 算法会使用到这些参数。 |
使用建议
1、注意关键词可以包含空格,但左右边的空格会被忽略,比如 ' a b c ' 等效于 'a b c'。另外关键词搜索使用完全匹配,比如包含关键词'数据库'的文档只能通过'数据库'关键词关联,而'数'和'数据'等词不能匹配到'数据库',如关键词 @-@ 查询未匹配到预期结果可以使用 bm25_tokenize(text, dictionary) 函数校验文档分词内容。
2、多列匹配查询,只能用 AND 连接。对于需要表示 OR 逻辑的查询可以将算子嵌入到查询关键词或者使用常规 union 等优化实现。
全文检索使用示例
1、创建测试表并插入测试数据。
DROP TABLE IF EXISTS comments;
CREATE TABLE comments (
id serial PRIMARY KEY,
document text,
title text
);
INSERT INTO comments (document, title) VALUES
('The quick brown fox jumps over the lazy dog', 'Animal story'),
('An agile fox was seen near the river bank', 'Wildlife report'),
('Dogs are loyal animals and good companions', 'Pet life'),
('Foxes are known for their cunning behavior', 'Wild instincts'),
('The quick blue hare outpaced the lazy dog', 'Animal race'),
('Dogs and foxes can both be found in Europe', 'Ecosystem overview');
2、默认使用系统分词器 cn_tokenizer,document 使用 cn_tokenizer 和 BM25 算法,参数为 b=0.75 和 k=1.2;title 使用 cn_tokenizer 和 TF_IDF 算法,无需参数。
CREATE INDEX bm25_idx_comments
ON comments USING fulltext(document, title)
WITH (
DICTS=cn_tokenizer#cn_tokenizer,
ALGORITHMS=BM25#TF_IDF,
COEFFICIENTS='b=0.75:k=1.2'
);
3、关键词匹配查询。
-- 以下查询只能通过BM25索引执行,关键词包含使用 @-@ 操作符,BM25评分使用 @~@ 操作符。
-- 1. 单关键词命中,查询document包含“fox”关键词的记录
SELECT * FROM comments WHERE document @-@ 'fox';
-- 2. 多关键词AND查询,查询document包含“fox”和“dog”关键词的记录
SELECT * FROM comments WHERE document @-@ 'fox AND dog';
-- 3. 多关键词OR查询,查询document包含“fox”或者“dog”关键词的记录
SELECT * FROM comments WHERE document @-@ 'fox OR dog';
-- 4. 复杂布尔逻辑查询,查询document包含“fox” 或者 “dog”和“quick”同时存在的关键词的记录
SELECT * FROM comments WHERE document @-@ 'fox OR (dog AND quick)';
-- 5. 多列匹配查询(必须用AND连接),查询document包含“fox” 和 title包含“Animal”的记录
SELECT * FROM comments WHERE document @-@ 'fox' AND title @-@ 'Animal';
4、BM25 Top-k 排序查询。
文本格式查询,查找与搜索词最相关文档,并按 BM25 算法打分排序,返回前3。
-- 有个疑问: 为什么bm25_score不需要传入参数?
SELECT *, bm25_score() as score
FROM comments
WHERE document @~@ 'quick fox dog'
ORDER BY score DESC NULLS LAST LIMIT 3;
5、外部分词( 使用数组存储, 是否支持tsvector? )创建索引。
DROP TABLE IF EXISTS docs;
CREATE TABLE docs (
id serial PRIMARY KEY,
raw_text text, -- 自动分词字段
tokens text[] -- 外部分词字段
);
INSERT INTO docs (raw_text, tokens) VALUES
('The quick brown fox jumps over the lazy dog', ARRAY['the', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']),
('An agile fox was seen near the river bank', ARRAY['an', 'agile', 'fox', 'was', 'seen', 'near', 'the', 'river', 'bank']),
('Dogs are loyal animals and good companions', ARRAY['dogs', 'are', 'loyal', 'animals', 'and', 'good', 'companions']);
CREATE INDEX idx_bm25_tokens
ON docs USING fulltext(tokens)
WITH (
ALGORITHMS = BM25,
COEFFICIENTS = 'b=0.75:k=1.2'
);