# 混合搜索

注意

本指南仅适用于DB版本1.5.0或更高版本。

本指南介绍了如何在MyScale中执行全文和向量混合搜索。

全文搜索向量搜索各有其优势和劣势。全文搜索适用于基本的关键字检索和文本匹配,而向量搜索在跨文档语义匹配和深度语义理解方面表现出色,但在短文本查询方面可能缺乏效率。为了充分利用两种方法的优势,开发了混合搜索。通过结合全文索引和向量搜索的特点,它可以有效地满足各种文本搜索需求,提高搜索文本的准确性和速度,并有效地满足用户对精确结果的期望。

# 教程概述

本文将指导您完成三个搜索实验:

在MyScale中执行向量搜索

所有文档和查询文本都使用all-MiniLM-L6-v2 (opens new window)模型进行向量生成,并使用余弦距离进行相似度测量。

在MyScale中执行文本搜索

MyScale集成了Tantivy (opens new window)库,以启用基于BM25的全文搜索(FTS)索引。通过对表中的所有文本数据进行索引,MyScale的FTS功能可以根据BM25分数对搜索结果进行排名,提供更相关和准确的搜索体验。

使用RRF策略融合向量和文本搜索结果

我们使用广泛使用的Python库ranx (opens new window)来结合向量搜索和文本搜索结果。这种融合增强了文本搜索在各种场景下的适应性,最终提高了搜索准确性。

MyScale中混合搜索的示意图

在开始之前,请确保您已经设置了一个MyScale集群。有关设置说明,请参阅我们的快速入门指南 (opens new window)

# 数据集

该实验使用了由RediSearch提供的Wikipedia摘要数据集 (opens new window),包含5,622,309个文档条目。使用all-MiniLM-L6-v2 (opens new window)模型对文本进行处理,生成存储在body_vector列中的384维向量。使用余弦距离计算向量之间的相似度。

提示

有关如何使用all-MiniLM-L6-v2的更多信息,请参阅HuggingFace的文档

数据集wiki_abstract_with_vector.parquet (opens new window)的大小为8.2GB。您可以在不需要下载到本地的情况下预览其内容,因为我们将在后续实验中直接通过S3导入到MyScale中。

id body title url body_vector
... ... ... ... ...
77 Jake Rodkin is an American .... and Puzzle Agent. Jake Rodkin https://en.wikipedia.org/wiki/Jake_Rodkin (opens new window) [-0.081793934,....,-0.01105572]
78 Friedlandpreis der Heimkehrer is ... of Germany. Friedlandpreis der Heimkehrer https://en.wikipedia.org/wiki/Friedlandpreis_der_Heimkehrer (opens new window) [0.018285718,...,0.03049711]
... ... ... ... ...

# 创建表和导入数据

在MyScale的SQL工作区中创建名为wiki_abstract_5m的表,执行以下SQL语句:

CREATE TABLE default.wiki_abstract_5m(
    `id` UInt64,
    `body` String,
    `title` String,
    `url` String,
    `body_vector` Array(Float32),
    CONSTRAINT check_length CHECK length(body_vector) = 384
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 128;

从S3导入数据到表中。在数据导入过程中,请耐心等待。

INSERT INTO default.wiki_abstract_5m
SELECT * FROM s3('https://myscale-datasets.s3.ap-southeast-1.amazonaws.com/wiki_abstract_with_vector.parquet', 'Parquet');

注意

数据导入的预计时间约为10分钟。

通过运行以下查询验证表中是否有5,622,309行数据:

SELECT count(*) FROM default.wiki_abstract_5m;

输出:

count()
5622309

为了提高向量搜索性能,将表优化为将其数据部分合并为一个部分(可选),在SQL工作区中执行以下SQL命令:

OPTIMIZE TABLE default.wiki_abstract_5m FINAL;

此优化步骤可能需要一些时间来完成。

通过运行以下查询检查此表的数据部分是否已压缩为一个部分:

SELECT COUNT(*) FROM system.parts WHERE table='wiki_abstract_5m' AND active=1;

如果成功,您将看到:

count()
1

# 构建索引

# 创建FTS索引

提示

要了解如何创建FTS索引,请参阅FTS索引文档

在设置FTS索引时,用户可以选择自定义分词器。在本示例中,使用stem分词器,并应用stop wordsstem分词器可以忽略文本中的词形,以获得更精确的搜索结果。通过使用stop words,可以过滤掉常见词汇,如"a"、"an"、"of"和"in",以提高搜索准确性。

ALTER TABLE default.wiki_abstract_5m
ADD INDEX body_idx (body)
TYPE fts('{"body":{"tokenizer":{"type":"stem", "stop_word_filters":["english"]}}}');

执行索引的材料化:

ALTER TABLE default.wiki_abstract_5m MATERIALIZE INDEX body_idx;

# 创建向量索引

提示

了解有关MSTG向量索引的更多信息,请参阅向量搜索文档

要在表default.wiki_abstract_5m上构建使用余弦距离计算方法的MSTG向量索引,请执行以下SQL语句:

ALTER TABLE default.wiki_abstract_5m
ADD VECTOR INDEX body_vec_idx body_vector
TYPE MSTG('metric_type=Cosine');

构建向量索引可能需要一些时间。要监视其进度,请运行以下SQL查询。如果状态列显示为"Built",则表示索引已成功创建。如果仍在进行中,则应显示为"InProgress"。

SELECT * FROM system.vector_indices;

# 执行混合搜索

# 初步准备

为了将查询文本转换为向量,我们将使用all-MiniLM-L6-v2 (opens new window)模型。

首先,通过运行以下命令安装sentence-transformers库:

pip install -U sentence-transformers

对于融合阶段,我们将使用ranx (opens new window)库。使用以下命令安装它:

pip install -U ranx

混合搜索实践将在一个Python文件中实现。在进行此实践之前,请确保将您的MyScale集群信息输入到提供的示例代码中。

提示

您可以在Web控制台中找到MyScale集群的连接详细信息。

import clickhouse_connect
from numba import NumbaTypeSafetyWarning
from prettytable import PrettyTable
from ranx import Run, fuse
from ranx.normalization import rank_norm
from sentence_transformers import SentenceTransformer
import warnings
warnings.filterwarnings('ignore', category=NumbaTypeSafetyWarning)
# 使用all-MiniLM-L6-v2模型
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# MyScale信息
host = "your cluster end-point"
port = 443
username = "your user name"
password = "your password"
database = "default"
table = "wiki_abstract_5m"
# 初始化MyScale客户端
client = clickhouse_connect.get_client(host=host, port=port,
                                       username=username, password=password)
# 使用一个表来输出您的内容
def print_results(result_rows, field_names):
    x = PrettyTable()
    x.field_names = field_names
    for row in result_rows:
        x.add_row(row)
    print(x)

# 执行向量搜索

提示

The British Graham Land expedition (BGLE) was a British expedition. For detailed information about them please refer to Wikipedia (opens new window).

我们的目标是在该数据集中识别由BGLE远征队访问和绘制的地点。为此,我们将在进行向量搜索查询时使用"Charted by BGLE"作为搜索字符串。

提示

请将本消息中提供的示例代码包含在之前准备工作期间创建的Python文件中,以确保程序的顺利执行。此外,请确保以类似的方式处理其他步骤。

terms = "Charted by BGLE"
terms_embedding = model.encode([terms])[0]
vector_search = f"SELECT id, title, body, distance('alpha=3')" \
                f"(body_vector,{list(terms_embedding)}) AS distance FROM {database}.{table} " \
                f"ORDER BY distance ASC LIMIT 100"
vector_search_res = client.query(query=vector_search)
print("\nVectorSearch results:")
print_results(vector_search_res.result_rows[:10], ["ID", "Title", "Body", "Distance"])

通过向量搜索检索到的文档在前10个结果中不包含与"Charted by BGLE"相关的任何信息,因为向量搜索在处理短文本查询时往往表现不佳。

ID Title Body Distance
1177089 B.G. discography This is the discography of B.G. 0.44516414403915405
2710950 Ruy Blas and the Blasé Roué Ruy Blas and the Blasé Roué is a burlesque written by A. C. 0.4736078381538391
4571962 BLL BLL may refer to: 0.48070353269577026
3797998 Bles Bles may refer to: 0.4849556088447571
4558187 The Complete Blam Blam Blam The Complete Blam Blam Bam is a compilation LP by Blam Blam Blam. It was released on November, 1992. 0.4863016605377197
5556733 BLG BLG may refer to: 0.49544525146484375
4591273 Blagg Blagg may refer to: 0.5007722973823547
3087541 Paul Bley discography This is the discography for Canadian jazz musician Paul Bley. 0.5030192136764526
3389086 Blot (album) Blot is the fourth full-length album by the Norwegian black/Viking metal band Einherjer. It was released on 21 November 2003 by Tabu Recordings. 0.5040180087089539
1876940 Blåljus! Blåljus! is a six-volume light pocket series of novels by Margit Sandemo and published by the Swedish subsidiade Boknöje AB of the Norwegian publication Bladkompaniet in year 2004. 0.5080643892288208

# 执行文本搜索

我们利用MyScale的TextSearch()函数检索一组相关结果以及BM25分数。为了方便后续的结果融合,我们首先必须临时存储这些文档数据。

text_search = f"SELECT id, title, body, TextSearch(body, '{terms}') AS score " \
              f"FROM {database}.{table} " \
              f"ORDER BY score DESC LIMIT 100"
text_search_res = client.query(query=text_search)
# 临时存储搜索结果。
stored_data = {}
for row in vector_search_res.result_rows:
    stored_data[str(row[0])] = {"title": row[1], "body": row[2]}
for row in text_search_res.result_rows:
    if str(row[0]) not in stored_data:
        stored_data[str(row[0])] = {"title": row[1], "body": row[2]}

# 使用RRF策略融合向量和文本搜索结果

我们使用ranx (opens new window)库来结合向量和文本搜索结果,使用RRF融合策略(Reciprocal Rank Fusion)来提高搜索准确性。对于其他融合策略,您可以参考ranx/fusion (opens new window)

# 从查询结果中提取id和分数。
bm25_dict = {"query-0": {str(row[0]): float(row[3]) for row in text_search_res.result_rows}}
# 对于ranx库,较高的分数应表示更高的相关性,
# 因此需要对余弦和L2等向量距离计算方法进行预处理。
max_value = max(float(row[3]) for row in vector_search_res.result_rows)
vector_dict = {"query-0": {str(row[0]): max_value - float(row[3]) for row in vector_search_res.result_rows}}
# 规范化查询结果分数。
vector_run = rank_norm(Run(vector_dict, name="vector"))
bm25_run = rank_norm(Run(bm25_dict, name="bm25"))
# 使用RRF融合查询结果。
combined_run = fuse(
    runs=[vector_run, bm25_run],
    method="rrf",
    params={'k': 10}
)
print("\nFusion results:")
pretty_results = []
for id_, score in combined_run.get_doc_ids_and_scores()[0].items():
    if id_ in stored_data:
        pretty_results.append([id_, stored_data[id_]["title"], stored_data[id_]["body"], score])
print_results(pretty_results[:10], ["ID", "Title", "Body", "Score"])

融合查询结果如下:混合搜索准确匹配了与BGLE远征队绘制的地点相关的五篇文章,展示了混合搜索在处理短文本查询时的优势。

ID Title Body Score
200245 Salmon Island Salmon Island () is the westernmost of the Fish Islands, lying off the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 0.09090909090909091
1177089 B.G. discography This is the discography of B.G. 0.09090909090909091
5024941 Woozle Hill Woozle Hill () is a hill near the center of Galindez Island, in the Argentine Islands in the Wilhelm Archipelago. First charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 0.08333333333333333
2710950 Ruy Blas and the Blasé Roué Ruy Blas and the Blasé Roué is a burlesque written by A. C. 0.08333333333333333
4571962 BLL BLL may refer to: 0.07692307692307693
4426976 Symington Islands Symington Islands () is a group of small islands lying west-northwest of Lahille Island, in the Biscoe Islands. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 0.07692307692307693
3797998 Bles Bles may refer to: 0.07142857142857142
197443 Tadpole Island Tadpole Island () is an island just north of Ferin Head, off the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 0.07142857142857142
202128 Sohm Glacier Sohm Glacier () is a glacier flowing into Bilgeri Glacier on Velingrad Peninsula, the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 0.06666666666666667
4558187 The Complete Blam Blam Blam The Complete Blam Blam Bam is a compilation LP by Blam Blam Blam. It was released on November, 1992. 0.06666666666666667

# 结论

本文提供了关于在文本搜索场景中使用MyScale混合搜索的用法见解,重点介绍了处理非结构化文本数据的方法和技术。在实际练习中,我们使用了维基百科摘要进行了示例。使用MyScale的高级全文和向量搜索功能执行混合搜索非常简单,并通过结合关键字和语义信息提供了更准确的结果。

Last Updated: Mon Apr 29 2024 08:19:07 GMT+0000