RAG——检索增强生成

RAG认知与项目实践

一、RAG - 检索增强生成

  1. 为什么需要RAG

    • 时效性

      大模型无法询问最新的时事,比如问LLM:“抗战胜利80周年阅兵展示了哪些武器?”

    • 知识覆盖度

      虽然大模型的训练数据集非常庞大,但仍可能无法覆盖所有领域的知识或特定领域的深度信息。

    • 幻觉问题

      大模型LLM再某些情况(提问方式不对、模型知识欠缺)下给出的回答很可能是错误的,或者涉及虚构甚至是故意欺骗的信息,这种回答被称之为AI幻觉,即大模型一本正经的胡说八道。

  2. 解决方案

    解决知识更新缓慢和幻觉问题 : RAG和微调

    时效性可以用Agent解决

    • 大模型微调(Fine-Tuning)

      再通用大模型基础上,针对超出其范围或者不擅长的特定领域或任务,使用专门的数据集或方法对模型进行相应的调整和优化,以提升其再该特定领域或任务重的实用性和性能表现

  3. 什么是RAG

    RAG,外挂知识库的方式,实现成本和复杂度都比微调更低,且RAG在知识库更新方面表现最佳。

方法 知识更新能力 实现复杂度 成本 使用场景
提示词工程 低(依赖模型原有知识) 静态知识场景
微调 中(需要重新训练) 领域知识需要定期更新
RAG 高(实时检索最新数据) 需要最新信息的场景

分析:

  • RAG在知识更新方面表现最佳,因为它可以实时访问最新数据源
  • 微调需要定期重新训练模型才能纳入新知识
  • 提示词工程几乎无法解决知识更新问题,完全依赖模型训练时的知识

RAG(Retrieval Augmented Generation/ 检索增强生成)是一种结合了检索和生成两种方法的技术。它通过先检索相关的文档,用检索出来的信息对提示词增强,再使用大模型生成答案

RAG的本质是:RAG = 大模型LLM + 外部数据


二、Naive RAG主要有三个步骤

  1. 索引化
    文档 → 分块 → 向量化(向量化文档) → 存储

  2. 检索
    用户提问 → 向量化(向量化问题) → 检索数据库 → 得到top-K个相关文档快(Top-K指的是排名靠前的K个文档)

  3. 增强生成
    增强提示词(原始问题 + 相关文档快) → 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 实战

  1. 创建client

    client = chromadb.Client() # 存在内存,临时存储
    client = chromadb.PersistentClient(path="路径") # 存入本地,可以做持久化
    
  2. 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")
    
  3. 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]]
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容