Appearance
大模型 RAG 应用开发基础及入门
大模型出现幻觉及缓解方案
幻觉的定义及出现的原因
1.1 幻觉的定义
大语言模型在处理自然语言时,有时会出现幻觉,表现为回答不准确或前后不一致的问题。这些幻觉可以分为两类:
- 事实性幻觉:指模型生成的内容与可验证的现实事实不一致,又分为事实不一致(与现实世界信息相矛盾)和事实捏造(压根没有,无法根据现实信息验证)。 示例:提问"第一个在月球上行走的人是谁?",模型回复"CharlesLindbergh在1951年月球先驱任务中第一个登上月球",实际是Neil Armstrong。
- 忠实性幻觉:指模型生成的内容与用户的指令或上下文不一致,细分为指令不一致(输出偏离用户指令)、上下文不一致(输出与上文信息不符)、逻辑不一致(推理步骤以及与最终答案之间不一致)三类。 示例:让模型总结今年10月的新闻,结果模型却在说2006年10月的事。
1.2 产生幻觉的原因
致使大模型产生幻觉的原因划分成三大来源:数据源、训练过程和推理。
1.2.1 数据源导致的幻觉
核心为数据缺陷和事实知识利用率低:
- 数据缺陷:包含错误信息、偏见(重复偏见、社会偏见),以及领域知识缺陷、过时的事实知识;
- 过度依赖训练数据中的模式:如位置接近性、共现统计数据等,例如错误将多伦多识别为加拿大首都。
1.2.2 训练过程导致的幻觉
训练分为预训练、对齐两个阶段,均可能产生问题:
- 预训练阶段
- 架构缺陷:单向建模(基于前一个token预测下一个)阻碍捕获复杂上下文,自注意力模块随token长度增加出现注意力稀释;
- 暴露偏差:推理时依赖自身生成的token,错误token会引发后续级联错误。
- 对齐阶段
- 能力错位:模型内在能力与标注数据的功能需求错位,生成超出知识边界的内容;
- 信念错位:基于RLHF等微调迎合人类偏好,牺牲信息真实性。
1.2.3 推理导致的幻觉
- 固有的抽样随机性:生成内容时根据概率随机生成;
- 不完美的解码表示:上下文关注不足(过度关注相邻文本忽视源上下文)、softmax瓶颈(输出概率分布的表达能力受限)。
02. 大模型幻觉的评估
事实性幻觉和忠实性幻觉有不同的评估方法,具体如下:
事实性幻觉评估
- 检索外部事实:将模型生成内容与可靠知识来源对比校验,例如验证喜马拉雅山脉最高峰的回答是否为珠穆朗玛峰;
- 不确定性估计
- 基于内部状态的方法:依赖模型内部状态,如通过关键概念的最小标记概率确定不确定性;
- 基于行为的方法:观察模型行为,无需访问内部状态,如采样多个响应并评估事实陈述的一致性。
忠实性幻觉评估
主流有5种方法:
- 基于事实的度量:测量生成内容和源内容之间事实的重叠程度;
- 分类器度量:使用训练过的分类器区分忠实内容和幻觉内容;
- 问答度量:使用问答系统验证源内容和生成内容的信息一致性;
- 不确定度估计:测量模型对其生成输出的置信度;
- 提示度量:让大模型作为评估者,通过特定提示策略评估生成内容的忠实性。
03. 大语言模型幻觉的缓解方案
幻觉与创造/创新/涌现一线之隔,无法被彻底解决,仅能在特定场合缓解,缓解方案对应幻觉产生原因,分为数据、预训练、对齐、推理相关,其中与LLM应用开发相关的主要是数据处理。
3.1 缓解数据相关幻觉(重点)
- 基础操作:收集高质量事实数据,进行数据清理消除偏见;
- 解决知识边界问题的两种方法:
- 知识编辑:直接编辑模型参数弥合知识差距;
- 检索增强生成(RAG):利用非参数知识源,分为三种类型:
- 一次性检索:将单次检索的外部知识直接预置到模型提示中;
- 迭代检索:在整个生成过程中不断收集知识;
- 事后检索:基于检索的修订完善模型输出。
3.2 缓解预训练相关幻觉
- 改进模型架构:使用双向自回归模型和注意力锐化技术,增强上下文理解;
- 优化训练目标:引入事实性增强的训练方法和上下文预训练,提升事实关联和逻辑一致性;
- 减少曝光偏差:采用新的监督信号和解码策略,减少训练与推理的幻觉。
3.3 缓解对齐相关幻觉
- 减少能力错位:改进人类偏好判断,确保生成内容在模型知识范围内;
- 减少信念错位:聚合人类偏好和调整模型内部激活,减少迎合行为,避免生成与自身认知相悖的内容。
3.4 缓解推理相关幻觉
- 增强事实性解码:动态调整解码策略,利用模型内部结构引导事实性回答;
- 增强忠实度解码:通过上下文和逻辑一致性策略,确保输出与用户指令或上下文保持一致。
检索增强生成RAG基础架构与手动模拟
01. 检索增强生成RAG
1.1 什么是RAG?
检索增强生成(RAG)是对大型语言模型输出进行优化的技术,让模型在生成响应时引用训练数据源之外的权威知识库,无需重新训练模型,是经济高效改进LLM输出的方法,能让LLM在各情境下保持相关性、准确性和实用性。
简单理解:RAG是从外部先检索对应的知识内容,将其和用户的提问一起构成Prompt,再让LLM生成内容。
1.2 RAG的重要性及优点
LLM单独开发的缺陷
将LLM比作“拒绝了解时事、却自信回答所有问题”的员工,单独使用存在以下问题:
- 训练数据静态,知识有时间限制,不了解新知识;
- 无法提供特定/即时数据,仅能给出通用/过时数据;
- 可能从非权威来源创建响应;
- 因术语混淆产生不确定响应。
RAG的核心优点
- 经济高效:无需高额的预训练和微调成本,即可为LLM引入新输入;
- 信息即时:可提供最新的研究、统计数据、新闻等,保证数据时效性;
- 增强用户信任度:支持来源归属,输出可包含引文/引用,用户可溯源验证;
- 开发人员控制权更高:可控制/更改信息来源、限制敏感信息检索权限,还能对错误信息来源进行故障排除和修复。
02. ChatGPT手动模拟
RAG的核心是通过Prompt拼接检索内容和用户提问,手动模拟流程如下(以提问“公司有销售什么产品么?”为例):
检索知识:调用检索器,以用户提问为搜索语句,检索得到公司产品文档(如潮汕手工牛肉丸、猪肉卷等产品的详细信息);
拼接Prompt:将检索到的内容
{context}、用户提问{query},按照预设模板拼接成最终提示词; 模板示例: 你是一个由OpenAI开发的聊天机器人,善于根据上下文内容帮助用户解决问题,回复的内容尽可能简洁,如果需要用户提供额外的信息,请进行引导,如果不知道就说不知道。上下文:
{context}用户提问:
{query}模型生成:将拼接好的Prompt传递给大语言模型,得到结构化的回答结果。
核心本质:无论复杂程度如何,RAG实际都是执行外部检索→拼接检索内容与原始提问→向LLM发起提问→获取输出的过程。
AI应用开发新宠--向量数据库的介绍与用途
01. "猫"与向量的关系与衍生
以猫的品种区分为例,引出词向量的概念:
- 单一特征(如体型大小)可将猫映射到一维坐标,但难以区分相似品种;
- 增加特征(如毛发长短)映射到二维坐标,区分度提升,仍有部分品种无法区分;
- 继续增加特征(如腿的长短、眼大小、尾巴长短等),映射到高维坐标,每个品种的猫可表示为多维向量,特征维度越多,区分度越高。
由此延伸:字、词、句、文本、图片等各类事物,都可通过多维向量记录其特征,将文本转换为记录特征的向量,即为词向量。
02. 向量数据库概念与用途
概念
向量数据库是专门用于存储和处理向量数据的数据库系统,因传统关系型数据库处理高维、复杂的向量数据时,存储和查询效率低下,向量数据库应运而生。
核心原理
向量空间中,概念上相近的对象,其对应的向量在空间中距离更近,通过计算向量间的距离,可判断对象的相似性。
常见使用场景
人脸识别、图像搜索、音频识别、智能推荐系统等,例如人脸识别:
- 将人脸归一化后生成对应向量,存储到向量数据库;
- 识别时,将待识别人脸转换为向量,在数据库中搜索距离最近的向量,实现匹配。
RAG中的应用
- 将知识文档按规则拆分,通过模型转换为向量,存储到向量数据库;
- 用户提问时,将提问
query转换为向量,在数据库中搜索特征最接近的文本块; - 将这些文本块作为补充知识,与提问一起传给LLM,让模型基于补充知识生成内容,实现知识库问答。
传统数据库与向量数据库的使用差异
01. 两种数据库的差异
1.1 搜索方式差异
- 传统数据库:擅长处理结构化数据,通过SQL进行精确匹配/条件搜索(如按ID查询用户),无法处理语义相似性搜索(如查询与“喜欢打篮球游泳与编程”语义相近的内容);
- 向量数据库:通过相似性搜索工作,快速找到与查询向量最相似的数据点,支持在相似性搜索基础上添加筛选条件,可实现语义、特征层面的匹配(如找风格相似的图片)。
1.2 数据处理与存储差异
| 维度 | 传统数据库 | 向量数据库 |
|---|---|---|
| 存储方式 | 基于行的存储,按行记录组织,每个行包含固定列的字段 | 基于列的存储,每列独立存储,支持列压缩(稀疏矩阵) |
| 数据表示 | 结构化的数值、字符串、时间等 | 高维向量,每个向量对应一个数据点,向量间距离表示相似性 |
| 处理效率 | 处理结构化数据高效,非结构化/半结构化数据效率低下 | 处理非结构化/半结构化数据高效,擅长高维数据检索 |
示例:电影数据存储
- 传统数据库:按电影ID、名称、导演、类型、评分的行式结构存储;
- 向量数据库:将每部电影表示为特征向量(如[动作,冒险,爱情,科幻]的标准化数值),按电影ID和特征向量的结构存储,通过计算向量距离找到相似电影。
1.3 优缺点横向对比
| 对比维度 | 传统的关系型数据库 | 向量数据库 |
|---|---|---|
| 数据类型 | 数值、字符串、时间等传统数据类型 | 向量数据,部分支持存储原始数据 |
| 数据规模 | 小,1亿条数据已属大规模 | 大,千亿级数据为底线 |
| 数据组织方式 | 基于表格,按行和列组织 | 基于向量,按向量维度组织 |
| 查找方式 | 精确查找(点查/范围查),结果非符合即不符合 | 近似查找,结果为与输入最相似的,对近似计算能力要求高 |
| 低延时、高并发 | 否 | 是 |
| 上层应用 | 较弱 | 提供统一API,适合大规模AI应用部署和使用 |
| 下游应用场景 | 央企、国企(容错率低,需准确搜索结果) | 互联网公司(容错率较高,成本低,可包容近似查找的误差) |
02. 相似性搜索算法
2.1 余弦相似度与欧式距离
为向量数据库中最常见的两种相似度计算方式,公式及适用场景如下:
余弦相似度
- 公式:\(similarity(A, B)=\frac{A \cdot B}{\| A\| \| B\| }=\frac{\sum_{i=1}^{n} A_{i} \cdot B_{i}}{\sqrt{\sum_{i=1}^{n} A_{i}^{2}} \cdot \sqrt{\sum_{i=1}^{n} B_{i}^{2}}}\)
- 取值范围:[-1,1]
- 适用场景:衡量向量方向上的相似性,不受向量长度影响,适合文本、图像、高维空间向量。
欧式距离
- 公式:\(distance(A, B)=\sqrt{\sum_{i=1}^{n}\left(A_{i}-B_{i}\right)^{2}}\)
- 取值范围:≥0
- 适用场景:衡量向量间的直线距离,适合低维空间或需要考虑各维度差异的情况,距离越小则向量越相似。
2.2 相似性搜索加速计算
向量数据库中,相似性搜索本质是查询向量与MxN矩阵(M为向量维度,N为向量数量)的逐行相似度计算,通过多种方式实现加速:
- 矩阵分解方法:SVD(奇异值分解)、PCA(主成分分析),将高维矩阵映射到低维空间,减少计算量;
- 索引结构和近似算法:LSH(局部敏感哈希)、ANN(近似最近邻,如KD-Tree、Ball-Tree),加速近邻搜索;
- GPU加速:利用GPU并行计算,提升大规模、高维向量的计算速度;
- 分布式计算:基于行与行的独立性,分布式计算每行与查询向量的相似度,提升整体效率。
补充:向量数据库还在存储结构、索引机制等层面做了大量优化,保障高维数据的快速检索。
Embedding嵌入模型介绍与使用
01. 什么是Embedding?
Embedding(嵌入)模型是将文字、图片、视频等非结构化数据映射到低维实值向量空间的技术,生成的Embedding向量是N维实值向量,可表示词、类别特征、时间序列特征等,且几何距离相近的向量,对应物体的含义也相近,甚至对向量的加减乘除运算具有语义意义。
一句话理解:Embedding是将非结构化数据映射成有意义向量数据的模型生成方法。
目前主流的4类生成embedding的模型
- Word2Vec:词嵌入模型,基于CBOW和skip-gram两种算法,将单词转化为连续向量表示;
- Glove:通过分析单词在语料库中的共现关系,学习单词的语义关系,可在大规模语料库上做有损压缩,得到低维度词向量;
- FastText:基于词袋模型,考虑单词的子词信息(将单词视为字符n-grams的集合),更好处理单词的形态和变体;
- 大模型Embeddings(重点):与大语言模型配套的嵌入模型,如OpenAI的text-embedding-ada-002(最大输入8191个tokens,输出维度1536)。
02. Embedding带来的价值
- 降维:将数十万甚至更高维度的原始数据映射到低维空间,大幅降低模型复杂度;
- 捕捉语义信息:语义相近的对象,其向量在空间中也相近,保留原始数据的重要信息;
- 适应性:数据驱动的学习方式,无需人工设计特征,自动适应数据特性;
- 泛化能力:捕捉数据的内在规律,对训练数据中未出现的新数据,仍能给出合理的向量表示;
- 可解释性:可通过t-SNE等可视化工具观察向量结构,理解模型行为和数据潜在规律。
03. Embedding的运算法则
训练好的Embedding向量具有语义关联性,甚至向量的算术运算能体现语义逻辑,经典示例:
核心示例:Queen = King - Man + Woman
- King、Man、Woman的词向量在空间中结构高度相似,色块分布集中,体现语义关联;
- 对King向量做“减Man加Woman”的算术运算后,得到的新向量与Queen的向量特征分布高度接近,是Glove词表中与Queen最相似的向量。
其他向量语义规律
- 同类别的词向量(如woman/girl、man/boy)在多个维度上高度相似;
- 不同语义类别的词(如人物相关/水),向量会在特定维度呈现明显差异;
- 性别、年龄等语义特征,可通过向量的维度差异体现。
核心结论:Embedding模型能捕捉数据的语义信息,为语义搜索、相似度匹配提供了基础。
OpenAI Embedding 接口使用实践测试
01. OpenAI Embedding 嵌入模型
OpenAI提供的线上Embeddings服务包含3种模型,核心参数对比如下:
| 模型 | 1美元支持嵌入的文档数(每个文档800个Token) | 性能评估 | 最大输入 | 向量维度 |
|---|---|---|---|---|
| text-embedding-3-small | 62,500 | 62.3% | 8191 | 1536(支持修改) |
| text-embedding-3-large | 9,615 | 64.6% | 8191 | 3072(支持修改) |
| text-embedding-ada-002 | 12,500 | 61.0% | 8191 | 1536 |
使用现状:OpenAI Embeddings模型效果好、价格低,但无本地版本,接口响应速度慢,处理大量文档时效率低,国内使用较少(国内多采用本地/本土服务商的嵌入模型)。
官网文档:https://platform.openai.com/docs/guides/embeddings
02. Embedding组件使用(LangChain)
LangChain中的Embeddings类为各类嵌入模型提供商(OpenAI、Cohere、Hugging Face等)提供标准接口,核心特性如下:
核心方法
embed_documents:嵌入文档列表,输入文档列表,输出对应的向量列表;embed_query:嵌入单个查询,输入字符串,输出对应的向量。
注意事项
Embeddings类并非Runnable组件,无法直接接入Runnable序列链,需通过RunnableLambda函数转换。
核心基类代码
python
class Embeddings(ABC):
"""Interface for embedding models."""
@abstractmethod
def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""Embed search docs."""
@abstractmethod
def embed_query(self, text: str) -> List[float]:
"""Embed query text."""
async def aembed_documents(self, texts: List[str]) -> List[List[float]]:
"""Asynchronous Embed search docs."""
return await run_in_executor(None, self.embed_documents, texts)
async def aembed_query(self, text: str) -> List[float]:
"""Asynchronous Embed query text."""
return await run_in_executor(None, self.embed_query, text)自定义嵌入模型
只需实现embed_documents和embed_query两个抽象方法即可。
LangChain使用OpenAI Embeddings示例
python
import dotenv
import numpy as np
from langchain_openai import OpenAIEmbeddings
from numpy.linalg import norm
dotenv.load_dotenv()
def cosine_similarity(vector1: list, vector2: list) -> float:
"""计算传入两个向量的余弦相似度"""
# 1.计算内积/点积
dot_product = np.dot(vector1, vector2)
# 2.计算向量的范数/长度
norm_vec1 = norm(vector1)
norm_vec2 = norm(vector2)
# 3.计算余弦相似度
return dot_product / (norm_vec1 * norm_vec2)
embeddings = OpenAIEmbeddings()
query_vector = embeddings.embed_query("你好,我是慕小课,我喜欢打篮球")
documents_vector = embeddings.embed_documents([
"你好,我是慕小课,我喜欢打篮球",
"这个喜欢打篮球的人叫慕小课",
"求知若渴,虚心若愚"
])
print(query_vector)
print(len(query_vector))
print("="*50)
print(len(documents_vector))
print("vector1与vector2的余弦相似度:", cosine_similarity(documents_vector[0], documents_vector[1]))
print("vector1与vector3的余弦相似度:", cosine_similarity(documents_vector[0], documents_vector[2]))CacheBackEmbedding 组件的使用与注意事项
01. CacheBackEmbedding 使用与场景
核心背景
嵌入模型计算向量需要昂贵算力,重复内容的Embeddings计算结果一致,二次计算会导致效率低下、增加无用功,因此LangChain提供CacheBackEmbedding包装类,实现向量计算结果的缓存。
实例化方式
通过类方法from_bytes_store实例化,核心参数:
underlying_embedder:底层使用的嵌入模型;document_embedding_cache:用于缓存文档嵌入的ByteStore存储库;batch_size(可选):存储更新之间要嵌入的文档数量,默认为None;namespace(可选):文档缓存的命名空间,用于避免缓存冲突,建议设置为嵌入模型名称,默认为空;query_embedding_cache(可选):用于缓存查询/文本嵌入的ByteStore,设为True则与document_embedding_cache共用存储,默认不缓存。
注意事项
- 默认不缓存
embed_query生成的向量,需手动设置query_embedding_cache; - 尽可能设置
namespace,避免不同嵌入模型对相同文本的缓存冲突。
使用示例
python
import dotenv
import numpy as np
from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import LocalFileStore
from langchain_openai import OpenAIEmbeddings
from numpy.linalg import norm
dotenv.load_dotenv()
def cosine_similarity(vector1: list, vector2: list) -> float:
"""计算传入两个向量的余弦相似度"""
# 1.计算内积/点积
dot_product = np.dot(vector1, vector2)
# 2.计算向量的范数/长度
norm_vec1 = norm(vector1)
norm_vec2 = norm(vector2)
# 3.计算余弦相似度
return dot_product / (norm_vec1 * norm_vec2)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
embeddings_with_cache = CacheBackedEmbeddings.from_bytes_store(
embeddings,
LocalFileStore("./cache/"),
namespace=embeddings.model,
query_embedding_cache=True,
)
query_vector = embeddings_with_cache.embed_query("你好,我是慕小课,我喜欢打篮球")
documents_vector = embeddings_with_cache.embed_documents([
"你好,我是慕小课,我喜欢打篮球",
"这个喜欢打篮球的人叫慕小课",
"求知若渴,虚心若愚"
])
print(query_vector)
print(len(query_vector))
print("="*50)
print(len(documents_vector))
print("vector1与vector2的余弦相似度:", cosine_similarity(documents_vector[0], documents_vector[1]))
print("vector1与vector3的余弦相似度:", cosine_similarity(documents_vector[0], documents_vector[2]))02. CacheBackEmbedding 运行流程
核心为缓存检索→增量计算→缓存更新,具体步骤:
- 嵌入数据前,先从持久化存储仓库中检索对应的向量;
- 逐个匹配待处理数据与缓存数据,筛选出未缓存的文本;
- 仅对未缓存的文本调用嵌入模型生成向量;
- 将新生成的向量按
batch_size批量存储到数据仓库,完成缓存更新。