原文:
zh.annas-archive.org/md5/355d709877e6e04dc1540c8ccd0b447d
译者:飞龙
第十七章:稳定扩散的应用
在前一章中,我们学习了扩散模型的工作原理、稳定扩散的架构以及扩散器 - 这个库。
虽然我们已经了解了生成图像(无条件和条件,从文本提示),但我们仍然没有学会如何控制图像 - 例如,我可能想要将图像中的猫替换为狗,使人以特定姿势站立,或者用感兴趣的主题替换超级英雄的面孔。在本章中,我们将学习有关帮助实现上述目标的扩散应用模型训练过程和编码的一些应用。具体来说,我们将涵盖以下主题:
-
从文本提示中进行涂抹以替换图像中的对象。
-
使用 ControlNet 从文本提示中生成特定姿势的图像。
-
使用 DepthNet 使用参考深度图像和文本提示生成图像。
-
使用 SDXL Turbo 从文本提示中更快地生成图像。
-
使用 Text2Video 从文本提示生成视频。
本章使用的代码位于 GitHub 仓库的Chapter17
文件夹中,网址为bit.ly/mcvp-2e
。您可以从笔记本运行代码并利用它们来理解所有步骤。
随着领域的发展,我们将会定期向 GitHub 仓库添加有价值的补充内容。请检查每章目录中的supplementary_sections
文件夹获取新的和有用的内容。
涂抹
涂抹是用另一幅图像替换图像的某一部分的任务。涂抹的一个示例如下:
图 17.1:前三项 - 图像、mask_image 和 prompt 作为输入,右侧的图像表示涂抹过程的输出。
在上一幅图像中,我们提供了一个与我们想要替换的主题 - 一只狗对应的掩模。此外,我们提供了我们想要用来生成图像的提示。使用掩模和提示,我们应该生成一个满足提示的输出,同时保持图像的其余部分不变。
在下一节中,我们将了解涂抹模型训练的工作流程。
模型训练工作流程
涂抹模型训练如下:
-
输入要求图像和与输入相关的标题。
-
选择一个主题(图 17.1 中的一只狗),并获取与该主题相对应的掩模。
-
使用标题作为提示。
-
将原始图像通过变分自动编码器传递,将输入图像(例如从 512x512 图像缩小到 64x64 图像)以提取对应于原始图像的潜变量。
-
创建文本潜变量(即使用 OpenAI CLIP 或任何其他潜变模型进行嵌入)以对应提示。将文本潜变量和噪声作为输入传递到 U-Net 模型以输出潜变量。
-
获取原始的潜变量(在步骤 4中获得)、调整大小的掩模(在步骤 2中获得)和潜变量(在步骤 5中获得)以分离背景潜变量和与掩模区域相对应的潜变量。实质上,这一步中的潜变量计算为
original_image_latents * (1-mask) + text_based_latents * mask
。 -
完成所有时间步后,我们获得与提示相对应的潜变量。
-
这些潜变量通过变分自动编码器(VAE)解码器传递,以获得最终图像。VAE 确保生成的图像内部协调一致。
修复绘制的整体工作流程如下:
图 17.2:修复绘制的工作流程
现在我们了解了工作流程,让我们继续学习如何在下一节中使用稳定扩散进行修复绘制。
使用稳定扩散进行修复绘制
要对图像进行修复绘制,我们将使用diffusers
软件包和其中的稳定扩散流程。让我们编写修复绘制的代码如下:
下面的代码在 GitHub 存储库的Chapter17
文件夹中的image_inpainting.ipynb
文件中提供,网址为bit.ly/mcvp-2e
-
安装所需的软件包:
!pip install diffusers transformers accelerate
-
导入所需的库:
import PIL import requests import torch from io import BytesIO from diffusers import StableDiffusionInpaintPipeline
-
定义修复绘制的流程:
pipeline = StableDiffusionInpaintPipeline.from_pretrained( "runwayml/stable-diffusion-inpainting", torch_dtype=torch.float16) pipeline = pipeline.to("cuda")
在上述代码中,我们利用了由runwayml
开发的修复绘制模型。此外,我们指定所有权重精度为 float16 而不是 float32,以减少内存占用。
-
从相应的 URL 获取图像及其对应的掩模:
def download_image(url): response = requests.get(url) return PIL.Image.open(BytesIO(response.content)).convert("RGB") img_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo.png" mask_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo_mask.png" init_image = download_image(img_url).resize((512, 512)) mask_image = download_image(mask_url).resize((512, 512))
原始图像及其相应的掩模如下:
图 17.3:您要替换的图像及其掩模
您可以使用标准工具如 MS-Paint 或 GIMP 创建掩模。
-
定义提示并通过流程传递图像、掩模和提示:
prompt = "Face of a white cat, high resolution, sitting on a park bench" image = pipeline(prompt=prompt, image=init_image, mask_image=mask_image).images[0]
现在,我们可以生成与提示及输入图像相对应的图像。
图 17.4:修复绘制后的图像
在本节中,我们学习了如何用我们选择的另一个主题替换图像的主题。在下一节中,我们将学习如何使生成的图像具有特定的兴趣姿势。
ControlNet
想象一个场景,我们希望图像的主题具有我们指定的某个姿势 – ControlNet 帮助我们实现这一目标。在本节中,我们将学习如何利用扩散模型修改 ControlNet 的架构,并实现这一目标。
架构
ControlNet 的工作原理如下:
-
我们将人体图像传递给 OpenPose 模型,以获取与图像对应的 Stick Figures(关键点)。OpenPose 模型是一种姿势检测器,与我们在第十章中探索的人类姿势检测模型非常相似。
- 模型的输入是一个人物轮廓图和与图像对应的提示,期望的输出是原始的人类图像。
-
我们创建 UNet2DConditionModel 的下采样块的副本。
-
复制块通过零卷积层传递(权重初始化设置为零的层)。这样做是为了能更快地训练模型。如果没有通过零卷积层传递,复制块的添加可能会修改输入(包括文本潜变量、嘈杂图像的潜变量和输入人物轮廓的潜变量)到上采样块,导致上采样器之前没有见过的分布(例如,当复制块最初没有贡献时,输入图像中的面部属性得到保留)。
-
复制块的输出然后添加到原始下采样块的输出中,在进行上采样时。
-
原始块已冻结,只有复制块被设置为训练。
-
当提示和人物轮廓图作为输入时,模型被训练用于预测输出(在给定姿势下,即人物轮廓图的姿势)。
这个工作流程在下图中有所体现:
图 17.5:ControlNet 工作流程
注意,相同的管道不仅可以扩展到精明图像,还可以扩展到粗略线条、涂鸦、图像分割地图和深度图。
现在我们理解了 ControlNet 的训练方式,让我们继续在下一节中编写代码。
实现 ControlNet
要实现 ControlNet,我们将利用 diffusers
库和一个预训练模型,该模型训练用于预测给定图像和提示的图像。让我们编写代码:
以下代码位于 GitHub 仓库中 Chapter17
文件夹中的 ControlNet_inference.ipynb
文件中,网址为 bit.ly/mcvp-2e
。
-
安装所需的库并导入它们:
%pip install -Uqq torch-snippets diffusers accelerate from torch_snippets import * import cv2 import numpy as np from PIL import Image
-
从给定图像中提取一个精明边缘图像:
image0 = read('/content/Screenshot 2023-11-06 at 11.14.37 AM.png') low_threshold = 10 high_threshold = 250 image = cv2.Canny(image0, low_threshold, high_threshold) image = image[:, :, None] canny_image = np.concatenate([image, image, image], axis=2) canny_image = Image.fromarray(canny_image) show(canny_image, sz=3) canny_image.save('canny.png')
上述代码的结果是一个精明图像,如下所示:
-
subplots([image0, canny_image])
图 17.6:原始图像(左)和精明图像(右)
-
从
diffusers
库导入帮助实现 ControlNet 的模块:from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler from diffusers.utils import load_image import torch
-
初始化 ControlNet:
generator = torch.Generator(device='cuda').manual_seed(12345) controlnet = ControlNetModel.from_pretrained( "lllyasviel/sd-controlnet-canny")
在上述代码中,我们加载了预训练的 ControlNet
模型。
-
定义管道和噪声调度程序以生成图像:
pipe = StableDiffusionControlNetPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", controlnet=controlnet ).to('cuda') pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.\ config)
上述管道的架构在 GitHub 笔记本中提供。该架构包含了从输入图像和提示中提取编码器所使用的不同模型。
-
将精明图像通过管道传递:
image = pipe( "a man with beard, realistic, high quality", negative_prompt="cropped, out of frame, worst quality, low quality, jpeg artifacts, ugly, blurry, bad anatomy, bad proportions, nsfw", num_inference_steps=100, generator=generator, image=canny_image, controlnet_conditioning_scale=0.5 ).images[0] image.save('output.png')
上述代码的结果如下:
图 17.7:输出图像
注意生成的图像与原始图像中原本存在的人物非常不同。然而,在根据提示生成新图像的同时,保留了原始图像中的姿势。在下一节中,我们将学习如何快速生成高质量的图像。
SDXL Turbo
类似于稳定扩散,已经训练了一个名为SDXL(稳定扩散特大)的模型,返回具有 1,024x1,024 尺寸的高清图像。由于其尺寸巨大以及去噪步骤的数量,SDXL 在生成图像时需要相当长的时间。我们如何在保持图像一致性的同时减少生成图像所需的时间?SDXL Turbo 在这里起到了救援作用。
架构
SDXL Turbo 通过执行以下步骤进行训练:
-
从预训练数据集(在
laion.ai/blog/laion-400-open-dataset/
上提供的大规模人工智能开放网络(LAION))中抽样一幅图像及其相应文本。 -
向原始图像添加噪音(所选时间步长可以是 1 到 1,000 之间的随机数)
-
训练学生模型(对抗扩散模型)生成能欺骗鉴别器的图像。
-
进一步地,以这样一种方式训练学生模型,使其输出与教师 SDXL 模型的输出非常相似(当从学生模型生成的添加噪声的输出作为输入传递给教师模型时)。这样,我们优化了两个损失 - 判别器损失(学生模型生成的图像与原始图像之间的差异)以及学生和教师模型输出之间的均方误差损失。请注意,我们只训练学生模型。
这在以下图示中有所体现:
图 17.8:SDXL turbo 训练
来源:stability.ai/research/adversarial-diffusion-distillation
对对抗损失和蒸馏损失的训练可以帮助模型对输入图像(教师模型的输出)的微小修改具有很好的泛化能力。
实施 SDXL Turbo
SDXL Turbo 的代码实现如下:
你可以在 GitHub 仓库的Chapter17
文件夹中的sdxl_turbo.ipynb
文件中找到代码,链接为bit.ly/mcvp-2e
。
-
安装所需的库:
%pip -q install diffusers accelerate torch-snippets torchinfo lovely_tensors
-
导入所需的包:
from diffusers import AutoPipelineForText2Image, AutoPipelineForImage2Image import torch
-
定义
SDXL Turbo
流程:pipeline = AutoPipelineForText2Image.from_pretrained("stabilityai/sdxl- turbo", torch_dtype=torch.float16, variant="fp16") pipeline = pipeline.to("cuda")
-
提供提示和负面提示(
n_prompt
),并获取输出图像。请注意,负面提示(n_prompt
)确保生成图像中不包含其中提到的属性:%%time prompt = "baby in superman dress, photorealistic, cinematic" n_prompt = 'bad, ugly, blur, deformed' image = pipeline(prompt, num_inference_steps = 1, guidance_scale=0.0, negative_prompt = n_prompt, seed = 42).images[0] image
图 17.9:生成的图像
前面的代码在不到 2 秒的时间内执行,而典型的 SDXL 模型生成一个图像需要超过 40 秒。
DepthNet
想象一个情景,您想修改背景,同时保持图像主题的一致性。您将如何解决这个问题?
一种方法是利用我们在第十六章学到的Segment Anything Model(SAM),并将背景替换为您选择的背景。但是,这种方法存在两个主要问题:
-
背景没有生成,因此您必须手动提供背景图像。
-
主题和背景颜色不一致,因为我们进行了拼接。
DepthNet 通过利用扩散方法来解决这个问题,我们将使用模型来理解图像的哪些部分是背景和前景,使用深度图。
工作流
DepthNet 的工作原理如下:
-
我们计算图像的深度掩模(深度是通过类似于《Vision Transformers for Dense Prediction》论文中提到的管道计算的:
arxiv.org/abs/2103.13413
)。 -
Diffusion UNet2DConditionModel 被修改为接受五通道输入,其中前四个通道是标准的噪声潜变量,第五个通道仅是潜变量深度掩模。
-
现在,训练模型以使用修改的扩散模型来预测输出图像,其中除了提示外,我们还有额外的深度图作为输入。
典型的图像及其相应的深度图如下:
图 17.10:一幅图像及其深度图
现在让我们继续实现 DepthNet。
实施 DepthNet
要实现 DepthNet,您可以使用以下代码:
完整代码可以在 GitHub 存储库中的Chapter17
文件夹的DepthNet.ipynb
文件中找到,网址为bit.ly/mcvp-2e
。
-
安装所需的软件包:
%pip -q install diffusers accelerate torch-snippets torchinfo lovely_tensors !wget https://png.pngtree.com/thumb_back/fw800/background/20230811/pngtree-two-glasses-with-a-lime-wedge-in-them-next-to-a-image_13034833.jpg -O lime_juice.png
-
导入所需的软件包:
import torch import requests from PIL import Image from torch_snippets import * from diffusers import StableDiffusionDepth2ImgPipeline
-
定义管道:
pipe = StableDiffusionDepth2ImgPipeline.from_pretrained( "stabilityai/stable-diffusion-2-depth", torch_dtype=torch.float16,)
-
指定提示并通过管道传递图像:
init_image = Image.open('/content/test.jpg') prompt = "glasses of lime juice with skyline view in background in a cool afternoon" n_propmt = "bad, deformed, ugly, bad anotomy" image = pipe(prompt=prompt, image=init_image, negative_prompt=n_propmt, strength=0.7, num_inference_steps = 100).images[0]
前面的代码导致以下输出(有关彩色图像,请参阅书的电子版本):
图 17.11:(左)输入图像(右)来自 DepthNet 的输出
请注意,在上述图片中,原始图片中的深度(左侧的图片)被保留,而提示修改了图片的内容/视图。
文本转视频
想象一种情况,您提供文本提示并希望从中生成视频。您如何实现这一点?
到目前为止,我们已经从文本提示生成了图像。从文本生成视频需要我们控制两个方面:
-
时间一致性跨帧(一个帧中的主题应与后续帧中的主题类似)
-
帧间操作的一致性(如果文本提示是一架火箭射向天空,则火箭在不断增加的帧数中应该有一致的向上轨迹)
在训练文本到视频模型时,我们应该处理上述两个方面,我们再次使用扩散模型来处理这些方面。
为了理解模型构建过程,我们将学习由 damo-vilab 构建的文本到视频模型。它利用Unet3DConditionModel
而不是我们在上一章节中看到的Unet2DConditionModel
。
工作流程
Unet3DConditionModel
包含CrossAttnDownBlock3D
块,而不是CrossAttnDownBlock2D
块。在CrossAttnDownBlock3D
块中,除了我们在上一章节看到的resnet
和attention
模块外,还有两个模块:
-
temp_conv:在
temp_conv
模块中,我们通过一个Conv3D
层传递输入。在这种情况下,输入考虑了所有帧(而在 2D 中,一次只有一个帧)。实质上,通过同时考虑所有帧,我们的输入是一个 5D 张量,形状为[bs, frames, channels, height, width]。您可以将此视为一种维持主题在帧间时间一致性的机制。 -
temp_attn:在
temp_attn
模块中,我们对帧维度而不是通道维度执行自注意力。这有助于在帧间保持动作的一致性。
CrossAttnUpBlock3D
和CrossAttnMidBlock3D
块仅在其子模块(我们已经讨论过的)方面有所不同,与它们的 2D 对应块相比没有功能上的差异。我们将留给您深入了解这些块的活动。
实现文本到视频
现在让我们继续实现执行文本到视频生成的代码:
以下代码位于 GitHub 存储库中Chapter17
文件夹中的text_image_to_video.ipynb
文件中,网址为bit.ly/mcvp-2e
。
-
安装所需的软件包并导入它们:
%pip -q install -U diffusers transformers accelerate torch-snippets lovely_tensors torchinfo pysnooper import torch from diffusers import DiffusionPipeline, DPMSolverMultistepScheduler from diffusers.utils import export_to_video from IPython.display import HTML from base64 import b64encode
-
定义文本到视频生成的流程:
pipe = DiffusionPipeline.from_pretrained(\ "damo-vilab/text-to-video-ms-1.7b", torch_dtype=torch.float16, variant="fp16") pipe.enable_model_cpu_offload()
-
提供提示、视频持续时间以及每秒帧数来生成视频:
prompt = 'bear running on the ocean' negative_prompt = 'low quality' video_duration_seconds = 3 num_frames = video_duration_seconds * 25 + 1
-
将上述参数传递给流水线:
video_frames = pipe(prompt, negative_prompt="low quality", num_inference_steps=25, num_frames=num_frames).frames
-
使用以下代码显示视频:
def display_video(video): fig = plt.figure(figsize=(4.2,4.2)) #Display size specification fig.subplots_adjust(left=0, right=1, bottom=0, top=1) mov = [] for i in range(len(video)): #Append videos one by one to mov img = plt.imshow(video[i], animated=True) plt.axis('off') mov.append([img]) #Animation creation anime = animation.ArtistAnimation(fig, mov, interval=100, repeat_delay=1000) plt.close() return anime video_path = export_to_video(video_frames) video = imageio.mimread(video_path) #Loading video HTML(display_video(video).to_html5_video()) #Inline video display in HTML5
有了以上内容,我们现在可以从文本生成视频了。您可以在相关笔记本中查看生成的视频。
概述
在本章中,我们学习了如何创意地利用扩散模型进行多种应用。在这个过程中,我们还了解了各种架构的工作细节以及代码实现。
这与深入理解扩散模型如何工作的坚实基础结合使用,将确保您能够利用稳定的扩散模型进行多种创意工作,修改和微调用于定制图像生成的架构,并结合/流水线多个模型以获得您所需的输出。
在下一章中,我们将学习部署计算机视觉模型以及在此过程中需要考虑的各种方面。
问题
-
使用稳定扩散进行图像修复的关键概念是什么?
-
ControlNet 背后的关键概念是什么?
-
SDXL Turbo 比 SDXL 更快的原因是什么?
-
DepthNet 背后的关键概念是什么?
加入我们的 Discord 了解更多信息
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
第十八章:将模型部署到生产环境
将模型部署到生产环境是促使外部方使用我们模型的一步。我们应该向世界公开我们的模型,并开始对真实世界未见过的输入进行预测。
部署训练过的 PyTorch 模型并不足以实现部署。我们需要额外的服务器组件,以建立从现实世界到 PyTorch 模型的通信渠道,以及从模型返回到现实世界的通信渠道。重要的是,我们要知道如何创建 API(用户可以通过该接口与模型交互),将其封装为独立的应用程序(以便可以部署在任何计算机上),并将其部署到云端 - 这样任何具有必要 URL 和凭据的人都可以与模型交互。为了成功将模型移至生产环境,所有这些步骤都是必需的。
此外,我们还必须处理预测延迟(获取预测所需的时间)和模型大小(在部署在像移动设备/手表之类的边缘设备时)的限制,而不会影响准确性。
此外,作为生产支持的一部分,我们将不断关注模型部署后输入的情况,然后检查在训练模型时使用的图像与在实际部署过程中输入的图像之间是否存在显著的漂移(偏差)。
在本章中,我们将部署一个简单的应用程序,并建立一个识别输入漂移的机制。
本章将涵盖以下主题:
-
了解 API 的基础知识
-
在本地服务器上创建 API 并进行预测
-
将模型容器化并部署到云端
-
识别数据漂移
-
使用Facebook AI Similarity Search(FAISS)构建向量存储库
本章中的所有代码片段均可在 GitHub 存储库的 Chapter18
文件夹中找到:bit.ly/mcvp-2e
。
此外,我们的 GitHub 存储库中还涵盖以下主题:
- 将模型转换为Open Neural Network Exchange(ONNX)格式
随着领域的发展,我们将定期向 GitHub 存储库添加有价值的补充内容。请查看每章节目录中的 supplementary_sections
文件夹,以获取新的和有用的内容。
需要注意的是部署模型的典型工作流程如下:
-
在本地服务器上创建 API 并进行预测
-
将应用程序容器化
-
在云端部署 Docker 容器
-
建立训练图像的向量存储库
-
识别现实世界图像中的偏差(漂移),以便我们知道是否需要对模型进行微调
了解 API 的基础知识
到目前为止,我们知道如何为各种任务创建深度学习模型。它接受/返回张量作为输入/输出。但是像客户/最终用户这样的外部人员只会用图像和类别来谈论。此外,他们希望通过可能与 Python 无关的渠道发送和接收输入/输出。互联网是最容易进行通信的渠道。因此,对于客户端来说,最佳部署场景是如果我们能够设置一个公开可用的 URL,并要求他们将他们的图像上传到那里。这种范例称为应用程序编程接口(API),它具有标准协议,接受输入并在互联网上传输输出,同时将用户从输入处理或输出生成的细节中抽象出来。
API 中一些常见的协议包括 POST
、GET
、PUT
和 DELETE
,这些协议由客户端作为请求发送到主机服务器,并附带相关数据。基于请求和数据,服务器执行相关任务,并以适当的数据形式返回响应,客户端可以在其下游任务中使用。在我们的案例中,客户端将发送一个带有图像作为文件附件的 POST 请求。我们应该保存文件,处理它,并返回适当的类作为请求的响应,这样我们的工作就完成了。
请求是通过互联网发送的组织数据包,用于与 API 服务器通信。通常,请求的组成部分如下:
-
终端点 URL:这是 API 服务的地址。例如,
www.packtpub.com/
就是连接到 Packt Publishing 服务并浏览其最新图书目录的终端点。 -
一系列标头:这些信息帮助 API 服务器返回输出;例如,如果标头包含客户端使用移动设备,则 API 可以返回一个移动友好的 HTML 页面布局。
-
一系列查询:这确保只从服务器数据库中获取相关的项目。例如,搜索字符串
PyTorch
将仅返回前面示例中与 PyTorch 相关的书籍。请注意,在本章中,我们不会处理查询,因为对图像的预测不需要查询,而是需要文件名。 -
一系列文件:这些文件可以上传到服务器,或者在我们的情况下,用于进行深度学习预测。
cURL 是一个计算机软件项目,提供了一个库和命令行工具,用于使用各种网络协议传输数据。它是最轻量级、常用和简单的应用程序之一,用于调用 API 请求并获取响应。
为了说明这一点,我们将在接下来的章节中实现一个名为 FastAPI 的现成 Python 模块,使我们能够做到以下几点:
-
设置通信 URL。
-
在发送到 URL 时,接受来自各种环境/格式的输入。
-
将每种输入形式转换为机器学习模型需要的确切格式。
-
使用训练好的基于深度学习的模型进行预测。
-
将预测转换为正确的格式,并通过预测响应客户端的请求。
我们将使用表面缺陷数据集(SDD)(www.vicos.si/resources/kolektorsdd/
)作为示例来演示这些概念。
在了解基本设置和代码后,您可以为任何类型的深度学习任务创建 API,并通过本地机器上的 URL 提供预测。下一个逻辑步骤是将应用程序容器化,并将其部署到云上,以便从世界任何地方访问应用程序。部署的应用程序随后将需要支持,因为重要的是我们了解如何识别现实世界数据中的偏差,如果模型表现不佳的话。
在接下来的几节中,我们将学习如何将应用程序封装在一个自包含的 Docker 镜像中,该镜像可以在云中任何地方进行部署和运行。让我们使用 FastAPI,一个 Python 库,来创建 API,并验证我们可以直接从终端进行预测(而不使用 Jupyter 笔记本)。
创建一个 API,并在本地服务器上进行预测。
在本节中,我们将学习如何在本地服务器上进行预测(与云无关)。从高层次来看,这包括以下步骤:
-
安装 FastAPI
-
创建一个接受传入请求的路由。
-
将传入请求保存到磁盘上。
-
加载请求的图像,然后进行预处理并使用训练好的模型进行预测。
-
对结果进行后处理,并将预测发送回相同的传入请求作为响应。
本节中的所有步骤都被总结为一个视频教程:tinyurl.com/MCVP-Model2FastAPI
。
让我们开始安装 FastAPI。
安装 API 模块和依赖项。
由于 FastAPI 是一个 Python 模块,我们可以使用pip
进行安装,并准备编写一个 API。我们现在将打开一个新终端,并运行以下命令:
$pip install fastapi uvicorn aiofiles jinja2
我们已安装了几个与 FastAPI 一起需要的依赖项。uvicorn
是一个用于设置 API 的最小低级服务器/应用程序接口。aiofiles
使服务器可以异步处理请求,例如同时接受和响应多个独立的并行请求。这两个模块是 FastAPI 的依赖项,我们不会直接与它们交互。
让我们创建所需的文件,并在下一节中编写它们的代码。
提供一个图像分类器。
在本节中,我们将学习如何在本地部署模型,以便从端点接收预测。
第一步是设置文件夹结构,如下所示:
图 18.1:模型服务的文件夹结构
设置非常简单,如下所示:
-
files
文件夹将作为传入请求的下载位置。 -
sdd.weights.pth
包含我们训练的 SDD 模型的权重。 -
sdd.py
将包含加载权重、接受传入图像、预处理、预测和后处理预测的逻辑。 -
server.py
将包含 FastAPI 功能,可以设置 URL,从 URL 接受客户端请求,发送/接收来自sdd.py
的输入/输出,并将输出作为响应发送给客户端请求。
注意
files
文件夹为空,仅用于存储上传的文件。
我们假设已经有了训练模型的权重作为sdd.weights.pth
。
训练过程类似于我们在第四章中进行图像分类的训练。
与训练模型相关联的笔记本位于书的 GitHub 存储库的Chapter18
文件夹中的第一部分的vector_stores.ipynb
笔记本中。
现在让我们理解ssd.py
和server.py
的构成并编写它们。
sdd.py
如前所述,sdd.py
文件应该具有加载模型并返回给定图像的预测的逻辑。
我们已经熟悉如何创建 PyTorch 模型。类的唯一额外组件是predict
方法,用于对图像进行必要的预处理和结果的后处理。您可以按照以下步骤操作:
以下代码可在书的 GitHub 存储库的Chapter18
文件夹中的ssd.py
中找到,网址为https://bit.ly/mcvp-2e
。
-
我们首先创建
model
类,该类构成了模型的架构:from torch_snippets import * class SDD(nn.Module): classes = ['defect','non_defect'] def __init__(self, model, device='cpu'): super().__init__() self.model = model.to(device) self.device = device
-
以下代码块突出显示了前向方法:
@torch.no_grad() def forward(self, x): x = x.view(-1,3,224,224).to(device) pred = self.model(x) conf = pred[0][0] clss = np.where(conf.item()<0.5,'non_defect','defect') print(clss) return clss.item()
在上述代码中,我们首先重塑输入图像,通过模型,并获取与图像对应的预测类。
以下代码块突出显示了predict
方法,用于进行必要的预处理和后处理:
-
def predict(self, image): im = (image[:,:,::-1]) im = cv2.resize(im, (224,224)) im = torch.tensor(im/255) im = im.permute(2,0,1).float() clss = self.forward(im) return {"class": clss}
总之,在前面的步骤中,在__init__
方法中,我们初始化模型(其中在上一步中加载了预训练权重)。在forward
方法中,我们通过模型传递图像并获取预测。在predict
方法中,我们从预定义路径加载图像,预处理图像,然后通过模型的forward
方法传递它,并在返回预测类时将输出包装在字典中。
server.py
这是 API 中连接用户请求与 PyTorch 模型的代码部分。让我们逐步创建文件:
以下代码可在书的 GitHub 存储库的Chapter18
文件夹中的server.py
中找到,网址为https://bit.ly/mcvp-2e
。
-
加载库:
import os, io from sdd import SDD from PIL import Image from fastapi import FastAPI, Request, File, UploadFile
FastAPI
是基本的服务器类,用于创建 API。
请求
、文件
和UploadFile
是客户端请求和它们将上传的文件的代理占位符。有关更多详细信息,请参阅官方 FastAPI 文档:fastapi.tiangolo.com/
。
-
加载模型:
# Load the model from sdd.py device = 'cuda' if torch.cuda.is_availabe() else 'cpu' model = SDD(torch.load('sdd.weights.pth', map_location=device)
-
创建一个
app
模型,它可以提供一个用于上传和显示的 URL:app = FastAPI()
-
创建一个
"/predict"
的 URL,以便客户端可以向"<hosturl>/predict"
发送POST
请求并接收响应(我们将在下一节学习有关<hosturl>
的信息,即服务器的信息):@app.post("/predict") def predict(request: Request, file:UploadFile=File(...)): content = file.file.read() image = Image.open(io.BytesIO(content)).convert('L') output = model.predict(image) return output
这就是全部!我们有所有的组件来利用我们的图像分类器在我们的本地服务器上进行预测。让我们设置服务器并在本地服务器上进行一些预测。
运行服务器
现在我们已经设置好所有的组件,可以运行服务器了。打开一个新终端并cd
到包含sdd.py
、server.py
的文件夹:
-
运行服务器:
$ uvicorn server:app
您将看到如下消息:
图 18.2:应用程序启动期间的消息
Uvicorn running on ...
消息表明服务器已启动并运行。
-
要获取预测结果,我们将在一个新的终端中运行以下命令,为
/home/me/Pictures/defect.png
中存在的样本图像获取预测:$ curl -X POST "http://127.0.0.1:8000/predict" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "file=@/home/me/Pictures/defect.png;type=image/png"
上述代码行的主要组件如下:
-
REST API 方法:所使用的方法是 POST,这表示我们希望将自己的数据发送到服务器。
-
URL – 服务器地址:服务器主机 URL 是
http://127.0.0.1:8000/
(这是本地服务器,使用8000
作为默认端口),/predict/
是提供给客户端创建POST
请求的路由;未来客户端必须将其数据上传到 URLhttp://127.0.0.1:8000/predict
。 -
头部:请求包含
-H
标志形式的组件。这些附加信息,例如以下内容:-
输入内容类型将是什么 -
multipart/form-data
- 这是 API 术语,表示输入数据以文件的形式提交。 -
预期的输出类型是什么 -
application/json
- 这意味着 JSON 格式。还有其他格式,如 XML、文本和octet-stream
,这些根据生成的输出复杂性而应用。
-
-
文件:最终的
-F
标志指向要上传的文件所在的位置及其类型。
一旦运行了上述代码,输出字典将被打印在终端中。
现在我们可以从本地服务器获取模型预测结果了。
现在我们已经了解了如何构建服务器,在接下来的部分中,我们将学习如何将应用程序容器化,以便可以在任何系统上运行。
容器化应用程序
我们宁愿安装一个具有所有内容的包,而不是安装多个单独的包(如运行应用程序所需的单独模块和代码),然后稍后再将它们连接起来。因此,我们需要学习如何使用 Docker,它本质上是一个带有代码的压缩操作系统。创建的 Docker 容器是轻量级的,将仅执行我们想要执行的任务。在我们的示例中,我们将创建的 Docker 镜像将运行用于预测 SDD 图像类别的 API。但首先,让我们了解一些 Docker 行话。
Docker 镜像 是打包代码及其所有依赖关系的标准软件单元。这样,应用程序可以从一个计算环境快速可靠地运行到另一个计算环境。Docker 镜像是一个轻量级、独立运行的软件包,包含运行应用程序所需的一切:代码、运行时、系统工具、系统库和设置。
Docker 容器 是镜像的快照,将在需要部署的地方实例化。我们可以从单个镜像创建任意数量的 Docker 容器,它们将执行相同的任务。可以将镜像视为蓝图,将容器视为从该蓝图创建的实例。
在高层次上,我们将学习如何执行以下任务:
-
创建一个 Docker 镜像。
-
将其创建为 Docker 容器并进行测试。
让我们从创建一个 Docker 容器开始。
构建 Docker 镜像
在前一节中,我们构建了一个 API,该 API 在本地服务器上接收图像并返回图像的类别及其概率。现在,是时候将我们的 API 封装成一个可以在任何地方部署的软件包了。
确保您的计算机安装了 Docker。您可以参考docs.docker.com/get-docker/
中的安装说明。
创建 Docker 容器的过程包括三个步骤:
-
创建一个
requirements.txt
文件。 -
创建一个 Dockerfile。
-
构建 Docker 镜像并创建 Docker 容器。
以下各节的代码还可以在这里进行视频演示:tinyurl.com/MCVP-2E-model2fastapi-docker
。
现在,我们将详细了解并理解这四个步骤,接下来,我们将学习如何将镜像发送到 AWS 服务器。
创建 requirements.txt
文件
我们需要告诉 Docker 镜像安装哪些 Python 模块以运行应用程序。requirements.txt
文件包含所有这些 Python 模块的列表:
-
打开一个终端,进入包含
sdd.py
、server.py
的文件夹。接下来,我们将在本地终端的root
文件夹中创建一个空白虚拟环境并激活它:$ python3 -m venv fastapi-venv $ source fastapi-env/bin/activate
我们创建空白虚拟环境的原因是确保只在环境中安装所需的模块,以便在发布时不浪费宝贵的空间。
-
安装所需的包(
fastapi
、uvicorn
、aiofiles
、torch
和torch_snippets
)以运行 SDD 应用程序:$ pip install fastapi uvicorn aiofiles torch torch_snippets
-
在同一个终端中,运行以下命令以安装所有必需的 Python 模块:
$ pip freeze > requirements.txt
上述代码将所有 Python 模块及其相应的版本号提取到 requirements.txt
文件中,这将用于在 Docker 镜像中安装依赖项。
现在我们已经具备了所有的先决条件,让我们在下一节中创建 Dockerfile。
创建 Dockerfile
如前所述,Docker 镜像是一个独立的应用程序,完全具备自己的操作系统和依赖项。给定一个计算平台(例如 EC2 实例),镜像可以独立运行并执行其设计的任务。为此,我们需要为 Docker 应用程序提供必要的指令 - 依赖项、代码和命令。
让我们在 SDD 项目的 root
目录中创建一个名为 Dockerfile
的文本文件,其中包含 server.py
、sdd.py
(我们已经在创建项目文件夹后放置了)。文件需要命名为 Dockerfile
(无扩展名),作为约定。文本文件的内容如下:
以下代码位于书籍 GitHub 仓库的 Chapter18
文件夹中的 Dockerfile
中,网址为 bit.ly/mcvp-2e
。
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
WORKDIR /app
COPY . /app
EXPOSE 5000
CMD ["uvicorn", "server:app", "--host", "0.0.0.0"]
让我们逐步理解上述代码:
-
FROM
指示我们要使用哪个操作系统基础。tiangolo/uvicorn-gunicorn-fastapi:python3.7
的位置是一个地址,由 Docker 从互联网解析,它获取一个已经安装了 Python 和其他 FastAPI 模块的基础镜像。 -
接下来,我们复制我们创建的
requirements.txt
文件。这提供了我们想要安装的包。在下一行中,我们要求镜像使用pip install
来安装包。 -
WORKDIR
是我们的应用程序将运行的文件夹。因此,我们在 Docker 镜像中创建一个名为/app
的新文件夹,并将root
文件夹的内容复制到镜像的/app
文件夹中。 -
最后,像前一节那样运行服务器。
这样,我们就设置了一个蓝图,可以从头开始创建一个全新的操作系统和文件系统(可以想象成一个新的可安装的 Windows 光盘),其中只包含我们指定的代码并且只运行一个应用程序,即 FastAPI。
构建 Docker 镜像和创建 Docker 容器
注意,到目前为止,我们只创建了 Docker 镜像的蓝图。让我们构建镜像并创建一个容器。
提供了构建 Dockerfile 过程的视频演示,链接在 YouTube 上:bit.ly/MCVP-Docker-Deployment
。
在同一个终端中(包含应用程序文件的根目录),从这里运行以下命令:
-
构建 Docker 镜像并将其标记为
sdd:latest
:$ docker build -t sdd:latest .
经过一长串输出后,我们得到以下内容,告诉我们镜像已构建完成:
图 18.3:创建 Docker 镜像
我们已成功创建了一个名为 sdd:latest
的 Docker 镜像(其中 sdd
是镜像名称,latest
是我们给出的标签,表示其版本号)。Docker 在系统中维护一个注册表,所有这些镜像都可以从中访问。这个 Docker 注册表现在包含一个独立的镜像,其中包含运行 SDD API 所需的所有代码和逻辑。
我们可以通过在命令提示符中输入 $ docker image ls
来随时检查 Docker 注册表。
-
使用
-p 5000:5000
启动构建好的镜像,在镜像内部将端口5000
转发到本地机器的5000
端口。最后一个参数是从镜像创建的容器的名称:$ docker run -p 5000:5000 sdd:latest
端口转发非常重要。通常,我们无法控制云暴露哪些端口。因此,尽管我们的 uvicorn
模型为 POST
操作创建了 5000
端口,作为演示,我们仍然使用 Docker 的功能将外部请求从 5000
端口路由到 5000
端口,这是 uvicorn
监听的位置。
这应该会显示最后几行的提示,如下所示:
图 18.4:应用程序启动过程中的消息
- 现在,从新终端运行
curl
请求并访问 API,如前文所述,但这次应用程序是通过 Docker 提供的:
图 18.5:执行预测请求
尽管到目前为止我们还没有将任何东西移至云端,但将 API 包装在 Docker 中可以避免我们再次担心 pip install
或复制粘贴代码的问题。
现在我们已经了解了如何将应用程序容器化,让我们继续,在接下来的部分中将 Docker 容器在云上部署和运行。
在云上运行和部署 Docker 容器
我们将依赖 AWS 来满足我们的云需求。我们将使用 AWS 的两个免费服务来达到我们的目标:
-
弹性容器注册表 (ECR):在这里,我们将存储我们的 Docker 镜像。
-
EC2:在这里,我们将创建一个 Linux 系统来运行我们的 API Docker 镜像。
在本节中,我们将仅关注 ECR。以下是将 Docker 镜像推送到云的步骤的高级概述:
-
在本地机器上配置 AWS。
-
在 AWS ECR 上创建一个 Docker 仓库并推送
sdd:latest
镜像。 -
创建一个 EC2 实例。
-
在 EC2 实例上安装依赖项。
-
在 EC2 实例上创建并运行推送的 Docker 镜像 第二步。
下面各节的代码摘要也可以在视频演示中找到:tinyurl.com/MCVP-FastAPI2AWS
。
让我们从下一节开始实施上述步骤,首先配置 AWS。
配置 AWS
我们将从命令提示符登录到 AWS 并推送我们的 Docker 镜像。让我们逐步进行:
-
在
aws.amazon.com/
创建一个 AWS 账户并登录。 -
在本地安装 AWS CLI(其中包含 Docker 镜像)。
-
通过在本地终端中运行
aws --version
来验证是否已安装。 -
在终端中运行
aws configure
,在询问时提供适当的凭据。这些凭据可以在 IAM 部分找到:$ aws configure AWS Access Key ID [None]: **AKIAIOSFODNN7EXAMPLE** AWS Secret Access Key [None]:**wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY** Default region name [None]: **region** Default output format [None]:**json**
我们现在已经从我们的计算机登录到了 Amazon 的服务。在接下来的部分,让我们连接到 ECR 并推送 Docker 镜像。
创建 AWS ECR 上的 Docker 仓库并推送镜像
现在,我们将创建 Docker 仓库,如下所示:
-
配置后,使用以下命令登录到 AWS ECR(以下代码都在一行上),其中需要提供
region
和您的aws_accound_id
,如下面代码中的粗体所示(请记住在每一步中使用您自己的这些变量值):$ aws ecr get-login-password --region **region****aws_account_id.****region** | docker login --username AWS --password-stdin dkr.ecr..amazonaws.com
上述代码行在亚马逊云中创建并连接您自己的 Docker 注册表。
-
通过运行以下命令在 CLI 中创建仓库:
$ aws ecr create-repository --repository-name sdd_app
上述代码创建了一个可以存放您的 Docker 镜像的云端位置。
通过运行以下命令为本地镜像打标签,以便在推送镜像时将其推送到已标记的仓库:
-
$ docker tag sdd:latest **aws_account_id****region**.dkr.ecr..amazonaws.com/sdd_app
-
运行以下命令将本地 Docker 镜像推送到云中的 AWS 仓库:
$ docker push **aws_account_id****region**.dkr.ecr..amazonaws.com/sdd_app
我们已成功在云中为我们的 API 创建了一个位置,并将 Docker 镜像推送到了该位置。正如您现在所知,这个镜像已经具备了运行 API 所需的所有组件。唯一剩下的就是在云中将其创建成 Docker 容器,这样我们就成功地将我们的应用程序推向了生产环境!
-
现在,让我们继续创建一个 Amazon Linux 2 AMI -
t2.micro
实例,带有 20 GB 的空间。在创建实例时,在配置安全组
部分添加允许 HTTP 流量规则
,以便我们将要部署的应用程序可以从任何地方访问。 -
复制看起来像这样的 EC2 实例名称:
ec2-18-221-11-226.us-east-2.compute.amazonaws.com
-
使用以下命令在本地终端登录到 EC2 实例:
$ ssh ec2-user@ec2-18-221-11-226.us-east-2.compute.amazonaws.com
接下来,让我们安装在 EC2 机器上运行 Docker 镜像所需的依赖项,然后我们就可以运行 API 了。
拉取镜像并构建 Docker 容器
所有以下命令都需要在我们之前登录到的 EC2 控制台中运行:
-
在 Linux 机器上安装和配置 Docker 镜像:
$ sudo yum install -y docker $ sudo groupadd docker $ sudo gpasswd -a ${USER} docker $ sudo service docker restart
groupadd
和 gpasswd
确保 Docker 具有运行所需的所有权限。
-
如您之前所做的那样,在 EC2 实例中配置 AWS,并重新启动机器:
$ aws configure AWS Access Key ID [None]: **AKIAIOSFODNN7EXAMPLE** AWS Secret Access Key [None]:**wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY** Default region name [None]: **us-west-****2** Default output format [None]:**json** $ reboot
-
使用以下命令从本地终端重新登录到实例:
$ ssh ec2-user@**ec2-****18****-****221****-****11****-****226****.us-east-****2****.compute.amazonaws.com**
-
现在,从已登录(已安装 Docker)的 EC2 控制台(其中已登录到 AWS ECR),登录 AWS ECR(根据以下命令中显示的粗体区域更改区域):
$ aws ecr get-login-password --region **region****aws_account_id.****region** | docker login --username AWS --password-stdin dkr.ecr..amazonaws.com
-
从 AWS ECR 拉取 Docker 镜像:
$ docker pull **aws_account_id****region**.dkr.ecr..amazonaws.com/sdd_app:latest
最后,在 EC2 机器上运行已拉取的 Docker 镜像:
-
docker run -p 5000:5000 **aws_account_id****region**.dkr.ecr..amazonaws.com/sdd_app
我们在 EC2 上运行我们的 API。我们只需获取机器的公共 IP 地址,并用该地址替换127.0.0.1
运行curl
请求。
-
现在您可以从任何计算机发起
POST
请求,EC2 实例将对其进行响应,并为我们上传的衣物图像提供预测:$ curl -X POST "http://54.229.16.169:5000/predict" -H "accept: application/ json" -H "Content-Type: multipart/form-data" -F "file=@/home/me/Pictures/ defect.png;type=image/png"
上述代码会产生以下输出:
-
{"class":"non-defect","confidence":"0.6488"}
在本节中,我们已经能够为 EC2 安装依赖项,拉取 Docker 镜像,并运行 Docker 容器,以便任何具有 URL 的用户可以对新图像进行预测。
整个过程的概述可以在此视频演示中查看:https://tinyurl.com/MCVP-FastAPI2AWS
。
可以在关联的 GitHub 存储库的Chapter18
文件夹中的 PDF 文件中找到具有屏幕截图的逐步说明,标题为Shipping and running the Docker container on cloud
。
现在我们了解了如何部署模型,接下来我们将学习如何识别在实际输入数据(在真实世界中)与训练时使用的数据相比时处于分布之外的场景。
识别数据漂移
在典型的表格数据集中,通过查看模型训练的数据集的摘要统计信息,可以相对容易地理解传入数据点是否为异常值。然而,计算机视觉模型并不那么直接 - 我们已经在第四章中看到它们的怪癖,在那里,仅仅通过将像素平移几个像素,预测的类别就发生了变化。然而,在图像的情况下,这并不是唯一的数据漂移场景。进入生产模型的数据与模型训练所用的数据有很多不同之处。这些可能是显而易见的事情,比如图像的照明不对或者图像中预期的主题错误,也可能是人眼看不见的微妙之处。
在本节中,我们将了解如何实时测量输入图像(用于预测的真实世界图像)与模型训练期间使用的图像之间的漂移方式。
我们测量漂移的原因如下:
-
如果真实世界的输入图像不像训练时使用的图像,那么我们可能会错误地进行预测。
-
此外,我们希望在尽早实施模型后尽快获得反馈,以便我们可以重新训练/微调模型。
我们将采用以下策略来确定新图像是否为分布之外:
-
从中间层提取嵌入(例如,
avgpool
层)。 -
对训练图像和来自真实世界的任何新图像执行上述步骤:
-
比较新图像的嵌入与所有训练图像的嵌入:
-
您还可以尝试仅使用与新图像同一类别的训练图像的嵌入进行实验。
-
如果新图像的嵌入与训练图像的嵌入之间的距离较大,则新图像属于分布之外。
我们将在代码中实现上述内容如下:
以下代码位于书籍 GitHub 存储库的Chapter18
文件夹中的measuring_drift.ipynb
中,网址为https://bit.ly/mcvp-2e
。
-
获取包含训练模型代码的存储库:
%%capture try: from torch_snippets import * except: %pip install torch-snippets gitPython lovely-tensors from torch_snippets import * from git import Repo repository_url = 'https://github.com/sizhky/quantization' destination_directory = '/content/quantization' if exists(destination_directory): repo = Repo(destination_directory) else: repo = Repo.clone_from(repository_url, destination_directory) %cd {destination_directory} %pip install -qq -r requirements.txt
-
训练模型:
# Change to `Debug=false` in the line below # to train on a larger dataset %env DEBUG=true !make train
-
获取训练集、验证集以及数据加载器:
from torch_snippets import * from src.defect_classification.train import get_datasets, get_dataloaders trn_ds, val_ds = get_datasets(DEBUG=True) trn_dl, val_dl = get_dataloaders(trn_ds, val_ds)
-
加载模型:
model = torch.load('model.pth').cuda().eval()
-
获取训练过程中使用的图像的嵌入:
results = [] for ix, batch in enumerate(iter(trn_dl)): inter = model.avgpool(model.features(batch[0].cuda()))[:,:,0,0].\ detach().cpu().numpy() results.append(inter) results = np.array(results) results = results.reshape(-1, 512)
-
获取样本验证图像的嵌入,并计算与训练图像的嵌入之间的距离:
im = val_ds[0]['image'][None].cuda() tmp = np.array(model.avgpool(model.features(im))[0,:,0,0].detach().cpu().\ numpy()) dists1 = np.sum(np.abs(results - tmp), axis=1)
-
现在,让我们使用一个完全无关的图像进行相同的练习:
!wget https://as2.ftcdn.net/v2/jpg/01/42/16/37/1000_F_142163797_YxZaY95j5ckLgb6KoM5KC11Eh9QiZsYx.jpg -O /content/sample_defects.jpg im = (cv2.imread(path)[:,:,::-1]) im = cv2.resize(im, (224,224)) im = torch.tensor(im/255) im = im.permute(2,0,1).float().to(device) im = im.view(1,3,224,224) tmp = np.array(model.avgpool(model.features(im))[0,:,0,0].detach().cpu().\ numpy()) dists2 = np.sum(np.abs(results - tmp), axis=1)
-
让我们绘制训练图像与验证图像以及无关(随机)图像之间距离分布:
import seaborn as sns df = pd.DataFrame( {'distance': np.r_[dists1, dists2], 'source': ['val image']*len(dists1)+['random image']*len(dists2)}) # Just switch x and y sns.boxplot(y=df["source"], x=df["distance"])
图 18.6:训练图像与验证图像以及随机图像之间的距离分布
从上述内容可以看出,与来自分布内部的图像的距离范围相比,随机图像的距离范围差异极大。通过这种方式,我们可以了解我们正在预测的图像是否落在训练图像的分布范围内。此外,当超过阈值的现实世界图像数量(或百分比)超出分布时,考虑重新训练模型是值得的。
在本节中,我们计算了预测图像与所有训练图像之间的距离。在这种情况下,由于训练图像的数量有限(几百个),这变得更加容易。
如果训练图像数量达到约一百万?我们如何快速执行距离计算?在接下来的部分中,我们将学习向量存储是如何帮助解决这个问题的。
使用向量存储
向量存储的直觉是:如果我们可以将所有向量分组到某些簇中,对于新向量,我们可以首先确定它可能属于的簇,然后计算新向量与属于同一簇的图像之间的距离。
这个过程有助于避免跨所有图像的计算,从而大大减少计算时间。
FAISS 是由 Meta 构建的开源库,用于在向量之间进行快速的近似相似性搜索。有广泛的开源和专有向量存储库。我们强烈建议您在理解向量存储的需求后,查看这些库。
现在我们了解了向量存储,让我们继续执行以下步骤:
-
将训练图像嵌入存储在向量存储中。
-
比较检索查询(验证/实际)图像的三个最接近图像所需的时间,即:
-
没有使用向量存储
-
我们使用向量存储
-
-
增加训练图像数量,然后比较在前述两种情况下检索给定图像最接近图像所需的时间。
让我们了解如何编写代码:
以下代码作为 Chapter18
文件夹中的 vector_stores.ipynb
在 GitHub 仓库 bit.ly/mcvp-2e
中提供。
-
安装 FAISS 库:
!pip install faiss-gpu
在前一节(关于数据漂移)的前五个步骤保持不变——即获取训练和验证数据集,训练和加载模型,并获取训练图像的嵌入。
-
对所有训练图像的向量进行索引:
import faiss import numpy as np index = faiss.IndexFlatL2(results.shape[1]) # L2 distance index.add(results) faiss.write_index(index, "index_file.index")
在上述代码中,我们获取训练嵌入(结果)并使用 IndexFlatL2
方法进行索引。接下来,我们将索引写入磁盘。
-
获取新图像的向量:
im = val_ds[0]['image'][None].cuda() tmp = np.array(model.avgpool(model.features(im))[0,:,0,0].detach().cpu().\ numpy()) query_vector = tmp.reshape(1,512).astype('float32')
-
查找最相似的向量:
%%time k = 3 # Number of nearest neighbors to retrieve D, I = index.search(query_vector.astype('float32'), k)
在上述代码中,D
表示距离,I
表示与查询图像最相似的图像的索引。我们可以看到,搜索所有图像花费了 0.2 毫秒。
-
大幅增加训练图像向量的数量:
vectors = np.array(results.tolist()*10000, dtype=np.float32) print(vectors.shape) index = faiss.IndexFlatL2(vectors.shape[1]) # L2 distance index.add(vectors) faiss.write_index(index, "index_file_960k.index")
在上述代码中,我们通过重复训练图像列表 10000 次来人为增加训练图像嵌入的数量。然后,我们对嵌入进行索引并写入磁盘。
-
计算搜索与查询向量最接近的向量所需的时间:
%%time k = 3 # Number of nearest neighbors to retrieve D, I = index.search(query_vector.astype('float32'), k)
请注意,在前述情况下,获取相似向量需要约 700 毫秒。
-
计算在没有索引的情况下检索三个最接近向量所需的时间:
%%time distances = np.sum(np.square(query_vector - vectors), axis=1) sorted_distances = np.sort(distances)
请注意,在没有索引的情况下,获取最接近向量需要约 5 秒钟。
在以下图表中,我们将看到在两种情况下(索引和非索引)随着训练图像数量增加,查找给定图像的最接近训练图像所需的时间:
图 18.7:在增加训练图像数量时,比较带有索引和不带索引时识别最接近匹配图像所需的时间。
注意,有索引与无索引之间的差异随着训练图像数量的增加呈指数级增长。尽管如此,在信息检索对企业至关重要的世界中,以最快的时间获得解决方案是重要的,而向量存储是节省时间的关键方式。
总结
在本章中,我们了解了将模型部署到生产环境中所需的额外步骤。我们学习了 API 是什么及其组成部分。在使用 FastAPI 创建 API 后,我们简要介绍了创建 API Docker 镜像、创建 Docker 容器并将其推送到云端的核心步骤。然后,我们学习了如何识别与原始数据集不符的图像的方法。此外,我们还学习了如何利用 FAISS 更快地计算与相似向量的距离。
总结一下,我们已经看到了将模型部署到生产环境中的所有步骤,包括构建 Docker 容器、在 AWS 云上部署、识别数据漂移以及了解何时重新训练模型的场景,并通过更少的计算快速执行图像相似性。
图像是迷人的。存储图像是人类最早的努力之一,也是捕捉内容最强大的方式之一。在 21 世纪,捕捉图像的便捷性开启了解决多种问题的多样方法,无论是需要人工干预还是不需要。在本书中,我们涵盖了一些常见和现代任务,包括使用 PyTorch 进行图像分类、目标检测、图像分割、图像生成、操作生成的图像、利用基础模型完成各种任务、将计算机视觉与自然语言处理技术结合以及强化学习。我们从头开始学习了各种算法的工作细节。我们还了解了如何制定问题、捕获数据、训练模型并从训练模型中推断。我们理解了如何选择代码库/预训练模型并为我们的任务定制它们,最后,我们学习了如何以优化的方式部署我们的模型并加入漂移监测。
我们希望你已经掌握了处理图像的必要技能,就像它是你的第二天性一样,并且能够解决你感兴趣的任务。
最重要的是,我们希望这对你来说是一段愉快的旅程,希望你在阅读这本书时能和我们写作时一样享受!
问题
-
什么是 REST API 以及它的作用是什么?
-
Docker 是什么,为什么它对部署深度学习应用程序很重要?
-
在生产环境中检测与训练时使用的图像明显不同的异常或新颖图像的一个简单而常见的技术是什么?
-
如何加速大量向量图像的相似性搜索(百万级)?
在 Discord 上了解更多
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
附录
第一章,人工神经网络基础
- 神经网络中的各种层是什么?
输入、隐藏和输出。
- 前向传播的输出是什么?
帮助计算损失值的预测。
- 连续因变量的损失函数与二元或分类因变量的损失函数有何不同?
均方误差 (MSE)通常用于连续因变量的损失函数,而二元交叉熵通常用于二元因变量。分类交叉熵用于分类因变量。
- 什么是随机梯度下降?
这是通过调整权重以减少梯度来降低损失的过程。
- 一个反向传播练习的目的是什么?
使用链式法则计算所有权重相对于损失的梯度。
- 在反向传播期间如何更新所有层中的权重?
它使用公式 W_new = W – alpha(dL/dW)* 进行更新。
- 在训练神经网络的每个 epoch 中执行了哪些功能?
对于每个 epoch 中的每个批次,您执行前向传播,计算损失,使用损失进行反向传播计算权重梯度,并更新权重。然后继续下一个批次,直到所有 epoch 完成。
- 在 GPU 上训练网络比在 CPU 上训练更快的原因是什么?
在 GPU 硬件上可以并行执行更多的矩阵操作。
- 训练神经网络时学习率的影响是什么?
学习率过高会导致权重爆炸,而学习率过小则根本不会改变权重。
- 学习率参数的典型值是多少?
通常在1e-2到1e-5之间,但这取决于许多因素,例如正在使用的架构、数据集和优化器。
第二章,PyTorch 基础
- 为什么在训练过程中应将整数输入转换为浮点值?
nn.Linear
(以及几乎所有 torch 层)只接受浮点数作为输入。
- 用于重塑张量对象的方法是什么?
tensor.view(shape)
、permute
、flatten
、squeeze
和unsqueeze
。
- 为什么使用张量对象计算速度比使用 NumPy 数组快?
只有张量对象才能并行运行在 GPU 上的能力。
- 在神经网络类中,
init
魔术函数包含什么?
调用super().__init__()
并指定神经网络层。
- 为什么在执行反向传播之前要进行零梯度操作?
确保前一次计算的梯度被清除的原因是什么?
- 哪些魔术函数构成数据集类?
__len__
和__getitem__
。
- 如何在新数据点上进行预测?
通过在张量上调用模型,如同调用函数一样 – model(x)
。
- 如何获取神经网络的中间层值?
通过创建一个自定义方法,该方法可以仅执行到中间层的 forward
,或者通过在 forward
方法本身中将中间层的值作为额外输出返回。
Sequential
方法如何帮助简化神经网络架构的定义?
我们可以通过连接一系列的层来避免创建 __init__
和 forward
方法。
- 在更新
loss_history
时,我们附加loss.item()
而不是loss
。这样做有什么作用,为什么附加loss.item()
而不只是loss
是有用的?
loss.item()
将一个零维的 torch 张量转换成 Python 浮点数,占用更少的内存,存储在 CPU 上,并可被其他库(如绘图、统计等)使用。
- 使用
torch.save(model.state_dict())
的优势是什么?
通过仅保存模型的状态字典而不是整个模型对象,可以显著减少所需的存储空间。此外,这种方法使得在不同设备和环境之间传输模型变得更加容易,促进了模型的部署和共享。由于状态字典是一个字典,模型(及其中间层)可以独立复制、更新、修改和存储。
第三章,使用 PyTorch 构建深度神经网络
- 如果输入数据集中的输入值没有进行缩放会发生什么?
当输入值未缩放时,调整权重到最佳值需要更长时间,因为输入值变化范围很广。有可能模型根本无法学习。
- 当训练神经网络时,背景为白色像素而内容为黑色像素时可能会出现什么问题?
数据的均值接近 1(1 表示白色,0 表示黑色)。这可能导致神经网络训练时间较长,因为初始阶段许多神经元被激活。网络需要在开始阶段学会忽略大部分白色内容。
- 批量大小对模型的训练时间和内存有什么影响?
批量大小越大,训练该批次所需的时间和内存就越多。
- 输入值范围对训练结束时权重分布的影响是什么?
如果输入值没有缩放到某个范围,某些权重可能导致过拟合或导致梯度消失/爆炸。
- 批量归一化如何帮助提高准确性?
就像我们为了 ANN 更好的收敛而需要对输入进行缩放一样,批量归一化为其下一层的收敛性而缩放激活值。
- 为什么在 dropout 层中,权重在训练和评估时表现不同?
在训练期间,通过 dropout 缩放权重,它随机将输入单元的一部分设置为零,有助于防止过拟合。在评估期间,通常关闭 dropout,这导致权重的行为与全网络容量被利用而不是缩放下的行为不同。
- 我们如何知道模型是否在训练数据上过拟合?
验证损失将远高于训练损失。
- 正则化如何帮助避免过拟合?
正则化技术帮助模型在受限环境中训练,从而迫使 ANN 以较少偏向的方式调整其权重。
- L1 和 L2 正则化在哪些方面有所不同?
L1 是权重绝对值的总和,而 L2 是权重平方的总和,加到损失值和典型损失中。
- 如何通过dropout减少过拟合?
通过在 ANN 中丢弃一些连接,我们迫使单个神经元在每次迭代时从更少的数据和不同的输入子集学习。这迫使模型学习而不依赖于特定的神经元或固定的连接。
第四章,介绍卷积神经网络
- 使用传统神经网络时,在第一章节中使用翻译图像的预测为何较低?
所有图像都居中在原始数据集中,因此 ANN 只学习了对居中图像的任务。
- 卷积是如何进行的?
通过在输入信号上滑动一个小的过滤器或内核进行卷积,逐元素相乘并求和以产生每个位置的输出值。对输入信号的每个位置重复此过程,从而产生一个新的输出信号,表示原始输入信号中的模式或特征的存在。
- 如何确定滤波器中的最佳权重值?
通过反向传播。
- 卷积和池化的组合如何帮助解决图像翻译问题?
虽然卷积提供重要的图像特征,但池化提取图像块中最显著的特征。这使得池化成为一种强大的操作;即使通过少量像素进行平移,池化仍会返回预期的输出。
- 靠近输入的卷积滤波器学习什么?
像边缘这样的低级特征。
- 池化的功能有助于模型构建的哪些方面?
通过减少特征图大小来减少输入大小,并使模型具有平移不变性。
- 为什么我们不能像对 Fashion-MNIST 数据集那样将输入图像展平,然后为真实世界图像训练模型?
如果图像尺寸稍大,连接两个层的参数数量将达到数百万,导致大量计算和潜在的不稳定训练。
- 数据增强如何帮助提高图像翻译?
数据增强创建了通过少量像素平移的图像副本。因此,即使图像中的对象偏离中心,模型也被迫学习正确的类别。
- 在哪种情况下我们会利用
collate_fn
来处理数据加载器?
当需要执行批级转换时,使用__getitem__
执行起来会很困难/慢。
- 变化训练数据点的数量对验证数据集的分类准确性有什么影响?
一般来说,数据集大小越大,模型的准确性越高。
第五章,图像分类的迁移学习
- VGG 和 ResNet 预训练架构是在什么上训练的?
ImageNet 数据集中的图像。
- 为什么 VGG11 的准确率不如 VGG16?
VGG11 比 VGG16 拥有更少的层/块/参数。
- VGG11 中的数字 11 代表什么?
11 层组。每个组都有两个卷积层,后面是 ReLU 和 maxpool2d。
- “残差网络”中的“残差”一词是什么意思?
“残差”一词指的是残差学习的概念,其中有一条快捷连接跳过了一个或多个层,并直接将输入添加到后续层的输出中,有助于训练非常深的网络。
- 残差网络的优势是什么?
它有助于防止梯度消失,并且通过允许梯度直接通过快捷连接传递到初始层来增加模型深度而不丢失准确性。
- 书中讨论的各种流行的预训练模型及其各自的特点是什么?
AlexNet 是第一个成功的卷积神经网络。VGG 通过使其更深而改进了 AlexNet。Inception 引入了一个 Inception 层,其中包含多个不同大小的卷积滤波器和池化操作。ResNet 引入了跳跃连接,有助于创建更深的网络。
- 在迁移学习期间,为什么应该使用与预训练模型训练中使用的相同均值和标准差来规范化图像?
模型是这样训练的,以期望输入图像被规范化为特定的均值和标准差。
- 在模型中何时以及为什么要冻结某些参数?
我们在重新训练活动中冻结某些参数(通常称为模型的骨干),以使这些参数在反向传播期间不会更新。它们不需要更新,因为它们已经训练得很好,这也将加快训练时间。
- 我们如何知道预训练模型中存在哪些模块?
print(model)
- 如何训练一个能够同时预测分类和数值的模型?
通过使用多个预测头,并针对每个头部单独训练。
- 如果我们执行与“实现年龄估计”和“性别分类”章节中编写的代码相同的代码,为什么年龄和性别预测代码对你自己的图像不一定总是有效?
图像如果与训练数据的分布不相似可能会产生意想不到的结果。图像可能来自不同的人口统计学/地理位置。
- 我们如何进一步提高我们在“实现面部关键点预测”章节中讨论的面部关键点识别模型的准确性?
我们可以在训练过程中添加噪声、颜色和几何增强。
第六章,图像分类的实际方面
- 如何获取类激活映射?
参考“生成 CAMs”部分中提供的八个步骤。
- 在训练模型时,批量归一化和数据增强如何帮助?
它们有助于减少过拟合。批量归一化通过在每一层归一化传入的数据来稳定学习过程,而数据增强则增加了训练集的多样性。
- CNN 模型过拟合的常见原因是什么?
过多的 CNN 层,没有批量归一化,缺乏数据增强和缺乏丢弃。
- 在数据科学家端使用训练和验证数据但在真实世界中不适用的各种情况是什么?
除了明显的情况如使用与训练期间不同的模型/库版本外,真实世界数据由于环境变化、传感器变化等多种因素可能与用于训练和验证模型的数据分布不同。此外,模型可能已经在训练数据上过拟合。
- 我们何时利用 OpenCV 包,以及在何时使用 OpenCV 胜过深度学习的优势是什么?
在受限环境中工作时,我们知道图像的行为范围有限,因此我们更喜欢使用 OpenCV,因为编码解决方案的时间更快。在受限环境中,速度更重要时,也更喜欢使用它。
第七章,目标检测的基础知识
- 区域提议技术如何生成提议?
它识别颜色、纹理、大小和形状相似的区域。
- 如果图像中有多个对象,如何计算交并比(IoU)?
对于每个对象与真实情况的 IoU 是独立计算的,使用 IoU 指标,其中分子是对象与真实情况之间的交集面积,分母是对象与真实情况之间的并集面积。
- 为什么 Fast R-CNN 比 R-CNN 更快?
对于所有提议,从 VGG 主干获取特征图是常见的。重复使用这个特征图减少了 Fast R-CNN 中的计算量,几乎比 R-CNN 再次为所有提议计算这些特征少了 90%。
- RoI 池化如何工作?
所有来自selectivesearch
的裁剪图像都通过自适应最大池化核传递,以使最终输出的大小相同。
- 在预测边界框修正后从特征图获取多层次有什么影响?
模型将难以捕捉特征之间的复杂关系,并且无法产生准确的边界框修正。
- 当计算整体损失时,为什么我们必须为回归损失分配更高的权重?
分类损失是交叉熵,通常为log(n)
阶,导致输出可以有很高的范围。然而,边界框回归损失在 0 到 1 之间。因此,必须将回归损失进行放大。
- 非最大抑制(non-max suppression)如何工作?
通过结合相同类别且具有高 IoU 的框,我们消除了冗余的边界框预测。
第八章,高级物体检测
- Faster R-CNN 相对于 Fast R-CNN 为何更快?
我们不需要每次使用selectivesearch
技术喂入大量不必要的提议。相反,Faster R-CNN 使用区域建议网络自动找到它们。
- YOLO 和 SSD 与 Faster R-CNN 相比为何更快?
我们不需要依赖新的提议网络。网络直接在一次运行中找到提议。
- 为什么 YOLO 和 SSD 是单次检测器算法?
网络一次性预测所有提议和预测。
- 物体性得分和类别得分之间有什么区别?
物体性得分标识物体是否存在,而类别得分预测具有非零物体性的锚框的类别。
第九章,图像分割
- 在 U-Net 架构中,放大(upscaling)如何帮助?
放大(upscaling)帮助特征图增加尺寸,使得最终输出与输入尺寸相同。
- 为什么我们需要在 U-Net 中使用完全卷积网络?
因为输入和输出都是图像,使用线性层预测图像形状的张量是困难的。
- RoI Align 如何改进 Mask R-CNN 中的 RoI 池化?
RoI Align 使用预测提议的偏移量来精细对齐特征图。
- U-Net 和 Mask R-CNN 在分割上的主要区别是什么?
U-Net 是完全卷积的,具有单一的端到端网络,而 Mask R-CNN 使用 Backbone、RPN 等小网络来执行不同的任务。Mask R-CNN 能够识别和分离多个相同类型的对象,但 U-Net 只能识别它们(不能将它们分隔为单独的实例)。
- 什么是实例分割?
如果同一图像中有不同类别的多个对象,则每个对象称为实例。将图像分割应用于分别预测所有实例的像素级别称为实例分割。
第十章,物体检测和分割的应用
- 将数据集转换为 Detectron2 特定格式的重要性是什么?
Detectron2 是一个可以同时训练/评估多种架构的框架。为了在同一代码中实现这种灵活性,重要的是对数据集施加特定的限制,以便框架可以操作数据集。因此,建议所有 Detectron2 数据集都采用 COCO 格式。
- 直接对图像中的人数进行回归是困难的。VGG 架构成功进行人群计数的关键见解是什么?
我们将目标图像转换为热图,其中所有像素的强度之和等于图像中的人数。
- 解释图像着色的自监督学习。
我们能够通过简单地将图像转换为黑白(BW)来创建给定图像的输入输出对。我们将 BW 图像视为输入,将彩色图像视为预期输出。
- 我们如何将 3D 点云转换为与 YOLO 兼容的图像?
在每个时间点,我们从顶部视角查看 3D 点云,并使用红色通道编码最高点,绿色通道编码最高点的强度,蓝色通道编码点的密度。
- 使用仅适用于图像的架构处理视频的简单方法是什么?
这样做的简单方法是将帧的维度视为批次维度,并汇总所有帧的特征向量以获得单个特征向量。
第十一章,自编码器和图像操作
- “自编码器”中的“编码器”是什么?
将图像转换为向量表示的神经网络。
- 自编码器优化的损失函数是什么?
像素级均方误差直接将预测与输入进行比较。
- 自编码器如何帮助将相似图像分组?
相似图像将返回相似的编码,更容易进行聚类。
- 卷积自编码器何时有用?
当输入为图像时,使用卷积自编码器有助于图像去噪、图像重建、异常检测、图像数据漂移、质量控制等任务。
- 如果从普通/卷积自编码器获得的嵌入向量空间中随机抽样,为什么会得到非直观的图像?
编码中值的范围不受限制,因此适当的输出高度依赖于正确的数值范围。一般而言,随机抽样假设平均值为 0,标准偏差为 1,这可能不是良好编码范围。
- 变分自编码器优化的损失函数是什么?
像素级均方误差和编码器均值和标准偏差的 KL 散度。
- 变分自编码器如何克服普通/卷积自编码器生成新图像的限制?
通过将预测的编码约束为正态分布,所有编码落入均值为 0,标准偏差为 1 的区域,易于抽样。
- 在对抗攻击期间,为什么修改输入图像像素而不是权重值?
在对抗攻击中,我们无法控制神经网络。
- 在神经风格迁移中,我们优化哪些损失?
生成图像的感知(VGG)损失与原始图像的损失以及生成图像和样式图像的 Gram 矩阵之间的风格损失有关。
- 在计算风格和内容损失时,为什么考虑不同层的激活而不是原始图像?
使用更多中间层确保生成图像保留有关图像的更细节部分。此外,使用更多损失使梯度上升更稳定。
- 在计算风格损失时,为什么要考虑 gram 矩阵损失而不是图像之间的差异?
gram 矩阵损失提供了图像风格的指示,即纹理、形状和颜色的排列方式,并忽略实际内容。这就是为什么它更适合风格损失的原因。
- 在构建用于生成深度伪造模型时为什么要扭曲图像?
扭曲图像有助于充当正则化器。此外,它有助于生成所需数量的图像,有助于增加数据集。
第十二章,使用 GAN 生成图像
- 如果生成器和判别器模型的学习率很高会发生什么?
模型的稳定性将会很低。
- 在生成器和判别器训练得非常好的情况下,给定图像是真实的概率是多少?
0.5
- 为什么我们使用 ConvTranspose2d 来生成图像?
我们不能使用线性层来放大/生成图像。ConvTranspose2d 是一种通过参数化/神经网络方式将图像上采样到更大分辨率的方法。
- 在条件 GAN 中,为什么嵌入的嵌入大小比类数更高?
使用更多参数使模型具有更多学习每个类别重要特征的自由度。
- 如何生成有胡须的男性图像?
通过使用条件 GAN。就像我们有男性和女性图像一样,我们可以在训练模型时有胡须的男性等类别的图像。
- 为什么我们在生成器的最后一层使用 Tanh 激活函数而不是 ReLU 或 sigmoid?
标准化图像的像素范围是 [-1,1]
,因此我们使用 Tanh。
- 即使我们没有对生成的数据进行反标准化,我们仍然获得了逼真的图像,为什么?
即使像素值不在 [0,255]
范围内,相对值对于 make_grid
实用程序来说也足够进行反标准化输入。
- 如果在训练 GAN 前不对应于图像裁剪面部会发生什么?
如果背景太多,GAN 可能会错误地生成背景和面部的信号,因此它可能会集中于生成更逼真的背景。
- 当训练生成器时,为什么判别器的权重不会得到更新(因为
generator_train_step
函数涉及判别器网络)?
这是一个逐步过程。在更新生成器时,我们假设判别器能够做到最好。
- 在训练判别器时为什么要获取真实和假图像的损失,而在训练生成器时只获取假图像的损失?
因为生成器创建的都是虚假图像。
第十三章,用于操作图像的高级 GAN
- 如果像 U-Net 这样的监督学习算法可以生成由轮廓图生成图像,为什么我们还需要 Pix2Pix GAN?
U-Net 在训练期间仅使用像素级损失。在 U-Net 生成图像时,由于没有现实感的损失,我们需要 Pix2Pix。
- 为什么我们需要优化 CycleGAN 中三种不同的损失函数?
在 CycleGAN 中,我们优化对抗损失、循环一致性损失和身份损失,以便在保留原始图像结构和外观的同时学习两个域之间更准确的映射。在CycleGAN 工作原理部分提供了详细答案。
- ProgressiveGAN 使用的技巧如何帮助构建 StyleGAN 模型?
ProgressiveGAN 每次帮助网络学习几个上采样层,这样当需要增加图像尺寸时,负责生成当前尺寸图像的网络就会更加优化。
- 如何识别与给定自定义图像对应的潜在向量?
通过调整随机生成的噪声,使得生成图像与目标图像之间的均方误差尽可能小。
第十四章,结合计算机视觉和强化学习
- 代理如何计算给定状态的值?
通过计算在该状态下的预期奖励。
- Q 表如何填充?
通过计算每个状态-动作对的预期奖励,即即时奖励和预期未来奖励的总和。这种计算在每个 episode 中都会得到改进,从而每次估计都会更加准确。
- 在状态动作值计算中为什么有折扣因子?
由于不确定性,我们不确定未来可能会如何运作。因此,我们通过折扣的方式减少未来奖励的权重。
- 为什么我们需要探索-利用策略?
仅利用会使模型停滞和可预测,因此模型应能够探索并找到未曾见过的步骤,这可能比模型已学到的更有回报。
- 为什么我们需要使用深度 Q 学习?
我们让神经网络学习可能的奖励系统,而无需使用可能耗时或需要完全环境可见性的昂贵算法。
- 使用深度 Q 学习如何计算给定状态-动作组合的值?
这只是神经网络的输出。输入是状态,网络预测给定状态中每个动作的预期奖励。
- 一旦代理在 CartPole 环境中最大化了奖励,是否有可能以后学习到次优策略?
如果神经网络由于糟糕的 episode 而忘记了,有一小部分非零机会学习到一些次优内容。
第十五章,结合计算机视觉和自然语言处理技术
- 自注意力的输入、计算步骤和输出是什么?
自注意力的输入是一系列向量。步骤包括计算每对向量之间的注意力分数,首先将向量转换为键向量和查询向量。这两个向量进行矩阵乘法,然后通过 softmax 函数获得注意力权重。然后使用注意力权重计算值向量的加权和。生成的上下文向量被结合并通常通过额外的层,如前馈神经网络,产生最终的输出。
- 如何在视觉变压器中将图像转换为序列输入?
在视觉变压器中,首先将图像切割成固定大小的网格补丁,然后通过 Conv2D 层将其转换为向量序列。
- 在 LayoutLM 模型中,BERT 变压器的输入是什么?
箱子中包含的文本和箱子的二维位置是模型的输入。
- BLIP 的三个目标是什么?
图像-文本匹配、图像驱动文本生成和图像-文本对比学习。
第十六章,计算机视觉中的基础模型
- 如何使用 CLIP 将文本和图像表示在相同的领域中?
通过使用对比损失,我们强制来自相同来源的图像和文本的嵌入尽可能相似,同时确保不同来源的嵌入尽可能不同。
- 在 Segment Anything 架构中,如何计算不同类型的令牌,如点令牌、边界框令牌和文本令牌?
通过使用一个接受点、框或文本的提示编码器模型,并使用分割意义对象的预训练任务来描述点下面的有意义对象、在框内部或由文本描述。
- 扩散模型如何工作?
它们通过逐步去噪声图像,从完全噪声到部分噪声到无噪声来工作。在每个步骤中,模型确保生成的图像仅比当前图像稍好。
- 什么使稳定扩散与普通扩散不同?
与普通扩散不同,在稳定扩散中,整个去噪训练发生在由变分自编码器编码/解码的潜空间中,使训练速度显著加快。
- 稳定扩散和 SDXL 模型之间有什么区别?
SDXL 已经在大小为 1,024 的图像上进行了训练,而标准模型则在 512 像素空间中工作。
第十七章,稳定扩散的应用
- 使用稳定扩散进行图像修复的关键概念是什么?
通过仅在掩码区域生成潜变量并在未掩码区域保留潜变量,我们确保在背景中保持空间一致性,同时根据需要修改前景。
- ControlNet 的关键概念是什么?
有两个关键概念。首先,我们通过复制稳定扩散 UNet2D 模型的下采样路径来创建一个 ControlNet 分支。该分支中所有模块的最后一层都是零卷积层,并且每个模块都作为跳跃连接添加到相应的上采样分支。其次,通过专门训练新分支以接受诸如 canny 之类的特殊图像,训练速度更快。
- SDXL-Turbo 比 SDXL 更快的关键因素是什么?
SDXL-Turbo 遵循一种师生训练范式,其中学生在一次去噪中学习几个步骤,使学生能够在更少的步骤中预测与老师相同的输出,即更快。
- DepthNet 背后的关键概念是什么?
修改稳定扩散模型 UNet2D,使其能接受五个通道而不是通常的四个,第五个通道是深度图。这允许准确预测深度图像。
第十八章,将模型移至生产环境
- REST API 是什么以及它的作用是什么?
这是一个程序通过 HTTP 方法在互联网上进行通信的接口。
- Docker 是什么,为什么它对部署深度学习应用程序如此重要?
Docker 是一个容器化平台,允许开发者将应用程序打包、发布和在封闭环境(称为容器)中运行。在深度学习部署中,这些容器非常方便,开发者不必担心在多台机器上下载/安装库,并且可以根据负载扩展容器的规模。
- 在生产环境中,检测与训练过程中明显不同的异常或新颖图像的简单常见技术是什么?
首先,我们测量新图像特征向量与训练数据特征向量之间的距离。如果这个距离与验证数据中样本图像的距离相比过大,我们可以判断图像是否异常。
- 如何加速图像大量向量(数百万个)的相似性搜索?
通过使用诸如 FAISS 等现有库。这些方法通常根据聚类等技术预先索引大量向量,以便在相似性搜索期间更快地检索可能的向量候选项。
在 Discord 上了解更多信息
加入我们社区的 Discord 空间,与作者和其他读者讨论: