【Datawhale AI夏令营 task3】源大模型RAG实战

一、什么是RAG

1.定义 

为了解决大模型的知识局限性、数据安全性、大模型幻觉,研究人员提出了检索增强生(Retrieval Augmented Generation, RAG)的方法。这种方法通过引入外部知识,使大模型能够生成准确且符合上下文的答案,同时能够减少模型幻觉的出现。由于RAG简单有效,它已经成为主流的大模型应用方案之一。

2.RAG通常包括以下三个基本步骤:

  • 索引:将文档库分割成较短的 Chunk,即文本块或文档片段,然后构建成向量索引。

  • 检索:计算问题和 Chunks 的相似度,检索出若干个相关的 Chunk。

  • 生成:将检索到的Chunks作为背景信息,生成问题的回答

  • 3.开源RAG框架

目前,开源社区中已经涌现出了众多RAG框架,例如:

  • TinyRAG:DataWhale成员宋志学精心打造的纯手工搭建RAG框架。

  • LlamaIndex:一个用于构建大语言模型应用程序的数据框架,包括数据摄取、数据索引和查询引擎等功能。

  • LangChain:一个专为开发大语言模型应用程序而设计的框架,提供了构建所需的模块和工具。

  • QAnything:网易有道开发的本地知识库问答系统,支持任意格式文件或数据库。

  • RAGFlow:InfiniFlow开发的基于深度文档理解的RAG引擎。

  • ···

这些开源项目各具优势,功能丰富,极大的推动了RAG技术的发展。

因此,我将以 Yuan2-2B-Mars 模型为基础,进行RAG实战。希望通过构建一个简化版的RAG系统,来帮助大家掌握RAG的核心技术,从而进一步了解一个完整的RAG链路。

二、源2.0-2B RAG实战

1.PAI实例创建

在实战之前,需要开通阿里云PAI-DSW试用,并在魔搭社区创建PAI实例,创建流程与速通手册一致~如果忘记如何创建的,可以参考下面的内容复习一下~

个人学习记录分享【Datawhale夏令营——taste1】——10分钟跑通一站式baseline-CSDN博客

2.环境准备

进入实例,点击终端。

运行下面代码,下载文件,并将Task 3:源大模型RAG实战中内容拷贝到当前目录。

git lfs install git clone https://www.modelscope.cn/datasets/Datawhale/AICamp_yuan_baseline.git cp AICamp_yuan_baseline/Task\ 3:源大模型RAG实战/* .

 双击打开Task 3:源大模型RAG实战.ipynb,然后运行所有单元格。

通过下面的命令,我们可以看到ModelScope已经提供了所需要的大部分依赖,如 torch,transformers 等。

查看已安装依赖

pip list

但是为了进行模型微调以及Demo搭建,还需要在环境中安装streamlit

安装 streamlit

pip install streamlit==1.24.0

安装成功后,我们的环境就准备好了。

3.向量模型下载

python

from modelscope import snapshot_download

model_dir = snapshot_download("AI-ModelScope/bge-small-zh-v1.5", cache_dir='.')

这里使用的是 modelscope 中的 snapshot_download 函数,第一个参数为模型名称 AI-ModelScope/bge-small-zh-v1.5,第二个参数 cache_dir 为模型保存路径,这里.表示当前路径模型大小约为91.4M,由于是从魔搭直接进行下载,速度会非常快。

下载完成后,会在当前目录增加一个名为 AI-ModelScope 的文件夹,其中 bge-small-zh-v1___5 里面保存着我们下载好的向量模型。

还需要下载源大模型 IEITYuan/Yuan2-2B-Mars-hf

 下载方法和 Task 1:零基础玩转源大模型 一致。

python

from modelscope import snapshot_download

model_dir = snapshot_download('IEITYuan/Yuan2-2B-Mars-hf', cache_dir='.')

 下载完成,如下图所示 

4.RAG实战

模型下载完成后,就可以开始RAG实战啦!

 (1)索引

为了构造索引,这里我们封装了一个向量模型类 EmbeddingModel:

class EmbeddingModel:
    """
    class for EmbeddingModel
    """

    def __init__(self, path: str) -> None:
        self.tokenizer = AutoTokenizer.from_pretrained(path)

        self.model = AutoModel.from_pretrained(path).cuda()
        print(f'Loading EmbeddingModel from {path}.')

    def get_embeddings(self, texts: List) -> List[float]:
        """
        calculate embedding for text list
        """
        encoded_input = self.tokenizer(texts, padding=True, truncation=True, return_tensors='pt')
        encoded_input = {k: v.cuda() for k, v in encoded_input.items()}
        with torch.no_grad():
            model_output = self.model(**encoded_input)
            sentence_embeddings = model_output[0][:, 0]
        sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
        return sentence_embeddings.tolist()

通过传入模型路径,新建一个 EmbeddingModel 对象 embed_model。初始化时自动加载向量模型的tokenizer和模型参数。

print("> Create embedding model...")
embed_model_path = './AI-ModelScope/bge-small-zh-v1___5'
embed_model = EmbeddingModel(embed_model_path)

EmbeddingModel 类还有一个 get_embeddings() 函数,它可以获得输入文本的向量表示。

注意,这里为了充分发挥GPU矩阵计算的优势,输入和输出都是一个 List,即多条文本和他们的向量表示。

(2)检索

为了实现向量检索,我们定义了一个向量库索引类 VectorStoreIndex

class VectorStoreIndex:
    """
    class for VectorStoreIndex
    """

    def __init__(self, doecment_path: str, embed_model: EmbeddingModel) -> None:
        self.documents = []
        for line in open(doecment_path, 'r', encoding='utf-8'):
            line = line.strip()
            self.documents.append(line)

        self.embed_model = embed_model
        self.vectors = self.embed_model.get_embeddings(self.documents)

        print(f'Loading {len(self.documents)} documents for {doecment_path}.')

    def get_similarity(self, vector1: List[float], vector2: List[float]) -> float:
        """
        calculate cosine similarity between two vectors
        """
        dot_product = np.dot(vector1, vector2)
        magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)
        if not magnitude:
            return 0
        return dot_product / magnitude

    def query(self, question: str, k: int = 1) -> List[str]:
        question_vector = self.embed_model.get_embeddings([question])[0]
        result = np.array([self.get_similarity(question_vector, vector) for vector in self.vectors])
        return np.array(self.documents)[result.argsort()[-k:][::-1]].tolist()

类似地,通过传入知识库文件路径,新建一个 VectorStoreIndex 对象 index。初始化时会自动读取知识库的内容,然后传入向量模型,获得向量表示。

print("> Create index...")
doecment_path = './knowledge.txt'
index = VectorStoreIndex(doecment_path, embed_model)

上文提到 get_embeddings() 函数支持一次性传入多条文本,但由于GPU的显存有限,输入的文本不宜太多。所以,如果知识库很大,需要将知识库切分成多个batch,然后分批次送入向量模型。这里,因为我们的知识库比较小,所以就直接传到了 get_embeddings() 函数。

其次,VectorStoreIndex 类还有一个 get_similarity() 函数,它用于计算两个向量之间的相似度,这里采用了余弦相似度。

最后,我们介绍一下 VectorStoreIndex 类的入口,即查询函数 query()。传入用户的提问后,首先会送入向量模型获得其向量表示,然后与知识库中的所有向量计算相似度,最后将 k 个最相似的文档按顺序返回,k默认为1。

question = '介绍一下广州大学'
print('> Question:', question)

context = index.query(question)
print('> Context:', context)

这里我们传入用户问题 介绍一下广州大学,可以看到,准确地返回了知识库中的第一条知识。

(3)生成

 为了实现基于RAG的生成,我们还需要定义一个大语言模型类 LLM

class LLM:
    """
    class for Yuan2.0 LLM
    """

    def __init__(self, model_path: str) -> None:
        print("Creat tokenizer...")
        self.tokenizer = AutoTokenizer.from_pretrained(model_path, add_eos_token=False, add_bos_token=False, eos_token='<eod>')
        self.tokenizer.add_tokens(['<sep>', '<pad>', '<mask>', '<predict>', '<FIM_SUFFIX>', '<FIM_PREFIX>', '<FIM_MIDDLE>','<commit_before>','<commit_msg>','<commit_after>','<jupyter_start>','<jupyter_text>','<jupyter_code>','<jupyter_output>','<empty_output>'], special_tokens=True)

        print("Creat model...")
        self.model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16, trust_remote_code=True).cuda()

        print(f'Loading Yuan2.0 model from {model_path}.')

    def generate(self, question: str, context: List):
        if context:
            prompt = f'背景:{context}\n问题:{question}\n请基于背景,回答问题。'
        else:
            prompt = question

        prompt += "<sep>"
        inputs = self.tokenizer(prompt, return_tensors="pt")["input_ids"].cuda()
        outputs = self.model.generate(inputs, do_sample=False, max_length=1024)
        output = self.tokenizer.decode(outputs[0])

        print(output.split("<sep>")[-1])

这里我们传入 Yuan2-2B-Mars 的模型路径,新建一个 LLM 对象 llm。初始化时自动加载源大模型的tokenizer和模型参数。

print("> Create Yuan2.0 LLM...")
model_path = './IEITYuan/Yuan2-2B-Mars-hf'
llm = LLM(model_path)

 LLM 类的入口是生成函数 generate(),它有两个参数:

  • question: 用户提问,是一个str

  • context: 检索到的上下文信息,是一个List,默认是[],代表没有使用RAG

 运行下面的代码,即可体验使用RAG技术之后 Yuan2-2B-Mars 模型的回答效果:

print('> Without RAG:')
llm.generate(question, [])

print('> With RAG:')
llm.generate(question, context)

 如下所示,如果模型依赖自己的底层知识进行回答,就很容易出现幻觉,生成 广州大学成立于1952年,前身为广州工学院 这样错误的内容。

使用RAG之后,模型能够结果知识库中的知识,准确回答用户的提问。

 

注意:运行完成后,别忘了回到魔搭,【关闭】实例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值