Mooreの小站

Moore的个人小站

AI之RAG技术与实战

2025-04-26

RAG 技术之向量化与相似度计算

一、课程回顾:为什么需要 RAG?

  1. 大模型的问题

    • 幻觉问题:生成内容可能偏离事实(信息偏差)。

    • 知识滞后:训练数据无法实时更新,缺乏最新信息。

    • 不可追溯:回答无明确信息来源,内容缺乏依据。

    • 专业领域受限:在医疗、法律等领域表现不足。

  2. RAG 的作用

    • 检索 - 生成架构:通过外部知识检索补充大模型,解决知识滞后和不可追溯问题。

    • 流程核心:文档处理(向量化存储)→ 用户问题向量化→ 相似度检索→ 生成回答。

二、向量化处理技术

  1. 基础向量化方法

    • 词袋模型(Bag of Words)

      • 优点:简单直接,统计词频。

      • 缺点:无语义信息,词汇表过长导致维度爆炸。

    • 词嵌入模型(Word Embedding)

      • Word2Vec:通过上下文预测中心词(CBOW)或反之(Skip-Gram),捕捉语义关系。

      • 预训练模型(如 BERT):基于大规模语料训练,支持中文,能处理上下文语义(如 “苹果” 在 “水果” 和 “手机” 中的不同含义)。

  2. 代码实操:使用 BERT 进行文本向量化

    from transformers import BertTokenizer, BertModel
    import torch
    
    # 1. 加载分词器和模型(需提前下载预训练模型)
    model_name = "bert-base-chinese"  # 或本地路径
    tokenizer = BertTokenizer.from_pretrained(model_name)
    model = BertModel.from_pretrained(model_name)
    
    # 2. 输入文本
    texts = [
        "苹果和李子是常见的水果",
        "我喜欢吃芒果和百香果",
        "苹果今年推出了新的手机",
        "深度学习是机器学习的重要分支"
    ]
    
    # 3. 文本分词和向量化
    inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
    
    # 4. 生成向量(获取最后一层隐藏层输出)
    with torch.no_grad():
        outputs = model(**inputs)
        embeddings = outputs.last_hidden_state.mean(dim=1)  # 句子级向量(平均池化)
    
    # 5. 查看向量维度
    print("向量维度:", embeddings.shape)  # 输出:torch.Size([4, 768])

    注意

    • 需安装依赖:pip install transformers torch

    • 模型下载较慢时,可指定本地路径(如model_name="path/to/local/bert")。

三、相似度计算算法

  1. 常见算法对比

    算法

    原理

    应用场景

    余弦相似度

    计算向量夹角,衡量方向相似性

    文本语义匹配

    欧几里得距离

    计算两点直线距离

    数值型数据差异

    曼哈顿距离

    计算各维度绝对差之和

    路径规划、图像

  2. 代码实操:余弦相似度计算

    import numpy as np
    
    # 假定向量数据(从BERT输出转换为numpy数组)
    vec1 = embeddings[0].numpy()
    vec2 = embeddings[2].numpy()
    
    # 计算余弦相似度
    def cosine_similarity(vec_a, vec_b):
        dot_product = np.dot(vec_a, vec_b)
        norm_a = np.linalg.norm(vec_a)
        norm_b = np.linalg.norm(vec_b)
        return dot_product / (norm_a * norm_b)
    
    similarity = cosine_similarity(vec1, vec2)
    print("余弦相似度:", similarity)  # 输出:接近0的值(因“苹果”语义不同)

    修复问题

    • 若报错 “模块未找到”,需安装numpypip install numpy

    • 向量需先转换为 numpy 数组(原代码中直接使用 PyTorch 张量可能导致计算错误)。

四、应用领域

  1. 信息检索:电商搜索、内容推荐(如抖音推荐系统)。

  2. 知识图谱:学科关联分析、医疗病例匹配。

  3. 对话系统:语音识别、智能客服的语义理解。

  4. 文本分类与聚类:文档相似度分组、重复内容检测。

五、问题总结与修复

  1. 代码运行报错 “模型未找到”

    • 原因:未正确指定模型名称或路径。

    • 修复:确保model_name为 Hugging Face 有效名称(如"bert-base-chinese")或本地模型路径。

  2. 向量化维度不一致

    • 原因:不同预训练模型输出维度不同(如 BERT 为 768,RoBERTa 可能为 1024)。

    • 修复:根据模型文档调整后续计算逻辑(如相似度算法兼容不同维度)。

  3. 相似度结果异常

    • 原因:未对向量进行归一化或使用错误池化方法(如未对词向量求平均)。

    • 修复:采用平均池化或 CLS token 池化生成句子级向量,确保输入向量为句子级表示。

六、课后任务

  1. 使用 AI 生成 JSON 格式的面试题数据(问题 + 答案),用于后续 RAG 流程测试。

  2. 尝试不同预训练模型(如 RoBERTa、ERNIE),对比向量化结果差异。

总结:向量化和相似度计算是 RAG 的核心环节,通过预训练模型可高效处理语义信息,结合余弦相似度等算法实现精准检索。实际应用中需注意模型加载、向量处理和算法选择的细节,确保流程稳定高效。

RAG 案例实战与向量数据库应用

一、RAG 案例实战:构建 AI 面试官系统

1. 系统设计与类定义

目标:实现文档加载、向量化、相似度检索的完整 RAG 流程。
类定义:创建RAG类,封装核心功能。

from transformers import BertTokenizer, BertModel
import torch
import numpy as np

class RAG:
    def __init__(self, model_name="bert-base-chinese"):
        """初始化模型和分词器"""
        self.tokenizer = BertTokenizer.from_pretrained(model_name)
        self.model = BertModel.from_pretrained(model_name)
        self.knowledge_base = []  # 存储知识库内容(问题-答案对)
        self.embeddings = []      # 存储问题向量化结果

    def load_knowledge(self, file_path):
        """加载JSON格式的知识库"""
        import json
        with open(file_path, "r", encoding="utf-8") as f:
            self.knowledge_base = json.load(f)
        # 提取问题并向量化
        self.embeddings = [self.text_to_embedding(q["question"]) for q in self.knowledge_base]

    def text_to_embedding(self, text):
        """文本向量化方法"""
        inputs = self.tokenizer(text, padding=True, truncation=True, return_tensors="pt")
        with torch.no_grad():
            outputs = self.model(**inputs)
            # 平均池化获取句子级向量
            embedding = outputs.last_hidden_state.mean(dim=1).numpy()[0]
        return embedding

    def search(self, query, top_k=1):
        """相似度检索"""
        query_emb = self.text_to_embedding(query)
        similarities = [self.cosine_similarity(query_emb, emb) for emb in self.embeddings]
        # 按相似度降序排序,获取top_k索引
        top_indices = np.argsort(similarities)[-top_k:][::-1]
        results = []
        for idx in top_indices:
            results.append({
                "question": self.knowledge_base[idx]["question"],
                "answer": self.knowledge_base[idx]["answer"],
                "similarity": similarities[idx]
            })
        return results

    @staticmethod
    def cosine_similarity(vec1, vec2):
        """余弦相似度计算"""
        dot_product = np.dot(vec1, vec2)
        norm1 = np.linalg.norm(vec1)
        norm2 = np.linalg.norm(vec2)
        return dot_product / (norm1 * norm2)

2. 知识库加载与向量化

  • 数据格式:JSON 文件存储问题 - 答案对(例:questions.json):

    [
        {"question": "什么是MySQL主键", "answer": "MySQL主键是唯一标识表中每行数据的字段..."},
        {"question": "事务的特性有哪些", "answer": "事务具有ACID特性:原子性、一致性、隔离性、持久性..."},
        ...
    ]
  • 代码调用

    rag = RAG()
    rag.load_knowledge("questions.json")

3. 查询处理与结果返回

  • 用户提问query = "MySQL中主键的作用是什么"

  • 检索逻辑

    results = rag.search(query, top_k=2)
    for res in results:
        print(f"问题: {res['question']}\n答案: {res['answer']}\n相似度: {res['similarity']:.4f}\n")
  • 输出示例

    问题: 什么是MySQL主键
    答案: MySQL主键是唯一标识表中每行数据的字段...
    相似度: 0.9234

二、向量数据库基础

1. 为什么使用向量数据库?

  • 优势

    • 持久化存储:解决内存存储数据易丢失问题。

    • 高效检索:支持大规模向量数据的快速相似度查询。

    • 语义匹配:基于向量距离计算,实现语义级检索(区别于传统数据库的精确匹配)。

  • 应用场景

    • 大模型知识外挂、推荐系统、图像 / 文本检索等。

2. 常用向量数据库

数据库

特点

适用场景

FAISS

高效、轻量,支持多种索引算法

本地小规模数据

Milvus

分布式、高可用,支持海量数据

企业级应用

Pinecone

云端托管,开箱即用,支持多模型集成

快速部署场景

3. 工作流程

  1. 数据预处理:文档分块、向量化(使用预训练模型如 BERT)。

  2. 向量存储:将向量化结果存入数据库,建立索引。

  3. 查询检索:用户问题向量化后,在数据库中检索相似向量,返回相关文档。

三、向量数据库实战:内存模式(以 FAISS 为例)

1. 安装与初始化

pip install faiss-cpu  # 或 faiss-gpu(需CUDA支持)
import faiss
import numpy as np

# 初始化FAISS客户端(内存模式)
index = faiss.IndexFlatL2(768)  # 768为向量维度,需与向量化结果一致

2. 添加数据

  • 数据结构:每个条目包含向量文档内容元数据

# 假设已有向量化后的向量列表embeddings(形状为[N, 768])
# 模拟文档内容和元数据
documents = [
    "Java开发入门指南",
    "Python数据分析实战",
    "MySQL数据库优化",
]
metadata = [{"source": "技术文档"}, {"source": "数据分析"}, {"source": "数据库"}]

# 添加到FAISS索引
index.add(np.array(embeddings, dtype=np.float32))

3. 查询示例

# 用户问题向量化
query_emb = np.array([...], dtype=np.float32).reshape(1, -1)

# 检索最相似的2个结果
k = 2
distances, indices = index.search(query_emb, k)

# 输出结果
for idx in indices[0]:
    print(f"匹配文档: {documents[idx]}\n元数据: {metadata[idx]}\n")

四、常见问题与修复

1. 文件读取乱码问题

  • 问题:加载 JSON / 文本文件时因编码错误导致读取失败。

  • 修复:指定编码格式encoding="utf-8"(尤其处理中文文件):

    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)

2. 向量化维度不一致

  • 问题:预训练模型输出维度与向量数据库索引维度不匹配(如 BERT 输出 768 维,索引定义为 384 维)。

  • 修复:确保向量化模型与索引维度一致,或在存储前对向量进行维度转换。

3. 相似度计算错误

  • 问题:未对向量化结果进行归一化,导致余弦相似度计算异常。

  • 修复:在cosine_similarity函数中添加归一化检查,确保向量非零:

    if norm1 == 0 or norm2 == 0:
        return 0.0  # 避免除零错误

五、课后任务

  1. 扩展知识库:使用 AI 生成更多领域的问题 - 答案对(如 Python、算法),丰富questions.json

  2. 对比实验:尝试不同向量数据库(FAISS vs. Milvus),观察检索速度和准确率差异。

  3. 性能优化:对大规模文档进行分块处理(如按段落切割),提升向量化效率。

总结:RAG 案例实战通过封装向量化和检索逻辑,实现了基于外部知识库的智能问答,而向量数据库则解决了大规模向量数据的存储和高效检索问题。实际应用中需注意数据预处理、模型选型和异常处理,确保系统稳定高效。