LLM应用实战:当KBQA集成LLM(二)

ops/2024/9/25 19:15:54/

1. 背景

又两周过去了,本qiang~依然奋斗在上周提到的项目KBQA集成LLM,感兴趣的可通过传送门查阅先前的文章《LLM应用实战:当KBQA集成LLM》。

本次又有什么更新呢?主要是针对上次提到的缺点进行优化改进。主要包含如下方面:

1. 数据落库

上次文章提到,KBQA服务会将图谱的概念、属性、实体、属性值全部加载到内存,所有的查询均在内存中进行,随之而来的问题就是如果图谱的体量很大呢,那内存不爆了么…

2. 支持基于属性值查实体

上篇文章不支持属性值查找实体,比如”最会照顾宝宝的是什么龙”,”什么龙是大龙和大龙生活,小龙和小龙生活”。本次已经此问题优化。

此篇文章是对这两周工作的一个整体总结,其中包含部分工程层面的优化。

2. 整体框架

整体框架和上篇大致相同,不同之处在于:

1. 对齐模块:先前是基于SIM筛选候选实体,本次基于ES进行候选实体召回

2. 解析模块:先前是基于hugegraph和内存中的实体信息进行解析,本次优化为基于hugegraphelasticsearch

3. 核心功能

3.1 数据库选型

由于需要支撑语义相似度检索,因此数据库选型为Milvus与Elasticsearch。

二者之间的比对如下:

Milvus

Elastic

扩展性层面

存储和计算分离

查询和插入分类

组件级别支持

服务器层面支持

多副本

动态分段 vs 静态分片

动态分段

静态分片

云原生

十亿级规模向量支持

功能性层面

权限控制

磁盘索引支撑

混合搜索

分区/命名空间/逻辑组

索引类型

11个(FLAT, IVF_FLAT, HNSW)等

1个(HNSW)

多内存索引支持

专门构建层面

为向量而设计

可调一致性

流批向量数据支持

二进制向量支持

多语言SDK

python, java, go, c++, node.js, ruby

python, java, go, c++, node.js, ruby, Rust, C#, PHP, Perl

数据库回滚

但由于Milvus针对国产化环境如华为Atlas适配不佳,而Es支持国产化环境,因此考虑到环境通用性,选择Es,且其文本搜索能力较强。

3.2 表结构设计

由于知识图谱的概念、属性一般量级较少,而实体数随着原始数据的丰富程度客场可短。因此将实体及其属性值在Es中进行存储。

针对KBQA集成LLM的场景,有两块内容会涉及语义搜索召回。

1. 对齐prompt中的候选实体

2. 解析模块中存在需要基于属性值查询实体的情况。

3. 涉及到数值类型的查询,如大于xx,最大,最小之类。

综合考虑,将Es的index结构设计如下:

属性

含义

类型

备注

name

实体名

keyword

concepts

所属概念

keyword

一个实体可能存在多个概念

property

属性

keyword

属性名称

value

属性值

text

ik分词器进行分词

numbers

数值属性值

double_range

会存在一个区间范围

embeddings

向量

elastiknn_dense_float_vector

1. 非数值属性对应value的向量

2. 使用elastiknn插件

3.3 安装部署

项目使用的Es版本是8.12.2,原因是elastiknn插件和Ik插件针对该版本均支持,且8.12.2版本是当前阶段的次新版本。

3.3.1 基于docker的ES部署

# 拉取镜像(最好先设置国内镜像加入)

docker pull elasticsearch:8.12.2

# es容器启动,存在SSL鉴权

docker run -d --name es01 --net host  -p 9200:9200 -it -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" elasticsearch:8.13.2

# 容器中拉取需要鉴权的信息到本地

docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt .

chmode 777 http_ca.crt

# 密码第一次启动的日志中有,需要保存下来

export ELASTIC_PASSWORD=xxxxxx

# 验证es是否启动成功

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD https://localhost:9200

3.3.2 elastiknn插件集成

elastiknn插件是为了优化ES自身的向量检索性能,安装此插件后,ES的向量检索性能会提升数倍,如果再增加SSD固态硬盘,性能会进一步提升数倍

#下载插件包

wget https://github.com/alexklibisz/elastiknn/releases/download/8.12.2.1/elastiknn-8.12.2.1.zip

# 导入容器中指定目录

docker cp  elastiknn-8.12.2.1.zip es01:/usr/share/elasticsearch/

# 进入容器,默认目录即为/usr/share/elasticsearch/

docker exec -it es01 bash

# 安装插件

elasticsearch-plugin install file:elastiknn-8.12.2.1.zip

# 退出,重启容器

docker restart es01

# 验证

# 创建mapping

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD -XPOST https://localhost:9200/test/_mapping -H 'Content-Type:application/json' -d '

{

         "properties": {

                  "embeddings": {

                          "type": "elastiknn_dense_float_vector",

                          "elastiknn": {

                                   "model": "lsh",

                                   "similarity": "cosine",

                                   "dims": 768,

                                   "L": 99,

                                   "k": 3

                          }

                  }

         }

}'

# 验证mapping是否生效

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD -XGET https://localhost:9200/test/_mapping?pretty

采坑总结:

1. elastiknn插件导入始终无法安装,且报错。

解决:

(1) 一定要注意,安装es插件需要指定路径,且增加”file:” 的前缀,不加此前缀,那就等着报错吧

(2) 拷贝到容器内部,一定要注意,不要将elastiknn-8.12.2.1.zip拷贝至/usr/share/elasticsearch/plugins目录,否则安装也报错。

3.3.3 ik分词器插件集成

#下载插件包

wget https://github.com/infinilabs/analysis-ik/releases/download/v8.12.2/elasticsearch-analysis-ik-8.12.2.zip

# 导入容器中指定目录

docker cp elasticsearch-analysis-ik-8.12.2.zip es01:/usr/share/elasticsearch/

# 进入容器,默认目录即为/usr/share/elasticsearch/

docker exec -it es01 bash

# 安装插件

elasticsearch-plugin install file:elasticsearch-analysis-ik-8.12.2.zip

# 退出,重启容器

docker restart es01

# 验证是否生效

curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD -XPOST https://localhost:9200/_analyze?pretty -H 'Content-Type:application/json' -d '{"text":"三角龙或者霸王龙","analyzer": "ik_smart"}'

# 返回结果中不包含”或者”,因为”或者”在默认的停用词表中。

采坑总结:

1. ik分词器插件导入始终无法安装,且报错。

解决:一定要注意,安装es插件需要指定路径,且增加”file:” 的前缀,不加此前缀,那就等着报错吧

2. ik分词器添加自定义专有名词以及停用词不生效(浪费了1天的时间来排查)

解决:

(1) 一定要注意,8.12.2版本的ik分词器如果想要配置自定义专有名词或停用词,配置的完整目录是/usr/share/elasticsearch/config/analysis-ik,而不是/usr/share/elasticsearch/plugins/analysis-ik,这点需要注意下。

在config/analysis-ik中配置IKAnalyzer.cfg.xml,修改内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>

         <comment>IK Analyzer 扩展配置</comment>

         <!--用户可以在这里配置自己的扩展字典 -->

         <entry key="ext_dict">extra_main.dic</entry>

          <!--用户可以在这里配置自己的扩展停止词字典-->

         <entry key="ext_stopwords">extra_stopword.dic</entry>

         <!--用户可以在这里配置远程扩展字典 -->

         <!-- <entry key="remote_ext_dict">words_location</entry> -->

         <!--用户可以在这里配置远程扩展停止词字典-->

         <!-- <entry key="remote_ext_stopwords">words_location</entry> -->

</properties>

(2) 一定要注意,extra_main.dicextra_stopword.dic的编码格式是UTF-8,如果编码格式不对的话,分词也不生效。

4. Es操作相关源码

4.1 es_client连接

self.es_client = Elasticsearch(config['url'], basic_auth=(config['user'], config['password']), ca_certs=config['crt_path'],http_compress=True,request_timeout=int(config['request_timeout']) if 'request_timeout' in config else 60,max_retries=int(config['max_retries']) if 'max_retries' in config else 5,retry_on_timeout=True)

4.2 构建表结构

def index(self, kg_id, force=False):"""构建表"""if force:try:self.es_client.indices.delete(index=kg_id, ignore_unavailable=True)except EngineError as e:logger.exception(f"code:{ES_DELETE_INDEX_ERROR}, message:{str(e)}")raise eif not self.es_client.indices.exists(index=kg_id):body = {'settings': {'index': {'number_of_shards': 2}},'mappings': {'dynamic': False,'properties': {'name': {'type': 'keyword'},'concepts': {'type': 'keyword'},'property': {'type': 'keyword'},'value': {'type': 'text', 'analyzer': 'ik_max_word', 'search_analyzer': 'ik_smart'},'numbers': {'type': 'double_range'},'embeddings': {'type': 'elastiknn_dense_float_vector', 'elastiknn': {'dims': 768, 'model': 'lsh', 'similarity': 'cosine', 'L': 99, 'k': 3}}}}}try:self.es_client.indices.create(index=kg_id, body=body)except EngineError as e:logger.exception(f"code:1008, message:{str(e)}")raise etry:   self.es_client.indices.refresh(index=kg_id, ignore_unavailable=True)except EngineError as e:logger.exception(f"code:1008, message:{str(e)}")raise e

说明:

1. value字段需要经过IK分词,分词方式ik_max_word,查询方式是ik_smart

2. embeddings的类型为elastiknn_dense_float_vector,其中向量维度为768,相似度计算使用cosine

4.3 候选实体查询

def get_candidate_entities(self, kg_id, query, limit=15):"""基于查询串查找候选实体名称"""body = {'_source': {'excludes': ["embeddings"]},'query': {'function_score': {'query': {'bool': {'must': [{'match': {'value': query}},{'bool': {'filter': {'bool': {'should': [{'term': {"property": "名称"}},{'term': {"property": "别名"}},]}}}}]}},'functions': [{'elastiknn_nearest_neighbors': {'field': 'embeddings','vec': self.get_callback_ans({'query': [query]})['result'][0]['embeddings'],'model': 'lsh','similarity': 'cosine','candidates': 100} }]}},'size': limit}return self.es_client.search(index=kg_id, body=body)['hits']['hits']

说明:

1. '_source': {'excludes': ["embeddings"]}表示输出结果中过滤embeddings字段

2. 查询以function_score方式,其中的query表示别名或名称与问题的匹配程度,functions表示打分方式,目前的打分是基于向量相似度进行打分,其中, self.get_callback_ans表示语义相似度模型将文本转换为向量。注意:最终的得分由两部分组成,一部分是文本匹配,一部分是语义相似度匹配,不过可以增加参数boost_mode进行设置。

4.4 基于属性及属性值进行查询

def search_by_property_value(self, kg_id, property, value, limit=100):body = {'_source': {'excludes': ["embeddings"]},'query': {'function_score': {'query': {'bool': {'must': [{'match': {'value': value}},{'term': {"property": property}}]}},'functions': [{'elastiknn_nearest_neighbors': {'field': 'embeddings','vec': self.get_callback_ans({'query': [value]})['result'][0]['embeddings'],'model': 'lsh','similarity': 'cosine','candidates': 100} }],'boost_mode': 'replace'}},'size': limit}try:return self.es_client.search(index=kg_id, body=body)['hits']['hits']except EngineError as e:logger.exception(f"code:{ES_SEARCH_ERROR}, message:{str(e)}")raise e

4.5 数值属性范围查询

主要解决的场景有:体重大于9吨的恐龙有哪些?身长小于10米的角龙类有哪些?

其中,如果提供了实体名称,则查询范围是基于这些实体进行查询比较。

def search_by_number_property(self, kg_id, property, operate, entities, limit=100):musts = [{'term': {'property': property}}, {'range': {'numbers': operate}}]if entities:musts.append({'terms': {'name': entities}})body = {'_source': {'excludes': ['embeddings']},'query': {'bool': {'must': musts}},'size': limit}try:return self.es_client.search(index=kg_id, body=body)['hits']['hits']except EngineError as e:logger.exception(f"code:{ES_SEARCH_ERROR}, message:{str(e)}")raise e

4.6 数值属性最大最小查询

实现最大最小的逻辑,采用了sort机制,按照numbers进行排序,最大则顺排,最小则倒排。

def search_by_number_property_maxmin(self, kg_id, property, entities, sort_flag):musts = [{'term': {'property': property}}]if entities:musts.append({'terms': {'name': entities}})body = {'_source': {'excludes': ["embeddings"]},'query': {'bool': {'must': musts}},'sort': {'numbers': sort_flag},'size': 1}try:return self.es_client.search(index=kg_id, body=body)['hits']['hits']except EngineError as e:logger.exception(f"code:{ES_SEARCH_ERROR}, message:{str(e)}")raise e

5. 效果

上一版未解决的问题,在本版本优化的结果,主要是基于属性值查找实体。

1. 问:头像鸭头的龙有哪些?

答:头像鸭头的有慈母龙、原角龙、鹦鹉嘴龙、姜氏巴克龙、奇异辽宁龙、多背棘沱江龙、陆家屯鹦鹉嘴龙、盖斯顿龙、小盾龙、肿头龙、弯龙

2. 问:老师说的有一个特别会照顾宝宝的恐龙是什么龙?

答:慈母龙会照顾宝宝。

3. 问:有哪些恐龙会游泳啊?

答:滑齿龙、慢龙和色雷斯龙是会游泳的恐龙。

4. 问:科学家在意大利阿尔卑斯山脉Preone山谷的乌迪内附近发现了一个会飞的史前动物化石,它是谁的化石?

答:科学家在意大利阿尔卑斯山脉Preone山谷的乌迪内附近发现的会飞的史前动物化石是沛温翼龙的化石。

6. 总结

一句话足矣~

本文主要是针对KBQA方案基于LLM实现存在的问题进行优化,主要涉及到图谱存储至Es,且支持Es的向量检索,还有解决了一部分基于属性值倒查实体的场景,且效果相对提升。

其次,提供了部分Es的操作源码,以飧读者。

附件:

1. es vs milvus: Milvus vs Elastic | Zilliz

2. docker安装es:Install Elasticsearch with Docker | Elasticsearch Guide [8.12] | Elastic

3. elastiknn性能分析

ES 8.x 向量检索性能测试 & 把向量检索性能提升100倍!_elastiknn-CSDN博客

4. es的function_score: Function score query | Elasticsearch Guide [8.12] | Elastic


http://www.ppmy.cn/ops/23635.html

相关文章

小程序中如何快速给分类添加商品

​快速在分类下面上传商品&#xff0c;并且能够设置商品顺序&#xff0c;关系到运营效率的高低。下面就具体介绍如何快速在某个分类下面设置商品。 一、在商品管理处&#xff0c;查询某个分类下面的商品。 进入小程序管理员后台->商品管理&#xff0c;点击分类输入框&…

Unity Meta Quest MR 开发(七):使用 Stencil Test 模板测试制作可以在虚拟与现实之间穿梭的 MR 传送门

文章目录 &#x1f4d5;教程说明&#x1f4d5;Stencil Test 模板测试&#x1f4d5;Stencil Shader&#x1f4d5;使用 Unity URP 渲染管线设置模板测试⭐Render Pipeline Asset 与 Universal Renderer Data⭐删除场景中的天空盒⭐设置虚拟世界的层级 Layer⭐设置模板测试 &#…

CentOS命令大全

系统信息查询命令 在CentOS系统中&#xff0c;了解如何查询系统信息对于系统管理和性能监控至关重要。以下是一些基本而强大的命令&#xff0c;用于获取关于您的系统的各种信息。 1. uname - 获取系统信息 uname命令用于打印系统信息&#xff0c;例如内核版本、主机名等。使…

深度学习基础之《TensorFlow框架(16)—神经网络案例》

一、mnist手写数字识别 1、数据集介绍 mnist数据集是一个经典的数据集&#xff0c;其中包括70000个样本&#xff0c;包括60000个训练样本和10000个测试样本 2、下载地址&#xff1a;http://yann.lecun.com/exdb/mnist/ 3、文件说明 train-images-idx3-ubyte.gz: training s…

深度学习之基础模型——长短时记忆网络LSTM

相关资料 &#xff08;1&#xff09;人人都能看懂的GRU - 知乎 (zhihu.com) 引入 在 RNN 那篇博客中提到&#xff0c;根据 RNN 反向传播 BPTT 的特点&#xff0c; RNN 对长时间序列问题难以处理&#xff0c;因为会出现梯度消失和梯度爆炸的问题。详细来说就是当输入序列比较…

【GitHub】如何在github上提交PR(Pull Request) + 多个pr同时提交、互不干扰

【GitHub】如何在github上提交PR(Pull Request 写在最前面1. 准备工作1.1 注册 GitHub 账号1.2 了解 Git 基础1.3 找到一个项目 2. 创建你的 PR2.1 Fork 和克隆仓库2.2 创建一个新的分支2.3 进行更改2.4 推送更改到 GitHub2.5 创建 Pull Request 3. 优化你的 PR3.1 保持提交清晰…

相关运算及实现

本文介绍相关运算及实现。 相关运算在相关检测及数字锁相放大中经常用到&#xff0c;其与卷积运算又有一定的联系&#xff0c;本文简要介绍其基本运算及与卷积运算的联系&#xff0c;并给出实现。 1.定义 这里以长度为N的离散时间序列x(n),y(n)为例&#xff0c;相关运算定义如…

qt——设置ComBox的默认显示内容

在Qt中设置QComboBox的默认显示内容是一个常见的需求。有几种方法可以设置或更改QComboBox的默认显示项&#xff0c;包括在开始时选择一个特定的索引&#xff0c;或者显示一个提示性的文本作为默认内容。下面将分别介绍这些方法。 方法1: 设置默认选中项 最直接的方法是在添加…