# 混合搜索
注意
本指南仅适用于DB版本1.5.0或更高版本。
本指南介绍了如何在MyScale中执行全文和向量混合搜索。
全文搜索和向量搜索各有其优势和劣势。全文搜索适用于基本的关键字检索和文本匹配,而向量搜索在跨文档语义匹配和深度语义理解方面表现出色,但在短文本查询方面可能缺乏效率。为了充分利用两种方法的优势,开发了混合搜索。通过结合全文索引和向量搜索的特点,它可以有效地满足各种文本搜索需求,提高搜索文本的准确性和速度,并有效地满足用户对精确结果的期望。
# 教程概述
本文将指导您完成三个搜索实验:
所有文档和查询文本都使用all-MiniLM-L6-v2 (opens new window)模型进行向量生成,并使用余弦距离进行相似度测量。
MyScale集成了Tantivy (opens new window)库,以启用基于BM25的全文搜索(FTS)索引。通过对表中的所有文本数据进行索引,MyScale的FTS功能可以根据BM25分数对搜索结果进行排名,提供更相关和准确的搜索体验。
我们使用广泛使用的Python库ranx (opens new window)来结合向量搜索和文本搜索结果。这种融合增强了文本搜索在各种场景下的适应性,最终提高了搜索准确性。
在开始之前,请确保您已经设置了一个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 words
。stem
分词器可以忽略文本中的词形,以获得更精确的搜索结果。通过使用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的高级全文和向量搜索功能执行混合搜索非常简单,并通过结合关键字和语义信息提供了更准确的结果。