LLM应用实战:当KBQA集成LLM(二)
1. 背景
又两周过去了,本qiang~依然奋斗在上周提到的项目KBQA集成LLM,感兴趣的可通过传送门查阅先前的文章《LLM应用实战:当KBQA集成LLM》。
本次又有什么更新呢?主要是针对上次提到的缺点进行优化改进。主要包含如下方面:
1. 数据落库
上次文章提到,KBQA服务会将图谱的概念、属性、实体、属性值全部加载到内存,所有的查询均在内存中进行,随之而来的问题就是如果图谱的体量很大呢,那内存不爆了么…
2. 支持基于属性值查实体
上篇文章不支持属性值查找实体,比如”最会照顾宝宝的是什么龙”,”什么龙是大龙和大龙生活,小龙和小龙生活”。本次已经此问题优化。
此篇文章是对这两周工作的一个整体总结,其中包含部分工程层面的优化。
2. 整体框架
整体框架和上篇大致相同,不同之处在于:
1. 对齐模块:先前是基于SIM筛选候选实体,本次基于ES进行候选实体召回
2. 解析模块:先前是基于hugegraph和内存中的实体信息进行解析,本次优化为基于hugegraph和elasticsearch
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.dic和extra_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 e if 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 e
try:
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: https://zilliz.com/comparison/milvus-vs-elastic
2. docker安装es:https://www.elastic.co/guide/en/elasticsearch/reference/8.12/docker.html
3. elastiknn性能分析:https://blog.csdn.net/star1210644725/article/details/134021552
4. es的function_score: https://www.elastic.co/guide/en/elasticsearch/reference/8.12/query-dsl-function-score-query.html
LLM应用实战:当KBQA集成LLM(二)的更多相关文章
- 屌炸天实战 MySQL 系列教程(二) 史上最屌、你不知道的数据库操作
此篇写MySQL中最基础,也是最重要的操作! 第一篇:屌炸天实战 MySQL 系列教程(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:屌炸天实战 MySQL 系列教程(二) 史上最屌.你不 ...
- 使用Visual Studio Team Services持续集成(二)——为构建定义属性
使用Visual Studio Team Services持续集成(二)--为构建定义属性 1.从VSTS帐户进入到Build 2.编辑构建定义并单击Options Description:如果这里明 ...
- 【实战】Docker入门实践二:Docker服务基本操作 和 测试Hello World
操作环境 操作系统:CentOS7.2 内存:1GB CPU:2核 Docker服务常用命令 docker服务操作命令如下 service docker start #启动服务 service doc ...
- 最佳实战Docker持续集成图文详解
最佳实战Docker持续集成图文详解 这是一种真正的容器级的实现,这个带来的好处,不仅仅是效率的提升,更是一种变革:开发人员第一次真正为自己的代码负责——终于可以跳过运维和测试部门,自主维护运行环境( ...
- 集成学习二: Boosting
目录 集成学习二: Boosting 引言 Adaboost Adaboost 算法 前向分步算法 前向分步算法 Boosting Tree 回归树 提升回归树 Gradient Boosting 参 ...
- 持续集成之二:搭建SVN服务器(subversion)
安装环境 Red Hat Enterprise Linux Server release 7.3 (Maipo) jdk1.7.0_80 subversion-1.10.3.tar.gz apr-1. ...
- iOS- 详解如何使用ZBarSDK集成扫描二维码/条形码,点我!
1.前言 目前市场主流APP里,二维码/条形码集成主要分两种表现形式来集成: a. 一种是调用手机摄像头并打开系统照相机全屏去拍摄 b. 一种是自定义照相机视图的frame,自己控制并添加相关扫码指南 ...
- SpringCloud实战之初级入门(二)— 服务注册与服务调用
目录 1.环境介绍 2.服务提供 2.1 创建工程 2.2 修改配置文件 2.3 修改启动文件 2.5 亲测注意事项 3.服务调用 3.1 创建工程 3.2 修改配置文件 3.3 修改启动文件 3.4 ...
- 2017.2.13 开涛shiro教程-第十二章-与Spring集成(二)shiro权限注解
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第十二章-与Spring集成(二)shiro权限注解 shiro注 ...
- (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案二
http://blog.csdn.net/yerenyuan_pku/article/details/52894958 前面我们已经集成了Spring4.2.5+Hibernate4.3.11+Str ...
随机推荐
- WPF实现html中的table控件
前言 相信很多做WPF开发的小伙伴都遇到过表格类的需求,虽然现有的Grid控件也能实现,但是使用起来的体验感并不好,比如要实现一个Excel中的表格效果,估计你能想到的第一个方法就是套Border控件 ...
- CentOS 7.6 防火墙打开、关闭,端口开启、关闭
查看CentOS版本 cat /etc/redhat-release 显示系统名.节点名称.操作系统的发行版号.操作系统版本.运行系统的机器 ID 号. uname -a 防火墙命令 #查询防火墙状态 ...
- 感悟:FPGA的串行及并行设计思路
前言 FPGA设计过程中, 会遇到大量的串行转并行或者并行转串行的问题; 这些问题主要体现在FPGA对于速度和面积的均衡上; 一般而言, FPGA使用并行的设计可以提高处理的速度, 消耗更多的资源; ...
- Kingbase 函数查询返回结果集
数据库使用过成中,时常会遇到需要返回一个结果集的情况,如何返回一个结果集,以及如何选择一个合适的方式返回结果集,是现场经常需要考虑的问题. 下面介绍KingbaseES中各种返回结果集的方式. 1.通 ...
- KingbaseES 物理备库影响主库的性能与垃圾回收
前言 KingbaseES 物理备库有些配置可能影响到主库性能,或者反过来说主库某些配置也会影响到备库.终极原因还是heap tuple 和dead tuple放在一起导致的. 首先,原理上讲,物理备 ...
- rv1126 mpp部署加解决问题
rv1126 mpp部署 + test 执行失败问题 1.1 mpp 环境部署 首先在我们自己的sdk中 ~/rv1126_linux_240110/external/mpp 将改目录拷贝到需要的 ...
- Scala 中断循环
一.采用 Scala 自带的函数,退出循环 1 package com.atguigu.break 2 3 object TestBreak { 4 import scala.util.control ...
- #Multi-SG#HDU 3032 Nim or not Nim?
题目 有\(n\)堆石子,每次可以从一堆中取出若干个或是将一堆分成两堆非空的石子, 取完最后一颗石子获胜,问先手是否必胜 分析 它的后继还包含了分成两堆非空石子的SG函数,找规律可以发现 \[SG[x ...
- 单元测试篇2-TDD三大法则解密
引言 在我们上一篇文章了解了单元测试的基本概念和用法之后,今天我们来聊一下 TDD(测试驱动开发) 测试驱动开发 (TDD) 测试驱动开发英文全称是Test Driven Development 简称 ...
- 可视化库 pygal 无法保存成本地文件
问题:在使用可视化库 pygal 保存图像到本地时,出现报错 第一次报错是,提示没有 cairosvg 这个模块,所以直接通过 pip 安装 pip install cairosvg 安装完了以后 ...