Llamaindex+Llava实现多模态RAG(入门)

本文介绍了RAG(Retrieval-AugmentedGeneration)的基本概念和在解决大语言模型幻觉问题上的应用,重点讲解了Llama-index在知识库加载、索引和存储方面的技术,以及如何利用Llama-index创建和优化多模态RAG系统,包括处理文档拆分、节点操作和多模态检索增强。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最近在做RAG相关的内容,发现多模态的问答加上本地知识库在垂直领域具有非常广泛的应用。本文第一部分会简单介绍RAG的基本知识,第二部分讲一下llamaindex的基础知识(有两部分没有讲,请自行参考官方文档),第三部分构建多模态的RAG系统。(注意,本文RAG模型全部部署在本地,需要20G以上的显卡!!

目录

1、 RAG基本知识

2、Llama-index基础知识

2.1 Loading

2.2 Indexing

2.3 Storing

3、创建多模态的RAG


1、 RAG基本知识

        RAG(Retrieval-Augmented Generation)大白话来说就是:增强检索(RA)和生成(G)。之所以提出RAG的概念主要还是为了解决大语言模型(如chatGPT)的模型幻觉问题,说白了就是回答问题不准确。并且首先于先验知识的局限,很多LLM对最新的知识无法回答。由此,RAG就此诞生。以下是RAG的简单框架图:1)用户提问+指令;2)转换为query(Q);3)检索本地知识库+增强内容;4)发送到LLM对知识进行增强;5)更具检索增强后的知识生成回答。(图片来源于:什么是 RAG?— 检索增强生成详解 — AWS

2、Llama-index基础知识

        那么问题来了,如何高效的构建知识库以及优化RAG的完整流程呢?如今的技术栈例如Langchain、Llama-index、FlowiseAI以及AutoGen。这些技术栈优化了RAG的整体流程,但有些对于新手来说确实不是很友好,主要原因包括个人认为是内容过于冗杂。在经过多次尝试后,决定从llama-index入手(后面发现和langchain的原理差不多)。

        Llama-index是一个非常强大的技术栈,对RAG任务具有非常的针对性。简单来说,可以把RAG的流程拆解为:loading、indexing、storing、querying以及evaluation(非常精简,并且每一部分的操作性非常强,类似于一个骨架)。

2.1 Loading

        这部分我认为最重要的一个概念就是document和nodes的概念,有点类似于知识图谱。我做了一个简单的图方便大家理解,这里我以一个pdf文档为例。首先一个pdf文档通过llama-index内置的工具可以把每一页pdf拆分为若干个Documents。但每个Document会包含很多很多内容,这样其实不利于检索,即使检索也会产生很多无关的上下文,增加token的消耗。所以,llama-index进一步将document拆分为nodes,更加精细化检索内容。

        Node的概念十分重要,node可以简单的理解为chunk。每个chunk包含文字,你可以自定义chunk的大小以及overlap(为了上下文的连续性)的大小。huggingface上面有个space可以可视化chunk/node的概念https://huggingface.co/spaces/m-ric/chunk_visualizer

        Llama-index为我们提供了多种node的模式,包括:BaseNode、TextNode(储存文本)、ImageNode(储存图像)以及IndexNode(仅储存索引)。但我们将document划分为chunk/node之后,我们可以查看每个节点的具体信息,请查看我分享在github上面的jupyter代码:Llama-index LoadingContribute to MMHHRR/urban_llmRAG development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/MMHHRR/urban_llmRAG/blob/main/llama-index/1.%20llamaindex_loading.ipynb        可以发现,Llama-index为每个node配置了一个id_(也可以自己设置),以及metadata、relationship以及text。需要注意的是llama-index里面为每个node匹配了一个relationship,如下:

SOURCE: The node is the source document.

PREVIOUS: The node is the previous node in the document.

NEXT: The node is the next node in the document.

PARENT: The node is the parent node in the document.

CHILD: The node is a child node in the document.

2.2 Indexing

        另一个重要的概念是indexing,可以参考我的github。我们可以从document以及node直接创建索引(index),非常简单的一个示例:

Llama-index IndexingContribute to MMHHRR/urban_llmRAG development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/MMHHRR/urban_llmRAG/blob/main/llama-index/2.%20llamaindex_Indexing.ipynb

document1 = Document(
    text="This is a super-customized document",
    metadata={
        "file_name": "super_secret_document.txt",
        "category": "finance",
        "author": "LlamaIndex"})
document2 = Document(
    text="Hello world!",
    metadata={
        "file_name": "computersss.txt",
        "category": "RAG",
        "author": "Human"})
##或者这样写,把多个document用[.. , ..]连接
document = [document1, document2]
document

         你还可以从node创建index。此外,Llama-index还为我们提供了management工具,我们可以轻易的插入节点(insertation)、删除节点(delete)、升级节点(updata)以及更新节点(refresh)。当然还可以追踪节点的变化。

print(index.ref_doc_info)
2.3 Storing

        可以先跑一下storing的代码。Storing具有多种储存模式,你可以将Document、Index、Vector储存在本地或者其他数据库中(例如Qdrant),其目的就是为了更加便捷的对文档或者图像进行检索。可以想象一下,数据被embeding后储存在strore中,然后根据query进行搜索(余弦相似度等搜索方法)。比如MultiModalVectorStoreIndex()方法可以将多模态数据进行储存。Llama-index StoringContribute to MMHHRR/urban_llmRAG development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/MMHHRR/urban_llmRAG/blob/main/llama-index/3.%20llamaindex_storing.ipynb

3、创建多模态的RAG

        首先导入包,以及设置系统的embeding模型。(注意:Llama-index更新的太快了,很多文档都不一样,本文使用的是现在时间(2024-4-1)的最新版本!)。其次,本文使用ollama本地化LLM模型,所以要装一下包。我提供了完整的代码,小伙伴们可以自己尝试一下!urban_llmRAG/coda/llamaindex_multimodal.ipynb at main · MMHHRR/urban_llmRAG · GitHubContribute to MMHHRR/urban_llmRAG development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/MMHHRR/urban_llmRAG/blob/main/coda/llamaindex_multimodal.ipynb

pip install llama_index
pip install qdrant_client
pip install llama_index-vector_stores-qdrant
pip install llama_index-embeddings-clip
pip install llama_index-multi_modal_llms-ollama
pip install llama_index-embeddings-huggingface
import qdrant_client
from llama_index.core import (ServiceContext, 
                               SimpleDirectoryReader,
                               SimpleDirectoryReader,
                               StorageContext,
                               Settings)
from llama_index.core.schema import ImageNode
from llama_index.core.schema import ImageDocument
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.core.response.notebook_utils import display_source_node
from llama_index.core.indices.multi_modal.base import MultiModalVectorStoreIndex
from llama_index.embeddings.clip import ClipEmbedding
from llama_index.multi_modal_llms.ollama import OllamaMultiModal
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

import os
import io 
import json
from PIL import Image 
import matplotlib.pyplot as plt

##设置embeding 模型和llva模型
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
mm_model = OllamaMultiModal(model="llava")

        解析一下原始的json文档,由于是图像问答(多模态),图片的text和对应的图像路径会储存在json中。

import json
with open('E:/RAG_LLM/metadata.json') as f:
	doc = json.load(f)
      
document = []
for obj in doc:
    caption = obj.get("Caption")
    captions = str(caption).replace("'", "").replace("[", "").replace("]", "")
    obj['City'] = str(obj.get("Image Name")).split('_')[3].split('.')[0]
    image_path = obj.get("Relative Path")
    image_paths = str(image_path)

    obj.pop("Caption", None)
    obj.pop("Relative Path", None)
    
    document.append([image_paths, captions, obj])
document[0]

# print--> 
['./test_img\\1.274615_103.797243_50f561a0fdc9f065f0005614_Singapore.JPG',
 'a car driving down a street next to a large bridge over a highway with a lot of trees',
 {'Image Name': '1.274615_103.797243_50f561a0fdc9f065f0005614_Singapore.JPG',
  'livelier': '4.514021433',
  'more beautiful': '5.15325436',
  'more boring': '4.950422095',
  'more depressing': '6.346440519',
  'safer': '2.17046243',
  'wealthier': '4.583960576',
  'City': 'Singapore'}]

        好的,我们现在将每个json储存在node里面,接着创建多模态的索引。

test_image_nodess =[ImageNode(image_path=p, text=t,metadata=k) for p,t,k in document]
test_image_nodess[0]

##MultiModalVectorStoreIndex可以储存多模态vector
multi_index = MultiModalVectorStoreIndex(test_image_nodess, show_progress=True)

        接着我们需要创建一个检索器。llamaindex提供了多种检索方式,在多模态vectorstore的基础上,我们可以直接使用多模态进行检索(图像或文本)、图像到图像检索、以及文本到图像检索。

##使用.as_retriever创建一个检索器
urban_retrieve = multi_index.as_retriever(similarity_top_k=3, image_similarity_top_k=3)
query_str = 'a building on the side of street in Singapore?'
img_path = '20.672456_-103.411312_514135aafdc9f04926004ab6_Guadalajara.JPG'

img, txt, score, metadata = retrieve_display(urban_retrieve.retrieve(query_str))  ##多模态检索

# img, txt, score, metadata = retrieve_display(urban_retrieve.image_to_image_retrieve(img_path))  ##图像到图像检索

# img, txt, score, metadata = retrieve_display(urban_retrieve.text_to_image_retrieve(img_path))  ##文本到图像检索

##将检索完成的图像/本文/元数据储存在这里
image_documents = [ImageDocument(image_path=img_path)]
for res_img in img:
    image_documents.append(ImageDocument(image_path=res_img))
context_str = "".join(txt)
metadata_str = metadata

##可视化一下
print(score)
plot_images(img)  ## visualization

        最后使用多模态的llm生成结果或者进一步问答。

##使用prompt,融入检索信息进行生成回答
## LLM templet
qa_tmpl_str = (
    "Given the provided information, including retrieved contents and metadata, \
     accurately and precisely answer the query without any additional prior knowledge.\n"
    "Please ensure honesty and responsibility, refraining from any racist or sexist remarks.\n"
    "---------------------\n"
    "Context: {context_str}\n"     ## 将上下文信息放进去
    "Metadata: {metadata_str} \n"  ## 将原始的meta信息放进去
    "---------------------\n"
    "Query: {query_str}\n"
    "Answer: "
)
query_str = 'a building on the side of street in Singapore?'

## use'.complete' invoke LLM
response = mm_model.complete(
    prompt=qa_tmpl_str.format(
        context_str=context_str,
        metadata_str=metadata_str,
        query_str=query_str, 
        ),
    image_documents=image_documents,
    )

        当然你还可以评估RAG的性能,这里暂时不讲,建议参考一下博文:https://itnext.io/visualize-your-rag-data-eda-for-retrieval-augmented-generation-0701ee98768ficon-default.png?t=N7T8https://itnext.io/visualize-your-rag-data-eda-for-retrieval-augmented-generation-0701ee98768fhttps://towardsdatascience.com/visualize-your-rag-data-evaluate-your-retrieval-augmented-generation-system-with-ragas-fc2486308557icon-default.png?t=N7T8https://towardsdatascience.com/visualize-your-rag-data-evaluate-your-retrieval-augmented-generation-system-with-ragas-fc2486308557

本文参考以下内容:

【1】https://docs.llamaindex.ai/en/stable/getting_started/concepts/

【2】https://docs.llamaindex.ai/en/stable/examples/multi_modal/image_to_image_retrieval/

【3】https://arxiv.org/abs/2312.10997(入门必看的论文)

<think>好的,用户现在想了解如何从零开始编写多模态大模型的代码实现教程。我需要结合之前的引用内容,特别是引用[2]、[3]、[4]中的信息来构建回答。首先,用户可能对多模态大模型的基本结构不太清楚,所以需要先解释核心概念,比如特征对齐、跨模态交互等。然后,根据引用[2]提到的从零构建LLM,可能需要分步骤讲解,比如数据准备、模型架构、预训练和微调等阶段。 接下来,引用[3]提到了LLaVA的训练阶段,包括特征对齐和视觉指令微调,这可以作为代码实现的具体步骤。例如,特征对齐阶段需要训练MLP连接器,这可能涉及到将图像和文本特征映射到同一空间。需要展示如何用PyTorch实现这样的连接层,并给出示例代码。 另外,引用[4]中的RAG+多模态模型实战可能涉及到API密钥的设置,但用户的问题更偏向于代码实现,所以可能需要忽略这部分,专注于模型架构和训练流程。同时,要确保代码示例符合系统级指令,比如数学公式用$...$格式,独立公式用$$...$$,并检查LaTeX语法是否正确。 用户还可能关心如何整合不同模态的数据,比如文本和图像的处理方法。需要解释如何处理图像特征(如使用ResNet提取),文本特征(如使用BERT编码),然后通过连接层进行对齐。此外,训练策略如两阶段训练(预训练和微调)也是重点,需要详细说明每个阶段的数据集和损失函数。 可能还需要考虑多模态交互机制,比如交叉注意力或融合层,这部分可以引用Transformer的结构,如引用[2]中的Self-Attention实现,展示如何将不同模态的特征进行交互。代码示例应包括定义模型类、前向传播方法,以及训练循环的基本结构。 最后,生成相关问题时要围绕用户可能的后续疑问,比如优化方法、部署应用、数据处理技巧等,确保问题覆盖全面,帮助用户进一步深入学习。同时,确保回答结构清晰,分步骤讲解,代码正确,符合用户的需求。</think>### 从零构建多模态大模型的代码实现教程 #### 1. 核心架构设计 多模态大模型的核心是**跨模态特征对齐与融合**。典型结构包含: - **图像编码器**:使用预训练的ResNet/ViT提取视觉特征 - **文本编码器**:使用BERT/GPT处理文本特征 - **跨模态交互层**:通过注意力机制实现特征融合 - **任务适配层**:输出预测结果 数学表达式描述特征对齐过程: $$ h_v = W_v \cdot \text{ResNet}(I),\quad h_t = W_t \cdot \text{BERT}(T) $$ 其中$W_v, W_t$是可训练的映射矩阵[^3] #### 2. 代码实现步骤(基于PyTorch) **步骤1:定义跨模态连接层** ```python import torch import torch.nn as nn class MultimodalProjector(nn.Module): def __init__(self, vis_dim=2048, txt_dim=768, hidden_dim=512): super().__init__() self.image_proj = nn.Sequential( nn.Linear(vis_dim, hidden_dim), nn.GELU(), nn.LayerNorm(hidden_dim) ) self.text_proj = nn.Sequential( nn.Linear(txt_dim, hidden_dim), nn.GELU(), nn.LayerNorm(hidden_dim) ) def forward(self, image_features, text_features): return self.image_proj(image_features), self.text_proj(text_features) ``` **步骤2:构建跨模态注意力层** ```python class CrossAttention(nn.Module): def __init__(self, dim=512, heads=8): super().__init__() self.mha = nn.MultiheadAttention(dim, heads) self.norm = nn.LayerNorm(dim) def forward(self, query, key_value): attn_out, _ = self.mha(query, key_value, key_value) return self.norm(query + attn_out) ``` **步骤3:整合完整模型** ```python class MultimodalModel(nn.Module): def __init__(self): super().__init__() self.vision_encoder = torch.hub.load('pytorch/vision', 'resnet50', pretrained=True) self.text_encoder = torch.hub.load('huggingface/pytorch-transformers', 'model', 'bert-base-uncased') self.projector = MultimodalProjector() self.cross_attn = CrossAttention() self.classifier = nn.Linear(512, num_classes) def forward(self, images, texts): # 特征提取 vis_features = self.vision_encoder(images) txt_features = self.text_encoder(texts).last_hidden_state.mean(1) # 特征对齐 proj_vis, proj_txt = self.projector(vis_features, txt_features) # 跨模态交互 fused_features = self.cross_attn(proj_vis.unsqueeze(0), proj_txt.unsqueeze(0)) # 任务预测 return self.classifier(fused_features.squeeze(0)) ``` #### 3. 训练策略实现 参考LLaVA的两阶段训练法[^3]: ```python # 第一阶段:特征对齐预训练 def pretrain_phase(): optimizer = torch.optim.AdamW(model.projector.parameters(), lr=1e-4) loss_fn = nn.CosineEmbeddingLoss() for batch in pretrain_loader: images, texts = batch vis_feat = model.vision_encoder(images) txt_feat = model.text_encoder(texts) proj_vis, proj_txt = model.projector(vis_feat, txt_feat) loss = loss_fn(proj_vis, proj_txt, torch.ones(batch_size)) optimizer.zero_grad() loss.backward() optimizer.step() # 第二阶段:指令微调 def finetune_phase(): optimizer = torch.optim.Adam(model.parameters(), lr=2e-5) loss_fn = nn.CrossEntropyLoss() for batch in finetune_loader: images, texts, labels = batch outputs = model(images, texts) loss = loss_fn(outputs, labels) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() ``` #### 4. 关键数学原理 跨模态注意力机制的核心公式: $$ \text{Attention}(Q,K,V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V $$ 其中$Q$来自一个模态,$K,V$来自另一模态,$d_k$是维度缩放因子[^2]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值