Datawhale AI夏令营第四期-大模型应用开发Task3笔记

一、引言

1,RAG

在实际业务场景中,通用的基础大模型可能存在无法满足我们需求的情况,主要有以原因:

  • 知识局限性:大模型的知识来源于训练数据,而这些数据主要来自于互联网上已经公开的资源,对于一些实时性的或者非公开的,由于大模型没有获取到相关数据,这部分知识也就无法被掌握

  • 数据安全性:为了使得大模型能够具备相应的知识,就需要将数据纳入到训练集进行训练。然而,对于企业来说,数据的安全性至关重要,任何形式的数据泄露都可能对企业构成致命的威胁。

  • 大模型幻觉:由于大模型是基于概率统计进行构建的,其输出本质上是一系列数值运算。因此,有时会出现模型“一本正经地胡说八道”的情况,尤其是在大模型不具备的知识或不擅长的场景中。

为了上述这些问题,研究人员提出了检索增强生成(Retrieval Augmented Generation, RAG)的方法。这种方法通过引入外部知识,使大模型能够生成准确且符合上下文的答案,同时能够减少模型幻觉的出现。

由于RAG简单有效,它已经成为主流的大模型应用方案之一。

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

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

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

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

2,一个完整的RAG链路

离线计算

首先,知识库中包含了多种类型的文件,如pdf、word、ppt等,这些 文档(Documents)需要提前被解析,然后切割成若干个较短的 Chunk,并且进行清洗和去重。由于知识库中知识的数量和质量决定了RAG的效果,因此这是非常关键且必不可少的环节。然后,我们会将知识库中的所有 Chunk 都转成向量,这一步也称为 向量化(Vectorization)或者 索引(Indexing)。向量化 需要事先构建一个 向量模型(Embedding Model),它的作用就是将一段 Chunk 转成 向量(Embedding)。

在线计算

在实际使用RAG系统时,当给定一条用户 查询(Query),需要先从知识库中找到所需的知识,这一步称为 检索(Retrieval)。

检索 过程中,用户查询首先会经过向量模型得到相应的向量,然后与 数据库 中所有 Chunk 的向量计算相似度,最简单的例如 余弦相似度,然后得到最相近的一系列 Chunk 。由于向量相似度的计算过程需要一定的时间,尤其是 数据库 非常大的时候。这时,可以在检索之前进行 召回(Recall),即从 数据库 中快速获得大量大概率相关的 Chunk,然后只有这些 Chunk 会参与计算向量相似度。这样,计算的复杂度就从整个知识库降到了非常低。召回 步骤不要求非常高的准确性,因此通常采用简单的基于字符串的匹配算法。由于这些算法不需要任何模型,速度会非常快,常用的算法有 TF-IDFBM25 等。

另外,也有很多工作致力于实现更快的 向量检索 ,例如 faissannoy

由于 向量模型 能力有限,而随着知识库的增大,已经超出了其容量,因此准确性就会下降。在这种情况下,相似度最高的结果可能并不是最优的。为了解决这一问题,提升RAG效果,研究者提出增加一个二阶段检索——重排 (Rerank),即利用 重排模型(Reranker),使得越相似的结果排名更靠前。这样就能实现准确率稳定增长,即数据越多,效果越好(如上图中紫线所示)。通常,为了与 重排 进行区分,一阶段检索有时也被称为 精排 。而在一些更复杂的系统中,在 召回精排 之间还会添加一个 粗排 步骤,这里不再展开,感兴趣的同学可以自行搜索。

综上所述,在整个 检索 过程中,计算量的顺序是 召回 > 精排 > 重排,而检索效果的顺序则是 召回 < 精排 < 重排 。当这一复杂的 检索 过程完成后,我们就会得到排好序的一系列 检索文档(Retrieval Documents)。然后我们会从其中挑选最相似的 k 个结果,将它们和用户查询拼接成prompt的形式,输入到大模型。最后,大型模型就能够依据所提供的知识来生成回复,从而更有效地解答用户的问题。至此,一个完整的RAG链路就构建完毕了。

3,开源RAG框架

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

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

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

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

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

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

二、实战

1,环境准备

进入实例,点击终端。

运行下面代码,下载文件,并将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已经提供了所需要的大部分依赖,如 torchtransformers 等。

# 查看已安装依赖
pip list

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

# 安装 streamlit
pip install streamlit==1.24.0

 

 

2,模型下载

在RAG实战中,我们需要构建一个向量模型。向量模型通常采用BERT架构,它是一个Transformer Encoder。输入向量模型前,首先会在文本的最前面额外加一个 [CLS] token,然后将该token最后一层的隐藏层向量作为文本的表示。如下图所示:

 

在本次学习中,我们选用基于BERT架构的向量模型 bge-small-zh-v1.5,它是一个4层的BERT模型,最大输入长度512,输出的向量维度也为512。bge-small-zh-v1.5 支持通过多个平台进行下载,因为我们的机器就在魔搭,所以这里我们直接选择通过魔搭进行下载。模型在魔搭平台的地址为 AI-ModelScope/bge-small-zh-v1.5。单元格2.2 模型下载 会自动执行向量模型和源大模型下载。

  • 首先是向量模型下载

  • # 向量模型下载
    from modelscope import snapshot_download
    model_dir = snapshot_download("AI-ModelScope/bge-small-zh-v1.5", cache_dir='.')
  • 另外,还需要下载源大模型 IEITYuan/Yuan2-2B-Mars-hf

    # 源大模型下载
    from modelscope import snapshot_download
    model_dir = snapshot_download('IEITYuan/Yuan2-2B-Mars-hf', cache_dir='.')

 3,实战

为了构造索引,这里我们封装了一个向量模型类 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)

为了实现向量检索,我们定义了一个向量库索引类 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)

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

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

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

为了实现基于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)

三、作业

3.1 知识库构建

1. 确定领域

首先,选择一个领域,该领域可能在大模型上存在一些挑战性问题,比如医学诊断、法律咨询、复杂的技术支持等。这些领域往往需要精确的信息和专业知识,而通用的大语言模型可能无法提供足够准确的答案。

2. 收集领域数据
  • 数据来源:可以从多种渠道获取数据,包括但不限于:
    • 百度百科、维基百科等在线百科全书
    • 学术论文数据库(如PubMed、Google Scholar)
    • 行业报告
    • 专业书籍
    • 专家访谈记录
    • 开放数据集
  • 版权合规:确保所使用的数据遵循相关的版权法规。
3. 构建知识库
  • 数据预处理:对收集的数据进行清洗、去重、格式化等预处理步骤。
    • 清洗:去除HTML标签、无关字符、错误信息等。
    • 去重:使用哈希算法检测重复文本并删除。
    • 分词:根据领域特点选择合适的分词工具。
    • 格式化:统一文本格式,如统一编码、标准化日期等。
  • 文档划分:将长文档划分为较小的段落或句子,便于后续的索引和检索。

3.2 RAG实战

1. 知识库索引
  • 切分策略:根据文本内容的特点,可以采用基于句子的切分、基于段落的切分或是基于主题的切分。
  • 向量模型选择:选择一个适合的向量模型来生成文本的嵌入表示。对于中文文本,可以选择如SimBERT、ERNIE等模型。这些模型应该经过微调以适应特定领域的语料库。
  • 索引构建:使用向量数据库(如Milvus、Pinecone、Qdrant等)存储文本的向量表示,以便快速检索。
2. 检索
  • 检索工具:使用上述向量数据库进行高效检索。
  • 查询扩展:可以通过同义词替换、上下文理解等方式增强查询能力。
3. 生成
  • Prompt设计:设计有效的prompt来引导模型从知识库中检索相关信息并生成高质量的回答。
    • 提供背景信息:告诉模型它可以从知识库中检索信息。
    • 明确需求:清楚地说明要解决的问题或所需的具体信息。
    • 使用示例:给出一个或多个例子来帮助模型理解任务要求。
  • 融合策略:决定如何将检索到的信息与模型生成的内容结合起来。一种常见的方法是在生成文本之前直接将检索到的信息插入到prompt中。

实战案例

假设我们要构建一个针对医学领域的知识库,并使用Yuan2-2B模型进行RAG实验。

知识库构建
  • 领域:选择医学领域,特别是心血管疾病治疗。
  • 数据来源:从PubMed下载相关文献摘要;从专业网站和论坛抓取问答数据。
  • 预处理:去除HTML标签、非结构化数据清洗、使用jieba进行分词、去除停用词等。
RAG实战
  • 索引:使用SimBERT对文本进行嵌入,并存储到Milvus中。
  • 检索:当用户提出问题时,使用Milvus检索最相关的文档片段。
  • 生成:设计prompt,比如:“给定以下关于心血管疾病的治疗信息,请回答…”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值