Skip to content

大模型 RAG 应用开发基础及入门 ​

大模型出现幻觉及缓解方案 ​

幻觉的定义及出现的原因 ​

1.1 幻觉的定义 ​

大语言模型在处理自然语言时,有时会出现幻觉,表现为回答不准确或前后不一致的问题。这些幻觉可以分为两类:

  1. 事实性幻觉:指模型生成的内容与可验证的现实事实不一致,又分为事实不一致(与现实世界信息相矛盾)和事实捏造(压根没有,无法根据现实信息验证)。 示例:提问"第一个在月球上行走的人是谁?",模型回复"CharlesLindbergh在1951年月球先驱任务中第一个登上月球",实际是Neil Armstrong。
  2. 忠实性幻觉:指模型生成的内容与用户的指令或上下文不一致,细分为指令不一致(输出偏离用户指令)、上下文不一致(输出与上文信息不符)、逻辑不一致(推理步骤以及与最终答案之间不一致)三类。 示例:让模型总结今年10月的新闻,结果模型却在说2006年10月的事。

1.2 产生幻觉的原因 ​

致使大模型产生幻觉的原因划分成三大来源:数据源、训练过程和推理。

1.2.1 数据源导致的幻觉 ​

核心为数据缺陷和事实知识利用率低:

  • 数据缺陷:包含错误信息、偏见(重复偏见、社会偏见),以及领域知识缺陷、过时的事实知识;
  • 过度依赖训练数据中的模式:如位置接近性、共现统计数据等,例如错误将多伦多识别为加拿大首都。
1.2.2 训练过程导致的幻觉 ​

训练分为预训练、对齐两个阶段,均可能产生问题:

  • 预训练阶段
    • 架构缺陷:单向建模(基于前一个token预测下一个)阻碍捕获复杂上下文,自注意力模块随token长度增加出现注意力稀释;
    • 暴露偏差:推理时依赖自身生成的token,错误token会引发后续级联错误。
  • 对齐阶段
    • 能力错位:模型内在能力与标注数据的功能需求错位,生成超出知识边界的内容;
    • 信念错位:基于RLHF等微调迎合人类偏好,牺牲信息真实性。
1.2.3 推理导致的幻觉 ​
  1. 固有的抽样随机性:生成内容时根据概率随机生成;
  2. 不完美的解码表示:上下文关注不足(过度关注相邻文本忽视源上下文)、softmax瓶颈(输出概率分布的表达能力受限)。

02. 大模型幻觉的评估 ​

事实性幻觉和忠实性幻觉有不同的评估方法,具体如下:

事实性幻觉评估 ​

  1. 检索外部事实:将模型生成内容与可靠知识来源对比校验,例如验证喜马拉雅山脉最高峰的回答是否为珠穆朗玛峰;
  2. 不确定性估计
    • 基于内部状态的方法:依赖模型内部状态,如通过关键概念的最小标记概率确定不确定性;
    • 基于行为的方法:观察模型行为,无需访问内部状态,如采样多个响应并评估事实陈述的一致性。

忠实性幻觉评估 ​

主流有5种方法:

  1. 基于事实的度量:测量生成内容和源内容之间事实的重叠程度;
  2. 分类器度量:使用训练过的分类器区分忠实内容和幻觉内容;
  3. 问答度量:使用问答系统验证源内容和生成内容的信息一致性;
  4. 不确定度估计:测量模型对其生成输出的置信度;
  5. 提示度量:让大模型作为评估者,通过特定提示策略评估生成内容的忠实性。

03. 大语言模型幻觉的缓解方案 ​

幻觉与创造/创新/涌现一线之隔,无法被彻底解决,仅能在特定场合缓解,缓解方案对应幻觉产生原因,分为数据、预训练、对齐、推理相关,其中与LLM应用开发相关的主要是数据处理。

3.1 缓解数据相关幻觉(重点) ​

  • 基础操作:收集高质量事实数据,进行数据清理消除偏见;
  • 解决知识边界问题的两种方法:
    1. 知识编辑:直接编辑模型参数弥合知识差距;
    2. 检索增强生成(RAG):利用非参数知识源,分为三种类型:
      • 一次性检索:将单次检索的外部知识直接预置到模型提示中;
      • 迭代检索:在整个生成过程中不断收集知识;
      • 事后检索:基于检索的修订完善模型输出。

3.2 缓解预训练相关幻觉 ​

  1. 改进模型架构:使用双向自回归模型和注意力锐化技术,增强上下文理解;
  2. 优化训练目标:引入事实性增强的训练方法和上下文预训练,提升事实关联和逻辑一致性;
  3. 减少曝光偏差:采用新的监督信号和解码策略,减少训练与推理的幻觉。

3.3 缓解对齐相关幻觉 ​

  1. 减少能力错位:改进人类偏好判断,确保生成内容在模型知识范围内;
  2. 减少信念错位:聚合人类偏好和调整模型内部激活,减少迎合行为,避免生成与自身认知相悖的内容。

3.4 缓解推理相关幻觉 ​

  1. 增强事实性解码:动态调整解码策略,利用模型内部结构引导事实性回答;
  2. 增强忠实度解码:通过上下文和逻辑一致性策略,确保输出与用户指令或上下文保持一致。

检索增强生成RAG基础架构与手动模拟 ​

01. 检索增强生成RAG ​

1.1 什么是RAG? ​

检索增强生成(RAG)是对大型语言模型输出进行优化的技术,让模型在生成响应时引用训练数据源之外的权威知识库,无需重新训练模型,是经济高效改进LLM输出的方法,能让LLM在各情境下保持相关性、准确性和实用性。

简单理解:RAG是从外部先检索对应的知识内容,将其和用户的提问一起构成Prompt,再让LLM生成内容。

1.2 RAG的重要性及优点 ​

LLM单独开发的缺陷 ​

将LLM比作“拒绝了解时事、却自信回答所有问题”的员工,单独使用存在以下问题:

  1. 训练数据静态,知识有时间限制,不了解新知识;
  2. 无法提供特定/即时数据,仅能给出通用/过时数据;
  3. 可能从非权威来源创建响应;
  4. 因术语混淆产生不确定响应。
RAG的核心优点 ​
  1. 经济高效:无需高额的预训练和微调成本,即可为LLM引入新输入;
  2. 信息即时:可提供最新的研究、统计数据、新闻等,保证数据时效性;
  3. 增强用户信任度:支持来源归属,输出可包含引文/引用,用户可溯源验证;
  4. 开发人员控制权更高:可控制/更改信息来源、限制敏感信息检索权限,还能对错误信息来源进行故障排除和修复。

02. ChatGPT手动模拟 ​

RAG的核心是通过Prompt拼接检索内容和用户提问,手动模拟流程如下(以提问“公司有销售什么产品么?”为例):

  1. 检索知识:调用检索器,以用户提问为搜索语句,检索得到公司产品文档(如潮汕手工牛肉丸、猪肉卷等产品的详细信息);

  2. 拼接Prompt:将检索到的内容{context}、用户提问{query},按照预设模板拼接成最终提示词; 模板示例: 你是一个由OpenAI开发的聊天机器人,善于根据上下文内容帮助用户解决问题,回复的内容尽可能简洁,如果需要用户提供额外的信息,请进行引导,如果不知道就说不知道。

    上下文:{context}

    用户提问:{query}

  3. 模型生成:将拼接好的Prompt传递给大语言模型,得到结构化的回答结果。

核心本质:无论复杂程度如何,RAG实际都是执行外部检索→拼接检索内容与原始提问→向LLM发起提问→获取输出的过程。

AI应用开发新宠--向量数据库的介绍与用途 ​

01. "猫"与向量的关系与衍生 ​

以猫的品种区分为例,引出词向量的概念:

  1. 单一特征(如体型大小)可将猫映射到一维坐标,但难以区分相似品种;
  2. 增加特征(如毛发长短)映射到二维坐标,区分度提升,仍有部分品种无法区分;
  3. 继续增加特征(如腿的长短、眼大小、尾巴长短等),映射到高维坐标,每个品种的猫可表示为多维向量,特征维度越多,区分度越高。

由此延伸:字、词、句、文本、图片等各类事物,都可通过多维向量记录其特征,将文本转换为记录特征的向量,即为词向量。

02. 向量数据库概念与用途 ​

概念 ​

向量数据库是专门用于存储和处理向量数据的数据库系统,因传统关系型数据库处理高维、复杂的向量数据时,存储和查询效率低下,向量数据库应运而生。

核心原理 ​

向量空间中,概念上相近的对象,其对应的向量在空间中距离更近,通过计算向量间的距离,可判断对象的相似性。

常见使用场景 ​

人脸识别、图像搜索、音频识别、智能推荐系统等,例如人脸识别:

  1. 将人脸归一化后生成对应向量,存储到向量数据库;
  2. 识别时,将待识别人脸转换为向量,在数据库中搜索距离最近的向量,实现匹配。

RAG中的应用 ​

  1. 将知识文档按规则拆分,通过模型转换为向量,存储到向量数据库;
  2. 用户提问时,将提问query转换为向量,在数据库中搜索特征最接近的文本块;
  3. 将这些文本块作为补充知识,与提问一起传给LLM,让模型基于补充知识生成内容,实现知识库问答。

传统数据库与向量数据库的使用差异 ​

01. 两种数据库的差异 ​

1.1 搜索方式差异 ​

  • 传统数据库:擅长处理结构化数据,通过SQL进行精确匹配/条件搜索(如按ID查询用户),无法处理语义相似性搜索(如查询与“喜欢打篮球游泳与编程”语义相近的内容);
  • 向量数据库:通过相似性搜索工作,快速找到与查询向量最相似的数据点,支持在相似性搜索基础上添加筛选条件,可实现语义、特征层面的匹配(如找风格相似的图片)。

1.2 数据处理与存储差异 ​

维度传统数据库向量数据库
存储方式基于行的存储,按行记录组织,每个行包含固定列的字段基于列的存储,每列独立存储,支持列压缩(稀疏矩阵)
数据表示结构化的数值、字符串、时间等高维向量,每个向量对应一个数据点,向量间距离表示相似性
处理效率处理结构化数据高效,非结构化/半结构化数据效率低下处理非结构化/半结构化数据高效,擅长高维数据检索

示例:电影数据存储

  • 传统数据库:按电影ID、名称、导演、类型、评分的行式结构存储;
  • 向量数据库:将每部电影表示为特征向量(如[动作,冒险,爱情,科幻]的标准化数值),按电影ID和特征向量的结构存储,通过计算向量距离找到相似电影。

1.3 优缺点横向对比 ​

对比维度传统的关系型数据库向量数据库
数据类型数值、字符串、时间等传统数据类型向量数据,部分支持存储原始数据
数据规模小,1亿条数据已属大规模大,千亿级数据为底线
数据组织方式基于表格,按行和列组织基于向量,按向量维度组织
查找方式精确查找(点查/范围查),结果非符合即不符合近似查找,结果为与输入最相似的,对近似计算能力要求高
低延时、高并发否是
上层应用较弱提供统一API,适合大规模AI应用部署和使用
下游应用场景央企、国企(容错率低,需准确搜索结果)互联网公司(容错率较高,成本低,可包容近似查找的误差)

02. 相似性搜索算法 ​

2.1 余弦相似度与欧式距离 ​

为向量数据库中最常见的两种相似度计算方式,公式及适用场景如下:

  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]
    • 适用场景:衡量向量方向上的相似性,不受向量长度影响,适合文本、图像、高维空间向量。
  2. 欧式距离

    • 公式:\(distance(A, B)=\sqrt{\sum_{i=1}^{n}\left(A_{i}-B_{i}\right)^{2}}\)
    • 取值范围:≥0
    • 适用场景:衡量向量间的直线距离,适合低维空间或需要考虑各维度差异的情况,距离越小则向量越相似。

2.2 相似性搜索加速计算 ​

向量数据库中,相似性搜索本质是查询向量与MxN矩阵(M为向量维度,N为向量数量)的逐行相似度计算,通过多种方式实现加速:

  1. 矩阵分解方法:SVD(奇异值分解)、PCA(主成分分析),将高维矩阵映射到低维空间,减少计算量;
  2. 索引结构和近似算法:LSH(局部敏感哈希)、ANN(近似最近邻,如KD-Tree、Ball-Tree),加速近邻搜索;
  3. GPU加速:利用GPU并行计算,提升大规模、高维向量的计算速度;
  4. 分布式计算:基于行与行的独立性,分布式计算每行与查询向量的相似度,提升整体效率。

补充:向量数据库还在存储结构、索引机制等层面做了大量优化,保障高维数据的快速检索。

Embedding嵌入模型介绍与使用 ​

01. 什么是Embedding? ​

Embedding(嵌入)模型是将文字、图片、视频等非结构化数据映射到低维实值向量空间的技术,生成的Embedding向量是N维实值向量,可表示词、类别特征、时间序列特征等,且几何距离相近的向量,对应物体的含义也相近,甚至对向量的加减乘除运算具有语义意义。

一句话理解:Embedding是将非结构化数据映射成有意义向量数据的模型生成方法。

目前主流的4类生成embedding的模型 ​

  1. Word2Vec:词嵌入模型,基于CBOW和skip-gram两种算法,将单词转化为连续向量表示;
  2. Glove:通过分析单词在语料库中的共现关系,学习单词的语义关系,可在大规模语料库上做有损压缩,得到低维度词向量;
  3. FastText:基于词袋模型,考虑单词的子词信息(将单词视为字符n-grams的集合),更好处理单词的形态和变体;
  4. 大模型Embeddings(重点):与大语言模型配套的嵌入模型,如OpenAI的text-embedding-ada-002(最大输入8191个tokens,输出维度1536)。

02. Embedding带来的价值 ​

  1. 降维:将数十万甚至更高维度的原始数据映射到低维空间,大幅降低模型复杂度;
  2. 捕捉语义信息:语义相近的对象,其向量在空间中也相近,保留原始数据的重要信息;
  3. 适应性:数据驱动的学习方式,无需人工设计特征,自动适应数据特性;
  4. 泛化能力:捕捉数据的内在规律,对训练数据中未出现的新数据,仍能给出合理的向量表示;
  5. 可解释性:可通过t-SNE等可视化工具观察向量结构,理解模型行为和数据潜在规律。

03. Embedding的运算法则 ​

训练好的Embedding向量具有语义关联性,甚至向量的算术运算能体现语义逻辑,经典示例:

核心示例:Queen = King - Man + Woman ​

  1. King、Man、Woman的词向量在空间中结构高度相似,色块分布集中,体现语义关联;
  2. 对King向量做“减Man加Woman”的算术运算后,得到的新向量与Queen的向量特征分布高度接近,是Glove词表中与Queen最相似的向量。

其他向量语义规律 ​

  1. 同类别的词向量(如woman/girl、man/boy)在多个维度上高度相似;
  2. 不同语义类别的词(如人物相关/水),向量会在特定维度呈现明显差异;
  3. 性别、年龄等语义特征,可通过向量的维度差异体现。

核心结论:Embedding模型能捕捉数据的语义信息,为语义搜索、相似度匹配提供了基础。

OpenAI Embedding 接口使用实践测试 ​

01. OpenAI Embedding 嵌入模型 ​

OpenAI提供的线上Embeddings服务包含3种模型,核心参数对比如下:

模型1美元支持嵌入的文档数(每个文档800个Token)性能评估最大输入向量维度
text-embedding-3-small62,50062.3%81911536(支持修改)
text-embedding-3-large9,61564.6%81913072(支持修改)
text-embedding-ada-00212,50061.0%81911536

使用现状:OpenAI Embeddings模型效果好、价格低,但无本地版本,接口响应速度慢,处理大量文档时效率低,国内使用较少(国内多采用本地/本土服务商的嵌入模型)。

官网文档:https://platform.openai.com/docs/guides/embeddings

02. Embedding组件使用(LangChain) ​

LangChain中的Embeddings类为各类嵌入模型提供商(OpenAI、Cohere、Hugging Face等)提供标准接口,核心特性如下:

核心方法 ​

  1. embed_documents:嵌入文档列表,输入文档列表,输出对应的向量列表;
  2. 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实例化,核心参数:

  1. underlying_embedder:底层使用的嵌入模型;
  2. document_embedding_cache:用于缓存文档嵌入的ByteStore存储库;
  3. batch_size(可选):存储更新之间要嵌入的文档数量,默认为None;
  4. namespace(可选):文档缓存的命名空间,用于避免缓存冲突,建议设置为嵌入模型名称,默认为空;
  5. query_embedding_cache(可选):用于缓存查询/文本嵌入的ByteStore,设为True则与document_embedding_cache共用存储,默认不缓存。

注意事项 ​

  1. 默认不缓存embed_query生成的向量,需手动设置query_embedding_cache;
  2. 尽可能设置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 运行流程 ​

核心为缓存检索→增量计算→缓存更新,具体步骤:

  1. 嵌入数据前,先从持久化存储仓库中检索对应的向量;
  2. 逐个匹配待处理数据与缓存数据,筛选出未缓存的文本;
  3. 仅对未缓存的文本调用嵌入模型生成向量;
  4. 将新生成的向量按batch_size批量存储到数据仓库,完成缓存更新。

Talk is cheap, show me the code!