为什么我的AI笨得跟猪一样?
为什么用了RAG, 我的AI还是笨得跟猪一样?
RAG 不用过多介绍, 大家都知道: 检索增强生成. 简单来说就是检索外部知识来提升大模型的生成效果. 为什么需要外部知识来增强大模型的生成效果呢? 因为大模型训练过程中可能没有用到某些知识, 特别是特殊领域的特殊知识, 例如
医疗行业的患者数据都是保密的, 训练大模型的企业没有办法拿到这些数据来进行训练 企业的产品手册或内部资料, 训练大模型的企业没有办法拿到这些数据来进行训练 由于训练大模型需要高质量的原始数据, 同时训练的内容越多, 成本越高、耗时也越长, 所以训练大模型的公司可能只会挑选公认的高质量数据, 例如论文、学术期刊、出版物、官方的产品手册等等比较正规的材料.
即使使用DeepSeek、Qwen、Gemini、gpt各大巨头最顶级的模型, 在回答没被训练过的内容时, 要么一本正经的胡说八道(幻觉)、要么不知道怎么回答.
为了解决这个问题RAG出现了. 微调另是一个解法, 本文重点讨论RAG.
最直观的RAG使用方法是在AI聊天工具中上传相关文档, 然后再提问, 让模型参考上传的文档进行解答. 如果还没试过的话, 下面有几种支持上传文档的在线AI聊天工具, 你可以试试. 这种用法的弊端非常明显: 附加知识是你自己跟进问题选出来的, 不是模型根据你的问题自动检索到的, 如果你知道所有问题的相关知识, 还要模型干什么; 而且用起来也费劲, 每个问题都需要附加内容; 由于需要上传到外网, 如果是敏感的私有知识库, 容易被泄露; 模型能支持的上下文大小有限(例如通常可能是32K tokens), 如果相关知识散落在多篇很大的文档中, 需要人为截取, 使用非常费劲;
https://chat.qwen.ai/ https://askmanyai.cn/ https://yuanbao.tencent.com/chat
为了解决以上问题, 出现了自动化的RAG产品. 例如
内容平台型的产品, 将你发布的文章自动作为私有知识库实现RAG, 并且可以对你自己或对外提供服务. 如: 知乎直答; 微信公众号AI助手, 通过腾讯元宝支持; github copilot 将代码仓库用于RAG; 可用于私有化部署的开源项目, 例如: OpenWebUI ; Dify ;
自动化RAG是怎么实现的呢? 可以分为检索和生成2个阶段, 检索又可以细化为几个阶段.
解析文档, 文档格式可能包括PPT、keynote、text、markdown、图片、pdf、word、excel 等. 需要统一解析为文本. 切分文本, 目的是把大的文档切片. 切片也很有讲究, 切太大容易超过大模型的上下文, 也容易在RAG过程中引入与问题无关的噪音信息; 切太小的话, 容易丢失与问题相关的关键信息, 导致召回率低下, 最终导致生成效果不理想; 向量化, 使用embedding模型, 把切分好的文本片段转换为向量, 便于进行向量距离相近检索, 快速找到与问题相关的片段, 用于RAG. 召回, 基于向量召回 或 基于模糊查询召回 或 基于关键词匹配召回 或 综合以上. 召回与问题有关的文档片段. 生成, 基于问题 以及 被召回的文本片段, 让大模型生成回复.
过程看起来是不是没毛病? 但是为什么用了RAG, 我的AI还是笨得跟猪一样呢?
原因分析?
为了表示RAG的效果, 不能仅凭感觉, 需要有数字化的衡量!
如何数字化的评测RAG效果? 有一个开源项目:
Ragas https://github.com/explodinggradients/ragas
这个开源项目提供的方法论非常有效. 它把评测目标拆成了2个方向, 召回和生成, 并且能数字化衡量这2个方向在各个阶段的问题. 方法和算法如下:
首先需要准备好一些问题和正确答案. 下面以单个问题来举例讲解. 栗子来自阿里云llm acp认证学习教程。
示例数据
回复(answer): 张伟是教研部负责大模型课程的同事。 正确答案(ground_truth): 张伟是教研部负责大数据方向的同事.
1、评测回答准确率
由 answer 和 ground_truth 的语义相似度和事实准确度计算得出。
Answer Correctness 的得分 = 0.25 * 语义相似度得分 + 0.75 * 事实准确度得分
1.1、语义相似度得分
语义相似度是通过 embedding 模型得到 answer 和 ground_truth 的文本向量,然后计算两个文本向量的相似度。向量相似度的计算有许多种方法,如余弦相似度、欧氏距离、曼哈顿距离等, Ragas 使用了最常用的余弦相似度。
1.2、事实准确度得分(以下图片来自阿里云大模型高级工程师LLM ACP认证学习资料)
1.2.1、把正确答案拆分成若干个观点.
正确答案(ground_truth)为: 张伟是教研部负责大数据方向的同事. 拆分为2个观点(事实): 工作方向, 张伟是教研部的; 工作部门, 张伟负责大数据方向;
1.2.2、把RAG的回复结果也拆分成若干观点.
回复(answer): 张伟是教研部负责大模型课程的同事。拆分为2个观点(回复): 工作方向, 张伟是教研部的; 工作部门, 张伟负责大模型课程;
1.2.3、遍历answer与ground_truth列表,并初始化三个列表,TP、FP与FN。
对于由answer生成的观点:
如果该观点与ground_truth的观点相匹配,则将该观点添加到TP列表中。比如:「张伟是教研部的」。 如果该观点在 ground_truth 的观点列表中找不到依据,则将该观点添加到FP列表中。比如:「张伟负责大模型课程」。
对于ground_truth生成的观点:
如果该观点在 answer 的观点列表中找不到依据,则将该陈述添加到FN列表中。比如:「张伟负责大数据方向」。 该步骤的判断过程均由大模型提供。
1.2.4、统计TP、FP与FN列表的元素个数,并按照以下方式计算f1 score分数:
f1 score = tp / (tp + 0.5 * (fp + fn)) if tp > 0 else 0
以上文为例:f1 score = 1/(1+0.5*(1+1)) = 0.5
1.3、分数汇总, 得到语义相似度和事实准确度的分数后,对两者加权求和,即可得到最终的 Answer Correctness 的分数。
Answer Correctness 的得分 = 0.25 * 语义相似度得分 + 0.75 * 事实准确度得分
2、计算召回率和召回精度
召回率可以表示: 召回的文本对正确答案涉及观点数的覆盖率?
召回精度可以表示: 召回文本与问题和正确答案的相关性, 以及越相关的文本是不是排在越前面?
假设有3次RAG的数据如下, 每次召回了2条文本(contexts).
["提供⾏政管理与协调⽀持,优化⾏政⼯作流程。" , "绩效管理部 韩杉 李⻜ I902 041 ..."] | |||||
["李凯 教研部主任" , "牛顿发现了万有引力"] | |||||
["牛顿发现了万有引力" , "张伟 教研部工程师,他最近在负责课程研发"] |
由上面的数据可以看到:
最后一行数据的回答是准确的 过程中检索到的参考资料(contexts)中也包含了正确答案的观点,即「张伟是教研部的」。这一情况体现在了 context recall 得分为 1。 但是 contexts 中并不是每一条都是和问题及答案相关的,比如「牛顿发现了万有引力」。这一情况体现在了 context precision 得分为 0.5。
得分算法:
Context recall : 召回的文本对正确答案涉及观点数的覆盖率?
你已经从上文了解到 context recall 是衡量 contexts 与 ground_truth 是否一致的指标。
在Ragas 中,context recall 用来描述 ground_truth 中有多少比例的观点可以得到 contexts 的支持,计算过程如下:
由大模型将 ground_truth 分解成 n 个观点(statements)。 比如,可以由ground_truth「张伟是教研部的成员」生成观点列表 [张伟是教研部的]。 由大模型判断每个观点能在检索到的参考资料(contexts)中找到依据,或者说 context 是否能支撑 ground_truth 的观点。 比如,这个观点在第三行数据的 contexts 中能找到依据「张伟 教研部工程师,他最近在负责课程研发」。 然后 ground_truth 观点列表中,能在 contexts 中找到依据的观点占比,作为 context_recall 分数。 这里的得分为 1 = 1/1。contexts中匹配了几个观点 / ground_truth有几个观点
Context precision : 召回文本与问题和正确答案的相关性, 以及越相关的文本是不是排在越前面? 有价值的数据排在越前面, 分就越高.
在Ragas 中,context precision 不仅衡量了 contexts 中有多少比例的 context 与 ground_truth 相关,还衡量了 contexts 中 context 的排名情况。计算过程比较复杂:
按顺序读取 contexts 中的 contexti ,根据 question 与 ground_truth,判断 contexti 是否相关。相关为 1 分,否则为 0 分。 比如上面第三行数据中,context1(牛顿发现了万有引力) 是不相关的,context2 相关。 对于每一个 context,以该 context 及之前 context 的分数之和作为分子,context 所处排位作为分母,计算 precision 分。 每个context算出一个得分: 1或0/其位置对于上面第三行数据,context1 precision 分为 0/1 = 0,context2 precision 分为1/2 = 0.5。对每一个 context 的 precision 分求和,除以相关的 context 个数,得到 context_precision。 对于上面第三行数据, context_precision = (0 + 0.5) / 1 = 0.5。
如果你暂时无法理解上面的计算过程也没有关系,你只需知道该指标衡量了 contexts 中 context 的排名情况。如果你感兴趣,鼓励你去阅读 Ragas 的源码。https://github.com/explodinggradients/ragas/blob/cc31f65d4b7c7cd6bbf686b9073a0dfaacfbcbc5/src/ragas/metrics/_context_precision.py#L250
3、其他推荐了解的指标
Ragas 还提供了很多其他的指标,这里就不一一介绍,你可以访问 Ragas 的文档来查看更多指标的适用场景和工作原理。
Ragas 支持的指标可以访问:https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/
为什么用了RAG, 我的AI还是笨得跟猪一样?
现在可以来回复这个问题了
解决之道
对照上表, 根据原因提供以下解决方案.
情况1: 模型不行的情况, 可以尝试更换参数量更大的模型. 或者召回的内容缺失上下文, 导致回复结果不佳, 此种情况要改进切分方法, 后面提到.
情况2: 干扰信息较多, 说明仅凭单一的向量召回或模糊查询或标签召回无法达到好的召回精度的, 建议引入rerank, 使用向量召回、模糊查询、标签召多种召回方法, 同时对所有的召回结果再使用rerank模型进行排序和limit. 其中标签召回需要先对内容使用大模型进行打标(俗称KV标注, 提取出每个片段的关键信息, 例如人物:刘德华, 地点:杭州这种标注)
这部分和数据库的关联较大, 涉及如下, 对更细致的优化有兴趣的同学可以单独联系我交流: 一般来说数据库在AI RAG应用中常用于存储历史对话和知识库对应的原始文本以及相关的向量值、标签值. 检索方法包括 “原始文本使用模糊搜索、全文检索进行过滤. 标签使用包含、相交进行过滤. 向量使用问题的向量进行相似过滤.”
向量检索加速, 可以使用pg/polardb pgvector插件, 创建向量索引 模糊查询加速, 可以使用pg/polardb pg_bigm插件, 创建倒排索引 全文检索加速, 可以使用pg/polardb pg_jieba插件, 创建倒排索引 倒排索引加速, 可以使用pg/polardb jsonb/array和倒排索引 例如某条切片包含标签 [人物_刘德华, 城市_杭州]array类型, 搜索时使用数组的包含/相交查询, 可以使用gin索引加速. 单行切片标签比较多的情况下, 可以创建字典表进行映射来压缩存储并提速.
情况3: 召回条数不够, 可以增大召回条目, 例如原来只召回1条最相关的信息, 可以提升为5条.
情况4: 召回率和召回精度都很低, 问题较大可能出在解析文本、切分文档、向量化、召回多个阶段.
解析文本相关. 例如两栏的pdf文本是不是页内换行没有被正确解析, 导致错乱. 要改进解析文本的程序.
切分文档相关. 要从切分方法入手, 选择合适自己的切分方法. 切分方法通常分为
按固定tokens数进行切分 + 前后的少许overlaps; tokens太多容易超出大模型上下文大小, 或引入过多的噪音信息. 太少可能导致信息散乱, 缺少上下文. 按句子进行切分; 按段落进行切分; 有些段落可能超长. 按语义; 保证一个分片内的内容语义上有关联. 按章节格式(例如markdown); 仅针对特殊文本格式.
甚至有可能还需要在切分之前先对文档进行处理, 如剔除重复内容. 对大型文档项目进行结构化(章节化)打标等, 并且在切分时植入打标信息.
向量化相关. 排除其他问题, 可能是embedding模块太老旧, 导致转换后问题和分片相似度计算结果不佳, 可以更换新的embedding模型, 更新知识库中所有切片的向量值. 然后再重试.
召回相关. 使用滑动窗口, 解决缺失上下文的问题, 通常文本中相邻内容的关联性较强, 例如abcde几个相邻分片, 在检索到c被名字时, 同时带出ab和de, 补齐上下文; 也可以尝试引入rerank;
整个流程的改进汇总:
解析文档阶段, 确保解析后的文档清晰条理正确, 包括能正确的解析出图片内容、公式等. 切分文本阶段, 选择合适自己的切分方法, 有必要的话在切分之前先对文档进行处理, 如剔除重复内容. 对大型文档项目进行结构化(章节化)打标等, 并且在切分时植入打标信息. 向量化阶段, 使用较新的embedding模型. 召回阶段, 基于向量召回 或 基于模糊查询召回 或 基于关键词匹配召回 或 综合以上. 召回与问题有关的文档片段, 然后使用rerank重排. 使用滑动窗口对rerank重排后得到的分片, 获取其上下文分片. 生成, 基于问题 以及 被召回的文本片段, 让大模型生成回复. 效果不佳时尝试更换参数量更大的模型.
扩展思考
问题扩写, 如果尝试了前面所有的情况, 可能是问题描述不清, 应该扩写, 或先咨询得到更多目标信息.
多轮对话, 不能直接把历史问题和回复原样给模型, 容易导致上下文超长. 应该先将历史对话进行总结后, 基于总结再改写问题.
除了RAG外,还有许多种大模型或者自然语言处理(NLP)的应用或任务,如Agent、NL2SQL、机器翻译、文本摘要等。Ragas提供了许多可以评测这些任务的指标。
参考
下列文章来自 https://github.com/digoal/blog
《想搭建私有大模型服务? 这几个工具一定了解下 Dify | OpenWebUI | AnythingLLM + Ollama》
《真开心, 我在github写的几千篇文章有免费的AI助手了, 不需要自建RAG.》
《Elasticsearch vs Paradedb pg_search - RAG绝配: PostgreSQL 混合搜索插件(全文检索+语义(向量)检索)》
《提升RAG召回效果的方法探讨》
《介绍一个开源项目 RAGflow : 基于深度文档理解构建的开源 RAG》
《AI大模型+全文检索+tf-idf+向量数据库+我的文章 系列之4 - RAG 自动提示微调》
《德说-第309期, 为什么deepseek开源利好云和硬件厂商?》
《如何 3分钟 通过OpenWebUI Ollama 搭建AI chat网站》
《3分钟速玩Dify(高度可定制的企业级AI应用开源项目)》
https://edu.aliyun.com/certification/acp26
https://github.com/AlibabaCloudDocs/aliyun_acp_learning
《PostgreSQL 阿里云rds pg发布高维向量索引,支持图像识别、人脸识别 - pase 插件, 以及ivfflat,hnsw搜索算法说明》
《数据库筑基课 - 向量类型》
《他山之石可以攻玉 | 向量召回IO性能问题解决之道 - 线性化降维(Hilbert & Z-Order)》
《向量数据库, 让AI聪明亿点点. 解决AI对新知识的幻觉、不知道问题, 解决AI在交流过程中的失忆问题》
《向量搜索优化3板斧: 空间、性能、召回(recall)》
《头大! 索引扫描和全表扫描结果不一样, 这向量数据库还能用? 教你一招大幅提升召回率(recall)》
《DB吐槽大会,第88期 - PG向量索引的几个问题》
https://github.com/explodinggradients/ragas