找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1530

积分

0

好友

230

主题
发表于 5 小时前 | 查看: 2| 回复: 0

在构建专业问答系统时,模型对特定事实的掌握往往面临挑战。例如,关于“免费额度15GB”的信息,大模型经过多次微调仍可能错误回答为“10GB”。针对这类精准事实召回问题,除了持续微调,采用RAG(检索增强生成)技术是更为高效的解决方案。RAG与微调分工明确:微调(Fine-Tuning)旨在改变模型的表达或推理方式;而RAG的核心是为模型提供一个可实时检索参考的外部知识库。

本文将摒弃如Dify、Ragflow等现成平台,聚焦于使用Python原生代码,从零实现一个RAG系统,并深入探讨其开发流程与关键调优点。

一、RAG 文档准备与向量化 (Knowledge Ingestion)

首先,准备知识文件以验证流程。在 ./data/knowledge_base.txt 中存入一条简单知识:

怎么安装 SDK?`pip install feiyue-sdk` 即可完成安装。

随后,编写生成向量知识库的Python脚本:

import os
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma
from langchain_community.document_loaders import DirectoryLoader, TextLoader

# 配置向量存储目录
VECTOR_DB_PATH = "./vector_store"
DATA_PATH = "./data"

def ingest_local_data(vector_db_path=VECTOR_DB_PATH, data_path=DATA_PATH):
    if not os.path.exists(data_path):
        print(f"❌ Error: {data_path} not found.")
        return

    # 1️⃣ 初始化 Embeddings 模型
    embeddings = HuggingFaceEmbeddings(
        model_name="BAAI/bge-small-zh-v1.5",
        model_kwargs={'device': 'cpu'}  # 可改为 'cuda' 或 'mps'
    )

    # 2️⃣ 初始化向量数据库
    vector_db = Chroma(
        persist_directory=vector_db_path,
        embedding_function=embeddings
    )

    # 3️⃣ 加载文档
    loader = DirectoryLoader(data_path, glob="**/*.txt", loader_cls=TextLoader)
    docs = loader.load()

    # 4️⃣ 文档智能切分
    splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=100)
    final_docs = splitter.split_documents(docs)

    # 5️⃣ 向量化并入库
    vector_db.add_documents(final_docs)
    print(f"✅ Ingested {len(final_docs)} chunks from {data_path}")

if __name__ == "__main__":
    ingest_local_data()

此阶段完成了RAG的知识库构建:

  1. 读取 ./data/ 目录下的 .txt 文件。
  2. 使用 RecursiveCharacterTextSplitter 将文档切分为块(chunk),设置最大块大小为600字符,相邻块重叠100字符以保持语义连贯。
  3. 选用 BAAI/bge-small-zh-v1.5 作为Embedding模型,将文本块转化为向量。
  4. 使用Chroma向量数据库进行持久化存储。

代码中未显式定义分隔符,但 RecursiveCharacterTextSplitter 默认使用 ["\n\n", "\n", " ", ""] 作为层级分隔符,智能切分文本。

运行脚本后,将在 ./vector_store 目录下生成 chroma.sqlite3 等文件。通过数据库工具查看,可发现其中存储了向量数据及元数据。至此,RAG的知识库准备阶段完成。

二、RAG 检索与问答实现 (Retrieval & Generation)

接下来,实现检索与生成的核心逻辑:

import os
import torch
from modelscope import snapshot_download
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
from langchain_core.prompts import ChatPromptTemplate
from langchain_huggingface import HuggingFacePipeline
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma
from langchain_classic.chains.combine_documents import create_stuff_documents_chain
from langchain_classic.chains import create_retrieval_chain

VECTOR_DB_PATH = "./vector_store"
BASE_MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"

class RAGService:
    def __init__(self, vector_db_path=VECTOR_DB_PATH):
        self.vector_db_path = vector_db_path

        # 自动选择计算设备
        if torch.cuda.is_available():
            self.device = "cuda"
        elif torch.backends.mps.is_available():
            self.device = "mps"
        else:
            self.device = "cpu"
        print(f"🖥️ Using device: {self.device}")

        # 初始化 Embeddings
        self.embeddings = HuggingFaceEmbeddings(
            model_name="BAAI/bge-small-zh-v1.5",
            model_kwargs={'device': self.device}
        )

        # 加载大语言模型 (LLM)
        self.llm = self._load_model()

        # 加载向量数据库
        self.vector_db = Chroma(
            persist_directory=self.vector_db_path,
            embedding_function=self.embeddings
        )

    def _load_model(self):
        print("📥 Downloading/Loading Model...")
        model_dir = snapshot_download(BASE_MODEL_ID)
        tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
        model = AutoModelForCausalLM.from_pretrained(
            model_dir,
            torch_dtype="auto",
            device_map="auto",
            trust_remote_code=True
        )
        pipe = pipeline(
            "text-generation",
            model=model,
            tokenizer=tokenizer,
            max_new_tokens=512,
            temperature=0.1,
            return_full_text=False
        )
        return HuggingFacePipeline(pipeline=pipe)

    def get_chain(self):
        system_prompt = (
            "你是一个专业的助手。请仅根据提供的上下文(Context)回答问题。"
            "如果你在上下文中找不到答案,请诚实告知。回答请简明扼要。"
            "\n\n上下文: {context}"
        )
        prompt = ChatPromptTemplate.from_messages([
            ("system", system_prompt),
            ("human", "{input}"),
        ])
        question_answer_chain = create_stuff_documents_chain(self.llm, prompt)
        return create_retrieval_chain(
            self.vector_db.as_retriever(search_kwargs={"k": 3}),
            question_answer_chain
        )

if __name__ == "__main__":
    service = RAGService()
    rag_chain = service.get_chain()
    user_input = "安装飞跃云SDK的指令是什么?"
    response = rag_chain.invoke({"input": user_input})
    print("\n🤖 AI Answer:\n", response["answer"])
    print("\n📄 Sources used:", [d.metadata.get('source') for d in response["context"]])

该流程的关键步骤如下:

  1. 问题向量化:用户问题通过相同的Embedding模型转换为查询向量。
  2. 向量相似度检索:在向量数据库中查找与查询向量最相似的Top-K个文档块(本例中K=3),常用余弦相似度等算法。
  3. 上下文增强生成:将检索到的文档块作为上下文,与问题一同提交给大语言模型,生成最终答案。

运行代码,成功输出答案 pip install feiyue-sdk 并溯源至源文件,标志着RAG检索与生成流程的贯通。

三、实战:构建个人知识库

在基础流程验证通过后,进行大规模知识库构建实战。将本地大量的 .md 文档进行切分与向量化,并将Embedding计算设备改为 mps 以利用Mac GPU加速。

执行后,成功将文档切分为5433个知识块(chunk)并存入向量库。随后进行检索测试,针对问题“我的低成本 LLM 微调用了哪个模型?秩用了多少?”,系统基本能给出正确回答并准确溯源,证明了 BAAI/bge-small-zh-v1.5 小Embedding模型与 Qwen2.5-0.5B-Instruct 小语言模型组合的有效性。

四、调优:修复检索信息偏移

在测试中,发现对于“秩(r)的值是多少?”这一问题,模型错误回答了“8”,而原文实际为“4”。为定位问题,我们深入探究向量数据库内部。

通过查询 embedding_metadata 表,可以观察到每个chunk存储了两条元数据:source(源文件路径)和 chroma:document(块文本内容)。这证实了 chunk_size=600chunk_overlap=100 的参数生效。

通过修改代码打印出每次检索所命中的 embedding_id,并反查数据库,发现召回的三个chunk中,仅有一个包含了相关描述:

“而且,r (秩)也可能有关系,秩从 8 改为 4 可能会比较容易训练...”

该句表述存在一定歧义,对AI模型的理解能力构成了挑战。最初的回答错误可能源于生成温度(temperature)设置过低(0.1),导致模型过于“保守”而未能正确解读上下文。

temperature 参数调整至0.3后重新提问,模型成功给出了“秩为4”的正确答案。这表明适当提高温度可以增强模型在模糊上下文下的推理能力。

然而,调优也引入了新问题:

  1. 幻觉(Hallucination):回答中开始出现知识库外的噪声信息。
  2. 过拟合风险:当前的参数(如温度、top-k)可能仅对当前测试问题表现良好,对其他问题的泛化能力有待验证。

此外,还有两个潜在优化点:

  1. 检索返回的文档数量(k值)需要权衡。k值过小可能导致信息不足;k值过大则可能超出小模型的有效上下文窗口。
  2. RAG系统在回答“知识库中不包含什么”(反向查询)时能力较弱,其答案质量严重依赖于检索环节召回的相关材料。



上一篇:Three.js实战:构建3D交互式机器学习数学公式可视化教学工具
下一篇:Java面试题解析:new String("abc")在JVM中创建对象数量的底层原理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 12:44 , Processed in 0.193780 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表