【书生大模型实战营(暑假场)】基础任务四 InternLM+LlamaIndex RAG 实践

基础任务四 InternLM+LlamaIndex RAG 实践

1 理解RAG,使用 RAG

1.1 RAG技术概览

预训练好的模型如果需要注入新的知识,一般具有两种方式:

  • 内部:在新的知识上微调更新模型权重;
  • 外部:额外注入上下文或外部信息,避免改变模型内部权重;

第一种方式一般代价较大,需要额外的训练数据和计算资源,且可能会影响原本模型学习到的泛化能力;第二种方式则只需要注入额外信息,避免了对模型进行训练或微调,更加容易实现。RAG 便是遵循第二种方式给预训练模型注入新的知识,可以让基础模型实现非参数知识更新,无需训练便可以掌握新领域知识

RAG ( Retrieval Augmented Generation )是一种结合了**检索(Retrieval)和生成(Generation)**的技术,旨在通过利用外部知识库来增强大型语言模型(LLMs)的性能。它通过检索与用户输入相关的信息片段,并结合这些信息来生成更准确、更丰富的回答。

RAG技术可以帮忙解决诸多问题,通过注入外部记忆,帮助解决生成幻觉,过时知识,以及缺乏透明和可解释的推理过程。
在这里插入图片描述

1.2 RAG工作原理

RAG不是某种特定的技术,更像是一种通用的框架。其基本框架如下:
在这里插入图片描述
其工作流程存在三个关键组件:

  • 索引indexing:将知识源(文档,网页等)分割成块并编码成向量,存储在向量数据库中;
  • 检索retrieval:将用户的问题也编码成向量,并在数据库中找到最相关的文档块(top-k chunks);
  • 生成 generation:将检索到的文档块与原始问题一起,作为prompt,输入到LLM中,生成最终回答。

1.3 向量数据库 Vector-DB

在 RAG中,关键一步的索引indexing 就是需要把知识源(文档,网页等)分割成块并编码成向量,存储在向量数据库中。向量数据库具有以下特性:

  • 数据存储:将文本及其他数据通过其他预训练的模型转换为固定长度的向量表示,这些向量能够捕捉文本的语义信息;
  • 相似性检索:根据用户的查询向量,使用向量数据库快速找出最相关的向量的过程。通常通过计算余弦相似度或其他相似性度量来完成。
    检索结果根据相似度得分进行排序,最相关的文档将被用于后续的文本生成;
  • 向量表示优化:包括使用更高级的文本编码技术,如句子嵌入或段落嵌入,以及对数据库进行优化以支持大规模向量搜索。

1.4 RAG发展进程

RAG的概念最早是由Meta ( Facebook ) 的Lewis等人在 2020 《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》中提出的。

总体来说,RAG是一个新颖的概念,
在这里插入图片描述
目前,RAG大概可以分为:

  • Naive RAG:问答系统,信息检索
  • Advanced RAG:摘要生成,内容推荐
  • Modular RAG:多模态任务,对话系统

1.5 RAG的优化

RAG形成了一个基本的框架,而具体的每个环节都可以进行优化提升改进,
在这里插入图片描述

1.6 RAG vs Fine-tuning

RAG 和 Fine-tuning 代表了预训练模型向下游任务的迁移学习的两条关键路径

RAG

  • 非参数记忆,利用外部知识库提供实时更新的信息。
  • 能够处理知识密集型任务,提供准确的事实性回答。
  • 通过检索增强,可以生成更多样化的内容。
  • 适用场景:适用于需要结合最新信息和实时数据的任务:开放域问答、实时新闻摘等。
  • 优势:动态知识更新,处理长尾知识问题。
  • 局限:依赖于外部知识库的质量和覆盖范围,还是garbage in,garbage out;依赖模型本身能力,对 LLM的增强是"如虎添翼",没有打破scaling law的范畴。

Fine-tuning

  • 参数记忆,通过在特定任务数据上训练,模型可以更好地适应该任务。
  • 通常需要大量标注数据来进行有效微调。
  • 微调后的模型可能过拟合,导致泛化能力下降
  • 适用场景:适用于数据可用且需要模型高度专业化的任务,如特定领域的文本分类、情感分析、文本生成等。
  • 优势:模型性能针对特定任务优化。
  • 局限:需要大量的标注数据,且对新任务的适应性较差。
    在这里插入图片描述
    下面这个框架,比较好的表现了现有的主流 LLM 增强方法,在外部知识和领域适应方面的特点:
  • Prompt Engineering:相对较为初级;
  • RAG:在外部知识方面较好;
  • Fine-tuning:在领域适应方面较好;
  • All of the above:Retriever Fine-tuning,Collaborative Fine-tuning,Generator Fine-tuning 等。
    具体选择哪种方法,还是要看任务要求和使用场景。
    在这里插入图片描述

1.7 RAG评估框架和基准测试

RAG的评估一般按照检索效果和生成效果综合评估,常用的评估指标和框架如:
在这里插入图片描述

1.8 RAG总结

在这里插入图片描述

2 LLamaIndex

LlamaIndex 是一个开源的索引|和搜索库,提供高效、可扩展的文本索引|和检索功能。

他是一个开源的为LLM设计的索引和搜索库,并非专门为RAG设计,但可以帮助构建有效的数据库,从而更好地构建 LLM应用。
在这里插入图片描述

2.1 LlamaIndex特点

LlamaIndex基本工作方式较为简单,和 Naive RAG 范式基本类似,即将外部数据/知识构建为可以查询的索引index,然后通过用户的输出查询 query去查找出相关的数据,然后将最终的 prompt,即用户输入和索引查询的得到的数据输入给 LLM,用于生成最终的输出,即实现了 RAG。
在这里插入图片描述
LlamaIndex 具有以下特点:

  • 数据索引和检索:
    – 对大规模数据进行索引,支持多种数据源(文件,数据库,网络等)
    – 提供高效检索机制,快速找到相关信息
  • 支持多众生成模型
    – 除 Llama 系列外,还支持 GPT系列,openAI API,InternLM 系列等多种大模型

2.2 LlamaIndex 的 RAG 应用

LlamaIndex 提供了 RAG 一般应用的全过程模块化,拓展方便
在这里插入图片描述

  • 数据加载 Loading:通过 LlamaHub提供了数百种连接器,适合不同格式的数据;
  • 数据索引 Indexing:可以创建向量嵌入vector embedding 和其他元数据策略来索引数据,使得查询更简单,能快速找到上下文信息;
  • 数据存储 Storing:数据被索引后,可以存储起来避免重复索引过程,提高系统效率;
  • 数据查询 Querying:多样查询策略,比如子查询,多步查询和混合策略。利用 LLM 和 LlamaIndex 数据结构确保准确高效的数据提取;
  • 效果评估 Evaluating:LlamaIndex 提供了客观评估方法,供用户比较不同改进策略的效果,确保系统性能稳定和优化。

可见 LlamaIndex 提供了RAG 一般应用的全过程模块化,且易于拓展。

3 InternLM+LlamaIndex RAG 实践:准备工作

3.1 虚拟环境配置

首先配置环境

conda create -n llamaindex python=3.10

conda activate llamaindex
conda install pytorch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2 pytorch-cuda=11.7 -c pytorch -c nvidia

pip install einops
pip install  protobuf

Protocol Buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.
Einops support Flexible and powerful tensor operations for readable and reliable code. Supports numpy, pytorch, tensorflow, jax, and others.

3.2 LlamaIndex安装

配置好环境后,可以安装 LlamaIndex 及其依赖

conda activate llamaindex
pip install llama-index==0.10.38 llama-index-llms-huggingface==0.2.0 "transformers[torch]==4.41.1" "huggingface_hub[inference]==0.23.1" huggingface_hub==0.23.1 sentence-transformers==2.7.0 sentencepiece==0.2.0

3.3 下载词向量模型

为了将文本向量化以实现索引,需要选择词向量模型进行 embedding,这里选用 Sentence Transformer,因为其开源,相对轻量,支持中文且效果较好的。运行以下指令,新建一个python文件

cd ~
mkdir llamaindex_demo
mkdir model
cd ~/llamaindex_demo
touch download_hf.py

在新建的 download_hf.py 中添加

import os

# 设置环境变量
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'

# 下载模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/model/sentence-transformer')

然后,进入新建的 llamaindex_demo 文件夹,执行 download_hf.py 脚本,下载模型

cd /root/llamaindex_demo
conda activate llamaindex
python download_hf.py

3.4 安装 NLTK 依赖资源

使用开源词向量模型构建词向量的时候,需要用到自然语言处理库 nltk 的资源。正常情况下,如果无法自动从互联网上下载,我们可以从国内仓库镜像地址下载相关资源,保存到服务器上。 我们用以下命令下载 nltk 资源并解压到服务器上,之后使用时服务器即会自动使用已有资源,无需再次下载。

cd /root
git clone https://gitee.com/yzy0612/nltk_data.git  --branch gh-pages
cd nltk_data
mv packages/*  ./
cd tokenizers
unzip punkt.zip
cd ../taggers
unzip averaged_perceptron_tagger.zip

4 InternLM+LlamaIndex RAG 实践:启动 LlamaIndex HuggingFaceLLM

为了方便,可以先进行 symbolic/symlink 软连接

cd ~/model
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b/ ./

然后在 llamaindex_demo 中新建 llamaindex_internlm.py 文件,并贴入代码:

cd ~/llamaindex_demo
touch llamaindex_internlm.py

贴入以下代码。用以调用LLM:

from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.core.llms import ChatMessage
llm = HuggingFaceLLM(
    model_name="/root/model/internlm2-chat-1_8b",
    tokenizer_name="/root/model/internlm2-chat-1_8b",
    model_kwargs={"trust_remote_code":True},
    tokenizer_kwargs={"trust_remote_code":True}
)

rsp = llm.chat(messages=[ChatMessage(content="xtuner是什么?")])
print(rsp)

之后运行该新建python文件,便可启动 LLM internlm2-chat-1_8b:

conda activate llamaindex
cd ~/llamaindex_demo/
python llamaindex_internlm.py

启动效果如下,可效果并不理想,模型显然缺乏 Xtuner 相关的知识。注意,这是我们没有 RAG 来增强外部知识时的效果。
在这里插入图片描述

5 InternLM+LlamaIndex RAG 实践:实现 LlamaIndex RAG

首先,在 llamaindex 虚拟环境中安装 LlamaIndex 词嵌入向量依赖

conda activate llamaindex
pip install llama-index-embeddings-huggingface llama-index-embeddings-instructor

由于我们的 RAG需要外部知识,我们可以用以下命令安装外部知识,即一些 XTuner 相关的介绍。

cd ~/llamaindex_demo
mkdir data
cd data
git clone https://github.com/InternLM/xtuner.git
mv xtuner/README_zh-CN.md ./

在这里插入图片描述
然后,运行以下指令,新建一个python文件,并贴入以下代码

cd ~/llamaindex_demo
touch llamaindex_RAG.py
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings

from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.huggingface import HuggingFaceLLM

#初始化一个HuggingFaceEmbedding对象,用于将文本转换为向量表示
embed_model = HuggingFaceEmbedding(
#指定了一个预训练的sentence-transformer模型的路径
    model_name="/root/model/sentence-transformer"
)
#将创建的嵌入模型赋值给全局设置的embed_model属性,
#这样在后续的索引构建过程中就会使用这个模型。
Settings.embed_model = embed_model

llm = HuggingFaceLLM(
    model_name="/root/model/internlm2-chat-1_8b",
    tokenizer_name="/root/model/internlm2-chat-1_8b",
    model_kwargs={"trust_remote_code":True},
    tokenizer_kwargs={"trust_remote_code":True}
)
#设置全局的llm属性,这样在索引查询时会使用这个模型。
Settings.llm = llm

#从指定目录读取所有文档,并加载数据到内存中
documents = SimpleDirectoryReader("/root/llamaindex_demo/data").load_data()
#创建一个VectorStoreIndex,并使用之前加载的文档来构建索引。
# 此索引将文档转换为向量,并存储这些向量以便于快速检索。
index = VectorStoreIndex.from_documents(documents)
# 创建一个查询引擎,这个引擎可以接收查询并返回相关文档的响应。
query_engine = index.as_query_engine()
response = query_engine.query("xtuner是什么?")

print(response)

然后,运行上面的脚本,为 LLM internlm2-chat-1_8b 用 RAG 赋能

conda activate llamaindex
cd ~/llamaindex_demo/
python llamaindex_RAG.py

在这里插入图片描述

经过 RAG 利用外部知识增强,LLM internlm2-chat-1_8b 的回复相比没有 RAG 时有了较大的改善,较好的把握了 XTuner 的意义。

6 InternLM+LlamaIndex RAG 实践:Streamlit 实现 LlamaIndex + RAG 网页App 部署

首先,进行准备工作,安装依赖并创建app脚本:

conda activate llamaindex
pip install streamlit==1.36.0

cd ~/llamaindex_demo
touch app.py
import streamlit as st
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.huggingface import HuggingFaceLLM

st.set_page_config(page_title="llama_index_demo", page_icon="🦜🔗")
st.title("llama_index_demo")

# 初始化模型
@st.cache_resource
def init_models():
    embed_model = HuggingFaceEmbedding(
        model_name="/root/model/sentence-transformer"
    )
    Settings.embed_model = embed_model

    llm = HuggingFaceLLM(
        model_name="/root/model/internlm2-chat-1_8b",
        tokenizer_name="/root/model/internlm2-chat-1_8b",
        model_kwargs={"trust_remote_code": True},
        tokenizer_kwargs={"trust_remote_code": True}
    )
    Settings.llm = llm

    documents = SimpleDirectoryReader("/root/llamaindex_demo/data").load_data()
    index = VectorStoreIndex.from_documents(documents)
    query_engine = index.as_query_engine()

    return query_engine

# 检查是否需要初始化模型
if 'query_engine' not in st.session_state:
    st.session_state['query_engine'] = init_models()

def greet2(question):
    response = st.session_state['query_engine'].query(question)
    return response

      
# Store LLM generated responses
if "messages" not in st.session_state.keys():
    st.session_state.messages = [{"role": "assistant", "content": "你好,我是你的助手,有什么我可以帮助你的吗?"}]    

    # Display or clear chat messages
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.write(message["content"])

def clear_chat_history():
    st.session_state.messages = [{"role": "assistant", "content": "你好,我是你的助手,有什么我可以帮助你的吗?"}]

st.sidebar.button('Clear Chat History', on_click=clear_chat_history)

# Function for generating LLaMA2 response
def generate_llama_index_response(prompt_input):
    return greet2(prompt_input)

# User-provided prompt
if prompt := st.chat_input():
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.write(prompt)

# Gegenerate_llama_index_response last message is not from assistant
if st.session_state.messages[-1]["role"] != "assistant":
    with st.chat_message("assistant"):
        with st.spinner("Thinking..."):
            response = generate_llama_index_response(prompt)
            placeholder = st.empty()
            placeholder.markdown(response)
    message = {"role": "assistant", "content": response}
    st.session_state.messages.append(message)

然后通过以下指令运行 app.py:

streamlit run app.py

在这里插入图片描述
在这里插入图片描述

7 闯关任务

我们直接问第六节部署为 streamlit app 的有 XTuner 相关知识实现 RAG 的 internlm2-chat-1_8b 一个具有一定“新闻”性质的问题:

“2024年巴黎奥运会上潘展乐取得什么项目的金牌,你有何看法?”

在没有添加这一事件相关知识时,模型无法回答相关问题,
在这里插入图片描述
而后,我们在 /root/llamaindex_demo/data 路径下添加新的有关潘展乐奥运会夺金的知识,知识来源于一则新闻:赢下奥运“飞鱼大战”,潘展乐仅用两年达成纪录全满贯,我们将其保存为一个 markdown 文件:

# 赢下奥运“飞鱼大战”,潘展乐仅用两年达成纪录全满贯

以46秒40的成绩夺冠并打破世界纪录,潘展乐赛后庆祝。 图/新华社
8月1日凌晨,2024年巴黎奥运会游泳项目男子100米自由泳决赛,中国选手潘展乐以46秒40夺冠并打破他保持的世界纪录,赢下奥运会“飞鱼大战”。这是中国代表团巴黎奥运会第9金,也是中国游泳队在本届奥运会获得的首枚金牌,同时是中国首枚男子100米自由泳奥运金牌。

## 中国泳军巴黎首金,领先亚军1秒08
自巴黎奥运会游泳项目开赛以来,中国游泳队在多个项目上取得突破,但始终与金牌无缘。7月27日,张雨霏和队友获得女子4×100米自由泳接力铜牌;7月29日,张雨霏获得女子100米蝶泳铜牌;7月30日,徐嘉余、唐钱婷分别获得男子100米仰泳、女子100米蛙泳银牌。
首金重任落在状态出色的潘展乐身上。7月28日的男子4×100米自由泳接力决赛,中国队虽然遗憾获得第4名,但担任首棒的潘展乐游出46秒92,打破男子100米自由泳奥运会纪录。男子100米半决赛,潘展乐以47秒21头名晋级决赛。
巴黎奥运会前,潘展乐已是男子100米自由泳世界纪录保持者。今年2月的世锦赛男子4×100米自由泳接力决赛,担任中国队首棒的潘展乐游出46秒80,打破罗马尼亚名将波波维奇保持的世界纪录(46秒86)。
8月1日凌晨的男子100米自由泳决赛,潘展乐的对手不仅有前世界纪录保持者波波维奇,还有里约奥运会金牌、东京奥运会银牌得主查莫斯。重压之下,潘展乐用巴黎奥运会游泳项目首个世界纪录,赢下“飞鱼大战”。
潘展乐的出发反应时间仅0.62秒,在8位决赛选手中位列次席;前50米用时22秒28,排在所有选手首位;后50米用时24秒12,同样是所有选手最快;最终用时46秒40,领先亚军多达1秒08,将原世界纪录(46秒80)提升了0.4秒。

## “飞鱼大战”中国赢了,两年达成纪录全满贯
过去10届奥运会,中国游泳队总计在奥运会赛场获得16金,从自由泳、蛙泳、蝶泳、混合泳到接力,多个单项有金牌入账。而男子100米自由泳又被称为“飞鱼大战”,和田径赛场的男子100米“飞人大战”相似,中国选手从未站上过领奖台。
8月1日,潘展乐不仅夺得中国男子100米自由泳奥运历史首金,也成为游泳赛场真正的“飞鱼”。从追平宁泽涛此前保持的全国纪录,到站上奥运会最高领奖台,潘展乐仅用了两年。
2022年6月的世锦赛,潘展乐在男子100米自由泳半决赛中游出47秒65,追平宁泽涛保持的全国纪录。2023年5月的全国游泳冠军赛,潘展乐以47秒22同时打破男子100米自由泳全国纪录、亚洲纪录(47秒56)。
去年的杭州亚运会,潘展乐以46秒97打破男子100米自由泳亚运会纪录、亚洲纪录,成为首位游进47秒的亚洲选手。今年2月的世锦赛,潘展乐以46秒80打破世界纪录。巴黎奥运会赛场,他先后游出46秒92、46秒40,两次打破奥运会纪录,并创造新的世界纪录。
从2022年到2024年,潘展乐在男子100米自由泳项目上快速达成了全国纪录、亚洲纪录、亚运会纪录、奥运会纪录、世界纪录的全满贯。
2024年男子100米自由泳最佳成绩排行榜前5名中,潘展乐独占4席(46秒40第1、46秒80第2、46秒92第4、46秒97第5)。在男子100米自由泳历史最佳成绩排行榜(前十)中,潘展乐独占5席,其中46秒40、46秒80高居前两名。

## 曾自嘲“看台观战选手”,如今加冕“飞鱼”
潘展乐出生于2004年8月4日,4岁进入温州市少年游泳学校练习游泳,启蒙教练对他的评价是“只喜欢游泳,超级喜欢游泳”。2014年浙江省运会,年仅10岁的潘展乐一人获得7枚金牌,2018年入选省队。
到了2021年陕西全运会,潘展乐逐渐进入大众视野,他帮助浙江队获得男子4×100米自由泳接力冠军,男子100米自由泳获得铜牌,当时的成绩是48秒59。不过,在竞争激烈的国内以及世界赛场,潘展乐一度是看客。
据新华社报道,“看台观战选手”曾是潘展乐的社交媒体网名,成年队参加游泳比赛,年轻队员都是在看台上观战,这大抵是潘展乐此前网名的由来。
从2022年世锦赛开始,潘展乐终于不再是看客,只是屡屡与世界大赛领奖台失之交臂。那届世锦赛的男子100米自由泳决赛,潘展乐游出47秒79,以0.08秒之差无缘领奖台。2023年世锦赛男子100米自由泳决赛,潘展乐游出47秒43,再次获得第4名,距离领奖台只差0.01秒。
直至2024年世锦赛,潘展乐终于获得男子100米自由泳冠军,并和队友获得男子4×100米自由泳接力、男子4×200米自由泳接力、混合4×100米自由泳接力金牌,单届世锦赛获得4金,并打破了男子100米自由泳世界纪录。
如今,巴黎奥运会成为潘展乐加冕“飞鱼”的最佳舞台,无论是奥运会纪录、世界纪录,还是夺冠的巨大优势,为中国游泳队取得的历史性突破,都让其世界“飞鱼”的称号实至名归。

在这里插入图片描述
经过 RAG 运用外部知识进行记忆增强,发现LLM 能较好的回答这个具有“新闻性”的问题。

InternLM+LlamaIndex RAG 实践相当容易上手,在完成模型部署后,只需要将额外的知识保存在指定的外部知识文件夹下即可,非常棒!

但是实践起来还是有很多需要考虑的问题,比如下面这个情况,我们将潘展乐有关知识 RAG外挂后,通过 streamlit app 交互的模型可以回答潘展乐夺金有关的问题,但也会出现一些知识泄露的安全问题,比如这里的回答中泄露了与问题无关的知识,来自 XTuner中文文档
在这里插入图片描述
反馈的二维码来源:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值