DLAI-视觉模型提示词工程笔记-全-

DLAI 视觉模型提示词工程笔记(全)

001:课程介绍 🎯

在本课程中,我们将学习视觉模型的提示工程。提示工程不仅适用于文本模型,也适用于视觉模型,包括图像分割、目标检测和图像生成模型。根据视觉模型的不同,提示可以是文本,也可以是像素坐标、边界框或分割掩码。

视觉提示概述

上一节我们介绍了课程的整体目标,本节中我们来看看视觉提示的具体应用。

在课程中,你将使用提示来引导“分割一切模型”(Segment Anything Model,简称 SAM),通过提供坐标、点或边界框来帮助模型识别物体的轮廓。例如,识别一只狗穿着的T恤。你还将应用负向提示来告诉模型在识别物体时应排除哪些区域。结合使用正向和负向提示,有助于你精确分离出感兴趣的区域或物体,比如狗穿着的T恤上的特定图案。

图像生成与迭代

但提示SAM只是一个例子。你将学习更多工具,以及用于分析(理解图像)和生成(或修改图像)的最佳提示实践。

以下是本课程将涉及的核心内容:

  • 图像生成:例如,你可以向Stable Diffusion模型提供文本提示“一条龙”来生成龙的图像。
  • 提示迭代:你可以迭代提示,例如改为“一条写实的绿龙”,以获得不同的图像。
  • 图像修复:你将引导扩散模型用生成的龙替换照片中的猫,同时保持照片其余部分完好。这被称为图像修复,即通过移除一个分割出的物体并用生成的图像替换它来编辑图片。

对于图像修复,你的提示不仅包括文本“一条写实的绿龙”,还包括龙将要替换的猫的轮廓(掩码)。你将通过图像分割获得这个轮廓。此外,你将通过提示一个目标检测模型(例如使用文本提示“穿着粉色夹克的可爱小狗”)来生成围绕该狗的边界框,这个边界框将作为SAM的输入。

模型超参数调优

你将在这个图像修复流程中,对提示和模型超参数进行迭代调优。

扩散模型的工作原理是将来自简单分布(如高斯噪声)的样本,转化为复杂的已学习分布(如图像)。以下是几个关键超参数:

  • 引导尺度:该参数决定了在反向扩散过程中,文本输入对目标分布的影响程度。公式可表示为:引导尺度越高,输出与文本提示的匹配度越高。较低的引导尺度允许模型从其学习的图像分布中自由采样,而较高的引导尺度会引导模型生成更贴近文本输入的样本。
  • 推理步数:该参数控制模型将噪声分布逐步转换回清晰样本的渐进程度。通常,更多的步数允许更详细和准确的生成,因为模型有更多机会细化数据。然而,更多的步数也意味着更长的计算时间。
  • 强度:在Stable Diffusion的背景下,该参数决定了在修复过程中初始分布的噪声程度,即用于擦除初始图像部分区域的添加噪声量。强度本质上决定了在扩散过程中保留多少初始图像。

模型个性化:DreamBooth

此外,如果你想个性化扩散模型,让它生成的不是普通的龙、猫或人,而是特定的龙、你特定的宠物猫或你最好的朋友,该怎么办?

你将使用一种由Google Research开发的微调方法——DreamBooth,来调整Stable Diffusion模型,使其将文本标签与特定对象(如你的好友)关联起来。在课程中,你将使用DreamBooth微调过程,让模型仅用六张Andrew的照片就将“AndrewNg”这个词与之关联。微调后,你可以用“一幅梵高风格的Andrew画作”这样的文本来提示模型,模型就能生成相应的图像。

工作流程与评估

视觉模型开发工作流程的一个独特之处在于,评估指标并不总能告诉你全部情况。通常,你需要可视化你的图像输出并手动检查,以了解模型在哪些地方做得好,哪些地方做得不好。我们也将讨论高效进行此类迭代的最佳实践。

假设你的目标检测模型突然表现不佳,而输入数据分布没有变化。你打开一些错误预测结果,发现图像中引入了一个新物体,你的模型将其误判为目标物体。这时就需要针对这个新物体进一步训练你的模型。仅靠评估指标很可能无法完整描述这里发生的情况,因此可视化输出非常重要。

同样,在对不同超参数集进行迭代时,有时你需要查看输出图像,才能理解超参数值如何影响它。实验跟踪工具可以帮助你并排比较这些输出图像,并跟踪和组织哪些输入导致了哪些输出,以便日后复现。

计算机视觉工作流程是高度迭代的,因此跟踪你的每一次实验运行非常有价值。

课程团队与启动

许多人共同努力创建了这门课程。我要感谢Comet团队的成员,以及DeepLearning.AI的Eddie Shu对本课程的贡献。

在第一课中,你将概览本课程将用到的、用于图像分割、目标检测和扩散模型的视觉提示。这听起来很棒,让我们开始吧!完成本课程后,我相信你在提示工程方面能成为一个真正的远见者。


本节课总结:本节课我们一起学习了视觉模型提示工程的课程介绍,涵盖了从图像分割(SAM)、图像生成与修复(Stable Diffusion),到模型个性化微调(DreamBooth)以及工作流程迭代与评估的核心概念和应用场景。

002:课程概述 🎯

在本节课中,我们将学习提示工程的基本概念,这些知识将为后续的代码实践打下基础。我们将探讨什么是提示、视觉提示与语言模型提示的区别,以及提示工程与传统机器学习工作流程的不同之处。

什么是提示?💡

上一节我们介绍了课程目标,本节中我们来看看提示的基本定义。你可能最熟悉用于大型语言模型的文本提示,但提示并不仅限于文本或大型语言模型。理论上,任何类型的数据都可以作为提示,包括文本和图像,也包括音频和视频。

一个提示就是一个输入,它引导着输出的分布。视觉输入对于扩散模型而言正是起到了这种作用。

以下是几种提示的示例:

  • 文本提示:例如,“一张宇航员在月球上骑马的逼真图像”。如果你使用过大型语言模型,对此会很熟悉。
  • 图像提示:正如我们提到的,提示是引导输出采样分布的输入。例如,你可以指示一个视觉模型,以我们提供的这幅梵高画作的风格来重新创作一张图像。
  • 视频提示:同理,视频也可以作为提示,因为视频本质上就是一系列图像的组合。
  • 音频提示:一段某人说出“一张宇航员在月球上骑马的逼真图像”的音频片段,可以作为音频模型的提示。

最终,这些数据类型在被输入到机器学习模型之前,都会被转换成数值表示。通常,这些输入会被进一步处理成嵌入,以节省空间和计算资源。

一个嵌入就是一个相对低维的空间,你可以将高维向量转换到这个空间中。一些流行的文本和图像嵌入技术包括 CLIPALIGN,本课程中你将使用的模型都集成了这些技术。

视觉提示与输入 🔄

视觉提示与语言模型提示类似,是一种与预训练模型交互以完成特定任务的方法,而这个任务可能并非模型被明确训练过的。这通常涉及传递一组描述你希望模型做什么的指令,有时会附带一些图像数据。

如前所述,这些指令可以有多种形式,包括文本和其他图像,也包括像素坐标边界框,这两种形式我们都将在本课程中使用。

现在我们来澄清一下提示输入之间的区别。提示是一组描述模型应执行任务的指令。因此,提示是输入到模型的总输入数据的一部分,总输入数据可能还包括额外的上下文数据。

在这个例子中,提示是“高亮左边的狗”这个指令,但总输入是包含狗的输入图像与提示的组合。同样重要的是要注意,模型的输入不一定包含提示。

提示工程工作流程 🛠️

提示工程工作流程与传统机器学习工作流程有着本质的不同,后者侧重于训练、测试和迭代。

事实上,在提示工程工作流程中,你完全不需要进行任何训练。相反,你将使用一个预训练模型,并专注于设计模型设置、输入数据和提示的最佳组合,以获得期望的输出。请注意,在这个工作流程中,我们从不更新模型权重,我们只是改变传递给模型的输入。

在本课程的大部分时间里,我们都不会进行模型训练。因此,我们需要使用一个针对我们特定用例微调过的模型,或者一个能很好地泛化到它可能从未见过的任务和概念的模型。我们将在本课程的最后一课中了解微调,以及一些用于跟踪和可视化训练过程的最佳实践,以确保其易于复现。

实验跟踪与课程实践 📊

无论你是构建传统的机器学习系统还是进行提示工程,跟踪所有数据、模型和超参数配置都将帮助你复现结果并更快地迭代。这被称为实验跟踪

你可以使用诸如 Comet 这样的可观测性工具,来自动化跟踪应用程序生命周期中模型训练阶段和生产监控阶段的所有这些组件。在本课程中,你将利用 Comet 来跟踪你的微调数据(使用其数据集版本控制功能),以及其可视化能力来帮助你查看将要生成的图像。你运行的所有代码也将保存在 Comet 中,确保你可以随时回顾每张图像,并确切知道它是如何生成的。

在本课程中,你将通过创建一个在 Comet 中跟踪的文本到图像修复管道,来探索几种提示大型视觉模型的不同方法。

以下是本课程的主要实践内容:

  1. 首先,你将使用 Meta 的 Segment Anything Model(简称 SAM)来创建并选择图像区域的分割掩码。
  2. 接着,你将探索如何使用像素坐标、边界框甚至文本来指示 SAM 生成哪些掩码。
  3. 然后,你将进行图像修复。在非 AI 语境中,修复是指修复部分褪色或受损的艺术品的方法。在 AI 语境中,图像修复指的是使用一系列视觉模型来编辑图像部分区域的动作:首先识别要编辑的图像区域,然后生成其替换内容。
  4. 你将使用的图像生成模型是 Stability AI 的 Stable Diffusion。你将亲眼看到提示的细微不同如何导致截然不同的结果。
  5. 你还将使用 Comet 跟踪所有这些输入和输出,以便可以并排检查所有结果,并选择最佳生成的图像。

拥有一个实验跟踪工具作为助手,将使你以及任何与你协作的人,能够在未来遵循完全相同的步骤来创建完全相同的图像。

课程目标与总结 🎓

提示工程通常是优化大型语言或视觉模型输出的最经济、最快捷的方法。但有时你可能会发现,仅靠提示工程不足以完成你的特定任务,这时对模型进行微调以执行该特定操作可能会获得更好的结果。因此,你还将探索如何基于你自己的自定义图像数据集,通过 Google 的 DreamBooth 训练技术来微调 Stable Diffusion。

本节课中我们一起学习了:

  • 视觉提示的基本概念及其与输入的区别。
  • 提示工程工作流程与传统机器学习工作流程的差异。
  • 实验跟踪的重要性以及 Comet 工具的作用。
  • 本课程将涵盖的实践内容,包括使用 SAM 进行分割、使用 Stable Diffusion 进行图像修复和微调。

到本课程结束时,我们的目标是让你能够理解什么是视觉提示;能够使用像素坐标、边界框、嵌入和自然语言来提示大型视觉模型;能够用少量图像微调扩散模型;并且知道如何跟踪、组织和可视化你所有的输入和输出,以便日后能够复现你的结果。

在下一课中,你将提示 Segment Anything 模型,使用像素坐标和边界框来生成掩码。让我们进入下一课。

003:图像分割模型(SAM)入门教程 🖼️

在本节课中,我们将学习如何使用“Segment Anything Model”(SAM)进行图像分割。我们将探索如何通过点坐标、边界框以及正负提示来引导模型生成精确的分割掩码。这些掩码是后续图像编辑等高级视觉任务的重要输入。

什么是图像分割?🔍

上一节我们介绍了课程概述,本节中我们来看看图像分割的基本概念。

图像分割是一种计算机视觉技术,它将数字图像划分为离散的像素组或图像片段。这项技术通常用于定位图像中的物体和边界,并广泛应用于物体检测、三维重建和图像编辑等工作流程中。

更精确地说,图像分割是为图像中的每个像素分配一个标签的过程,使得具有相同标签的像素共享某些特征。

探索SAM模型 🚀

了解了基础概念后,我们来看看本课的核心工具:Segment Anything Model(SAM)。

原始的Segment Anything Model是计算机视觉领域的一个里程碑,它已成为许多高级下游任务(如图像分割、图像描述和图像编辑)的基础步骤。但其高昂的计算成本有时会阻碍其在工业场景(如边缘设备)中的更广泛应用。

SAM的计算成本主要来自其Transformer架构。如下图所示,SAM接受边界框坐标或单像素坐标以及输入图像。这些输入分别由图像编码器和提示编码器编码,然后馈送到掩码解码器中。最终,SAM会为该点或边界框输出前三个有效的掩码。

Fast SAM 是一个基于CNN的Segment Anything模型,仅使用原作者发布数据集的2%进行训练。它在32x32像素的图像上实现了可比的性能,但运行速度提高了50倍。

提示Fast SAM与提示原始SAM略有不同,它会自动检测图像中所有高于可配置置信度阈值的掩码,然后根据用户提供的提示过滤所有生成的掩码。

环境设置与图像准备 ⚙️

我们已经在此环境中安装了必要的包。如果您在自己的环境中跟随操作,需要先安装几个包。

首先,我们将打开本课全程使用的示例图像并查看它。

# 示例:打开图像
from PIL import Image
image_path = "example.jpg"
image = Image.open(image_path)
image.show()

在创建第一个掩码之前,您需要将图像调整为1024像素宽,因为这是SAM模型期望的输入尺寸。为此,我们编写了一个自定义函数,您可以从实用工具库中导入。

# 示例:调整图像大小
def resize_image(image, target_width=1024):
    # 计算缩放比例
    ratio = target_width / image.width
    target_height = int(image.height * ratio)
    resized_image = image.resize((target_width, target_height), Image.Resampling.LANCZOS)
    return resized_image

resized_image = resize_image(image)

调整后,图像看起来与之前完全相同,只是尺寸发生了变化。

使用点坐标提示生成掩码 🎯

本节我们将学习如何使用点坐标来提示模型。我们将从使用SAM模型隔离左侧的狗开始。为此,您需要在图像上定义一个点,模型将以此作为分割的起点。

首先,导入一个我们为您创建的实用函数,用于检查您指定的点在图像上的位置。

# 导入可视化点函数
from utils import show_points_on_image

由于在提示SAM模型时可以指定多个点,我们将使用嵌套列表来定义输入点。第一个坐标指X轴,第二个坐标指Y轴。我们还可以将输入标签定义为正或负。正点包含在掩码中,负点则从掩码中排除。

对于第一个示例,让我们使用一个正点。

# 定义点坐标和标签
input_point = [[500, 300]]  # 示例坐标,需根据实际图像调整
input_label = [1]  # 1 表示正提示

使用上面的自定义函数,我们可以可视化该点在图像上的位置。

show_points_on_image(resized_image, input_point, input_label)

现在您已经定义了点提示并调整了输入图像的大小,让我们将这些信息传递给模型以生成您的第一个掩码。

以下是生成掩码的步骤:

  1. 在图像上运行模型。这将把整个图像分割成多个掩码,然后我们可以使用点提示进行过滤。
  2. 根据上面定义的点,过滤上一步生成的掩码。
# 步骤1:运行模型生成所有掩码
# 假设使用 Fast SAM
from fastsam import FastSAM
model = FastSAM('FastSAM-s.pt')
everything_results = model(resized_image, device='cpu', retina_masks=True, imgsz=1024, conf=0.4, iou=0.9)

# 步骤2:根据点提示过滤掩码
from fastsam import FastSAMPrompt
prompt_process = FastSAMPrompt(resized_image, everything_results, device='cpu')
masks = prompt_process.point_prompt(points=input_point, pointlabel=input_label)

生成掩码后,您可以在原始图像上可视化它们以确保生成正确。

# 可视化掩码
from utils import show_masks_on_image
show_masks_on_image(resized_image, masks)

使用多点与正负提示 ✨

不过,您不必只选择一个点。如果您想获得更大的掩码,也可以指定两个点。让我们尝试为两只狗创建一个掩码,这被称为语义掩码。

和之前一样,您首先需要指定两个点作为SAM模型的提示。

# 为两只狗定义点
input_point_multi = [[500, 300], [800, 350]]  # 两个点的坐标
input_label_multi = [1, 1]  # 两个都是正提示

再次可视化这些点以确保位置正确。

show_points_on_image(resized_image, input_point_multi, input_label_multi)

然后,您可以执行与之前相同的过程来获取掩码。

# 使用多点提示生成掩码
masks_multi = prompt_process.point_prompt(points=input_point_multi, pointlabel=input_label_multi)
show_masks_on_image(resized_image, masks_multi)

到目前为止,我们只使用了正提示,即告诉模型我们想要分割什么。为了指定物体的子部分,使用负提示(即告诉模型我们不希望分割什么)也可能很有帮助。

这次,让我们在狗的夹克上选择一个带有正标签的2D坐标,在狗身上选择一个带有负标签的2D坐标,以向模型表明我们只想分割狗身上的夹克。

为此,我们将像之前一样传递一个2D坐标列表,但这次我们将为负标签指定0。

# 使用正负提示
input_point_pos_neg = [[600, 320], [550, 400]]  # 第一个点是夹克(正),第二个点是狗身体(负)
input_label_pos_neg = [1, 0]  # 1为正,0为负
show_points_on_image(resized_image, input_point_pos_neg, input_label_pos_neg)

如您所见,绿色星表示正提示,红色星表示负提示。希望这能向模型表明我们只想分割夹克。

您将使用与之前相同的步骤来创建掩码。

masks_jacket = prompt_process.point_prompt(points=input_point_pos_neg, pointlabel=input_label_pos_neg)
show_masks_on_image(resized_image, masks_jacket)

使用边界框提示 📦

在使用机器学习模型时,通常使用边界框。边界框是叠加在图像上的矩形,用于突出显示对象,常用于物体检测工作流。

这次,我们将指定一个边界框提示来突出显示右侧的狗,而不是指定一个提示点。

边界框的格式为 [x_min, y_min, x_max, y_max],前两个坐标代表框的左上角,后两个坐标代表右下角。

# 定义边界框坐标 [x_min, y_min, x_max, y_max]
input_box = [[700, 250, 950, 500]]  # 围绕右侧狗的示例框

我们将从模型的总输出中隔离出掩码。

# 使用边界框提示生成掩码
masks_box = prompt_process.box_prompt(bbox=input_box)

然后,我们将掩码转换为布尔值(而不是0和1),因为这是边界框提示函数所期望的。

# 转换掩码格式以便可视化
# 注意:具体转换取决于模型输出格式,此处为示例
final_masks = [mask.cpu().numpy().astype(bool) for mask in masks_box]

最后,我们在原始图像上显示掩码。

show_masks_on_image(resized_image, final_masks)

效果并非完美,但考虑到我们使用的是如此小的模型,它已经做得非常好了。如果您有GPU,我们建议您尝试使用完整版的SAM(如 facebook/sam-vit-largefacebook/sam-vit-huge)来完成本教程。

理解掩码输出与二进制格式 💾

需要指出的是,在上面的示例中,我们定义了自己的自定义函数来将分割掩码覆盖在原始图像上。然而,模型的实际输出是包含相关元数据(如分数和标签)的二进制掩码。

二进制掩码是由0和1(或True和False)组成的数组,其中1或True表示掩码所在位置,0或False表示掩码不在的位置。

让我们看一下上面创建的分割掩码,但这次以其原始格式——二进制掩码的形式查看。同时,将这个二进制掩码可视化为图像。

# 查看第一个掩码的原始二进制格式
binary_mask = masks[0].cpu().numpy()  # 假设masks是张量列表
print("二进制掩码形状:", binary_mask.shape)
print("唯一值:", np.unique(binary_mask))

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vismdl-pmt-engi/img/8e34e23f06a2b146c002fceeb1a87144_39.png)

# 使用matplotlib可视化二进制掩码
import matplotlib.pyplot as plt
plt.imshow(binary_mask, cmap='gray')
plt.title("二进制掩码(白色为True/1)")
plt.axis('off')
plt.show()

在这张图像中,您可以看到白色区域指的是1或True值,黑色区域指的是0或False值。这些二进制掩码将作为下一节课“稳定扩散修复管道”的输入。

尝试您自己的图像 🖼️

现在,在您自己的图像上试试吧。您可以将自己的图像(如JPG或PNG文件)上传到此笔记本的文件目录,并使用PIL的Image.open函数打开它。

# 上传并处理自己的图像
from PIL import Image
import os

# 假设您上传了名为'my_image.jpg'的文件
custom_image_path = 'my_image.jpg'
if os.path.exists(custom_image_path):
    custom_image = Image.open(custom_image_path)
    custom_image_resized = resize_image(custom_image)
    # 在此图像上重复上述提示步骤...
else:
    print("请先上传图像文件。")

总结与展望 🎓

在本节课中,我们一起学习了如何使用Segment Anything Model(SAM)进行图像分割。我们涵盖了以下核心内容:

  • 图像分割的基本概念。
  • SAM及其轻量版Fast SAM 的工作原理。
  • 使用单点、多点、正负提示以及边界框来生成分割掩码。
  • 理解并可视化模型输出的二进制掩码

那么,这些方法如何转化为实际应用呢?

  • 2D点提示 在您使用交互式UI(如照片编辑软件)时可能相关,但对于此类提示,您很可能需要某种与模型的人工交互,这对于现实生活中的用例并不总是现实的。
  • 使用边界框作为SAM输入真正令人兴奋的是,我们可以开始考虑自动化物体检测和物体分割环节。为此,我们需要创建一个模型管道,并将物体检测模型的输出作为SAM的输入。

在下一节课中,您将学习如何使用OWL-ViT模型进行零样本物体检测,并将其与Segment Anything模型链接在一个管道中。让我们继续下一课的学习。

004:零样本目标检测与图像编辑流水线 🚀

在本节课中,我们将学习如何利用自然语言提示,构建一个从文本描述到图像分割掩码的完整流水线。我们将使用零样本目标检测模型 OWL-ViT 来定位图像中的目标,然后将其输出的边界框作为输入,传递给 Mobile SAM 模型以生成精确的分割掩码。最后,我们将利用生成的掩码对图像进行编辑(例如模糊人脸)。

概述

上一节我们介绍了如何使用点提示或边界框来创建掩码。本节中,我们将探索如何用自然语言文本来生成这些掩码。为了实现这一点,我们将构建一个模型流水线:第一个模型(OWL-ViT)的输出将作为第二个模型(Mobile SAM)的输入。

1. 理解 OWL-ViT 模型 🦉

OWL-ViT 是一个零样本目标检测模型,这意味着它无需针对特定目标进行训练,就能根据简单的文本提示检测图像中的物体。

该模型的训练分为两个阶段:

  1. 预训练阶段:模型通过一种利用对比损失的技术,学习将图像与文本片段关联起来。
  2. 微调阶段:模型被专门训练用于目标检测任务,学习识别物体并将其与特定词语或字符串关联。

现在,让我们进入代码实践,看看如何实际使用它。

2. 设置环境与加载图像

首先,我们创建一个 Comet 实验来跟踪和比较本流水线最终生成的掩码。即使没有 Comet 账户,你也可以使用匿名实验功能。

# 初始化 Comet ML(匿名实验)
import comet_ml
experiment = comet_ml.AnonymousExperiment(project_name="vision-pipeline")

我们将使用与上一课相同的图像:两只并排坐着的狗。我们通过 Comet Artifacts 下载所需图像,并使用 PIL 库显示它。

from PIL import Image
# 假设 image_path 是从 Artifacts 下载的图像路径
image = Image.open(image_path)
image.show()

3. 使用 OWL-ViT 进行目标检测

接下来,我们从 Hugging Face 加载 OWL-ViT 模型。我们将使用 Transformers 库的 pipeline 方法,并选择基础模型 owlvit-base-patch32

from transformers import pipeline

# 加载零样本目标检测管道
detector = pipeline(model="google/owlvit-base-patch32", task="zero-shot-object-detection")

现在,我们指定想要在图像中识别的物体(例如“狗”),并将文本提示传递给检测器模型以生成边界框。candidate_labels 参数接受一个文本提示列表,因此可以同时检测多个类别。

# 定义文本提示并执行检测
text_prompt = ["dog"]
predictions = detector(image, candidate_labels=text_prompt)

检查输出变量,模型应该为标签“狗”检测到两个位于不同位置的边界框。我们使用工具函数将这些边界框绘制在原始图像上。

# 预处理输出以便绘图
processed_outputs = pre_process_outputs(predictions)
# 在图像上显示边界框和标签
show_boxes_and_labels_on_image(image, processed_outputs)

结果显示,OWL-ViT 模型成功根据文本输入“狗”生成了两个边界框,高亮标出了图像中的每只狗。

4. 使用 Mobile SAM 生成分割掩码

成功识别出狗之后,流水线的下一步是利用这些边界框为每只狗创建分割掩码。方法与前一次课程类似,但这里我们将使用 Mobile SAM 模型。

Mobile SAM 通过模型蒸馏技术,将大型 SAM 模型的知识转移到一个小得多的模型中,从而能在没有 GPU 的设备上高效运行,同时保持相近的性能。

from ultralytics import SAM

# 加载 Mobile SAM 模型
sam_model = SAM('mobile_sam.pt')

现在,我们使用加载的 SAM 模型和从 OWL 模型得到的边界框来生成分割掩码。我们需要定义标签来指定每个边界框是属于要分割的目标(标签为 1)还是背景(标签为 0)。在我们的案例中,所有边界框都对应目标。

import numpy as np

# 根据边界框数量生成标签数组(全为1,表示目标)
bounding_boxes = [box['box'] for box in processed_outputs] # 假设 processed_outputs 包含边界框坐标
labels = np.ones(len(bounding_boxes))

然后,使用原始图像、边界框和标签通过 Mobile SAM 模型生成分割掩码。

# 使用 SAM 模型预测掩码
results = sam_model.predict(image, bboxes=bounding_boxes, labels=labels)

predict 函数返回一个结果对象,其中包含掩码、原始图像和其他元数据。我们可以从中提取布尔掩码数组。

# 从结果中提取掩码
masks = results[0].masks.data

最后,使用工具函数可视化生成的掩码。

# 在图像上显示掩码
show_masks_on_image(image, masks)

Mobile SAM 模型成功为左右两只狗创建了非常精确的掩码。至此,我们已成功构建了一个从文本提示到边界框,再到生成掩码的完整流水线。

5. 应用流水线进行图像编辑:模糊人脸

现在,让我们尝试一个不同的用例:模糊图像中的人脸。我们将使用包含五个人的新图像。

首先,加载并显示新图像。为了提高处理效率,我们将图像宽度调整为 600 像素。

# 加载新图像并调整大小
new_image = Image.open(people_image_path)
new_image_resized = new_image.resize((600, int(new_image.height * 600 / new_image.width)))

我们创建一个新的 Comet 实验来跟踪这个新流水线的各个步骤,并记录原始图像。

接下来,使用文本提示“human face”和 OWL-ViT 模型来创建图像中人脸的边界框。

# 使用 OWL-ViT 检测人脸
face_predictions = detector(new_image_resized, candidate_labels=["human face"])

检查生成的边界框,模型应该找到了五个,与图中人数相符。我们将图像和边界框记录到 Comet 平台。

然后,使用 Mobile SAM 模型,以边界框作为提示,生成人脸的分割掩码。

# 提取边界框坐标
face_boxes = [pred['box'] for pred in face_predictions]
face_labels = np.ones(len(face_boxes))

# 使用 Mobile SAM 生成人脸掩码
face_results = sam_model.predict(new_image_resized, bboxes=face_boxes, labels=face_labels)
face_masks = face_results[0].masks.data

现在,我们利用生成的掩码来模糊原始图像中的人脸。方法是:先创建整个图像的模糊版本,然后仅将掩码覆盖区域替换为模糊图像。

from PIL import ImageFilter

# 创建图像的模糊版本
blurred_image = new_image_resized.filter(ImageFilter.GaussianBlur(radius=15))

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vismdl-pmt-engi/img/8a9d39936fc7785e3f54151c1707dfc4_26.png)

# 将多个掩码合并为一个综合掩码
combined_mask = combine_masks(face_masks)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vismdl-pmt-engi/img/8a9d39936fc7785e3f54151c1707dfc4_28.png)

# 使用掩码,将原图中对应区域替换为模糊图像
final_image = apply_mask_to_image(new_image_resized, blurred_image, combined_mask)

结果显示,图像中所有人脸都已被成功模糊。我们将最终图像记录到 Comet 平台。

6. 调试与优化提示词

如果我们只想模糊没有戴太阳镜的人脸呢?我们可以尝试将候选标签改为“a person without sunglasses”。

new_prompt = ["a person without sunglasses"]
new_predictions = detector(new_image_resized, candidate_labels=new_prompt)

运行后发现,模型只生成了一个边界框(指向了太阳镜本身),而不是四个未戴太阳镜的人脸。这导致后续步骤错误地模糊了太阳镜。

通过切换到 Comet 平台对比两次实验,我们可以清晰地看到问题所在:

  • 第一次提示“human face”:OWL-ViT 成功识别了所有人脸,Mobile SAM 正确分割并模糊了它们。
  • 第二次提示“a person without sunglasses”:OWL-ViT 将边界框定位在了太阳镜上,导致最终模糊了错误区域。

Comet 平台允许我们可视化图像、边界框,并可以基于模型置信度分数过滤边界框,这有助于我们分析和调试模型表现。

7. 尝试更多图像

你可以尝试调整文本提示,或者使用笔记本中提供的其他图像(如咖啡馆、街道、地铁中的人群)来练习所学技术,构建自己的图像编辑流水线。

总结

在本节课中,我们一起学习了如何构建一个视觉模型流水线。我们首先使用零样本目标检测模型 OWL-ViT,根据自然语言文本提示(如“狗”或“人脸”)在图像中定位目标并生成边界框。接着,我们将这些边界框作为输入,传递给经过优化的 Mobile SAM 模型,以生成精确的图像分割掩码。最后,我们利用生成的掩码实现了具体的图像编辑功能,例如模糊特定区域。这个从 文本 -> 边界框 -> 分割掩码 -> 图像编辑 的完整流程,展示了如何将不同的视觉模型串联起来解决复杂的实际任务。在下一节课中,我们将探索如何使用稳定扩散模型进行图像修复。

005:图像生成

在本节课中,我们将学习如何使用扩散模型,特别是Stable Diffusion,来生成和编辑图像。我们将探讨如何结合文本提示、图像和遮罩,并通过调整超参数来精细控制生成过程。

扩散模型概述

在之前的课程中,我们学习了如何使用计算机视觉模型以边界框和分割遮罩的形式从图像中提取信息。接下来的课程中,我们将学习如何使用扩散模型为图像添加信息。

在深入之前,我们先简要回顾一下扩散模型的工作原理。如果你希望深入了解,建议学习DeepLearning.AI的扩散模型入门课程。请注意,本课程不涉及扩散模型的数学细节,我们的目标是提供一个高层次概述,以便你能高效地完成练习。

扩散模型如何工作

本节课中使用的扩散模型非常出色,它们能够根据文本提示生成细节惊人的图像。这个过程称为“扩散”,其高级工作原理如下:

  1. 模型首先生成一张纯高斯噪声的图像。
  2. 然后,模型逐步、一次一步地对图像进行去噪。
  3. 最终,模型去除足够的噪声,留下一张精美的图像。

从某种意义上说,我们是在“欺骗”模型。我们给它一张纯噪声的图像,却告诉它在噪声之下实际上有一张狗的图片。直观地看,你可以认为模型是在从噪声中“雕琢”出图像。从数学上讲,模型是将来自简单分布(如高斯噪声)的样本,转换到复杂的学习分布(如图像)。我们的文本提示会改变目标分布,引导模型雕琢出满足提示的图像。

实际执行去噪任务的模型是一个U-Net模型,我们使用文本编码器来生成提示嵌入,以指导生成过程。

超越文本提示:图像、遮罩与超参数

然而,你将使用的模型不仅能接受文本提示。你还可以为它提供图像、遮罩和各种超参数,以更好地控制扩散过程。你甚至可以引导模型以一张初始图像为基础,通过一种称为“修复绘画”的技术,仅编辑图像的特定部分。本节课,你将学习如何做到这一点。

让我们开始第一个练习。

练习:使用修复绘画编辑图像

为了激发这个练习,想象一下你的一位不太懂电脑的朋友请你编辑一张他们的照片。他们给你一张他们俯身靠近一只猫的照片,并要求你将猫替换成一条龙。你可以花几天时间用Photoshop编辑图像,或者使用扩散模型在几分钟内完成。

我们将从Comet下载遮罩和图像作为工件。你之前已经了解过工件,简单回顾一下,工件是版本控制的资产,可以是模型、数据集,或者在本例中是你朋友的照片和猫的分割遮罩。

下载工件后,我们可以从中提取图像并使用Matplotlib查看。

现在,我们有了朋友俯身靠近猫的图像,以及猫的分割遮罩。这就是我们开始扩散编辑过程所需的全部内容。

在初始化模型之前,我们需要导入torch并正确设置设备。如果我们有GPU,就使用GPU,否则就使用CPU。

设备设置好后,我们可以从diffusers库初始化一个Stable Diffusion修复绘画管道。

请注意,我们使用的是stable-diffusion-2-inpainting模型,这是一个专门为修复绘画任务训练的扩散模型。我们还根据硬件情况使用了不同的浮点精度格式,这可以节省一些内存。

管道初始化后,我们需要开始考虑用于生成图像的参数。

关键参数:种子与推理步数

最重要但也可能最容易被忽视的参数是种子。我们需要手动设置种子编号,以便我们的结果在以后可以复现。

此外,我们还需要:

  • 提示词:描述我们想要生成的内容。
  • 图像:原始图像。
  • 遮罩图像:指定要编辑的区域。
  • 一个我们尚未讨论的超参数:推理步数

在扩散管道中,你可以控制在图像去噪过程中管道执行的推理步数。当允许管道执行更多推理步数时,去噪过程会更渐进。这通常可以带来更高质量的图像。然而,存在一个收益递减点,超过这个点你只是在浪费计算资源。你也可能得到一个看起来有点过度处理的图像,其特征过于平滑或颜色不真实。

回到我们的代码,让我们准备生成一些图像。首先,初始化一个新的Comet实验,用于记录我们的图像、元数据和参数。然后,将参数传入我们的Stable Diffusion管道并生成输出。之后,我们可以提取图像,并将图像、提示词、种子和推理步数全部记录到Comet。完成后,结束实验并分析结果。

我们不会在这个环境中实际运行此代码,因为在CPU上运行需要很长时间。但我们提前在另一个环境中运行了此代码,并将结果记录到了一个Comet实验中,现在可以查看。

这张图像是使用与上述完全相同的代码生成的,只是在有GPU的系统上运行,所以速度更快一些。如你所见,结果还不完美。我们确实覆盖了猫,但得到的不是龙,而是一个看起来有点吓人、像骨骼的蜥蜴状东西。

在这种情况下,我们首先尝试的可能只是将推理步数大幅增加,比如增加到100步。

如果你看,这和之前的代码完全相同,我们只是将推理步数从3改为了100。和之前一样,我们不会运行这段代码,但我们有另一个使用完全相同代码运行的Comet实验,现在可以查看它的输出。

好的,现在看起来好多了。然而,我们仍然有一些看起来有点奇怪的特征,我认为我们可以做得更好。

调整引导尺度

接下来我们可以尝试调整另一个称为引导尺度的超参数。引导尺度是一个数值,决定了模型应遵循提示词的紧密程度。基本上,你可以将其视为在去噪过程中缩放文本输入对目标分布的影响程度。

更高的引导尺度意味着更高的提示词保真度,但可能降低图像质量。你可以看这个例子:我给一个扩散模型一个提示词:“一幅虎斑猫的油画,打扮得像《黑客帝国》里的角色”。当引导尺度设置较低时,我们得到一张相当不错的猫的油画,但其中没有太多《黑客帝国》的元素。当引导尺度设置较高时(这里是两倍高),我们得到一张戴着《黑客帝国》风格太阳镜的猫的图像,但猫本身的图像质量不是很好。

关于引导尺度需要知道的一点是,每个扩散模型的默认值都不同,整个领域没有统一标准。例如,Stable Diffusion 1.5(一个你可以在CPU上使用的较小模型)的默认值是7.5,而其他模型可能是5。你真的需要查看模型规格说明才能知道正确的值。

回到我们的代码,接下来要做的是运行一个实验,尝试几个不同的引导尺度值。首先,创建一个包含我们将要在管道中使用的不同引导尺度值的NumPy数组。设置好引导尺度后,我们将运行一些与之前编写的代码非常相似的代码。不同之处在于,这次我们运行多个实验,并且我们还将引导尺度参数传递到我们的管道中。

同样,我们不会在这里运行这段代码,在CPU上运行需要很长时间。但我们有另一个Comet实验,已经运行了相同的代码并记录了其输出。

通过调用display_experiment并传入tab=“Images”参数,我们实际上可以看到真正的Comet应用程序的图像仪表板。在这里,你可以看到所有的图像元数据。这里我们可以看到不同的输出。

这两张图像似乎是最好的,至少在引导尺度为10和20时。所以我们知道,当引导尺度在10到20之间时,我们得到了最好的输出。我们可能会做进一步的实验,尝试10到20之间更多的引导尺度值,直到找到完美的数字。

现在,让我们继续尝试另一个超参数。

调整强度参数

接下来我们要看的超参数是扩散模型在图像到图像应用中独有的一个,称为强度超参数。当我们使用扩散模型编辑图像(就像我们在修复绘画中所做的那样)时,我们必须为其添加大量噪声以删除我们想要替换的部分。我们添加多少噪声决定了有多少信息被移除。如果我们添加少量噪声,图像看起来与原始图像非常相似。如果我们添加大量噪声,我们想要替换的部分将被完全移除。

强度决定了添加多少噪声。让我们运行一个关于强度的实验,类似于我们的引导尺度实验,遍历一系列可能的值。

如你所见,代码大体相同。我们为强度创建一组可能的值。我们遍历它们,并将它们全部传递到我们的Stable Diffusion管道中。对于每个输出,我们将图像和元数据记录到Comet,以便稍后分析。和之前一样,我们不会在这里运行这个实验,而是通过Comet实验查看这段代码的结果。

我们可以看到,当强度非常低时,猫几乎没有变化。随着强度增加,越来越多的猫被移除。不幸的是,改变强度似乎并没有为我们带来更好的图像。这没关系,这是实验过程的一部分。

使用负面提示词

我们想要探索添加到Stable Diffusion管道中的最后一个输入是负面提示词

负面提示词应该非常直观。它只是一个告诉模型图像不应该是什么样子的提示词。因此,因为我们使用了“一条逼真的绿色龙”作为提示词,我们可能会给它一个负面提示词,比如“卡通”。这应该会增强我们输出的真实感。

再次,你可以看到我们的代码基本没有变化。我们使用了之前实验的最佳结果,将引导尺度设置为10,如果我们有GPU则使用100个推理步,并且我们还传入了这个负面提示词。同样,我们不会在这里运行代码,但我们在其他地方运行了这段代码,并将输出记录到了一个Comet实验中,现在可以查看。

看,我们优化了我们的龙,去除了前几代中我们不喜欢的一些特征,同时用负面提示词增强了好的品质。利用你在这里学到的所有知识以及我们在其他课程中涵盖的内容,你应该能够构建一个管道:获取图像、生成遮罩、使用修复绘画进行编辑,并以一种流线型的方式完成所有工作,有点像由Stable Diffusion驱动的Photoshop。

本节课就到这里。在下一课中,我们将进一步深入扩散模型,学习如何教一个扩散模型生成它从未见过的东西的图像。

006:微调扩散模型 🎨

在本节课中,我们将要学习当提示工程不足以实现特定图像生成目标时,如何对扩散模型进行微调。我们将重点介绍两种资源需求相对较低的微调技术:DreamBooth和LoRA。


扩散模型与微调的必要性

扩散模型是强大的图像生成工具。但有时,无论进行多少提示工程或超参数优化,都无法得到期望的结果。当提示工程不够用时,可以选择进行微调。

微调通常需要大量计算资源。幸运的是,有一种名为DreamBooth的微调方法,可以显著减少资源消耗。本节课程将学习如何使用少量图像对Stable Diffusion模型进行微调,以生成自定义内容。

上一节我们介绍了通过提示和参数控制模型输出。本节中,我们将探索如何教会模型生成它从未见过的主体图像。

两种微调技术简介

为了实现这一目标,我们将学习两种新的扩散模型微调技术。

第一种技术是DreamBooth。DreamBooth旨在使用极少的示例数据教会扩散模型新的主体。例如,本项目仅使用六张Andrew的图像。

第二种技术是LoRA,全称为低秩适应。我们稍后将更详细地介绍LoRA。现在,让我们从DreamBooth开始。

DreamBooth的挑战与核心思想

在探讨DreamBooth背后的数学原理之前,首先思考为何用如此小的数据集微调扩散模型会很困难。这会导致哪些问题?

第一个也是最明显的问题与数据的鲁棒性有关。六张Andrew的头像可能足以让模型生成不错的头像,但模型很可能无法在其他情境下生成Andrew的图像。

第二个问题与语言漂移有关。微调模型时,我们总是在提升模型在下游任务上的性能与损害模型在其他任务上的性能之间走钢丝。换句话说,如果我们用六张Andrew的照片对模型进行激进地微调,每张照片都配有“一张名为Andrew的男人的照片”这样的提示,那么模型对“照片”和“男人”的嵌入定义很可能开始漂移,以匹配我们小型、狭窄数据集的分布。这将损害模型后续生成除Andrew照片以外任何内容的质量。

DreamBooth在很大程度上通过利用扩散模型对世界的现有理解来解决这两个问题。DreamBooth论文的作者称之为先验语义知识。你可以将其类比为阅读新单词时使用上下文线索。当模型看到“一张名为Andrew的男人的照片”这样的提示时,它可能不知道Andrew长什么样,但它通常知道“男人的照片”是什么样子。然后它可以在训练中利用这些信息,使我们能够用更小的示例数据集完成任务。

DreamBooth的具体实践方法

更具体地说,这是DreamBooth的实际操作步骤。

首先,选择一个模型很少见到的标记,例如带括号的[V]。训练循环将覆盖模型与此标记关联的信息。这与你可能遇到的其他修改扩散模型的技术类似。

然后,将这个稀有标记与另一个描述主体所属类别的通用标记配对。例如,对于Andrew,我们可能选择标记对[V] man

接着,将包含此标记对的提示与我们的Andrew图像关联起来。例如,如果数据集中有一张Andrew打篮球的图像,我们可能会将其与提示“一张[V] man打篮球的照片”关联起来。模型应该能够利用其对句子中所有其他词语的先验理解来指导生成。因此,即使我们没有给模型一个非常鲁棒的数据集,因为它通常知道“男人打篮球的照片”应该是什么样子,它有望识别出区分Andrew打篮球照片与普通男人打篮球照片的具体差异。

微调后,模型应将标记对[V] man专门与Andrew关联起来。这或许解决了数据集小的问题,但并未解决我们之前讨论的语言漂移问题。事实上,根据我们目前的描述,你可能会觉得在训练过程中几乎肯定会破坏模型对“男人”的定义。

DreamBooth以一种非常巧妙的方式解决了这个问题。论文作者创建了一个自定义损失度量,他们称之为先验保留损失,它实现了一种有趣的正则化形式。先验保留会惩罚模型偏离其对世界的现有理解太远的情况。

先验保留损失的工作原理

一般来说,它的工作原理如下。

你创建一个图像与提示配对的数据集。在本例中,你可能有六张Andrew的照片,配文如“一张[V] man的照片”。在DreamBooth术语中,我们称这些为实例图像实例提示。在本项目中,你将为每张图像使用单一提示。但你应该尝试为每张图像编写自定义提示。你甚至可以使用像BLIP这样的模型自动为图像生成标题,这在处理大量图像时非常有用。

然后,你选择一个能代表所有你担心模型在微调过程中会丢失的概念的提示。在DreamBooth文献中,我们称之为类别提示。例如,如果你的实例提示是“一张[V] man的照片”,你的类别提示就简单地是“一张男人的照片”。

使用类别提示,从扩散管道生成100到200张图像。利用这些图像,你可以构建一个鲁棒的分布,代表模型对这些提示概念的先验理解。

在训练过程中,你计算两种损失。首先,计算针对实例数据的损失。基本上,就是当我们给出修改后的标记对时,模型在重建这些Andrew图像方面表现如何。其次,计算类别数据的损失。换句话说,当使用类别提示时,模型生成的图像与先验分布的接近程度如何。

因为类别分布是在你进行任何微调之前直接从模型中采样的,它为你衡量模型漂移了多远提供了一个具体基准。基本上,如果给定相同的提示,模型在微调前后生成的图像差异巨大,我们就知道发生了一些漂移。然后,你可以将这两个项(我们称之为实例损失和类别损失)结合起来,得到一个综合的损失度量。

代码实现准备

现在我们已经从理论角度熟悉了DreamBooth,让我们尝试实际实现它。本节中将使用的大部分代码改编自Hugging Face的DreamBooth训练脚本,你可以在本笔记本底部找到链接。鼓励你探索这些示例,以了解所有可用的复杂优化。

在这个例子中,我们已尽最大努力简化代码以突出高级概念。你还将使用Comet在整个练习中记录训练指标。Comet自动与你在整个项目中将要使用的许多库集成,因此在导入任何其他库之前导入并初始化Comet非常重要。

我们在整个项目中的大部分重点将放在超参数上,以及如何调整它们以控制扩散模型的输出。我们将把超参数存储在一个简单的字典中,以便在整个课程中更新。

这些术语大部分你应该已经熟悉,但这里有几件事需要指出。首先,你应该注意到我们根据你是否能访问GPU来导入不同的模型。如果你有GPU,我们将使用Stable Diffusion XL。这是较大的Stable Diffusion模型之一,能生成非常高质量的图像。如果你在CPU上运行,我们将使用Stable Diffusion版本1.5,这是一个较早的模型,仍然可以生成高质量图像,只是功能不那么强大。

我们有实例提示和类别提示。我们还设置了一个手动种子,以确保结果可重现。你可以看到,如果我们有GPU,我们将分辨率设置为1024像素,否则设置为512像素。这是因为不同的Stable Diffusion模型在不同尺寸下表现更好。此外,我们将推理步骤数设置为50,引导尺度设置为5.0。默认情况下,我们将生成200张类别图像,并将先验损失权重设置为1.0。先验损失权重是在计算先验保留损失时缩放类别损失的数值。如果我们只关心确保模型学习新概念,可以将其设置为0。如果我们非常担心模型在训练过程中不发生漂移,可以将其增加到更高的值。

接下来,我们想要初始化一个可以在整个项目中使用的Comet实验。

在开始训练模型之前,你需要生成类别图像。我们将编写生成类别图像的代码,但实际上不会运行它,因为在CPU上运行需要相当长的时间。相反,我们已经将整个类别图像数据集作为Comet工件提供,可以立即下载。作为复习,Comet工件是存储在Comet中并进行版本控制的资产。生成类别图像最简单的方法是使用我们提供的这个DreamBooth训练器实用程序类。我们将在整个项目中大量使用它,以抽象出运行训练管道所需的一些样板代码。现在,如果你在任何时候对这些实用方法内部的实际操作感到好奇,你总是可以使用双问号运算符查看源代码。

要从Comet下载我们的类别数据,我们将利用之前初始化的实验。数据集下载后,我们可以使用训练器上的display_images方法查看一下。

正如你所见,Stable Diffusion 1.5在生成男人图像方面做得不错,尽管它似乎对黑白照片和小胡子情有独钟。

接下来,我们需要下载我们的实例数据集,其中包含Andrew的图像。我们也将其保存为Comet工件,可以使用非常相似的代码下载。

下载完成后,我们可以使用之前使用的相同display_images方法查看一下。

查看此数据集时,有两件非常重要的事情需要注意。第一,注意图像尺寸不同,质量也不同。这不是我们做了大量预处理的数据集。这说明使用DreamBooth,你可以利用现成的数据非常有效地微调模型。第二件重要的事情是,Andrew有很多蓝色衬衫。

初始化模型与引入LoRA

现在我们的数据集已下载,可以继续初始化模型。回想一下,扩散管道由一个文本编码器模型、一个变分自编码器和一个用于实际去噪过程的U-Net模型组成。我们可以使用训练器的initialize_models方法初始化所有这些模型以及分词器。

在不同时间步向图像添加随机噪声是扩散过程的重要组成部分。因此,我们需要一个噪声调度器来生成噪声。Hugging Face的Diffusers库为访问这类调度器提供了许多非常好的抽象。

现在,我们几乎准备好开始训练模型了。但首先,我们需要谈谈LoRA,我们将用它来实际微调模型。

微调大型模型背后的一个关键挑战是,权重矩阵(听起来可能很明显)非常大。如果你的权重矩阵大小为M x N,那么你可能需要为每次更新计算一个同样大小为M x N的梯度矩阵。对于许多优化器(如带动量的Adam),你实际上会计算多个这样的矩阵,导致内存占用非常大。然而,在微调过程中生成的更新矩阵也往往具有非常低的秩。这意味着矩阵的秩(或线性无关列的数量)往往远小于矩阵的实际列数。直觉上,这告诉我们,我们可能可以在更小的矩阵中捕获更新权重所需的大量重要信息。这基本上就是LoRA所做的。LoRA不是计算一个完整的M x N更新矩阵来在训练过程的每一步更新权重,而是训练一组新的更小的权重,我们称之为适配器,然后将其添加到原始权重中。

从数学上看,这是LoRA的样子。在正常的训练更新中,下一时间步的权重等于当前时间步的权重加上一个更新矩阵,其中权重和更新矩阵都是大小为M x N的矩阵。在LoRA中,时间步T的权重等于原始权重加上两个矩阵A和B在时间步T的乘积。现在,矩阵A是一个M x R矩阵,而矩阵B是一个R x N矩阵,且R小于M和N中的较小者。本质上,我们取两个较小的矩阵,并训练它们,而不是训练原始权重。这些较小的权重然后可以添加到初始权重中,以达到微调的效果。

除了减少内存占用,LoRA的一个优点是你可以拥有许多不同的LoRA适配器集,并且可以轻松切换。例如,如果你以后想为另一个人的照片微调一个LoRA适配器,你可以非常轻松地换掉你的Andrew适配器。

在这个例子中,我们只微调U-Net模型,所以它是我们唯一需要为LoRA准备的模型。此外,我们还需要初始化优化器并提取将要优化的参数。最后,我们需要初始化训练数据、训练数据加载器和学习率调度器。然后,我们将把所有相关模型以及数据集加载到Accelerator中,这是一个Hugging Face库,允许我们更高效地进行训练。

训练循环详解

现在,让我们进入实际的训练循环。首先,我们需要计算总批次大小。我们将在这个管道中使用梯度累积。这是一种我们每隔几步才更新一次权重的技术。因此,我们需要将总批次大小设置为梯度累积步数乘以单步批次大小。

对于训练循环本身,首先我们想设置进度条来跟踪训练。

扩散过程有几个步骤。首先,我们希望将图像转换为其潜在表示。为此,我们将图像的像素表示传递到变分自编码器中,然后从潜在分布中采样。一旦将图像转换为其潜在表示,我们接下来需要在训练时采样要添加到潜在表示中的噪声。最后,为了完成前向扩散过程,我们需要在随机时间步将噪声添加到潜在表示中。

现在,如果前向扩散过程完成,我们需要执行反向扩散。为此,我们需要获取提示的嵌入,并使用U-Net模型预测图像中的噪声。一旦有了预测,我们就可以计算先验损失和实例损失,并将它们与先验损失权重相加,以计算先验保留损失。

损失计算完成后,我们可以像在任何其他训练循环中一样进行反向传播并更新优化器。

在每个周期结束时,我们希望将损失指标记录到Comet。训练结束时,我们希望保存LoRA权重,将参数记录到Comet,并添加一个小标签,以便我们记住这是一个DreamBooth训练项目。然后,我们可以在加速器上调用结束训练,就完成了。同样,我们不会在这个环境中实际运行此训练代码,因为在CPU上这将花费相当长的时间。

结果分析与视觉验证

然而,我们确实有一个来自完全相同的模型训练运行的现有Comet实验,可以用来分析结果。我们可以调用此实验的显示功能,查看它记录到Comet的指标。现在,我们可以编辑布局以更好地查看最重要的指标。特别是,我们希望看到损失、先验损失和实例损失如何比较。

在本课程中,我们强调了动手处理图像数据并目视检查模型输出的重要性。这个项目真正强调了这一点,因为如果我们查看这些损失指标,你会注意到一些非常有趣的数据。首先,你可以看到损失似乎在先验损失和实例损失之间来回过度校正。先验损失的峰值往往对应于实例损失的谷值,反之亦然。这种跷跷板效应可能会让你认为先验保留损失度量没有真正起作用,因为模型似乎没有收敛。

然而,如果我们查看模型的输出,会看到一个非常不同的故事。为了说明这一点,我们将使用模型生成一堆不同的Andrew图像,以及一堆不包含Andrew的图像。

首先,让我们设置一些提示和一些验证提示。你可以看到,我们有几个旨在生成看起来像Andrew的男人的图像的提示,然后有几个移除了[V]标记的等效提示。提示就位后,我们想从刚刚训练完的模型创建一个扩散管道。请注意,我们使用Stable Diffusion 1.5模型初始化了一个预训练的扩散管道,然后将LoRA权重加载到该模型上。使用我们的管道,我们遍历每个提示,生成图像并将其记录到Comet。同样,我们不会在这里运行此代码,因为在CPU上需要一些时间。但我们已经将其运行在Comet上,并将结果存储在一个实验中,我们可以在下面可视化。

我们可以像这样加载现有的实验,然后通过将tab=images参数传递给实验的显示方法来查看记录的图像。这就是Comet仪表板中实际图像选项卡的样子。在其中,我们可以看到管道记录的图像。如果我们点击其中一张,可以看到这是我们的验证提示之一,因为这里没有[V]标记。看这张图像,它看起来像一个正常的男人。

然而,如果我们找到其等效的实例提示,我们会看到,当我们添加[V] man时,生成的壁画看起来很像Andrew。同样,这张“[V] man打篮球”的图像看起来很像Andrew,而这张“男人打篮球”的图像看起来一点也不像Andrew。尽管损失曲线不会告诉我们模型有所改进,但通过查看输出,我们可以清楚地看到它正在学习。

总结与后续探索

对我们来说,关键是要目视检查模型的输出,无论损失曲线显示什么。很多时候,结果会让我们感到惊讶。

这把我们带到了本课的结尾。鼓励你更进一步,尝试实验其他一些超参数。你也可以尝试使用像Google Colab这样的GPU环境来微调更大的Stable Diffusion模型。你甚至可以自己收集数据集,或尝试使用此处提供的代码定位不同的标记。你应该能够承担各种不同的项目。

在本节课中,我们一起学习了当提示工程不足时,如何使用DreamBooth和LoRA技术对扩散模型进行微调。我们探讨了微调面临的挑战,如数据鲁棒性和语言漂移,并了解了DreamBooth如何利用先验语义知识和先验保留损失来解决这些问题。我们还简要介绍了LoRA的低秩适应原理及其在高效微调中的优势。最后,我们强调了目视检查模型输出对于评估微调效果的重要性。

007:总结 🎉

在本节课中,我们将回顾并总结整个课程的核心内容。我们一起学习了如何利用先进的视觉模型进行图像分割、目标检测和图像生成,并探索了如何通过提示工程和微调技术来定制模型以满足特定需求。


上一节我们介绍了如何结合多种技术生成全新图像,本节中我们来看看整个课程的要点总结。

首先,我们掌握了使用SAM模型来精准分割图像中任意部分的方法。其核心在于通过提示(如点或框)来引导模型识别特定区域。

其次,我们学习了如何仅用文本提示OL VIT模型,以完成图像中的对象分割。这展示了自然语言指令控制视觉模型的能力。

接着,我们探索了将目标检测、图像分割与引导扩散模型相结合的流程。以下是该流程的一个简化表示:

# 伪代码示例:结合多种技术生成图像
detected_objects = object_detection(image)
segmented_mask = image_segmentation(detected_objects)
new_image = guided_diffusion_model.generate(segmented_mask)

此外,我们还深入了解了使用Dreambooth方法对Stable Diffusion模型进行微调的技术。这使得模型能够生成包含用户自定义对象(如特定宠物或个人物品)的图像。其核心公式可以概括为在原有模型参数 θ 上进行针对性调整:

θ' = θ + Δθ,其中 Δθ 代表通过少量自定义图像学习到的参数更新。

如果你能访问GPU计算资源(例如通过Google Colab),你将有机会进行以下尝试:

以下是你可以进一步探索的方向:

  • 迭代优化你自己的文本提示,以获得更理想的生成结果。
  • 利用GPU加速,对模型进行更深入的微调。
  • 将本课程中学习的图像处理流程扩展到视频编辑领域。

随着更多基础模型的发布,你将拥有更多机会将提示工程技术应用于你的计算机视觉项目中。我们期待看到你接下来构建的作品。


本节课中我们一起学习了视觉提示工程的关键技术:从图像分割与目标检测的基础操作,到结合引导扩散模型进行程序化图像生成,再到使用Dreambooth方法微调模型以生成定制化内容。掌握这些工具和方法,将为你打开计算机视觉应用创新的大门。

posted @ 2026-03-26 08:16  布客飞龙II  阅读(0)  评论(0)    收藏  举报