6. RAG基础
学习RAG之前,我们先了解一下LLM的缺陷
LLM缺陷
- LLM存在幻觉缺陷

早期的模型、会这么回答、现在已经完成修正
幻觉:是指模型生成的内容看似合理,但实际与事实不符、逻辑错误或缺乏真实依据的现象
形成原因:
- 训练数据噪声:模型从海量文本中学习,但数据本身包含错误、偏见或虚构内容(例如网络谣言、小说情节),模型可能误将其视为真实知识。
- 概率优先而非真实性:模型目标是生成“最符合上下文概率分布”的文本,而非追求事实正确性。当缺乏明确答案时,模型倾向于用高频词汇或合理句式"填补空白"(做填词游戏)。
在一些较为浅显的领域,大型语言模型可能通过之前的大量训练数据获得了相应的能力。但当涉及到更深层次的专业领域时,这些模型往往会出现所谓的“幻觉”问题,即生成的信息可能偏离事实或专业知识的真实性。
形成原因:
训练数据的覆盖广度与深度矛盾(广度优先):LLM的训练数据覆盖全网公开文本(书籍、网页、论文等),但领域知识往往分布稀疏
- RAG的引入
RAG的初衷是为了增强大模型的事实性,时效性,减少幻觉,引入专业知识优化生成回复
RAG的概念
- RAG(Retrieval Augmented Generation)顾名思义,通过检索外部数据,增强大模型的生成效果。
- 类比:你可以把这个过程想象成开卷考试。让LLM先翻书,再回答问题。
- 举例:我想询问LLM有关于我公司内部的请假流程它肯定不知道,但是我把公司的员工手册先给它看,当我在询问的时候他就会知道
RAG系统工作流程图解
RAG论文: https://arxiv.org/pdf/2312.10997

可以看出,它主要包括三个步骤:
- 索引化:将文档切分成片段,再编码为向量,存储在向量数据库里。
- 检索:检索到与问题相似度最高的top k 的片段。
- 生成:将原始问题和检索得到的“块“都作为LLM的输入,令其生成最终的回答。
RAG的研究范式一直在持续演进,我们将其发展过程分为三个阶段:朴素RAG (Naive RAG)、高级RAG (Advanced RAG) 和模块化RAG(Modular RAG),如图所示

模块化RAG
用是通过将检索过程拆分为多个独立且可灵活组合的模块,针对性优化不同环节,从而解决传统 RAG 的局限性(如检索噪声大(比方文本里面的错别字,一些干扰信息这些)、灵活性低等,以下是其具体作用及优势:
- 分层过滤:将检索拆分为多个模块(例如:粗筛一精筛一上下文适配),逐步缩小候选范围,减少噪声干扰。
- 动态权重调整:不同模块可针对不同任务分配权重(如法律领域侧重法条精确性,创意写作侧重语义关联性)。
NaviIve RAG
Naive RAG遵循一个传统的处理流程,包括索引、检索和生成三个阶段

下面是文档分块的图片、也就是上图中Document这个步骤解释

分块策略:
• 按照句子来切分
• 按照字符数来切分
• 按固定字符数 结合overlapping window(重叠分割)
• 递归方法 RecursiveCharacterTextSplitter
向量与Embedding
向量
在数学中,向量(也称为欧几里得向量、几何向量),指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。
- 将文本转成一组浮点数:每个下标 i,对应一个维度
- 整个数组对应一个 n 维空间的一个点,即文本向量又叫 Embeddings
- 向量之间可以计算距离,距离远近对应语义相似度大小
AI中数据的本质
在计算机当中所有数据的存储都是二进制,不管是图片还是文字,都是以二进制的内容进行存储的
One-hot编码(词汇表):在机器学习和深度学习的应用中,文本数据通常需要转换为数值型数据,One-hot编码是一种常用的转换方式。在One-hot编码中,每个汉字都被表示为一个只有一个元素为1其余元素全为0的向量。向量的长度等于汉字的总数 量,1的位置表示该汉字的索引。One-hot编码的一个主要优点是它简单直观,但当汉字的数量非常大时,One-hot编码会占用大量的内存.
基于深度学习的语言模型:在最近的发展中,一种基于深度学习的自然语言处理技术被用于表示文字,包括汉字。这种技术将每个字或词映射到一个高维向量(称为词嵌入),这个向量可以捕捉到字或词的语义信息。
2. 表示学习
表示学习:通过学习算法自动地从原始数据中学习到一种表示形式或特征表示,该表示形式能够更好地表达数据的重要特征和结构。表示学习的目标是将输人数据转换为具有良好表示能力的特征空间,使得在该空间中的数据具有更好的可分性、可解释性或推理能力
理解方式:
我们会把数据(文本,图片..)存成向量的方式给到大模型, 那大模型如何才能知道这些词的意思和关系呢,就需要表示学习理解方式(如“国王 - 男人 + 女人 ≈ 女王”)实际的数据维度是非常高的

2.1 如何学习
我们需要让大模型知道一个词的含义该怎么做呢
举例说明:
我想让大模型知道苹果, 但是大模型是没有办法理解的
可以把苹果的一些特征给抽出来,把抽出来的数据内容用数字来表示(颜色RGB, 大小, 形状, .....)

然后可以把这些数据存在向量空间当中

会有很多这样的苹果数据, 下次我们让大模型看一个苹果,发现他的向量的位置离这个向量空间的位置很近,那么大模型就会知道这个是苹果 (使用余弦相似度或欧氏距离计算向量距离 )
那么大模型是怎么回答问题的呢?

我们所提的问题会转换成向量, 然后会拿这个向量在向量空间进行对比,找到和苹果相近的内容,比方说健康,水果,离的比较近那么大模型会根据概率的问题把这句话完整的写出来(当然其中还需要很多其他的东西)
我们之前给的数据的向量其实是非常复杂的,表示学习会对向量进行优化得到的一个新的向量内容我们会叫做嵌入
总结: 我们拿到的数据会进过表示学习得到新的向量
Embedding
嵌入:表示学习的一种形式,通常用于将高维数据映射到低维空间中的表示形式。嵌入可以是词嵌入、图像嵌入、图嵌入等。例如,在自然语言处理中,词嵌入将词语映射到低维向量空间,以捕捉词语之间的语义和句法关系。在图像处理中,图像嵌入将图像映射到低维向量空间,以表示图像的视觉特征。
转换代码
我们在使用的时候不需要过多关注我们都是使用现成的转换工具
from openai import OpenAI
from dotenv import load_dotenv
import os
load_dotenv()
client = OpenAI(api_key=os.getenv("api_key"), base_url=os.getenv("base_url"))
def get_embeddings(texts, model="text-embedding-v1"):
# texts 是一个包含要获取嵌入表示的文本的列表,
# model 则是用来指定要使用的模型的名称
# 生成文本的嵌入表示。结果存储在data中。
data = client.embeddings.create(input=texts, model=model).data
# print(data)
# 返回了一个包含所有嵌入表示的列表
return [x.embedding for x in data]
test_query = ["我爱你"]
vec = get_embeddings(test_query)
print(vec)
# "我爱你" 文本嵌入表示的列表。
print(len(vec))
# "我爱你" 文本的嵌入表示。
print(vec[0])
# "我爱你" 文本的嵌入表示的维度。3072
print(len(vec[0]))
向量模型本地部署
- Huggingface:https://huggingface.co/
- Hugging Face 是一个专注于 机器学习(尤其是自然语言处理,NLP)的开源平台和社区,提供从模型开发、训练、部署到共享的一站式工具和资源
- 国内的镜像地址:https://hf-mirror.com/
- 魔搭社区:https://www.modelscope.cn/my/overview
在网址中找到自己需要的文本向量模型
模型下载
# 模型下载
from modelscope import snapshot_download
# pip install modelscope
model_dir = snapshot_download("BAAI/bge-large-zh-v1.5", cache_dir="D:\LLM\Local_model")使用本地的向量模型进行向量化
# pip install sentence-transformers
# 用来加载模型,并使用模型生成向量
from sentence_transformers import SentenceTransformer
model_path = r"D:\LLM\Local_model\BAAI\bge-large-zh-v1___5" # 替换为实际路径
model = SentenceTransformer(model_path)
sentences = [
"你好",
'你'
]
# 生成向量(默认返回numpy数组)
embeddings = model.encode(sentences)
print(embeddings)
print(embeddings.shape) # 输出: (2, 1024) (维度取决于模型)
向量数据库
向量数据库是专为处理AI时代海量非结构化数据(如文本、图像、音频)而设计的数据库。它与处理订单、用户信息等结构化数据的传统数据库不同,核心在于理解并比较数据的“语义”,而非进行精确的“关键词”匹配
向量数据库一般是存在内存中、也在存储在本地上的、有一些向量数据库会采用SQLite数据库
常见的向量数据库

Pinecone
- 重复检测:帮助用户识别和删除重复的数据
- 排名跟踪:跟踪数据在搜索结果中的排名,有助于优化和调整搜索策略
- 数据搜索:快速搜索数据库中的数据,支持复杂的搜索条件
- 分类:对数据进行分类,便于管理和检索
- 去重:自动识别和删除重复数据,保持数据集的纯净和一致性
Milvus
- 毫秒级搜索万亿级向量数据集
- 简单管理非结构化数据
- 可靠的向量数据库,始终可用
- 高度可扩展和适应性强
- 混合搜索
- 统一的Lambda结构
- 受到社区支持,得到行业认可
Chroma
- 功能丰富:支持查询、过滤、密度估计等多种功能
- 即将添加的语言链(LangChain)、LlamaIndex等更多功能
- 相同的API可以在Python笔记本中运行,也可以扩展到集群,用于开发、测试和生产
Faiss
- 不仅返回最近的邻居,还返回第二近、第三近和第k近的邻居
- 可以同时搜索多个向量,而不仅仅是单个向量(批量处理)
- 使用最大内积搜索而不是最小欧几里得搜索
- 也支持其他距离度量,但程度较低。
- 返回查询位置附近指定半径内的所有元素(范围搜索)
- 可以将索引存储在磁盘上,而不仅仅是RAM中
如何选型向量数据库
在选择适合项目的向量数据库时,需要根据项目的具体需求、团队的技术背景和资源情况来综合评估。以下是一些建议和注意事项:
- 向量嵌入的生成
- 如果已经有了自己的向量嵌入生成模型,那么需要的是一个能够高效存储和查询这些向量的数据库
- 如果需要数据库服务来生成向量嵌入,那么应该选择提供这类功能的产品
- 延迟要求
- 对于需要实时响应的应用程序,低延迟是关键。需要选择能够提供快速查询响应的数据库
- 如果应用程序允许批量处理,那么可以选择那些优化了大批量数据处理的数据
- 开发人员的经验
- 根据团队的技术栈和经验,选择一个易于集成和使用的数据库
- 如果团队成员对某些技术或框架更熟悉,那么选择一个能够与之无缝集成的数据库会更有利
chromadb向量数据库演示
# 安装模块
pip install chromadb==0.5.3创建连接
import chromadb
client = chromadb.Client()
# 创建 client 时,设置数据持久化路径, 默认存储在内存,程序运行完时会丢失
# client = chromadb.PersistentClient(path=r"D:\software\chroma")
# 存在这个集合(向量数据库名)就返回,不存在就创建
collection = client.get_or_create_collection(name="test")增删改查
import chromadb
client = chromadb.Client()
# 创建 client 时,设置数据持久化路径, 默认存储在内存,程序运行完时会丢失
# client = chromadb.PersistentClient(path=r"D:\software\chroma")
# 存在这个集合就返回,不存在就创建
collection = client.get_or_create_collection(name="test")
# 添加数据
collection.add(
documents=["Article by john", "Article by Jack", "Article by Jill"], # 文本内容列表,每个元素是一段文本(如文章、句子等)
embeddings=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], # 嵌入向量列表,每个元素是一个与 documents 对应的向量表示
ids=["1", "2", "3"] # 自定义 ID 列表,用于唯一标识每条记录
)
# 查询数据
aa = collection.get(
ids=["1"],
where_document={"$contains": "john"}, # 表示文本内容中包含 "john" 的文档
include=["embeddings"] # 包含嵌入向量, 出于性能考虑,默认不返回嵌入向量
)
# print(aa)
# 删除数据
collection.delete(
ids=["1"]
)
# print(collection.get(include=["embeddings"]))
# 修改数据
collection.update(
documents=["Article by john", "Article by Jack", "Article by Jill"],
embeddings=[[10,2,3],[40,5,6],[70,8,9]],
ids=["1", "2", "3"])
print(collection.get(include=["embeddings"]))操作案例
import chromadb
from chromadb.config import Settings
import json
from openai import OpenAI
from dotenv import load_dotenv
import os
class MyVectorDBConnector:
def __init__(self, collection_name):
# 创建一个客户端
chroma_client = chromadb.Client(Settings(allow_reset=True))
# 创建一个 collection
self.collection = chroma_client.get_or_create_collection(name=collection_name)
def get_embeddings(self, texts, model="text-embedding-v3"):
'''封装 qwen 的 Embedding 模型接口'''
# print('texts', texts)
data = client.embeddings.create(input=texts, model=model).data
return [x.embedding for x in data]
def add_documents(self, instructions, outputs):
'''向 collection 中添加文档与向量'''
# get_embeddings(instructions)
# 将数据向量化
embeddings = self.get_embeddings(instructions)
# 把向量化的数据和原文存入向量数据库
self.collection.add(
embeddings=embeddings, # 每个文档的向量
documents=outputs, # 文档的原文
ids=[f"id{i}" for i in range(len(outputs))] # 每个文档的 id
)
# print(self.collection.count())
def search(self, query):
'''检索向量数据库'''
# 把我们查询的问题向量化, 在chroma当中进行查询
results = self.collection.query(
query_embeddings=self.get_embeddings([query]),
n_results=2,
)
return results
if __name__ == '__main__':
load_dotenv()
client = OpenAI(api_key=os.getenv("api_key"), base_url=os.getenv("base_url"))
# 读取文件
with open('train_zh.json', 'r', encoding='utf-8') as f:
data = [json.loads(line) for line in f]
# print(data)
# print(data[0:100])
# 获取前10条的问题和输出
instructions = [entry['instruction'] for entry in data[0:10]]
outputs = [entry['output'] for entry in data[0:10]]
# 创建一个向量数据库对象
vector_db = MyVectorDBConnector("demo")
# 向向量数据库中添加文档
vector_db.add_documents(instructions, outputs)
# print(vector_db.collection.get())
# user_query = "白癜风"
user_query = "得了白癜风怎么办?"
results = vector_db.search(user_query)
# print(results)
for para in results['documents'][0]:
print(para + "\n")