关注

[RAG 与文本向量化详解]RAG篇

RAG 与文本向量化详解

本文详细介绍了 RAG(检索增强生成)技术的核心概念、文本向量化的原理、以及完整的 RAG 流程,包括文档加载、文本分割、向量存储、相似度检索和生成回答等环节。


目录

  1. 什么是文本向量化

  2. Embedding 模型介绍

  3. 余弦相似度

  4. 向量数据库

  5. RAG 解决什么问题

  6. RAG 完整流程

  7. 文档加载器

  8. 文本分割器

  9. 向量存储

  10. 相似度检索

  11. RAG 链式调用

  12. LangChain 代码实战

  13. 常见问题与解决方案


1. 什么是文本向量化

1.1 概念定义

文本向量化(Text Embedding) 是将文本数据转换为数学向量(数值数组)的过程。在自然语言处理中,计算机无法直接理解人类文字,需要将文字转换成数学表示才能进行计算和分析。

文本 → [0.123, -0.456, 0.789, ..., 0.321]  (向量)

1.2 为什么需要文本向量化

特性说明
数学运算文字无法直接做加减乘除,向量可以
语义相似语义相近的文本,向量距离也相近
可计算可以用余弦相似度、欧氏距离等衡量文本关系
机器学习机器学习模型只能处理数值数据

1.3 Embedding 的维度

现代 Embedding 模型通常生成 768 维1024 维1536 维 等高维向量。维度越高,理论上能表达的语义信息越丰富,但计算成本也越高。

text-embedding-v2 (阿里云): 1536 维
tongyi-embedding-vision-plus: 1024 维

2. Embedding 模型介绍

2.1 什么是 Embedding 模型

Embedding 模型是一种专门训练用于将文本转换为向量的神经网络模型。它能够理解文本的语义含义,将语义相似的文本映射到向量空间中相近的位置。

2.2 主流 Embedding 模型

模型提供商维度特点
text-embedding-v2阿里云 DashScope1536中文效果好,稳定
text-embedding-v3阿里云 DashScope1536最新版本,效果更好
text-embedding-v4阿里云 DashScope-最新版本
text-embedding-3-smallOpenAI1536体积小,速度快
text-embedding-3-largeOpenAI3072效果最好

2.3 Embedding 模型的工作原理

输入文本: "今天天气很好"
         ↓
    分词/标记化
         ↓
    神经网络编码
         ↓
输出向量: [0.123, -0.456, 0.789, ..., 0.321]  (1536维)

2.4 在 LangChain 中使用

from langchain_community.embeddings import DashScopeEmbeddings
​
# 初始化 Embedding 模型
embeddings = DashScopeEmbeddings(
    model="text-embedding-v2",  # 或 text-embedding-v3
    dashscope_api_key=os.getenv("aliQwen-api")
)
​
# 单文本向量化
vector = embeddings.embed_query("要转换的文本")
print(f"向量维度: {len(vector)}")
print(f"向量前5个值: {vector[:5]}")

3. 余弦相似度

3.1 什么是余弦相似度

余弦相似度(Cosine Similarity) 是衡量两个向量相似程度的指标,取值范围是 [-1, 1]。值越接近 1,表示两个向量越相似;值越接近 -1,表示两个向量越相反;值接近 0,表示两个向量无关联。

3.2 计算公式

Cosine Similarity = (A · B) / (|A| × |B|)

其中:

  • A · B:向量 A 和 B 的点积(各分量相乘后求和)

  • |A|:向量 A 的模(长度)

  • |B|:向量 B 的模(长度)

3.3 图解说明

向量A: [1, 1]  ────────────→  角度 θ = 0°,相似度 = 1.0(完全相同)
向量B: [1, 1]  ────────────↗
​
向量A: [1, 1]  ────────────→  角度 θ = 90°,相似度 = 0.0(正交无关)
向量B: [0, 1]  ────────────↑
​
向量A: [1, 1]  ────────────→  角度 θ = 180°,相似度 = -1.0(完全相反)
向量B: [-1, -1]  ←──────────┘

3.4 余弦相似度与向量距离

余弦相似度 = 1 - 余弦距离
​
向量距离越小 → 相似度越高 → 语义越相近

4. 向量数据库

4.1 什么是向量数据库

向量数据库(Vector Database) 是专门用于存储和检索向量数据的数据库系统。它能够在大规模向量集合中快速找到与查询向量最相似的结果。

4.2 常见向量数据库

数据库特点适用场景
FAISSFacebook 开源,本地文件存储,轻量小规模数据、快速原型
Redis内存数据库,支持向量检索生产环境,高并发
Milvus专门向量数据库,分布式大规模向量检索
Pinecone云服务,完全托管不想维护基础设施
Chroma轻量级,面向 LLM简单应用,本地开发

4.3 FAISS 本地向量存储

from langchain_community.vectorstores import FAISS
​
# 创建向量存储
vector_store = FAISS.from_documents(
    documents=texts,      # 分割后的文档列表
    embedding=embeddings  # Embedding 模型
)
​
# 保存到本地文件
vector_store.save_local("faiss_index")
​
# 从本地文件加载(下次使用)
vector_store = FAISS.load_local(
    "faiss_index", 
    embeddings,
    allow_dangerous_deserialization=True
)

4.4 FAISS 存储的文件结构

faiss_index/
├── index.faiss    # 向量索引文件(存储数学向量)
└── index.pkl      # 元数据文件(存储原始文本和对应关系)
文件作用
index.faiss存储所有文档的向量表示
index.pkl存储原始文档内容、metadata,以及向量与文本的映射关系

5. RAG 解决什么问题

5.1 大语言模型的局限性

问题说明示例
知识截止日期模型训练数据有时间限制GPT-4 不知道 2023 年后的新闻
幻觉问题模型可能生成看似合理但错误的内容把不存在的书名写进答案
专业知识缺乏垂直领域知识不足医疗、法律、金融等专业问题
信息更新无法及时获取最新信息股价、天气、赛事结果

5.2 RAG 的解决思路

RAG = Retrieval(检索)+ Augmented(增强)+ Generation(生成)

用户问题 → 检索相关文档 → 将文档作为上下文 → 大模型生成答案

5.3 RAG vs 无 RAG 对比

无 RAG(直接提问):

用户: "我们公司 Q3 利润是多少?"
模型: "作为 AI,我无法访问您公司的内部数据..."

有 RAG(检索增强):

用户: "我们公司 Q3 利润是多少?"
    ↓
检索: 找到《公司Q3财报.pdf》中相关段落
    ↓
注入: 将财报数据作为上下文提供给模型
    ↓
生成: "根据公司Q3财报,贵公司Q3净利润为XXX万元,同比增长XX%"

5.4 RAG 的优势

  1. 实时性:可以访问最新文档和数据

  2. 可解释性:答案有文档支撑,不是凭空生成

  3. 成本低:无需微调模型即可注入新知识

  4. 灵活性:可以随时更新知识库

  5. 安全性:敏感数据可私有化部署


6. RAG 完整流程

6.1 流程概览

┌─────────────────────────────────────────────────────────────────┐
│                         RAG 工作流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  【离线阶段 - 知识库构建】                                         │
│  ┌──────────┐    ┌────────────┐    ┌─────────────┐              │
│  │  文档加载  │ → │  文本分割   │ → │  向量嵌入    │              │
│  └──────────┘    └────────────┘    └─────────────┘              │
│                                              │                   │
│                                              ↓                   │
│                                       ┌─────────────┐            │
│                                       │  向量数据库  │            │
│                                       └─────────────┘            │
│                                                                 │
│  【在线阶段 - 问答查询】                                           │
│  ┌──────────┐    ┌────────────┐    ┌─────────────┐  ┌─────────┐ │
│  │ 用户问题  │ → │  向量检索   │ → │  上下文组装  │→│ 大模型生成│ │
│  └──────────┘    └────────────┘    └─────────────┘  └─────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6.2 各环节详细说明

环节 1: 文档加载(Document Loading)

将各种格式的文档(PDF、Word、TXT、HTML、Markdown 等)读取为统一格式。

环节 2: 文本分割(Text Splitting)

将长文档切割成适合检索的小块(chunks),控制每块的长度和重叠度。

环节 3: 向量化(Embedding)

使用 Embedding 模型将每个文本块转换为向量表示。

环节 4: 向量存储(Vector Storage)

将向量和原始文本存入向量数据库,建立索引以便快速检索。

环节 5: 相似度检索(Similarity Search)

将用户问题向量化,在向量数据库中查找最相似的 K 个文档块。

环节 6: 组装上下文(Context Assembly)

将检索到的文档块组装成提示词(Prompt)的一部分。

环节 7: 大模型生成(Generation)

将用户问题和组装好的上下文一起发送给大模型,生成最终答案。


7. 文档加载器

7.1 LangChain 文档加载器一览

加载器支持格式说明
TextLoader.txt纯文本文件
PyPDFLoader.pdfPDF 文档
Docx2txtLoader.docxWord 文档
UnstructuredMarkdownLoader.mdMarkdown 文件
CSVLoader.csvCSV 表格文件
JSONLoader.jsonJSON 文件
PyMuPDFLoader.pdf更高质量的 PDF 解析

7.2 Document 对象结构

加载后的文档是 Document 对象:

class Document:
    page_content: str  # 文档内容(文本)
    metadata: dict     # 元数据(来源文件、页码等)

# 示例
doc = Document(
    page_content="这是文档的正文内容...",
    metadata={"source": "report.pdf", "page": 1}
)

8. 文本分割器

8.1 为什么需要文本分割

问题说明
向量长度限制Embedding 模型有最大 token 限制
语义完整性太小会丢失上下文,太大会有噪声
检索精度精准的小块更容易找到相关内容

8.2 常用文本分割器

分割器说明适用场景
CharacterTextSplitter按字符数分割简单场景
RecursiveCharacterTextSplitter按字符递归分割,保持语义完整通用场景
TokenTextSplitter按 token 数分割精确控制 token 数量
MarkdownTextSplitter按 Markdown 标题结构分割Markdown 文档
PythonCodeTextSplitter按 Python 代码结构分割代码文件

8.3 关键参数

RecursiveCharacterTextSplitter(
    chunk_size=100,      # 每个块的最大字符数
    chunk_overlap=30,    # 块之间的重叠字符数
    length_function=len  # 计算文本长度的函数
)

8.4 chunk_size 和 chunk_overlap 的关系

原始文本: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" (26个字符)
chunk_size=10, chunk_overlap=5

分割结果:
块1: "ABCDEFGHIJ"     (位置 0-9)
块2: "FGHIJKLMNOP"     (位置 5-14) ← 与块1重叠 FGHIJ
块3: "LMNOPQRSTUV"    (位置 10-19) ← 与块2重叠 LMNOP
块4: "QRSTUVWXYZ"     (位置 15-24) ← 与块3重叠 QRSTUV

重叠的作用:确保边界附近的上下文不会丢失。比如 "什么是机器学习" 可能跨两个块,有重叠就能保留完整语义。

8.5 代码示例

方式一:split_documents(直接分割 Document 对象):

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=30,
    length_function=len
)

# 直接分割 Document 对象列表
documents = loader.load()  # 加载文档
splitter_documents = text_splitter.split_documents(documents)

print(f"分割后的文档数量: {len(splitter_documents)}")

方式二:split_text + create_documents(先分割文本,再转 Document):

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=30,
    length_function=len
)

# 先分割文本
texts = text_splitter.split_text(long_text)

# 再转换为 Document 对象
documents = text_splitter.create_documents(texts)

8.6 分割策略建议

场景chunk_sizechunk_overlap说明
问答系统500-100050-100答案通常在几句话内
对话总结1000-2000100-200需要更多上下文
论文检索50050精确检索段落
代码检索200-50020-50按函数/类分割

9. 向量存储

9.1 创建向量存储

from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS

# 1. 初始化 Embedding 模型
embeddings = DashScopeEmbeddings(
    model="text-embedding-v2",
    dashscope_api_key=os.getenv("aliQwen-api")
)

# 2. 创建向量存储
vector_store = FAISS.from_documents(
    documents=splitter_documents,  # 分割后的文档列表
    embedding=embeddings           # Embedding 模型
)

9.2 保存和加载

# 保存到本地
vector_store.save_local("faiss_index")

# 从本地加载
vector_store = FAISS.load_local(
    "faiss_index",
    embeddings,
    allow_dangerous_deserialization=True  # 允许反序列化 pickle 文件
)

9.3 添加新文档

# 添加单个文档
vector_store.add_documents(new_documents)

# 添加后重新保存
vector_store.save_local("faiss_index")

9.4 向量数据库对比

特性FAISSRedisMilvus
存储位置本地文件内存/磁盘分布式集群
部署难度⭐ 简单⭐⭐ 中等⭐⭐⭐ 复杂
查询速度极快极快
数据规模百万级亿级十亿级
成本免费需要 Redis需要服务器

10. 相似度检索

10.1 基础检索

# 相似度搜索(返回文档列表)
results = vector_store.similarity_search(
    query="用户问题",
    k=3  # 返回最相似的 3 个文档
)

for doc in results:
    print(doc.page_content)
    print(doc.metadata)

10.2 带相似度分数的检索

# 返回 (文档, 距离/分数) 元组
results = vector_store.similarity_search_with_score(
    query="用户问题",
    k=3
)

for doc, score in results:
    similarity = 1 - score  # 距离转相似度
    print(f"相似度: {similarity:.4f}")
    print(f"内容: {doc.page_content}")

10.3 As Retriever(转为检索器)

# 转换为 Retriever 对象,可用于 RAG 链
retriever = vector_store.as_retriever(
    search_kwargs={"k": 2}  # 指定返回数量
)

# 在链中使用
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

10.4 相似度分数解读

分数范围 (距离)相似度 (1-距离)语义关系
0.0 - 0.10.9 - 1.0非常相似,几乎相同
0.1 - 0.30.7 - 0.9比较相似
0.3 - 0.50.5 - 0.7部分相似
0.5 - 0.70.3 - 0.5有一定关联
0.7+0.3 以下不太相关

11. RAG 链式调用

11.1 LangChain LCEL 语法

LangChain 表达式语言(LangChain Expression Language)允许用 | 运算符组合多个组件:

rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

等价于:

def rag_chain(question):
    context = retriever.invoke(question)  # 1. 检索相关文档
    prompt_text = prompt.format(context=context, question=question)  # 2. 组装 Prompt
    response = llm.invoke(prompt_text)  # 3. 大模型生成
    return response

11.2 完整 RAG 链

from langchain.chat_models import init_chat_model
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough

# 1. 初始化大模型
llm = init_chat_model(
    model="qwen-plus",
    model_provider="openai",  # 这里只是调用方式,不是真正的 OpenAI
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 2. 定义 Prompt 模板
prompt_template = """
请根据以下提供的文本内容来回答问题。
仅使用提供的文本信息,如果文本中没有相关信息,请回答"抱歉,提供的文本中没有这个信息"。

文本内容:
{context}

问题:{question}

回答:
"""

prompt = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 3. 构建 RAG 链
rag_chain = (
    {
        "context": retriever,
        "question": RunnablePassthrough()
    }
    | prompt
    | llm
)

# 4. 提问
question = "什么是机器学习?"
result = rag_chain.invoke(question)
print(result.content)

11.3 内部执行流程

用户: "什么是机器学习?"
           │
           ▼
┌──────────────────────────────────────────────┐
│  Step 1: Retriever (向量检索)                  │
│  - 将问题向量化                                 │
│  - 在 FAISS 中搜索相似文档                      │
│  - 返回 top-k 相关文档                          │
└──────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────┐
│  Step 2: Prompt 组装                          │
│  - 将检索到的文档填入 {context}                 │
│  - 将用户问题填入 {question}                   │
└──────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────┐
│  Step 3: LLM 生成                             │
│  - 将组装好的 Prompt 发送给大模型               │
│  - 返回生成的回答                              │
└──────────────────────────────────────────────┘
           │
           ▼
输出: "根据提供的文本,机器学习是..."

12. LangChain 代码实战

12.1 完整示例:RAG 问答系统

"""
RAG 完整流程示例:使用本地文档进行问答
"""
import os
from dotenv import load_dotenv
load_dotenv(encoding='utf-8')

from langchain_community.document_loaders import TextLoader
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain.chat_models import init_chat_model

# ========== 第一步:准备文档 ==========
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()

# ========== 第二步:分割文档 ==========
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    length_function=len
)
texts = text_splitter.split_documents(documents)

print(f"原始文档数: {len(documents)}")
print(f"分割后文档数: {len(texts)}")

# ========== 第三步:向量化并存储 ==========
embeddings = DashScopeEmbeddings(
    model="text-embedding-v2",
    dashscope_api_key=os.getenv("aliQwen-api")
)

vector_store = FAISS.from_documents(
    documents=texts,
    embedding=embeddings
)

# 保存向量库
vector_store.save_local("faiss_index")
print("向量库已保存")

# ========== 第四步:构建 RAG 链 ==========
# 初始化大模型
llm = init_chat_model(
    model="qwen-plus",
    model_provider="openai",
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 定义 Prompt
prompt_template = """请根据以下文本内容回答问题。
如果文本中没有相关信息,请如实告知。

文本内容:
{context}

问题:{question}

回答:"""

prompt = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 创建检索器
retriever = vector_store.as_retriever(search_kwargs={"k": 2})

# 构建 RAG 链
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

# ========== 第五步:问答 ==========
question = "你的问题是什么?"
result = rag_chain.invoke(question)
print(f"\n问题: {question}")
print(f"回答: {result.content}")

12.2 不同文档格式的加载方式

PDF 文档:

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("document.pdf", extraction_mode="plain")
documents = loader.load()

Word 文档:

from langchain_community.document_loaders import Docx2txtLoader

loader = Docx2txtLoader("document.docx")
documents = loader.load()

CSV 表格:

from langchain_community.document_loaders import CSVLoader

loader = CSVLoader("data.csv", source_column="content")
documents = loader.load()

JSON 文件:

from langchain_community.document_loaders import JSONLoader

loader = JSONLoader("data.json", jq_schema=".content", text_content=False)
documents = loader.load()

13. 常见问题与解决方案

13.1 依赖问题

问题解决方案
ModuleNotFoundError: No module named 'langchain_unstructured'pip install langchain-unstructured
ModuleNotFoundError: No module named 'unstructured'pip install unstructured
ModuleNotFoundError: No module named 'faiss'pip install faiss-cpupip install faiss-gpu
ModuleNotFoundError: No module named 'langchain_text_splitters'pip install langchain-text-splitters

13.2 路径问题

问题原因解决方案
FileNotFoundError: no such file相对路径找不到文件使用绝对路径或 Path(__file__).parent
UNC 路径不支持WSL 调用 Windows CMD切换到 Windows 路径格式

正确处理路径:

from pathlib import Path

# 获取脚本所在目录
current_dir = Path(__file__).parent.absolute()

# 拼接文件路径
file_path = current_dir / "rag.txt"

# 使用
loader = TextLoader(file_path, encoding="utf-8")

13.3 Embedding 模型问题

问题解决方案
API Key 无效检查环境变量 aliQwen-api 是否正确设置
模型不存在使用 text-embedding-v2text-embedding-v3(v4 可能不稳定)
向量维度不匹配确保 Embedding 模型版本一致

13.4 向量数据库问题

问题解决方案
Redis 连接失败检查 Redis 服务是否启动,或改用 FAISS 本地存储
FAISS 文件损坏删除 faiss_index 目录,重新生成
加载文件报错allow_dangerous_deserialization=True 允许反序列化

13.5 检索效果优化

问题优化方法
检索不到相关内容降低 chunk_size,增加 k
返回内容噪声太多提高 chunk_size,减少重叠
上下文丢失增加 chunk_overlap
回答不准确优化 Prompt 模板,添加"只使用提供的文本"等指令

附录:关键概念速查表

概念说明
Embedding将文本转换为向量的模型
Vector文本的数学向量表示
Cosine Similarity余弦相似度,衡量向量间的相似程度
Chunk分割后的文本小块
Retriever从向量数据库检索相关文档的组件
RAGRetrieval Augmented Generation,检索增强生成
LCELLangChain Expression Language,链式调用语法
FAISSFacebook AI Similarity Search,向量检索库
DocumentLangChain 中的文档对象,包含内容和元数据

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2201_75633021/article/details/160768617

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--