卖不甜枣 发表于 2024-7-26 10:58:55

使用 ElasticSearch 作为知识库,存储向量及相似性搜索

一、ElasticSearch 向量存储及相似性搜索

在当今大数据时代,快速有用地搜索和分析海量数据成为了许多企业和组织的紧张需求。Elasticsearch 作为一款功能强大的分布式搜索和分析引擎,为我们提供了一种优秀的办理方案。除了传统的文本搜索,Elasticsearch 还引入了向量存储的概念,以实现更准确、更高效的相似性搜索。
在 Elasticsearch 中,我们可以将文档或数据转换为数值化向量的方法存入。每个文档被表示为一个向量,其中每个维度对应于文档中的一个特征或属性。这种向量化的表示使得文档之间的相似性计算变得大概。
使用场景:


[*] 相似文档搜索:通过将文档转换为向量,并使用向量相似性函数,如 dot product 或 cosine similarity,可以快速找到与查询文档最相似的文档,从而实现准确且高效的相似文档搜索。
[*] 推荐系统:将用户和商品等表示为向量,可以根据用户的喜欢和举动,推荐与其兴趣相似的商品。
[*] 图像搜索:将图像转换为向量表示,并使用相似性度量,可以在图像库中快速找到与查询图像相似的图像。
下面基于上篇文章使用到的 Chinese-medical-dialogue-data 中文医疗对话数据作为知识内容进行实行。
本篇实行使用 ES 版本为:7.14.0
二、Chinese-medical-dialogue-data 数据集

GitHub 地址如下:
   https://github.com/Toyhom/Chinese-medical-dialogue-data
数据分了 6 个科目类型:
https://i-blog.csdnimg.cn/blog_migrate/2d5bd602aa6d9e6bb8ceb4c347f6285f.png
数据格式如下所示:
https://i-blog.csdnimg.cn/blog_migrate/185eb6e78e000f09c71fa4fd4fc1e0d2.png
其中 ask 为病症的问题描述,answer 为病症的回答。
由于数据较多,本次实行仅使用 IM_内科 数据的前 5000 条数据进行测试。
三、Embedding 模型

Embedding 模型使用开源的 chinese-roberta-wwm-ext-large ,该模型输出为 1024 维。
huggingface 地址:
   https://huggingface.co/hfl/chinese-roberta-wwm-ext-large
基本使用如下:
from transformers import BertTokenizer, BertModel
import torch

# 模型下载的地址
model_name = 'D:\\AIGC\\model\\chinese-roberta-wwm-ext-large'

def embeddings(docs, max_length=300):
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)
    # 对文本进行分词、编码和填充
    input_ids = []
    attention_masks = []
    for doc in docs:
      encoded_dict = tokenizer.encode_plus(
            doc,
            add_special_tokens=True,
            max_length=max_length,
            padding='max_length',
            truncation=True,
            return_attention_mask=True,
            return_tensors='pt'
      )
      input_ids.append(encoded_dict['input_ids'])
      attention_masks.append(encoded_dict['attention_mask'])

    input_ids = torch.cat(input_ids, dim=0)
    attention_masks = torch.cat(attention_masks, dim=0)

    # 前向传播
    with torch.no_grad():
      outputs = model(input_ids, attention_mask=attention_masks)

    # 提取最后一层的CLS向量作为文本表示
    last_hidden_state = outputs.last_hidden_state
    cls_embeddings = last_hidden_state[:, 0, :]
    return cls_embeddings


if __name__ == '__main__':
    res = embeddings(["你好,你叫什么名字"])
    print(res)
    print(len(res))
    print(len(res))
运行后可以看到如下日志:
https://i-blog.csdnimg.cn/blog_migrate/7ef55c872b2c330c1e7b951932cc31c8.png
四、ElasticSearch 存储向量

创建向量索引

PUT http://127.0.0.1:9200/medical_index
{
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
            "ask_vector": {
                "type": "dense_vector",
                "dims": 1024
            },
                        "ask": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            },
            "answer": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            }
      }
    }
}
其中 dims 为向量的长度。
https://i-blog.csdnimg.cn/blog_migrate/2fcac385197567e4a48ea6bc7f9d61ae.png
查看创建的索引:
GET http://127.0.0.1:9200/medical_index
https://i-blog.csdnimg.cn/blog_migrate/bba60ef145207dbb7427e824c6e0dce7.png
数据存入 ElasticSearch

引入 ElasticSearch 依赖库:
pip install elasticsearch -i https://pypi.tuna.tsinghua.edu.cn/simple
from elasticsearch import Elasticsearch
from transformers import BertTokenizer, BertModel
import torch
import pandas as pd


def embeddings_doc(doc, tokenizer, model, max_length=300):
    encoded_dict = tokenizer.encode_plus(
      doc,
      add_special_tokens=True,
      max_length=max_length,
      padding='max_length',
      truncation=True,
      return_attention_mask=True,
      return_tensors='pt'
    )
    input_id = encoded_dict['input_ids']
    attention_mask = encoded_dict['attention_mask']

    # 前向传播
    with torch.no_grad():
      outputs = model(input_id, attention_mask=attention_mask)

    # 提取最后一层的CLS向量作为文本表示
    last_hidden_state = outputs.last_hidden_state
    cls_embeddings = last_hidden_state[:, 0, :]
    return cls_embeddings


def add_doc(index_name, id, embedding_ask, ask, answer, es):
    body = {
      "ask_vector": embedding_ask.tolist(),
      "ask": ask,
      "answer": answer
    }
    result = es.create(index=index_name, id=id, doc_type="_doc", body=body)
    return result


def main():
    # 模型下载的地址
    model_name = 'D:\\AIGC\\model\\chinese-roberta-wwm-ext-large'
    # ES 信息
    es_host = "http://127.0.0.1"
    es_port = 9200
    es_user = "elastic"
    es_password = "elastic"
    index_name = "medical_index"

    # 数据地址
    path = "D:\\AIGC\\dataset\\Chinese-medical-dialogue-data\\Chinese-medical-dialogue-data\\Data_数据\\IM_内科\\内科5000-33000.csv"

    # 分词器和模型
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)

    # ES 连接
    es = Elasticsearch(
      ,
      port=es_port,
      http_auth=(es_user, es_password)
    )

    # 读取数据写入ES
    data = pd.read_csv(path, encoding='ANSI')
    for index, row in data.iterrows():
      # 写入前 5000 条进行测试
      if index >= 500:
            break
      ask = row["ask"]
      answer = row["answer"]
      # 文本转向量
      embedding_ask = embeddings_doc(ask, tokenizer, model)
      result = add_doc(index_name, index, embedding_ask, ask, answer, es)
      print(result)


if __name__ == '__main__':
    main()
https://i-blog.csdnimg.cn/blog_migrate/18e1a6a5cca18a50090bf1f682d116a7.png
五、相似性搜索

1. 余弦相似度算法:cosineSimilarity

from elasticsearch import Elasticsearch
from transformers import BertTokenizer, BertModel
import torch


def embeddings_doc(doc, tokenizer, model, max_length=300):
    encoded_dict = tokenizer.encode_plus(
      doc,
      add_special_tokens=True,
      max_length=max_length,
      padding='max_length',
      truncation=True,
      return_attention_mask=True,
      return_tensors='pt'
    )
    input_id = encoded_dict['input_ids']
    attention_mask = encoded_dict['attention_mask']

    # 前向传播
    with torch.no_grad():
      outputs = model(input_id, attention_mask=attention_mask)

    # 提取最后一层的CLS向量作为文本表示
    last_hidden_state = outputs.last_hidden_state
    cls_embeddings = last_hidden_state[:, 0, :]
    return cls_embeddings


def search_similar(index_name, query_text, tokenizer, model, es, top_k=3):
    query_embedding = embeddings_doc(query_text, tokenizer, model)
    print(query_embedding.tolist())
    query = {
      "query": {
            "script_score": {
                "query": {"match_all": {}},
                "script": {
                  "source": "cosineSimilarity(params.queryVector, 'ask_vector') + 1.0",
                  "lang": "painless",
                  "params": {
                        "queryVector": query_embedding.tolist()
                  }
                }
            }
      },
      "size": top_k
    }
    res = es.search(index=index_name, body=query)
    hits = res['hits']['hits']
    similar_documents = []
    for hit in hits:
      similar_documents.append(hit['_source'])
    return similar_documents


def main():
    # 模型下载的地址
    model_name = 'D:\\AIGC\\model\\chinese-roberta-wwm-ext-large'
    # ES 信息
    es_host = "http://127.0.0.1"
    es_port = 9200
    es_user = "elastic"
    es_password = "elastic"
    index_name = "medical_index"

    # 分词器和模型
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)

    # ES 连接
    es = Elasticsearch(
      ,
      port=es_port,
      http_auth=(es_user, es_password)
    )

    query_text = "我有高血压可以拿党参泡水喝吗"

    similar_documents = search_similar(index_name, query_text, tokenizer, model, es)
    for item in similar_documents:
      print("================================")
      print('ask:', item['ask'])
      print('answer:', item['answer'])


if __name__ == '__main__':
    main()

打印日志如下:
https://i-blog.csdnimg.cn/blog_migrate/8693a2ad86d5bb4370578a5eb03a59cc.png
   ================================
ask: 我有高血压这两天女婿来的时间给我拿了些党参泡水喝,您好高血压可以吃党参吗?
answer: 高血压病人可以口服党参的。党参有降血脂,降血压的作用,可以彻底消除血液中的垃圾,从而对冠心病以及心血管疾病的患者都有肯定的稳定预防工作作用,因此平常口服党参能阔别三高的危害。别的党参除了益气养血,降低中枢神经作用,调解消化系统功能,健脾补肺的功能。感谢您的进行咨询,盼望我的解释对你有所帮助。
================================
ask: 我准备过两天去看我叔叔,顺便带些人参,但是他有高血压,您好人参高血压可以吃吗?
answer: 人参有肯定的调压作用,重要用来气虚体虚的患者,如果有气血不足,气短乏力,神经衰弱,神经衰弱忘记等不适症状的话,可以得当口服人参调养身体,但是对于高血压的病人,如果恒久食用人参的话,大概会对血压引发肯定影响,所以,比较好到医院中医科实施辨证论治调治,看怎样适合食用人参。
================================
ask: 我妈妈有点高血压,比较近我朋友送了我一些丹参片,我想知道高血压能吃丹参片吗?
answer: 丹参片具备活血化瘀打通血管的作用可以致使血液粘稠度减低,所以就容易致使血管内血液供应便好防止出现血液粘稠,致使血压降落,所以对降血压是有肯定帮助的,高血压患者是常常使用丹参片实施治疗的。可以预防,因为血液粘稠引来的冠心病心绞痛以及外周血管脑水肿症状。
2. 点积算法:dotProduct

计算给定查询向量和文档向量之间的点积度量。
from elasticsearch import Elasticsearch
from transformers import BertTokenizer, BertModel
import torch


def embeddings_doc(doc, tokenizer, model, max_length=300):
    encoded_dict = tokenizer.encode_plus(
      doc,
      add_special_tokens=True,
      max_length=max_length,
      padding='max_length',
      truncation=True,
      return_attention_mask=True,
      return_tensors='pt'
    )
    input_id = encoded_dict['input_ids']
    attention_mask = encoded_dict['attention_mask']

    # 前向传播
    with torch.no_grad():
      outputs = model(input_id, attention_mask=attention_mask)

    # 提取最后一层的CLS向量作为文本表示
    last_hidden_state = outputs.last_hidden_state
    cls_embeddings = last_hidden_state[:, 0, :]
    return cls_embeddings


def search_similar(index_name, query_text, tokenizer, model, es, top_k=3):
    query_embedding = embeddings_doc(query_text, tokenizer, model)
    print(query_embedding.tolist())
    query = {
      "query": {
            "script_score": {
                "query": {"match_all": {}},
                "script": {
                  "source": "dotProduct(params.queryVector, 'ask_vector')+1.0",
                  "lang": "painless",
                  "params": {
                        "queryVector": query_embedding.tolist()
                  }
                }
            }
      },
      "size": top_k
    }
    res = es.search(index=index_name, body=query)
    hits = res['hits']['hits']
    similar_documents = []
    for hit in hits:
      similar_documents.append(hit['_source'])
    return similar_documents


def main():
    # 模型下载的地址
    model_name = 'D:\\AIGC\\model\\chinese-roberta-wwm-ext-large'
    # ES 信息
    es_host = "http://127.0.0.1"
    es_port = 9200
    es_user = "elastic"
    es_password = "elastic"
    index_name = "medical_index"

    # 分词器和模型
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)

    # ES 连接
    es = Elasticsearch(
      ,
      port=es_port,
      http_auth=(es_user, es_password)
    )

    query_text = "我有高血压可以拿党参泡水喝吗"

    similar_documents = search_similar(index_name, query_text, tokenizer, model, es)
    for item in similar_documents:
      print("================================")
      print('ask:', item['ask'])
      print('answer:', item['answer'])


if __name__ == '__main__':
    main()

https://i-blog.csdnimg.cn/blog_migrate/d1993b72184a1e1a69a8cb3a23a67b4e.png
   ================================
ask: 我有高血压这两天女婿来的时间给我拿了些党参泡水喝,您好高血压可以吃党参吗?
answer: 高血压病人可以口服党参的。党参有降血脂,降血压的作用,可以彻底消除血液中的垃圾,从而对冠心病以及心血管疾病的患者都有肯定的稳定预防工作作用,因此平常口服党参能阔别三高的危害。别的党参除了益气养血,降低中枢神经作用,调解消化系统功能,健脾补肺的功能。感谢您的进行咨询,盼望我的解释对你有所帮助。
================================
ask: 我准备过两天去看我叔叔,顺便带些人参,但是他有高血压,您好人参高血压可以吃吗?
answer: 人参有肯定的调压作用,重要用来气虚体虚的患者,如果有气血不足,气短乏力,神经衰弱,神经衰弱忘记等不适症状的话,可以得当口服人参调养身体,但是对于高血压的病人,如果恒久食用人参的话,大概会对血压引发肯定影响,所以,比较好到医院中医科实施辨证论治调治,看怎样适合食用人参。
================================
ask: 我妈妈有点高血压,比较近我朋友送了我一些丹参片,我想知道高血压能吃丹参片吗?
answer: 丹参片具备活血化瘀打通血管的作用可以致使血液粘稠度减低,所以就容易致使血管内血液供应便好防止出现血液粘稠,致使血压降落,所以对降血压是有肯定帮助的,高血压患者是常常使用丹参片实施治疗的。可以预防,因为血液粘稠引来的冠心病心绞痛以及外周血管脑水肿症状。
3. L1曼哈顿隔断:l1norm

计算给定查询向量和文档向量之间的L1隔断。
from elasticsearch import Elasticsearch
from transformers import BertTokenizer, BertModel
import torch


def embeddings_doc(doc, tokenizer, model, max_length=300):
    encoded_dict = tokenizer.encode_plus(
      doc,
      add_special_tokens=True,
      max_length=max_length,
      padding='max_length',
      truncation=True,
      return_attention_mask=True,
      return_tensors='pt'
    )
    input_id = encoded_dict['input_ids']
    attention_mask = encoded_dict['attention_mask']

    # 前向传播
    with torch.no_grad():
      outputs = model(input_id, attention_mask=attention_mask)

    # 提取最后一层的CLS向量作为文本表示
    last_hidden_state = outputs.last_hidden_state
    cls_embeddings = last_hidden_state[:, 0, :]
    return cls_embeddings


def search_similar(index_name, query_text, tokenizer, model, es, top_k=3):
    query_embedding = embeddings_doc(query_text, tokenizer, model)
    print(query_embedding.tolist())
    query = {
      "query": {
            "script_score": {
                "query": {"match_all": {}},
                "script": {
                  "source": "1 / (1 + l1norm(params.queryVector, doc['ask_vector']))",
                  "lang": "painless",
                  "params": {
                        "queryVector": query_embedding.tolist()
                  }
                }
            }
      },
      "size": top_k
    }
    res = es.search(index=index_name, body=query)
    hits = res['hits']['hits']
    similar_documents = []
    for hit in hits:
      similar_documents.append(hit['_source'])
    return similar_documents


def main():
    # 模型下载的地址
    model_name = 'D:\\AIGC\\model\\chinese-roberta-wwm-ext-large'
    # ES 信息
    es_host = "http://127.0.0.1"
    es_port = 9200
    es_user = "elastic"
    es_password = "elastic"
    index_name = "medical_index"

    # 分词器和模型
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)

    # ES 连接
    es = Elasticsearch(
      ,
      port=es_port,
      http_auth=(es_user, es_password)
    )

    query_text = "我有高血压可以拿党参泡水喝吗"

    similar_documents = search_similar(index_name, query_text, tokenizer, model, es)
    for item in similar_documents:
      print("================================")
      print('ask:', item['ask'])
      print('answer:', item['answer'])


if __name__ == '__main__':
    main()

https://i-blog.csdnimg.cn/blog_migrate/8f7a1cd306f9b06052e48e10dbcec0e5.png
   ================================
ask: 我有高血压这两天女婿来的时间给我拿了些党参泡水喝,您好高血压可以吃党参吗?
answer: 高血压病人可以口服党参的。党参有降血脂,降血压的作用,可以彻底消除血液中的垃圾,从而对冠心病以及心血管疾病的患者都有肯定的稳定预防工作作用,因此平常口服党参能阔别三高的危害。别的党参除了益气养血,降低中枢神经作用,调解消化系统功能,健脾补肺的功能。感谢您的进行咨询,盼望我的解释对你有所帮助。
================================
ask: 我准备过两天去看我叔叔,顺便带些人参,但是他有高血压,您好人参高血压可以吃吗?
answer: 人参有肯定的调压作用,重要用来气虚体虚的患者,如果有气血不足,气短乏力,神经衰弱,神经衰弱忘记等不适症状的话,可以得当口服人参调养身体,但是对于高血压的病人,如果恒久食用人参的话,大概会对血压引发肯定影响,所以,比较好到医院中医科实施辨证论治调治,看怎样适合食用人参。
================================
ask: 我妈妈有点高血压,比较近我朋友送了我一些丹参片,我想知道高血压能吃丹参片吗?
answer: 丹参片具备活血化瘀打通血管的作用可以致使血液粘稠度减低,所以就容易致使血管内血液供应便好防止出现血液粘稠,致使血压降落,所以对降血压是有肯定帮助的,高血压患者是常常使用丹参片实施治疗的。可以预防,因为血液粘稠引来的冠心病心绞痛以及外周血管脑水肿症状。
4. l2 欧几里得隔断:l2norm

计算给定查询向量和文档向量之间的欧几里德隔断。
from elasticsearch import Elasticsearch
from transformers import BertTokenizer, BertModel
import torch


def embeddings_doc(doc, tokenizer, model, max_length=300):
    encoded_dict = tokenizer.encode_plus(
      doc,
      add_special_tokens=True,
      max_length=max_length,
      padding='max_length',
      truncation=True,
      return_attention_mask=True,
      return_tensors='pt'
    )
    input_id = encoded_dict['input_ids']
    attention_mask = encoded_dict['attention_mask']

    # 前向传播
    with torch.no_grad():
      outputs = model(input_id, attention_mask=attention_mask)

    # 提取最后一层的CLS向量作为文本表示
    last_hidden_state = outputs.last_hidden_state
    cls_embeddings = last_hidden_state[:, 0, :]
    return cls_embeddings


def search_similar(index_name, query_text, tokenizer, model, es, top_k=3):
    query_embedding = embeddings_doc(query_text, tokenizer, model)
    print(query_embedding.tolist())
    query = {
      "query": {
            "script_score": {
                "query": {"match_all": {}},
                "script": {
                  "source": "1 / (1 + l2norm(params.queryVector, doc['ask_vector']))",
                  "lang": "painless",
                  "params": {
                        "queryVector": query_embedding.tolist()
                  }
                }
            }
      },
      "size": top_k
    }
    res = es.search(index=index_name, body=query)
    hits = res['hits']['hits']
    similar_documents = []
    for hit in hits:
      similar_documents.append(hit['_source'])
    return similar_documents


def main():
    # 模型下载的地址
    model_name = 'D:\\AIGC\\model\\chinese-roberta-wwm-ext-large'
    # ES 信息
    es_host = "http://127.0.0.1"
    es_port = 9200
    es_user = "elastic"
    es_password = "elastic"
    index_name = "medical_index"

    # 分词器和模型
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)

    # ES 连接
    es = Elasticsearch(
      ,
      port=es_port,
      http_auth=(es_user, es_password)
    )

    query_text = "我有高血压可以拿党参泡水喝吗"

    similar_documents = search_similar(index_name, query_text, tokenizer, model, es)
    for item in similar_documents:
      print("================================")
      print('ask:', item['ask'])
      print('answer:', item['answer'])


if __name__ == '__main__':
    main()

https://i-blog.csdnimg.cn/blog_migrate/fa0c48da0f59b43a8de0fcd0e13cdd2f.png
   ================================
ask: 我有高血压这两天女婿来的时间给我拿了些党参泡水喝,您好高血压可以吃党参吗?
answer: 高血压病人可以口服党参的。党参有降血脂,降血压的作用,可以彻底消除血液中的垃圾,从而对冠心病以及心血管疾病的患者都有肯定的稳定预防工作作用,因此平常口服党参能阔别三高的危害。别的党参除了益气养血,降低中枢神经作用,调解消化系统功能,健脾补肺的功能。感谢您的进行咨询,盼望我的解释对你有所帮助。
================================
ask: 我准备过两天去看我叔叔,顺便带些人参,但是他有高血压,您好人参高血压可以吃吗?
answer: 人参有肯定的调压作用,重要用来气虚体虚的患者,如果有气血不足,气短乏力,神经衰弱,神经衰弱忘记等不适症状的话,可以得当口服人参调养身体,但是对于高血压的病人,如果恒久食用人参的话,大概会对血压引发肯定影响,所以,比较好到医院中医科实施辨证论治调治,看怎样适合食用人参。
================================
ask: 我妈妈有点高血压,比较近我朋友送了我一些丹参片,我想知道高血压能吃丹参片吗?
answer: 丹参片具备活血化瘀打通血管的作用可以致使血液粘稠度减低,所以就容易致使血管内血液供应便好防止出现血液粘稠,致使血压降落,所以对降血压是有肯定帮助的,高血压患者是常常使用丹参片实施治疗的。可以预防,因为血液粘稠引来的冠心病心绞痛以及外周血管脑水肿症状。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 使用 ElasticSearch 作为知识库,存储向量及相似性搜索