python使用elasticserch进行混合搜索
基于python使用elasticserch
安装elasticsearch
pip install elasticsearch
连接elasticsearch
es_username = "elastic"
es_password = "123456"
es = Elasticsearch(
['https://0.0.0.0:9200'],
basic_auth=(es_username, es_password),verify_certs=False
)
创建索引
python
#settings: 定义了索引的设置,包括了主分片数量和副本数量。一个主分片和0副本。
#id类型为 keyword,意味着它是一个精确值字段,不会被分词。
#title 和 content: 表示标题和内容的字段,类型为 text,使用了中文分词器(ik_smart 和 ik_max_word)进行分析,并且都被配置为存储。
#title_vec 和 vectors.features: 表示向量字段的字段,类型为 dense_vector,意味着它们存储了稠密向量。这些向量字段还指定了维度(dims),相似度度量方式(similarity,这里是余弦相似度),以及是否被索引(index,设置为 True)。
#vectors.segment: 表示文档中的某一段落的字段,类型为 text,使用了中文分词器,并且被配置为存储。
index_name = "zft_lnn"
index_settings = {
'settings': {
'number_of_shards': 1,
'number_of_replicas': 0
},
'mappings': {
"properties": {
"id": {
"store": True,
"type": "keyword"
},
"title": {
"search_analyzer": "ik_smart",
"analyzer": "ik_max_word",
"store": True,
"type": "text"
},
"content": {
"search_analyzer": "ik_smart",
"analyzer": "ik_max_word",
"store": True,
"type": "text"
},
"title_vec": {
"dims": 768,
"similarity": "cosine",
"index": True,
"type": "dense_vector"
},
"vectors": {
"type": "nested",
"properties": {
"features": {
"dims": 768,
"similarity": "cosine",
"index": True,
"type": "dense_vector"
},
"segment": {"search_analyzer": "ik_smart",
"analyzer": "ik_max_word",
"store": True,
"type": "text"}
}
}
}
}
}
python
es.indices.create(index=index_name, body=index_settings)
写入数据
待写入数据存储在json文件中,里面是query和answer的对子。
import json
# 读取JSON文件
with open('answer.json', 'r', encoding='utf-8') as file:
data = json.load(file)
count = 1
for item in data:
title = item["query"]
content = item["answer"]
index_name = "zft_bge"
title_vec = vector_model.encode(title)
content_vec = vector_model.encode(content)
id = str(count)
count += 1
datas = {}
datas["id"] = id
datas["title"] = title
datas["content"] = content
datas["title_vec"] = title_vec
datas["content_vec"] = content_vec
document = {
"id": datas["id"],
"title": datas["title"],
"title_vec":datas["title_vec"],
"content":datas["content"],
"content_vec":datas["content_vec"]
}
response = es.index(index=index_name,id=datas["id"], body=document)
搜索数据
关键字搜索
IK 分词器可以将中文文本切分成一个个有意义的词语,并计算这些词语的词频(term frequency,TF),用于构建倒排索引。在搜索时,Elasticsearch 会根据查询词的词频和文档中各个词语的词频来计算文档的相关性得分,从而排序返回搜索结果。
python
boostqu = {
"match": {
"title": {
"query": qa,
"boost": 1.5
}
}
}
query = {
"query": {
"function_score": {
"query": {
"bool": {
"should": boostqu
}
},
"functions": [
{
"script_score": {
"script": {
"source": "Math.sqrt(_score)/2",
"lang": "painless",
}
}
}
],
"boost_mode": "replace"
}
},
}
results = es.search(index=index_name, body=query)
hits = results["hits"]["hits"]
for h in hits:
print(h["_source"]["title"],h['_score'])
KNN搜索
在 Elasticsearch 中,KNN 搜索通常用于基于向量的相似性搜索,例如基于词嵌入向量或其他类型的向量来查找最相似的文档或项。
python
query_vector = vector_model.encode(qa)
query = {
"query": {
"bool": {
"should": [
{
"knn": {
"field": "title_vec",
"query_vector": query_vector,
"num_candidates": 10,
"boost": 1.0
}
}
]
}
}
}
results = es.search(index=index_name, body=query)
hits = results["hits"]["hits"]
for h in hits:
print(h["_source"]["title"],h['_score'])
向量搜索
python
query_vector = vector_model.encode(qa)
query = {
"query": {
# 通过 script score 来自定义查询的分值计算
"script_score": {
"query": {
# 此处可以结合其他约束条件
"match_all": {}
},
# 稠密向量查询的重点在此处,通过 script 的方式指定具体的计算方式
# 此处是计算余弦相似度,也可以替换其他的相似度
"script": {
# 余弦相似度计算需要两个参数:本次待查询的向量 queryVector 和索引中的字段 entity_name_embedding
"source": "cosineSimilarity(params.queryVector, 'title_vec')",
"lang": "painless",
# 此处将本次待查询的向量传入,注意"params"中 queryVector 和 "source"中的书写要保持一致。
"params": {
# 768 维的待查询向量
"queryVector": query_vector
}
},
"boost": 1.0,
}
},
}
results = es.search(index=index_name, body=query)
hits = results["hits"]["hits"]
for h in hits:
print(h["_source"]["title"],h['_score'])
混合搜索
在混合搜索中,可以在不同方式的搜索加入权重,进行去权重的相加,从而得到一个更好的结果,这个权重参数更多的需要结合实际场景进行设置。
python
#混合搜索
def multiserch(es,vector_model,qa):
query_vector = vector_model.encode(qa)
key_boostqu = [{"match": {"title": {"query": qa, "boost": 1.2}}},
{"match": {"content": {"query": qa, "boost": 0.8}}},]
query = {
"query": {
"bool": {
"should": [
{
"function_score": {
"query": {
"bool": {
"should": key_boostqu
}
},
"functions": [
{
"script_score": {
"script": {
"source": "Math.sqrt(_score)/2",
"lang": "painless"
}
}
}
],
"boost_mode": "replace"
}
},
{
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "cosineSimilarity(params.queryVector, 'title_vec')",
"lang": "painless",
"params": {
"queryVector": query_vector
}
},
"boost": 1.0
}
}
]
}
},
"knn": {
"field": "title_vec",
"query_vector": query_vector,
"k": 1,
"num_candidates": 10,
"boost": 0.5
},
"knn": {
"field": "content_vec",
"query_vector": query_vector,
"k": 1,
"num_candidates": 10,
"boost": 0.5
},
"size": 3
}
results = es.search(index=index_name, body=query)
hits = results["hits"]["hits"]
for h in hits:
print(h["_source"]["title"],h['_score'])
print(h["_source"]["content"])
content = hits[0]["_source"]["content"]
return content