自己手写了一个大模型RAG项目-04.知识库构建

大家好,我是程序锅。

github上的代码封装程度高,不利于小白学习入门。

常规的大模型RAG框架有langchain等,但是langchain等框架源码理解困难,debug源码上手难度大。

因此,我写了一个人人都能看懂、人人都能修改的大模型RAG框架代码。

整体项目结构如下图所示:手把手教你大模型RAG框架架构

手把手教你大模型RAG框架架构

整个小项目分为10个章节,和github高度封装的RAG代码不同,我们将从0到1搭建大模型RAG问答系统,所有代码评论区回复rag免费获取!

前序文章:
3.项目环境依赖按照

本篇文章将介绍4.知识库构建,知识库构建算是大模型RAG领域最核心的部分了(见图1),主要由知识存储、知识处理、知识向量化、知识检索和知识比较四大部分构成。不过,我们也可以从数据流转角度出发,涉及文件预处理、文件切分、向量化、构建索引等部分。

图1.RAG技术路线

一、文件预处理

输入:专业知识文件(pdf、word、txt等)

输出:结构化的json文件

在构建一个高效的RAG系统时,首要步骤是准备知识文档。现实场景中,我们面对的知识源可能包括多种格式,如Word文档、TXT文件、CSV数据表、Excel表格,甚至是PDF文件、图片和视频等。因此,第一步需要使用专门的文档加载器(例如PDF提取器)或多模态模型(如OCR技术),将这些丰富的知识源转换为大语言模型可理解的纯文本数据。

例如,处理PDF文件时,可以利用PDF提取器抽取文本内容;对于图片和视频,OCR技术能够识别并转换其中的文字信息。

文件预处理如果考虑全面,会涉及很多很多工作。比如你的pdf怎么解析、excel表格如何解析、如何去清洗*%@等垃圾字符等等。文件预处理模块很大程度会决定知识库的丰富程度。

在这个小项目中,为了每个人都能看懂并且能够修改。只涉及pdf、md、txt文件的纯文本解析。

import PyPDF2
import markdown
import html2text
import json
from tqdm import tqdm
import tiktoken
from bs4 import BeautifulSoup
import re
import os
    def read_pdf(cls, file_path: str):
        # 读取PDF文件
        with open(file_path, 'rb') as file:
            reader = PyPDF2.PdfReader(file)
            text = ""
            for page_num in range(len(reader.pages)):
                text += reader.pages[page_num].extract_text()
            return text

    def read_markdown(cls, file_path: str):
        # 读取Markdown文件
        with open(file_path, 'r', encoding='utf-8') as file:
            md_text = file.read()
            html_text = markdown.markdown(md_text)
            # 使用BeautifulSoup从HTML中提取纯文本
            soup = BeautifulSoup(html_text, 'html.parser')
            plain_text = soup.get_text()
            # 使用正则表达式移除网址链接
            text = re.sub(r'http\S+', '', plain_text) 
            return text

    def read_text(cls, file_path: str):
        # 读取文本文件
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()

二、文件切分

文件预处理后,我们还需执行一项关键步骤:文档切片。我们需要将文档分割成多个文本块,以便更高效地处理和检索信息。

首先为什么分割成多个文本块呢?

由于embedding模型对输入token有限制,因此只能分割成多个文本块,才能满足embedding模型的需求。

有人就会说:如果不考虑embedding模型,分块是越大越好吗?

不是。在分块的时候,也需要尽可能减少嵌入内容中的噪声,从而更有效地找到与用户查询最相关的文档部分。如果分块太大,可能包含太多不相关的信息,从而降低了检索的准确性。

因此,文件切分后的段落大小,必须有一个最大token长度max_token_len和最小token长度min_token_len。其中max_token_len是为了满足embedding模型对输入token的限制,也需减少嵌入内容中的噪声;min_token_len是为了要求切分后的段落不能太小,切分后的段落太小,可能会丢失必要的上下文信息,导致生成的回应缺乏连贯性或深度。

下面直接上代码:

 def get_chunk2(cls, text: str, min_token_len: int = 600, max_token_len: int = 1600, cover_content: int = 150):
        chunk_text = []
        chunk_lenth = []

        curr_len = 0
        curr_chunk = ''

        lines = text.split('\n')  # 假设以换行符分割文本为行
        for line in lines:
            line = line.replace(' ', '')
            line_len = len(enc.encode(line))
            if curr_len > max_token_len:
                while (curr_len > max_token_len):
                    split_a = enc.encode(curr_chunk)[:max_token_len]
                    split_b = enc.encode(curr_chunk)[max_token_len:]
                    curr_chunk = enc.decode(split_a)
                    chunk_text.append(curr_chunk)
                    chunk_lenth.append(max_token_len)
                    curr_chunk = curr_chunk[-cover_content:] + enc.decode(split_b)
                    curr_len = cover_content + curr_len - max_token_len
            else:
                if (curr_len <= min_token_len):
                    curr_chunk += line
                    curr_chunk += '\n'
                    curr_len += line_len
                    curr_len += 1
                else:
                    chunk_text.append(curr_chunk)
                    chunk_lenth.append(curr_len)
                    curr_chunk = curr_chunk[-cover_content:] + line
                    curr_len = line_len + cover_content
        if curr_chunk:
            chunk_text.append(curr_chunk)
            chunk_lenth.append(curr_len)
        return chunk_text

三、向量化

什么是向量化?

举个例子,设计4个句子,其中3个与大模型相关,1个与java相关。段落通过向量化后,使得与大模型相关的段落会在向量空间中距离比较近。java和大模型没那么相关,因此在向量空间中离得较远。

回到主题上来。长文本呢切分后需要给文本块做向量化。这里需要明确一点,知识库存的是向量化后的文本块。如下图示例:

由上面这一个示意图可以看出,知识库本质上就是一个列表,其中包含多个维度相同的向量。比如我们这个小项目采用智谱的embedding模型,模型编码为:embedding-2,向量化后的维度为1024。

代码如下:

import zhipu
class ZhipuEmbedding(BaseEmbeddings):
    """
    class for Zhipu embeddings
    """
    def __init__(self, path: str = '', is_api: bool = True, embedding_dim = 1024) -> None:
        super().__init__(path, is_api)
        if self.is_api:
            from zhipuai import ZhipuAI
            self.client = ZhipuAI(api_key=os.getenv("ZHIPUAI_API_KEY")) 
        self.embedding_dim = embedding_dim


    def get_embedding(self, text: str) -> List[float]:
        response = self.client.embeddings.create(
        model="embedding-2",
        input=text,
        )
        return response.data[0].embedding

四、构建索引

好了现在我们已经得到了知识库,我们需要思考一个问题。

问题向量与知识库匹配,是不是很慢?

答案肯定是的,如果不构建任何索引,非常简单粗暴的方法就是写一个for循环,挨个比较。

为了演示简单,暂时没有构建索引。后续文章会介绍如何采用Milvus 、Faiss等向量知识库技术改进构建索引部分,加快向量搜索。

所有代码评论区回复rag免费获取!

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
模型+RAG(Retrieval-Augmented Generation)是一种结合了检索和生成的方法,用于实现数据采集。具体步骤如下: 1. 数据收集:首先需要收集大量的原始数据,可以是文本、图像、音频等形式的数据。这些数据可以从互联网、数据库、文档等多个渠道获取。 2. 数据预处理:对收集到的原始数据进行预处理,包括数据清洗、去重、标注等操作。这一步骤旨在提高数据的质量和准确性,为后续的模型训练做准备。 3. 模型训练:使用大模型进行训练,可以选择使用预训练的语言模型(如GPT)或自定义的模型。在训练过程中,可以采用生成式对抗网络(GAN)等方法来增强模型的生成能力。 4. 检索模块构建:为了提高生成结果的准确性和相关性,需要构建一个检索模块。该模块可以使用传统的信息检索技术,如倒排索引、向量检索等,也可以使用深度学习方法,如BERT、Dense Retrieval等。 5. 数据采集:利用构建好的检索模块,对用户提出的问题或需求进行检索,获取与之相关的数据。可以根据检索结果的相关性进行排序,选择最相关的数据进行生成。 6. 数据生成:基于检索到的数据,使用大模型进行生成。可以采用生成式模型,根据检索到的数据进行文本、图像等内容的生成。生成的结果可以根据需求进行进一步的处理和优化。 7. 结果评估:对生成的结果进行评估,可以使用人工评估或自动评估的方式。评估指标可以包括生成结果的准确性、流畅性、相关性等。 8. 迭代优化:根据评估结果,对模型和检索模块进行优化和调整。可以通过增加训练数据、调整模型参数、改进检索算法等方式来提升系统的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值