基于 RAG 的生成式 AI——用于无人机技术的多模态模块化RAG

0 阅读31分钟

我们将在本章中通过模块化RAG将生成式AI提升到一个新的水平。我们将构建一个系统,通过不同的组件或模块来处理不同类型的数据和任务。例如,一个模块处理使用LLMs的文本信息,这也是我们在前几章中一直在做的;而另一个模块则管理图像数据,识别和标注图像中的对象。想象一下,将这项技术应用于无人机上。如今,无人机已在多个行业中变得不可或缺,提供了更先进的航拍、农业监测以及高效的搜救操作。它们甚至可以使用先进的计算机视觉技术和算法来分析图像,识别行人、汽车、卡车等对象。然后,我们可以激活LLM代理来检索、增强并回答用户的问题。

在本章中,我们将构建一个多模态模块化RAG程序,通过来自多个源的文本和图像数据来回答关于无人机技术的查询。我们将首先定义模块化RAG、多模态数据、多源检索、模块化生成以及增强输出的主要方面。接着,我们将构建一个多模态模块化RAG驱动的生成式AI系统,利用LlamaIndex、Deep Lake和OpenAI的技术,将其应用于无人机技术领域。

我们的系统将使用两个数据集:第一个包含我们在上一章中构建的关于无人机的文本信息,第二个包含来自Activeloop的无人机图像和标签。我们将使用Deep Lake处理多模态数据,LlamaIndex进行索引和检索,并通过OpenAI的LLMs进行生成查询。我们还将添加带有文本和图像的多模态增强输出。最后,我们将为文本响应构建性能指标,并引入基于GPT-4o(OpenAI强大的多模态LLM,MMLLM)的图像识别指标。通过本章的学习,您将掌握如何利用创新的多模态和多源功能构建多模态模块化RAG工作流程。

本章内容包括以下主题:

  • 多模态模块化RAG
  • 多源检索
  • 基于OpenAI LLM的多模态多源检索
  • Deep Lake多模态数据集
  • 基于图像元数据的检索
  • 增强的多模态输出

让我们从定义多模态模块化RAG开始。

什么是多模态模块化RAG?

多模态数据结合了文本、图像、音频和视频等不同形式的信息,以丰富数据分析和解读。同时,当系统使用不同模块来处理不同数据类型和任务时,它便成为模块化的RAG系统。每个模块都是专业化的,例如,一个模块专注于文本,另一个模块专注于图像,从而展示出复杂的集成能力,通过检索到的多模态数据来增强响应生成。

本章的程序还将实现多源特性,使用我们构建的两个数据集。我们将使用上一章中构建的关于无人机技术的LLM数据集,同时也将使用Deep Lake多模态VisDrone数据集,该数据集包含数千张由无人机拍摄并标注的图像。

我们选择无人机作为示例,因为它们在各个行业中变得至关重要,提供了增强的航拍能力、高效的农业监测和有效的搜救操作。无人机还便于野生动物追踪、简化商业交付并提升基础设施检查的安全性。此外,无人机支持环境研究、交通管理和消防,它们能够增强执法部门的监控能力,通过提升可达性、安全性和成本效益,革新了多个领域。

图4.1展示了我们将在本章中实现的工作流程,基于第一章《为什么需要检索增强生成?》中的生成式RAG生态系统(见图1.3)。在前几章中,我们添加了嵌入和索引功能,但本章将重点放在检索和生成上。我们构建的系统模糊了检索和生成之间的界限,因为生成器在本章的代码中被密集地用于检索(无缝的评分和排序)以及生成。

image.png

本章的目标是构建一个以无人机技术为重点的模块化RAG问答系统。你可以借助前几章的笔记本中实现的功能,例如第2章中的Deep Lake向量存储功能(“基于Deep Lake和OpenAI的RAG嵌入向量存储”),以及第3章中与LlamaIndex一起实现的索引功能(“使用LlamaIndex、Deep Lake和OpenAI构建基于索引的RAG”)。如果有需要,可以花点时间回顾前几章的内容。

接下来,我们将浏览本章中的多模态、多数据源、模块化RAG生态系统,如图4.1所示。本章中使用的标题和小节均以斜体表示。此外,每个阶段前面都标明了其在图4.1中的位置。

  • (D4) 加载在第3章创建的包含无人机文本数据的LLM数据集。
  • (D4) 使用LlamaIndex的VectorStoreIndex创建向量存储索引,初始化LLM查询引擎,并将创建的索引设置为查询引擎,以便同时作为OpenAI GPT模型的检索器和生成器。
  • (G1) 定义用于多模态模块化RAG的用户输入,以便同时用于LLM查询引擎(针对文本数据集)和多模态查询引擎(针对VisDrone数据集)。

加载文本数据集、创建查询引擎并将用户输入定义为文本和多模态数据集的基准查询后,接下来为第2章创建的文本数据集生成响应。

在查询文本数据集时,(G1)、(G2) 和 (G4) 在同一无缝的LlamaIndex流程中重叠,该流程负责数据检索和内容生成。响应在会话期间保存为llm_response。

现在将把多模态VisDrone数据集加载到内存并进行查询:

  • (D4) 多模态流程开始,首先加载并可视化多模态数据集。接着,程序将浏览多模态数据集结构,选择一个图像并添加边界框。

然后对VisDrone多模态数据集应用与文本数据集相同的流程:

  • (D4) 通过使用LlamaIndex创建基于VisDrone数据的向量存储索引,构建一个多模态查询引擎,并将创建的索引设置为查询引擎,以便同时作为OpenAI GPT模型的检索器和生成器。
  • (G1) 多模态搜索引擎的用户输入与多模态模块化RAG的用户输入相同,因为它同时用于LLM查询引擎(针对文本数据集)和多模态查询引擎(针对VisDrone数据集)。

此时,多模态VisDrone数据集已加载并建立索引,查询引擎已准备就绪。用户输入的目的是让LlamaIndex查询引擎使用LLM(在本例中是OpenAI模型)从VisDrone中检索相关文档。然后,检索功能将追踪多模态数据集中响应的来源,找到源节点的图像。事实上,我们在通过其文本响应来获取图像:

  • (G1)、(G2) 和 (G4) 在对VisDrone多模态数据集运行查询时无缝地与LlamaIndex查询重叠。
  • 处理(G4)响应以查找源节点并检索其图像,引导我们回到(D4)进行图像检索。这会选择并处理源节点的图像。

此时,我们获得了文本和图像响应。然后可以构建摘要并应用准确性性能指标,显示构建程序时每个阶段的耗时:

  • (G4) 以多模态模块化摘要的形式呈现包含LLM响应和多模态响应图像的合并输出。
  • (E) 最后,创建LLM性能指标和多模态性能指标,然后将它们相加,形成多模态模块化RAG性能指标。

通过本章的多模态模块化RAG系统,我们可以得出两个结论:

  1. 本章中构建的系统是实际项目中基于RAG的生成式AI的多种设计方式之一。每个项目都有其特定需求和架构。
  2. 从生成式AI到复杂的RAG驱动生成式AI的快速演变需要无缝集成的跨平台组件的发展,如本章中所展示的LlamaIndex、Deep Lake和OpenAI。这些平台还与其他框架(如Pinecone和LangChain)集成,我们将在第6章“利用Pinecone扩展RAG银行客户数据”中讨论。

现在,让我们深入Python,构建多模态模块化RAG程序。

构建适用于无人机技术的多模态模块化RAG程序

在接下来的小节中,我们将从零开始逐步在Python中构建一个多模态模块化的RAG驱动生成系统,实现以下功能:

  • 使用LlamaIndex管理的OpenAI LLM处理并理解有关无人机的文本
  • 包含无人机拍摄图像及其标签的Deep Lake多模态数据集
  • 显示图像和使用边界框识别图像内对象的功能
  • 一个能够使用文本和图像回答无人机技术相关问题的系统
  • 旨在衡量多模态模块化响应准确性的性能指标,包括GPT-4o的图像分析

此外,请确保您已经在第2章中创建了LLM数据集,因为我们将在本节加载它。不过,即使不运行笔记本,您仍然可以阅读本章内容,因为该章节已经包含了代码和解释。

现在,让我们开始动手!在GitHub仓库中打开本章的Multimodal_Modular_RAG_Drones.ipynb笔记本,网址为:github.com/Denis2054/R…。所需的安装包与上一章“安装环境”部分中列出的相同。以下每个小节将逐步引导您构建多模态模块化笔记本,从LLM模块开始。让我们逐步深入每个笔记本部分。

加载 LLM 数据集

我们将加载在第 3 章创建的无人机数据集。请确保插入您的数据集路径:

import deeplake
dataset_path_llm = "hub://denis76/drone_v2"
ds_llm = deeplake.load(dataset_path_llm)

输出将确认数据集已加载,并显示您的数据集链接:

此数据集可在 Jupyter Notebook 中通过 `ds.visualize()` 或在 https://app.activeloop.ai/denis76/drone_v2 上可视化
hub://denis76/drone_v2 已成功加载。

程序现在创建一个字典来存储数据,并将其加载到 pandas DataFrame 中以便可视化:

import json
import pandas as pd
import numpy as np

# 创建一个字典来存储数据
data_llm = {}
# 遍历数据集中的张量
for tensor_name in ds_llm.tensors:
    tensor_data = ds_llm[tensor_name].numpy()
    # 检查张量是否为多维的
    if tensor_data.ndim > 1:
        # 展平多维张量
        data_llm[tensor_name] = [np.array(e).flatten().tolist() for e in tensor_data]
    else:
        # 将 1D 张量直接转换为列表并解码文本
        if tensor_name == "text":
            data_llm[tensor_name] = [t.tobytes().decode('utf-8') if t else "" for t in tensor_data]
        else:
            data_llm[tensor_name] = tensor_data.tolist()

# 从字典创建一个 Pandas DataFrame
df_llm = pd.DataFrame(data_llm)
df_llm

输出将显示文本数据集的结构:embedding(向量)、id(唯一字符串标识符)、metadata(此处为数据源)、以及包含内容的 text 字段。

image.png

现在我们将初始化 LLM 查询引擎。

初始化 LLM 查询引擎

如在第 3 章《使用 LlamaIndex、Deep Lake 和 OpenAI 构建基于索引的 RAG》中所示,我们将从数据集 (ds) 的无人机文档集合 (documents_llm) 中初始化一个向量存储索引。GPTVectorStoreIndex.from_documents() 方法创建一个索引,通过基于向量相似度的检索来加快文档的检索速度:

from llama_index.core import VectorStoreIndex
vector_store_index_llm = VectorStoreIndex.from_documents(documents_llm)

as_query_engine() 方法将该索引配置为具有特定参数的查询引擎,与第 3 章中相似,用于控制相似度和检索深度,从而让系统通过找到最相关的文档来回答查询:

vector_query_engine_llm = vector_store_index_llm.as_query_engine(similarity_top_k=2, temperature=0.1, num_output=1024)

现在,程序引入用户输入。

多模态模块化 RAG 的用户输入

在模块化 RAG 系统的上下文中定义用户输入的目的是构建一个查询,以便有效利用文本和图像处理能力。这使系统能够通过利用多个信息源(如不同数据集中的文本和图像数据)生成全面且准确的响应:

user_input="How do drones identify a truck?"

在此场景中,用户输入作为基准,或用于评估系统处理能力的标准查询。这将成为系统如何利用其可用资源(例如来自各种数据集的文本和图像数据)来处理和响应查询的参考点。在此示例中,基准是经验性的,将用于根据该参考点评估系统的表现。

查询文本数据集

我们将按第 3 章中所示运行向量查询引擎请求:

import time
import textwrap

# Start the timer
start_time = time.time()
llm_response = vector_query_engine_llm.query(user_input)
# Stop the timer
end_time = time.time()

# Calculate and print the execution time
elapsed_time = end_time - start_time
print(f"Query execution time: {elapsed_time:.4f} seconds")
print(textwrap.fill(str(llm_response), 100))

执行时间令人满意:

Query execution time: 1.5489 seconds

输出内容也令人满意:

Drones can identify a truck using visual detection and tracking methods, which may involve deep neural networks for performance benchmarking.

程序现在加载多模态无人机数据集。

加载并可视化多模态数据集

我们将使用 Deep Lake 上提供的现有 VisDrone 公共数据集:datasets.activeloop.ai/docs/ml/dat…。我们不会创建向量存储,而是将现有数据集直接加载到内存中:

import deeplake
dataset_path = 'hub://activeloop/visdrone-det-train'
ds = deeplake.load(dataset_path)  # 返回 Deep Lake 数据集,但不会在本地下载数据

输出将显示在线数据集的链接,您可以使用 Deep Lake 提供的工具,通过 SQL 或自然语言处理命令来探索数据集:

Opening dataset in read-only mode as you don't have write permissions.
This dataset can be visualized in Jupyter Notebook by ds.visualize() or at https://app.activeloop.ai/activeloop/visdrone-det-train
hub://activeloop/visdrone-det-train loaded successfully.

让我们显示数据集的摘要,以便通过代码进一步探索:

ds.summary()

输出提供了有关数据集结构的有用信息:

Dataset(path='hub://activeloop/visdrone-det-train', read_only=True, tensors=['boxes', 'images', 'labels'])
tensor    htype            shape              dtype     compression
------    -----            -----              -----     -----------
boxes     bbox         (6471, 1:914, 4)       float32          None
images    image        (6471, 360:1500,                            
                        480:2000, 3)          uint8            jpeg
labels    class_label  (6471, 1:914)          uint32           None

该结构包含图像、图像中对象的边界框 (boxes) 以及描述图像和边界框的标签 (labels)。让我们通过代码可视化数据集:

ds.visualize()

输出将展示带有边界框的图像。

image.png

现在,让我们进一步操作,将数据集内容显示在一个 pandas DataFrame 中,以便查看图像的样子:

import pandas as pd
# 创建一个具有定义结构的空 DataFrame
df = pd.DataFrame(columns=['image', 'boxes', 'labels'])
# 使用 enumerate 遍历样本
for i, sample in enumerate(ds):
    # 图像数据(可以选择存储路径或压缩表示)
    # df.loc[i, 'image'] = sample.images.path  # 存储图像路径
    df.loc[i, 'image'] = sample.images.tobytes()  # 存储压缩图像数据
    # 边界框数据(作为列表的列表)
    boxes_list = sample.boxes.numpy(aslist=True)
    df.loc[i, 'boxes'] = [box.tolist() for box in boxes_list]
    # 标签数据(作为列表)
    label_data = sample.labels.data()
    df.loc[i, 'labels'] = label_data['text']
df

图 4.4 中的输出展示了数据集的内容:

image.png

数据集中有 6,471 行图像和 3 列:

  • image 列:包含图像。数据集中的图像格式通过字节序列 b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00...' 确定为 JPEG,特别是字节序列 b'\xff\xd8\xff\xe0' 表示 JPEG 图像文件的开头。
  • boxes 列:包含图像中边界框的坐标和尺寸,通常格式为 [x, y, width, height]
  • labels 列:包含 boxes 列中每个边界框的标签。

我们可以显示图像标签的列表:

labels_list = ds.labels.info['class_names']
labels_list

输出提供了标签列表,定义了数据集的范围:

['ignored regions',  'pedestrian',  'people',  'bicycle',  'car',  'van',  'truck',  'tricycle',  'awning-tricycle',  'bus',  'motor',  'others']

至此,我们已经成功加载了数据集,接下来将探索多模态数据集的结构。

导航多模态数据集结构

在本节中,我们将选择一张图像并使用数据集的 image 列进行显示。然后,我们将为该图像添加所选标签的边界框。程序首先选择一个图像。

选择和显示图像

我们将选择数据集中的第一张图像:

# 选择图像
ind = 0
image = ds.images[ind].numpy()  # 获取第一张图像并返回为 numpy 数组

现在,让我们在没有边界框的情况下显示图像:

import deeplake
from IPython.display import display
from PIL import Image
import cv2  # 导入 OpenCV

image = ds.images[0].numpy()
# 从 BGR 转换为 RGB(如有必要)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 创建 PIL 图像并显示
img = Image.fromarray(image_rgb)
display(img)

显示的图像中包含卡车、行人和其他类型的物体。

image.png

现在图像已经显示,程序将添加边界框。

添加边界框并保存图像

我们已经显示了第一张图像,接下来程序会获取所选图像的所有标签:

labels = ds.labels[ind].data()  # 获取所选图像的标签
print(labels)

输出显示了 value(包含标签的数值索引)和 text(包含相应的文本标签):

{'value': array([1, 1, 7, 1, 1, 1, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 6, 6, 6, 6,
       1, 1, 1, 1, 1, 1, 6, 6, 3, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 6, 6, 6], dtype=uint32), 'text': ['pedestrian', 'pedestrian', 'tricycle', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'truck', 'truck', 'truck', 'truck', 'truck', 'truck', 'truck', 'truck', 'truck', 'truck', 'pedestrian', 'truck', 'truck', 'truck', 'truck', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'truck', 'truck', 'bicycle', 'truck', 'truck', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'pedestrian', 'truck', 'truck', 'truck']}

我们可以将数值和相应的文本标签以两列方式显示:

values = labels['value']
text_labels = labels['text']
# 确定格式化时最大文本标签的长度
max_text_length = max(len(label) for label in text_labels)
# 打印标题
print(f"{'Index':<10}{'Label':<{max_text_length + 2}}")
print("-" * (10 + max_text_length + 2))  # 添加分隔线
# 打印索引和标签(两列)
for index, label in zip(values, text_labels):
    print(f"{index:<10}{label:<{max_text_length + 2}}")

输出为我们提供了标签内容的清晰表示:

Index     Label     
----------------------
1         pedestrian
1         pedestrian
7         tricycle  
1         pedestrian
1         pedestrian
1         pedestrian
1         pedestrian
6         truck     
6         truck     
...

我们可以将图像的类名(标签的普通文本)进行分组:

ds.labels[ind].info['class_names']  # 所选图像的类名

现在我们可以分组并显示描述图像的所有标签:

ds.labels[ind].info['class_names']  # 所选图像的类名

我们可以看到图像包含的所有类:

['ignored regions', 'pedestrian', 'people', 'bicycle', 'car', 'van', 'truck', 'tricycle', 'awning-tricycle', 'bus', 'motor', 'others']

有时标签类的数量会超过人眼在图像中能看到的数量。

我们首先创建一个函数,用于添加边界框,显示它们,并保存图像:

def display_image_with_bboxes(image_data, bboxes, labels, label_name, ind=0):
    # 显示带有特定标签边界框的图像
    image_bytes = io.BytesIO(image_data)
    img = Image.open(image_bytes)
    # 提取所选图像的类名
    class_names = ds.labels[ind].info['class_names']
    # 筛选特定标签(如果类名缺失则显示所有)
    if class_names is not None:
        try:
            label_index = class_names.index(label_name)
            relevant_indices = np.where(labels == label_index)[0]
        except ValueError:
            print(f"Warning: Label '{label_name}' not found. Displaying all boxes.")
            relevant_indices = range(len(labels))
    else:
        relevant_indices = []  # 未找到标签,不显示边框
    # 绘制边界框
    draw = ImageDraw.Draw(img)
    for idx, box in enumerate(bboxes):  # 遍历边界框
        if idx in relevant_indices:   # 检查该边界框是否相关
            x1, y1, w, h = box
            x2, y2 = x1 + w, y1 + h
            draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
            draw.text((x1, y1), label_name, fill="red")
    # 保存图像
    save_path = "boxed_image.jpg"
    img.save(save_path)
    display(img)

我们可以为特定的标签添加边界框。在这个例子中,我们选择了“卡车”标签:

import io
from PIL import ImageDraw

# 获取所选图像的标签和图像数据
labels = ds.labels[ind].data()['value']
image_data = ds.images[ind].tobytes()
bboxes = ds.boxes[ind].numpy()
ibox = "truck"  # 图像中的类别

# 显示包含所选标签边界框的图像
display_image_with_bboxes(image_data, bboxes, labels, label_name=ibox)

现在显示的图像包含卡车的边界框:

image.png

构建一个多模态查询引擎

在本节中,我们将对 VisDrone 数据集进行查询,并检索符合我们在本笔记本的“多模态模块化 RAG 的用户输入”部分输入的用户需求的图像。为实现这一目标,我们将:

  1. 为包含 VisDrone 数据集的图像、边框数据和标签的 df DataFrame 中的每一行创建一个向量索引。
  2. 创建一个查询引擎,用于查询数据集的文本数据,检索相关图像信息,并提供文本响应。
  3. 解析响应节点,找到与用户输入相关的关键词。
  4. 解析响应节点,找到源图像。
  5. 为源图像添加边界框。
  6. 保存图像。

创建向量索引和查询引擎

代码首先创建一个文档,将其处理以创建多模态无人机数据集的向量存储索引。在 GitHub 中的笔记本的“加载和可视化多模态数据集”部分中创建的 df DataFrame 没有唯一的索引或嵌入。我们将使用 LlamaIndex 在内存中创建它们。

程序首先为 DataFrame 分配一个唯一的 ID:

# DataFrame 的名称是 'df'
df['doc_id'] = df.index.astype(str)  # 从行索引创建唯一 ID

这行代码向 df DataFrame 添加了一个名为 doc_id 的新列。它通过将 DataFrame 的行索引转换为字符串来为每一行分配唯一标识符。接着初始化了一个名为 documents 的空列表,我们将用它来创建向量索引:

# 创建文档(提取每个图像标签的相关文本)
documents = []

现在,iterrows() 方法遍历 DataFrame 的每一行,生成索引和行对的序列:

for _, row in df.iterrows():
    text_labels = row['labels']  # 每个标签现在都是字符串
    text = " ".join(text_labels)  # 将文本标签连接为一个字符串
    document = Document(text=text, doc_id=row['doc_id'])
    documents.append(document)

documents 被追加了数据集中的所有记录,并创建了一个 DataFrame:

# DataFrame 的名称是 'df'
df['doc_id'] = df.index.astype(str)  # 从行索引创建唯一 ID
# 创建文档(提取每个图像标签的相关文本)
documents = []
for _, row in df.iterrows():
    text_labels = row['labels']  # 每个标签现在都是字符串
    text = " ".join(text_labels)  # 将文本标签连接为一个字符串
    document = Document(text=text, doc_id=row['doc_id'])
    documents.append(document)

现在文档已经准备好用 GPTVectorStoreIndex 来索引:

from llama_index.core import GPTVectorStoreIndex
vector_store_index = GPTVectorStoreIndex.from_documents(documents)

数据集接下来被无缝地配备了可以在索引字典中可视化的索引:

vector_store_index.index_struct

输出显示数据集中已经添加了一个索引:

IndexDict(index_id='4ec313b4-9a1a-41df-a3d8-a4fe5ff6022c', summary=None, nodes_dict={'5e547c1d-0d65-4de6-b33e-a101665751e6': '5e547c1d-0d65-4de6-b33e-a101665751e6', '05f73182-37ed-4567-a855-4ff9e8ae5b8c': '05f73182-37ed-4567-a855-4ff9e8ae5b8c'

现在我们可以在多模态数据集上运行查询。

在 VisDrone 多模态数据集上运行查询

现在我们将 vector_store_index 设为查询引擎,正如我们在第 3 章的“向量存储索引查询引擎”部分中所做的那样:

vector_query_engine = vector_store_index.as_query_engine(similarity_top_k=1, temperature=0.1, num_output=1024)

我们也可以在无人机图像数据集上运行查询,就像我们在第 3 章对 LLM 数据集所做的那样:

import time
start_time = time.time()
response = vector_query_engine.query(user_input)
# 停止计时
end_time = time.time()
# 计算并打印执行时间
elapsed_time = end_time - start_time
print(f"Query execution time: {elapsed_time:.4f} seconds")

执行时间令人满意:

Query execution time: 1.8461 seconds

现在我们来检查文本响应:

print(textwrap.fill(str(response), 100))

我们可以看到输出逻辑清晰,因此是令人满意的。 无人机使用摄像头、激光雷达和 GPS 等各种传感器来识别和跟踪卡车等物体。

处理响应

现在我们将解析响应中的节点,找到响应中的唯一词,并为此笔记本选择一个:

from itertools import groupby
def get_unique_words(text):
    text = text.lower().strip()
    words = text.split()
    unique_words = [word for word, _ in groupby(sorted(words))]
    return unique_words

for node in response.source_nodes:
    print(node.node_id)
    # 获取节点文本中的唯一词:
    node_text = node.get_text()
    unique_words = get_unique_words(node_text)
    print("Unique Words in Node Text:", unique_words)

我们找到了一个唯一的词(“truck”)及其唯一索引,这将直接引导我们找到生成响应的节点的源图像:

1af106df-c5a6-4f48-ac17-f953dffd2402
Unique Words in Node Text: ['truck']

我们可以选择更多的词,并根据每个项目的具体要求以多种不同的方式设计此函数。

现在我们将通过遍历源节点来搜索图像,就像我们在上一章的“查询响应和源”部分中对 LLM 数据集所做的那样。多模态向量存储和查询框架非常灵活。一旦我们学会了如何在 LLM 和多模态数据集上进行检索,我们就准备好应对任何挑战!

现在让我们选择并处理与图像相关的信息。

选择和处理源节点的图像

在运行图像检索和显示函数之前,先删除我们在本笔记本的“添加边界框并保存图像”部分显示的图像,以确保我们正在处理新图像:

# 删除之前保存的任何图像
!rm /content/boxed_image.jpg

现在我们可以搜索源图像,调用边界框,显示并保存我们之前定义的函数:

display_image_with_bboxes(image_data, bboxes, labels, label_name=ibox)

程序现在通过关键字“truck”搜索源节点,应用边界框,显示并保存图像:

import io
from PIL import Image

def process_and_display(response, df, ds, unique_words):
    """处理节点,查找数据集中的相应图像,并用边界框显示它们。
    
    Args:
        response: 包含源节点的响应对象。
        df: 包含 doc_id 信息的 DataFrame。
        ds: 包含图像、标签和边框的数据集。
        unique_words: 用于筛选的唯一词列表。
    """
    ...
            if i == row_index:
                image_bytes = io.BytesIO(sample.images.tobytes())
                img = Image.open(image_bytes)
                labels = ds.labels[i].data()['value']
                image_data = ds.images[i].tobytes()
                bboxes = ds.boxes[i].numpy()
                ibox = unique_words[0]  # 图像中的类别
                display_image_with_bboxes(image_data, bboxes, labels, label_name=ibox)

# 假设你已经准备好了 'response'、'df'、'ds' 和 'unique_words' 对象:
process_and_display(response, df, ds, unique_words)

输出令人满意。

image.png

多模态模块化总结

我们已经逐步构建了一个多模态模块化程序,现在可以将其汇总。我们将创建一个函数来显示响应源图像,显示用户输入、打印用户输入和 LLM 输出,并显示图像。

首先,我们创建一个函数来显示由多模态检索引擎保存的源图像:

# 1. 用户输入=user_input
print(user_input)
# 2. LLM 响应
print(textwrap.fill(str(llm_response), 100))
# 3. 多模态响应
image_path = "/content/boxed_image.jpg"
display_source_image(image_path)

然后,我们可以显示用户输入、LLM 响应和多模态响应。输出首先显示文本响应(用户输入和 LLM 响应):

How do drones identify a truck?
Drones can identify a truck using visual detection and tracking methods, which may involve deep neural networks for performance benchmarking.

接着显示包含卡车边界框的图像:

image.png

通过向经典的 LLM 响应中添加图像,我们增强了输出。多模态 RAG 输出增强将通过为输入和输出添加信息来丰富生成式 AI。然而,与所有 AI 程序一样,设计性能指标需要有效的图像识别功能。

性能指标

衡量多模态模块化 RAG 的性能需要两种类型的测量:文本和图像。衡量文本比较简单,但衡量图像则是一个挑战。分析多模态响应的图像与处理文本响应有很大不同。我们从多模态查询引擎中提取了一个关键词,然后解析响应以显示源图像。但是,我们需要构建一种创新的方法来评估响应的源图像。我们先从 LLM 的性能开始。

LLM 性能指标

LlamaIndex 通过其查询引擎无缝调用了例如 GPT-4 这样的 OpenAI 模型,并在响应中提供了文本内容。对于文本响应,我们将使用第 2 章“使用余弦相似度评估输出”部分和第 3 章“向量存储索引查询引擎”部分中相同的余弦相似度指标。

评估函数使用 sklearnsentence_transformers 来评估两段文本之间的相似性——在本例中是输入和输出:

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')

def calculate_cosine_similarity_with_embeddings(text1, text2):
    embeddings1 = model.encode(text1)
    embeddings2 = model.encode(text2)
    similarity = cosine_similarity([embeddings1], [embeddings2])
    return similarity[0][0]

现在我们可以计算基准用户输入和初始 LLM 响应之间的相似度:

llm_similarity_score = calculate_cosine_similarity_with_embeddings(user_input, str(llm_response))
print(user_input)
print(llm_response)
print(f"Cosine Similarity Score: {llm_similarity_score:.3f}")

输出显示了用户输入、文本响应,以及两段文本之间的余弦相似度:

How do drones identify a truck?
Drones can identify a truck using visual detection and tracking methods, which may involve deep neural networks for performance benchmarking.
Cosine Similarity Score: 0.691

输出令人满意。但我们现在需要设计一种方式来衡量多模态的性能。

多模态性能指标

要评估返回的图像,我们不能仅依赖数据集中的标签。对于小型数据集,我们可以手动检查图像,但当系统扩展时,自动化是必要的。在本节中,我们将使用 GPT-4o 的计算机视觉功能来分析图像,解析图像以找到我们寻找的对象,并提供该图像的描述。然后,我们将对 GPT-4o 提供的描述和它应该包含的标签应用余弦相似度。GPT-4o 是一种多模态生成式 AI 模型。

首先,我们对图像进行编码,以简化数据传输到 GPT-4o。Base64 编码将二进制数据(如图像)转换为 ASCII 字符,这些字符是标准文本字符。这种转换非常重要,因为它确保图像数据可以通过旨在平稳处理文本数据的协议(如 HTTP)进行传输。它还避免了与二进制数据传输相关的问题,例如数据损坏或解释错误。

程序使用 Python 的 base64 模块对源图像进行编码:

import base64

IMAGE_PATH = "/content/boxed_image.jpg"
# 打开图像文件并将其编码为 base64 字符串
def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")

base64_image = encode_image(IMAGE_PATH)

现在我们创建一个 OpenAI 客户端并将模型设置为 gpt-4o

from openai import OpenAI

# 设置 API 密钥
client = OpenAI(api_key=openai.api_key)
MODEL = "gpt-4o"

唯一的词是通过解析响应从多模态数据集的 LLM 查询中得到的结果:

u_word = unique_words[0]
print(u_word)

现在我们可以将图像提交给 OpenAI GPT-4o:

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": f"You are a helpful assistant that analyzes images that contain {u_word}."},
        {"role": "user", "content": [
            {"type": "text", "text": f"Analyze the following image, tell me if there is one {u_word} or more in the bounding boxes and analyze them:"},
            {"type": "image_url", "image_url": {
                "url": f"data:image/png;base64,{base64_image}"}
            }
        ]}
    ],
    temperature=0.0,
)
response_image = response.choices[0].message.content
print(response_image)

我们指示系统和用户角色分析图像,寻找我们的目标标签 u_word,在本例中为卡车。然后我们将源节点图像提交给模型。描述图像的输出是令人满意的:

The image contains two trucks within the bounding boxes. Here is the analysis of each truck:
1. **First Truck (Top Bounding Box)**:
   - The truck appears to be a flatbed truck.
   - It is loaded with various materials, possibly construction or industrial supplies.
   - The truck is parked in an area with other construction materials and equipment.
2. **Second Truck (Bottom Bounding Box)**:
   - This truck also appears to be a flatbed truck.
   - It is carrying different types of materials, similar to the first truck.
   - The truck is situated in a similar environment, surrounded by construction materials and equipment.

Both trucks are in a construction or industrial area, likely used for transporting materials and equipment.

现在我们可以将此响应提交给余弦相似度函数,首先通过添加一个 "s" 来与响应中的多个卡车对齐:

resp = u_word + "s"
multimodal_similarity_score = calculate_cosine_similarity_with_embeddings(resp, str(response_image))
print(f"Cosine Similarity Score: {multimodal_similarity_score:.3f}")

输出很好地描述了图像,但包含了许多“卡车”之外的描述,这限制了与请求输入的相似性:

Cosine Similarity Score: 0.505

人类观察者可能会认可图像和 LLM 的响应。然而,即使得分很高,问题依然存在。复杂的图像难以精确详细地分析,尽管在这方面的进展不断取得。现在让我们计算系统的整体性能。

多模态模块化 RAG 性能指标

为了获得系统的整体性能,我们将 LLM 响应和两个多模态响应的得分总和除以 2:

score = (llm_similarity_score + multimodal_similarity_score) / 2
print(f"Multimodal, Modular Score: {score:.3f}")

结果显示,尽管人类观察结果可能感到满意,但自动评估复杂图像的相关性仍然困难:

Multimodal, Modular Score: 0.598

该指标可以改进,因为人类观察者可以看到图像是相关的。这也解释了为什么顶级 AI 代理(如 ChatGPT、Gemini 和 Bing Copilot)总是包含一个包括点赞和点踩的反馈过程。

现在让我们总结本章,并准备探索如何通过人类反馈进一步改进 RAG。

总结

本章引导我们进入了多模态模块化 RAG 的世界,它使用不同模块来处理不同的数据类型(文本和图像)及任务。我们利用了 LlamaIndex、Deep Lake 和 OpenAI 的功能,这些内容在前几章中已探索过。Deep Lake VisDrone 数据集进一步介绍了无人机技术,用于分析图像和识别对象。数据集包含图像、标签和边界框信息。无人机技术的工作涉及多模态数据,促使我们开发可应用于多个领域的技能,如野生动物跟踪、简化商业交付以及更安全的基础设施检查。

我们构建了一个由多模态模块化 RAG 驱动的生成式 AI 系统。第一步是定义一个针对 LLM 和多模态查询的基线用户查询。我们首先对第 3 章中实现的 Deep Lake 文本数据集进行查询。LlamaIndex 无缝运行了查询引擎以检索、增强并生成响应。然后,我们加载了 Deep Lake VisDrone 数据集,并使用 LlamaIndex 在内存中对其进行索引,以创建索引向量搜索检索管道。通过 LlamaIndex 查询该数据集,LlamaIndex 使用了例如 GPT-4 这样的 OpenAI 模型,并解析生成的文本以找到关键词。最后,我们搜索响应的源节点,找到源图像,显示它,并将 LLM 和图像响应合并为增强的输出。我们对文本响应应用了余弦相似度。评估图像具有挑战性,因此我们首先对检索到的图像运行了 GPT-4o 图像识别,以获得可以应用余弦相似度的文本。

探索由多模态模块化 RAG 驱动的生成式 AI 使我们深入了解了 AI 的前沿技术。构建复杂系统为实际的 AI 项目做好了准备,这些项目通常需要实施多源、多模态和非结构化数据,从而导致模块化、复杂的系统。得益于对响应源的透明访问,RAG 的复杂性可以被利用、控制和改进。接下来我们将看到如何利用响应源的透明性引入人类反馈以改进 AI。下一章将带我们深入了解 AI 中的透明性和精确性。

问题

请回答以下问题,用“是”或“否”:

  1. 多模态模块化 RAG 是否处理不同类型的数据,如文本和图像?
  2. 无人机是否仅用于农业监测和航拍?
  3. 本章中使用的 Deep Lake VisDrone 数据集是否仅用于文本数据?
  4. 是否可以在无人机图像上添加边界框以识别卡车和行人等物体?
  5. 模块化系统是否同时检索文本和图像数据以获得查询响应?
  6. 为多模态 VisDrone 数据集构建向量索引是否是查询的必要条件?
  7. 检索到的图像是否在不添加任何标签或边界框的情况下进行处理?
  8. 多模态模块化 RAG 的性能指标是否仅基于文本响应?
  9. 本章描述的多模态系统是否只能处理与无人机相关的数据?
  10. 在多模态 RAG 中,评估图像是否像评估文本一样简单?

参考文献

延伸阅读