RAG认知与项目实践
一、RAG - 检索增强生成
-
为什么需要RAG
-
时效性
大模型无法询问最新的时事,比如问LLM:“抗战胜利80周年阅兵展示了哪些武器?”
-
知识覆盖度
虽然大模型的训练数据集非常庞大,但仍可能无法覆盖所有领域的知识或特定领域的深度信息。
-
幻觉问题
大模型LLM再某些情况(提问方式不对、模型知识欠缺)下给出的回答很可能是错误的,或者涉及虚构甚至是故意欺骗的信息,这种回答被称之为AI幻觉,即大模型一本正经的胡说八道。
-
-
解决方案
解决知识更新缓慢和幻觉问题 : RAG和微调
时效性可以用Agent解决
-
大模型微调(Fine-Tuning)
再通用大模型基础上,针对超出其范围或者不擅长的特定领域或任务,使用专门的数据集或方法对模型进行相应的调整和优化,以提升其再该特定领域或任务重的实用性和性能表现
-
-
什么是RAG
RAG,外挂知识库的方式,实现成本和复杂度都比微调更低,且RAG在知识库更新方面表现最佳。
| 方法 | 知识更新能力 | 实现复杂度 | 成本 | 使用场景 |
|---|---|---|---|---|
| 提示词工程 | 低(依赖模型原有知识) | 低 | 低 | 静态知识场景 |
| 微调 | 中(需要重新训练) | 高 | 高 | 领域知识需要定期更新 |
| RAG | 高(实时检索最新数据) | 中 | 中 | 需要最新信息的场景 |
分析:
- RAG在知识更新方面表现最佳,因为它可以实时访问最新数据源
- 微调需要定期重新训练模型才能纳入新知识
- 提示词工程几乎无法解决知识更新问题,完全依赖模型训练时的知识
RAG(Retrieval Augmented Generation/ 检索增强生成)是一种结合了检索和生成两种方法的技术。它通过先检索相关的文档,用检索出来的信息对提示词增强,再使用大模型生成答案
RAG的本质是:RAG = 大模型LLM + 外部数据
二、Naive RAG主要有三个步骤
索引化
文档 → 分块 → 向量化(向量化文档) → 存储检索
用户提问 → 向量化(向量化问题) → 检索数据库 → 得到top-K个相关文档快(Top-K指的是排名靠前的K个文档)增强生成
增强提示词(原始问题 + 相关文档快) → LLM → 生成答案
分块策略
- 按照字符数来切分
- 按固定字符数 结合 overlapping winow (重复窗口)
- 按照句子来切分
- 递归方法 RecursiveCharacterTextSplitter langchain方法(推荐)
- 等
from langchain.text_splitter import RecursiveCharacterTextSplitter
text = ("自然语言处理(NLP),作为计算机科学、人工智能与语言学的交融之地,致力于赋予计算机解析和处理人类语言的能力。"
"在这个领域,机器学习发挥着至关重要的作用。利用多样的算法,机器得以分析、领会乃至创造我们所理解的语言。"
"从机器翻译到情感分析,从自动摘要到实体识别,NLP的应用已遍布各个领域。随着深度学习技术的飞速进步,"
"NLP的精确度与效能均实现了巨大飞跃。如今,部分尖端的NLP系统甚至能够处理复杂的语言理解任务,"
"如问答系统、语音识别和对话系统等。NLP的研究推进不仅优化了人机交流,也对提升机器的自主性和智能水平起到了关键作用。")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=50,
chunk_overlap=5,
separators=["\n\n", "\n", "。", ",", ""],
)
lst = text_splitter.split_text(text)
print(lst)
for i,item in enumerate(lst):
print(f"第{i}段,长度为{len(item)},内容:{item}")
向量化
Ollama本地向量化
from ollama import Client
client = Client("http://127.0.0.1:11434")
response = client.embed(
model='bge-m3',
input=['The sky is blue because of Rayleigh scattering','我爱你中国']
)
lst = [i for i in response.embeddings]
for i,item in enumerate(lst):
print(f"第{i}段,长度为{len(item)},内容:{item}")
百炼平台向量化
import os
from openai import OpenAI
input_text = ["衣服的质量杠杠的","我爱你中国"]
client = OpenAI(
# 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:api_key="sk-xxx",
# 新加坡和北京地域的API Key不同。获取API Key:https://help.aliyun.com/zh/model-studio/get-api-key
api_key=os.getenv("DASHSCOPE_API_KEY"),
# 以下是北京地域base-url,如果使用新加坡地域的模型,需要将base_url替换为:https://dashscope-intl.aliyuncs.com/compatible-mode/v1
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
completion = client.embeddings.create(
model="text-embedding-v4",
input=input_text
)
lst = [item.embedding for item in completion.data]
for i,item in enumerate(lst):
print(f"第{i}段,长度为{len(item)},内容:{item}")
向量间的相似度计算
常用的向量相似度计算方法包括:
- 余弦相似度Cosine:基于两个向量夹角的余弦值来衡量相似度(语义相关性)越接近1越相似
- 欧式距离L2:通过计算向量之间的欧几里得距离来衡量相似度(二维空间就是勾股定理 c² = a² + b²)越小越相似
- 点积:计算两个向量的点积,适合归一化后的向量
第一种和第二种使用较多
向量数据库
向量数据库是一种专门设计用来存储和查询向量嵌入数据的数据库。
1.Pinecone(必须用它的服务器托管,但存在数据安全问题)
- 重复检测:帮助用户识别和删除重复数据
- 排名跟踪:跟踪数据在搜索结果中的排名,有助于优化和调整搜索策略
- 数据搜索:快速搜索数据库中的数据,支持复杂的搜索条件
- 分类:对数据进行分类,便于管理和检索
- 去重:自动识别和删除重复数据。保持数据集的纯净和一致性
- 闭源
2. Milvus
- 毫秒级搜索十几亿向量数据集
- 简单管理非结构化数据
- 可靠的向量数据库,始终可用
- 高度可扩展和适应性强。支持分布式部署
- 混合搜索
- 收到社区支持,得到行业认可,企业应用较多
- 开源
- 中国创业公司ZILLIZZ研发
3. Chroma
- 功能丰富:支持查询、过滤、密度估计等多种功能
- 很多开发框架如LangChain都支持
- 相同API可以在Python笔记中运行,也可以扩展到集群,用于开发、测试和生产闭源
- 轻量级、易用性,易于集成和使用,特别适合小型或中型项目
- 开源
4. Faiss
- 出身名门Meta,起源于Meta的AI研究需求
- 可以同时搜索多个向量,而不仅仅是单个向量
- 用于高校处理大规模密集向量相似度搜索
- 支持多种距离度量、丰富的索引结构、向量化技术
- 高校的并行处理能力
- 腾讯率先在国内大规模引用。Faiss尤其适用于图像检索和推荐系统
Chroma 实战
-
创建client
client = chromadb.Client() # 存在内存,临时存储 client = chromadb.PersistentClient(path="路径") # 存入本地,可以做持久化 -
collection的使用
2.1 列出所有collection列表:client.list_collections() 2.2 创建:client.create_collection(name="collection名称",embedding_function=向量化函数) 2.3 获取:collection = client.get_collection(name="my_collection",embedding_function=emb_fn) 2.4 没有就创建,有则获取:client.get_or_create_collection(name="collection名称") 2.5 删除:client.delete_conllection(name="my_collection") -
collection的主要功能
# 添加文档 collection.add( documents = [...], # 原始文本内容(可选) embeddings = [[...],[...]], # 文档的向量表达(必须) ids = [...] # 每个文档的唯一ID(必须) ) # 相似度检测 results = collection.query( query_embeddings = [0.1,0.2,...], # 问题向量化后的向量 n_results = 5, # 返回前5个最相似的结果 )注意:
项目 说明 向量维度必须一致 查询时使用的embedding model必须与插入时的一致 ID唯一性 插入时每个文档必须有唯一的ID 距离越小越相似 distances 字段表示与查询向量的距离,数值越小越相似 支持批量查询 可以一次传多个查询文本向量,得到多个结果列表
三、全文检索和向量相似度检索
BM25分数归一化到[0,1]区间方法
用数组中的(每个元素 - 最小值)/ (最大值 - 最小值),实现将分数缩放到0和1之间的目的
例如:[1,2,3,4,5]归一化后的结果是[0, 0.25, 0.5, 0.75, 1]
(1 - 1) / (5 - 1) = 0 / 4 = 0
(2 - 1) / (5 - 1) = 1 / 4 = 0.25
(3 - 1) / (5 - 1) = 2 / 4 = 0.5
(4 - 1) / (5 - 1) = 3 / 4 = 0.75
(5 - 1) / (5 - 1) = 4 / 4 = 1
距离转换相似度分数归一化方法
欧氏距离越小,相似度越高,所以用1减去归一化分数
vector_scores = np.linalg.norm(query_embedding - doc_embeddings,axis=1)
max_score = np.max(vector_scores)
min_score = np.min(vector_scores)
vector_scores_normalized = 1 - (vector_scores - min_score) / (max_score - min_score)
Numpy数组归一化
使用np.array()函数把bm25_scores转为NumPy数组
bm25_scores 原本可能是Python 列表,转换为NumPy数组后,能更方便地进行归一化
bm25_scores = np.array(bm25_scores) # 转换成np数组
max_score = bm25_scores.max() # 最高分数
min_score = bm25_scores.min() # 最低分数
# 开始归一化
bm25_scores_normalized = (bm25_scores - min_score) / (max_score - min_score)
将两种方法的分数进行加权组合
权重均为0.5,这样可以综合考虑两种方法的优点,得到更准确的文档相关性评分
combined_scores = bm25_weight * bm25_scores_normalized + (1-bm25_weight)*vector_scores_normalized
根据组合分数对结果排序并返回3个最相关的文档
# 数组进行降序排列,返回倒序后的索引下标
top_index = combined_scores.argsort()[::-1]
# 输出混合搜索的结果:最相关文档outputs
hybrid_results = [outputs[i] for i in top_index[:top_k]]
# hybrid_results = np.array(outputs)[top_index[:top_k]]