前言
扩散模型这几年很火,如 DALL·E 2和 Stable Diffusion,以其生成高质量逼真图像的能力而闻名,可以用于各种图像合成和编辑任务。然而,这些模型的易用性也引发了对其潜在滥用的担忧,例如,可能被用于创建不当或有害的数字内容。我们在之前文章中提到,这些工作可能会被用于生成色情图像等有害内容,或者可能会受到越狱攻击。
不过除此以外,其还存在另外一种恶意用途。
例如,恶意行为者可能会下载网上发布的照片,并使用现成的扩散模型对其进行恶意编辑。
在上图中可以看到,本来的原图是两个男人在体育场看比赛,但是通过提供这张图片,加上文字,比如两个男人跳交际舞,那么就会得到上图右边的图片,这就是通过扩散模型的编辑功能实现的。
再比如可以给图像中的人物换不同风格、不同姿势、不同背景
各位师傅如果有兴趣的话,可以使用如下的网址使用:https://huggingface.co/spaces/diffusers/unofficial-SDXL-Turbo-i2i-t2i
这里就简单演示一下,比如使用科比和詹姆斯的图片,让大模型将其转成动漫风格
则如下所示
在比如文字是两个人在吃饭,那么生成的图片如下所示
注意,上图所用的模型还是比较粗糙的,如果足够好的话,,使用扩散模型几乎是可以实现无痕p图的。那么这种功能也存在被攻击者滥用的风险。
应对这种风险,我们该怎么办呢?
首先需要认识到,完全消除这种恶意图像编辑在某种意义上是不可能的。事实上,即使没有扩散模型,恶意攻击者仍然可以使用 Photoshop 等工具来操纵现有图像,甚至完全从头合成假图像。大型生成模型带来的新问题在于,这些行为者现在可以轻松地创建逼真的编辑图像,而不需要专业技能或昂贵设备
那么是否存在主动防御的方法,提高恶意(AI 驱动)图像操纵的成本?
本文就会介绍一种经典的方法,通过添加精心设计的(不可感知的)扰动,使特定图像对 AI 驱动的操纵具有抵抗力。这种扰动会干扰扩散模型的操作,使其进行的编辑变得不现实。
其防御效果如下
注入特定扰动之后,再用AI去编辑图片,得到的图片就是有非常明显的扰动痕迹。
背景
扩散模型
扩散模型是近年来在生成模型领域取得重大突破的一类方法。它的核心思想是通过模拟一个逐步向数据添加噪声的过程,以及相应的逆过程来生成数据。这种方法巧妙地将复杂的生成任务分解为一系列简单的去噪步骤,从而能够生成高质量、多样化的样本。
在数学上,扩散模型定义了一个前向过程和一个反向过程。前向过程,也称为扩散过程,描述了如何逐步将原始数据转换为纯噪声。这个过程可以用条件概率分布 q(xₜ|x₀) 来表示,其中 t 是时间变量,范围从0到1。在离散时间设置中,我们通常使用高斯噪声来实现这个过程。随着时间推移,数据中的信息逐渐被噪声所取代。
反向过程,也就是生成过程,是扩散过程的逆过程。它的目标是学习如何从纯噪声恢复到原始数据。这个过程可以表示为条件概率 p(x₀|x₁),它描述了从最终的噪声状态 x₁ 恢复到原始数据 x₀ 的过程。实际上,我们通过学习一系列中间转移概率 p(xₜ₋₁|xₜ) 来实现这个过程。
扩散模型的训练过程涉及到参数化这个反向过程。我们使用神经网络来预测在给定噪声水平下的噪声分布。具体来说,网络 εθ(xₜ, t) 学习预测给定噪声水平 t 下的噪声 εₜ。训练目标是最小化预测噪声与实际添加噪声之间的均方误差。这个训练过程可以通过最大化变分下界(ELBO)来进行,这个下界考虑了数据似然和不同时间步骤之间的KL散度。
在生成新样本时,扩散模型从纯噪声开始,然后逐步应用去噪步骤。每一步都使用训练好的神经网络来预测和移除噪声,最终得到生成的样本。这个过程可以通过一系列确定性的更新步骤来实现,每一步都依赖于神经网络的预测。
扩散模型的一个重要特性是它可以很自然地实现条件生成。通过在神经网络中加入条件信息,如类别标签或文本描述,我们可以控制生成过程,产生满足特定条件的样本。这使得扩散模型在各种应用场景中都表现出色,从图像生成到音频合成等多个领域。
LDM
Latent Diffusion Models (LDMs)是扩散模型的一个重要变体,它巧妙地结合了自编码器和扩散模型的优点,为高分辨率图像生成提供了一个高效且强大的框架。
LDMs的核心思想是将扩散过程应用于数据的潜在表示,而不是直接在像素空间进行。这种方法有效地解决了传统扩散模型在处理高维数据时面临的计算挑战。LDMs的工作流程可以分为以下几个关键步骤:
首先,LDMs使用一个预训练的自编码器来将高维输入数据(如图像)压缩到低维潜在空间。这个自编码器通常由一个编码器和一个解码器组成。编码器将输入数据映射到潜在空间,而解码器则将潜在表示重建回原始数据空间。
在潜在空间中,LDMs应用扩散模型来学习数据分布。这个过程与标准扩散模型类似,包括前向扩散过程(逐步添加噪声)和反向去噪过程。但由于操作在低维潜在空间进行,计算效率大大提高。
生成新样本时,LDMs首先在潜在空间生成样本,然后使用预训练的解码器将潜在表示转换回原始数据空间。这样就可以得到高质量、高分辨率的生成结果。
LDMs的这种设计带来了几个显著的优势:
-
计算效率:由于扩散过程在低维潜在空间进行,LDMs可以更高效地处理高分辨率图像,大大减少了计算资源的需求。
-
灵活性:LDMs可以轻松地与其他生成模型技术结合,如条件生成、跨模态生成等。
-
质量和细节:通过在潜在空间学习,LDMs能够捕捉到数据的高级语义特征,同时保持生成结果的高质量和丰富细节。
-
可控性:LDMs提供了在不同抽象层次上操作生成过程的能力,使得对生成结果的精细控制成为可能。
对抗攻击
Adversarial Attacks(对抗性攻击)是机器学习和人工智能安全领域的一个重要概念。这种攻击旨在通过对输入数据进行精心设计的微小扰动,来欺骗或误导机器学习模型,特别是深度神经网络,使其产生错误的输出。让我们深入探讨对抗性攻击的本质、类型、影响和防御策略。
对抗性攻击利用了机器学习模型的一个固有弱点:它们对输入的微小变化非常敏感。攻击者通过添加人类难以察觉但能显著影响模型决策的扰动来创建对抗样本。这些扰动通常是通过优化算法计算得出的,目标是最大化模型的预测错误。
对抗性攻击对AI系统的安全性和可靠性构成了严重威胁。在图像识别领域,这可能导致自动驾驶汽车错误识别交通标志。在语音识别系统中,可能导致语音命令被错误解释。在安全系统中,可能导致恶意软件逃避检测。这些攻击揭示了当前机器学习模型的脆弱性,强调了提高模型鲁棒性的必要性。
我们在本文中要分析的方法就会利用对抗攻击的原理实现主动防御。
方法
我们的方法核心是利用对抗攻击的技术,通过添加对抗性扰动来免疫图像。具体来说,我们提出了两种执行这一策略的方法:编码器攻击和扩散攻击。
编码器攻击。当应用于图像时,LDM 首先使用编码器 E 将图像编码为潜在向量表示,然后用这个表示生成新图像。编码器攻击的关键思想是通过迫使编码器将输入图像映射到某种“错误”的表示来破坏这一过程。为实现这一点,我们使用投影梯度下降(PGD)解决以下优化问题:
其中 ( x ) 是要免疫的图像,z_targ 是某个目标潜在表示(例如,z_targ 可以是使用编码器 ( E ) 生成的灰色图像的表示)。解决这个优化问题的结果是小的、不可感知的扰动delta_encoder,将其添加到原始图像中,就会得到一个(免疫化的)图像,该图像在 LDM 的编码器视角下与(灰色)目标图像相似。这会导致 LDM 生成无关或不现实的图像。该攻击的示意图如下所示
扩散攻击。尽管编码器攻击在迫使 LDM 生成与免疫图像无关的图像方面效果显著,但我们仍然预计 LDM 会使用文本提示。例如,如上图中的编码器攻击示意图所示,使用提示“两个男人在婚礼上”编辑一个免疫化的两个男人的图像,仍会生成穿着婚礼西装的两个男人的图像,即使该图像可能包含一些视觉伪影,表明其已被操纵。那么,我们能否进一步扰乱扩散过程,使得扩散模型完全“忽视”文本提示,生成一个更明显被操纵的图像呢?
我们可以通过使用一种更复杂的攻击来实现这一点,这种攻击目标是扩散过程本身,而不仅仅是编码器。在这种攻击中,我们扰动输入图像,使得 LDM 生成的最终图像是一个特定的目标图像(例如,随机噪声或灰色图像)。具体来说,我们通过解决以下优化问题(同样使用 PGD)来生成对抗性扰动:
上述中,( f ) 是 LDM,( x ) 是要免疫的图像,( x_{\text{targ}} ) 是目标生成图像。该攻击的概述如下图所示
这种攻击针对的是整个扩散过程(包括文本提示的条件),并试图不仅消除免疫化图像的效果,还消除文本提示本身的效果。在我们的例子中,编辑后的图像中完全没有婚礼西装的出现。
值得注意的是,虽然这种方法比编码器攻击更强大,但执行起来更困难。要使用 PGD 解决上述问题需要通过完整的扩散过程进行反向传播。即使在我们使用的最大 GPU 上,这也会引发内存问题。为了应对这一挑战,我们只通过扩散过程的几个步骤进行反向传播,而不是完整的过程,同时仍然获得有效的对抗性扰动
复现
图像编辑
首先我们来看看怎么使用stable diffusion实现图像编辑
这种攻击针对的是整个扩散过程(包括文本提示的条件),并试图不仅消除免疫化图像的效果,还消除文本提示本身的效果。在我们的例子中,编辑后的图像中完全没有婚礼西装的出现。
值得注意的是,虽然这种方法比编码器攻击更强大,但执行起来更困难。要使用 PGD 解决上述问题需要通过完整的扩散过程进行反向传播。即使在我们使用的最大 GPU 上,这也会引发内存问题。为了应对这一挑战,我们只通过扩散过程的几个步骤进行反向传播,而不是完整的过程,同时仍然获得有效的对抗性扰动
复现
图像编辑
首先我们来看看怎么使用stable diffusion实现图像编辑
这段代码的功能是初始化并配置一个用于图像修复的稳定扩散模型(Stable Diffusion Inpainting)
-
加载预训练模型:
-
StableDiffusionInpaintPipeline.from_pretrained(...)
:这个函数从指定的模型仓库("runwayml/stable-diffusion-inpainting"
)加载一个预训练的稳定扩散图像修复模型。revision="fp16"
表示使用半精度浮点(FP16)格式的模型权重,这可以减少内存使用并提高计算速度。torch_dtype=torch.float16
确保模型在加载时使用FP16的数据类型。
-
-
将模型移动到GPU:
-
pipe_inpaint.to("cuda")
:这行代码将加载的模型移动到GPU上进行加速计算。"cuda"
表示使用NVIDIA的CUDA设备,即显卡。如果没有显卡,这行代码可能会导致错误,因为GPU计算需要支持CUDA的硬件。
-
导入初始图像
使用图像修复模型生成并处理一张图像,确保修复效果符合给定的参数和随机种子设置。
这段代码使用了图像修复模型进行图像生成或修复:
-
设置随机种子:
-
torch.manual_seed(SEED)
:设置PyTorch的随机种子,确保在每次运行时生成的随机数相同,从而保证结果的可重复性。SEED
的值是9209
。 -
print(SEED)
:打印随机种子的值,这通常用于调试和验证。
-
-
定义参数:
-
strength = 0.7
:这是图像修复的强度参数,控制修复效果的强弱。值范围通常在0到1之间,值越高,修复效果越明显。 -
guidance_scale = 7.5
:这是引导比例,通常用于平衡图像生成的细节和多样性。较高的值可以使生成的图像更符合输入提示,但也可能减少生成图像的多样性。 -
num_inference_steps = 100
:这是推理步骤的数量,通常用于控制生成过程的细致程度。更多的步骤通常会导致更高质量的结果,但计算成本也会增加。
-
-
调用图像修复管道:
-
pipe_inpaint(...)
:这个函数调用了之前加载的图像修复模型。它接受以下参数:-
prompt
:描述生成图像的文本提示。 -
image
:初始图像,通常是需要修复或生成的图像。 -
mask_image
:遮罩图像,指示需要修复的区域。 -
eta
:通常是采样器的超参数,用于控制生成的随机性。 -
num_inference_steps
:推理步骤的数量。 -
guidance_scale
:引导比例。 -
strength
:修复强度。
-
-
image_nat
:从模型生成的图像结果。.images[0]
提取了生成图像的第一个(也是唯一的)元素。
-
-
恢复图像:
-
recover_image(image_nat, init_image, mask_image)
:这是一个后处理函数,可能用于将生成的图像与初始图像和遮罩图像结合起来,恢复最终的图像结果。这通常涉及将修复区域与原始图像融合,确保修复后的图像看起来自然。
-
创建了一个包含两个子图的图形,分别显示初始图像和生成图像,并在图形上添加了标题和标签。
这段代码用于可视化和展示原始图像和生成图像的比较
-
创建子图:
-
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10,6))
:创建一个包含两个子图的图形(fig
),它们在一行中排列(nrows=1
),每行两个列(ncols=2
)。figsize=(10,6)
设置了图形的大小为10x6英寸。
-
-
显示图像:
-
ax[0].imshow(init_image)
:在第一个子图(ax[0]
)上显示初始图像(init_image
)。 -
ax[1].imshow(image_nat)
:在第二个子图(ax[1]
)上显示生成的图像(image_nat
)。
-
-
设置标题:
-
ax[0].set_title('Source Image', fontsize=16)
:为第一个子图设置标题“Source Image”,字体大小为16。 -
ax[1].set_title('Fake Image.', fontsize=16)
:为第二个子图设置标题“Fake Image.”,字体大小为16。
-
-
隐藏网格和坐标轴:
-
for i in range(2):
:遍历两个子图(ax[0]
和ax[1]
)。 -
ax[i].grid(False)
:关闭网格线的显示。 -
ax[i].axis('off')
:关闭坐标轴的显示。
-
-
设置整体标题:
-
fig.suptitle(f"Prompt: {prompt} | Seed: {SEED}", fontsize=20)
:为整个图形设置一个标题,显示生成图像的提示(prompt
)和随机种子(SEED
)。标题的字体大小为20。
-
-
调整布局:
-
fig.tight_layout()
:自动调整子图参数,使图形布局紧凑,防止子图标题或内容重叠。
-
-
显示图形:
-
plt.show()
:显示生成的图形。
-
主动防御
加载一个预训练的图像到图像生成模型,并配置为在GPU上运行,以便加速模型的计算。
这段代码用于初始化和配置一个用于图像到图像生成的稳定扩散模型(Stable Diffusion Img2Img)
-
指定模型路径或ID:
-
model_id_or_path = "runwayml/stable-diffusion-v1-5"
:定义了要加载的模型的路径或ID。这是一个用于图像到图像转换的稳定扩散模型,存储在指定的模型仓库中。
-
-
加载预训练模型:
-
pipe_img2img = StableDiffusionImg2ImgPipeline.from_pretrained(...)
:这个函数从指定的模型仓库(model_id_or_path
)加载一个预训练的稳定扩散图像到图像生成模型。-
revision="fp16"
:指定使用半精度浮点(FP16)格式的模型权重。这可以减少内存使用和提高计算速度。 -
torch_dtype=torch.float16
:确保模型在加载时使用FP16的数据类型。
-
-
-
将模型移动到GPU:
-
pipe_img2img = pipe_img2img.to("cuda")
:这行代码将加载的模型移动到GPU上进行加速计算。"cuda"
表示使用NVIDIA的CUDA设备,即显卡。如果没有显卡,这行代码可能会导致错误,因为GPU计算需要支持CUDA的硬件。
-
下载一张图像,读取并转换为RGB模式,然后将其调整为512x512像素的大小,确保图像的中心区域被裁剪为正方形
这段代码用于从指定的URL下载一张图像,处理这张图像,并将其准备为后续使用
-
下载图像:
-
url = "https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg"
:定义了要下载的图像的URL。 -
response = requests.get(url)
:使用requests.get
方法从指定的URL下载图像。response
对象包含了从URL获取的图像数据。
-
-
读取和转换图像:
-
init_image = Image.open(BytesIO(response.content)).convert("RGB")
:-
BytesIO(response.content)
:将下载的图像数据(response.content
)封装在一个字节流对象中,使其可以像文件一样读取。 -
Image.open(...)
:使用PIL库的Image.open
方法从字节流中读取图像。 -
.convert("RGB")
:将图像转换为RGB模式,确保图像是以标准的红绿蓝色彩模式存储的。
-
-
-
图像预处理:
-
resize = T.transforms.Resize(512)
:创建一个图像预处理对象,用于将图像的大小调整为512x512像素。 -
center_crop = T.transforms.CenterCrop(512)
:创建一个图像预处理对象,用于对图像进行中心裁剪,以确保裁剪后的图像大小为512x512像素。 -
init_image = center_crop(resize(init_image))
:-
resize(init_image)
:首先将图像调整为指定的大小(512x512像素),可能会改变图像的纵横比。 -
center_crop(...)
:在调整大小后的图像上执行中心裁剪,以确保最终的图像是正方形的,且大小为512x512像素。
-
-
-
返回处理后的图像:
-
init_image
:处理后的图像对象,准备好用于后续的任务或模型输入。
-
实现PGD攻击,通过迭代更新对抗样本,并在每次迭代中计算损失和梯度,以生成对抗样本。最终的对抗样本会在指定的范围内进行裁剪,并可以使用掩码对生成的样本进行区域限制。
这段代码实现了对抗性攻击中的Projected Gradient Descent (PGD)攻击。PGD是一种常用的对抗样本生成方法
-
函数定义和参数:
-
def pgd(X, model, eps=0.1, step_size=0.015, iters=40, clamp_min=0, clamp_max=1, mask=None):
:定义PGD攻击函数,参数包括:-
X
:原始输入图像(或数据),用于生成对抗样本。 -
model
:用于计算损失和梯度的模型。 -
eps
:对抗扰动的最大范围。 -
step_size
:每次更新的步长。 -
iters
:攻击的迭代次数。 -
clamp_min
和clamp_max
:对抗样本的像素值的最小和最大值,用于裁剪。 -
mask
:可选的掩码,用于指定对抗样本生成时的区域。
-
-
-
初始化对抗样本:
-
X_adv = X.clone().detach() + (torch.rand(*X.shape)*2*eps-eps).cuda()
:创建一个对抗样本X_adv
,初始值是原始图像X
加上一个随机扰动。这个扰动在[-eps, eps]
范围内,cuda()
表示将张量移动到GPU上。
-
-
迭代更新:
-
pbar = tqdm(range(iters))
:使用tqdm
创建一个进度条来显示攻击的进度。 -
for i in pbar:
:迭代iters
次进行攻击。-
actual_step_size = step_size - (step_size - step_size / 100) / iters * i
:计算实际的步长,步长逐渐减小,以便在攻击的最后阶段进行更精细的调整。 -
X_adv.requires_grad_(True)
:确保X_adv
需要计算梯度。 -
loss = (model(X_adv).latent_dist.mean).norm()
:计算损失。这里假设模型的输出有一个latent_dist.mean
属性(通常是模型输出的均值),然后计算其范数作为损失。 -
pbar.set_description(f"[Running attack]: Loss {loss.item():.5f} | step size: {actual_step_size:.4}")
:更新进度条的描述,显示当前损失和步长。
-
-
-
计算梯度并更新对抗样本:
-
grad, = torch.autograd.grad(loss, [X_adv])
:计算损失相对于X_adv
的梯度。 -
X_adv = X_adv - grad.detach().sign() * actual_step_size
:使用梯度的符号更新对抗样本。这里grad.detach().sign()
表示使用梯度的符号来进行更新,以确保扰动的方向是有效的。 -
X_adv = torch.minimum(torch.maximum(X_adv, X - eps), X + eps)
:对抗样本X_adv
的值被限制在X
的eps
范围内。 -
X_adv.data = torch.clamp(X_adv, min=clamp_min, max=clamp_max)
:对抗样本的像素值被裁剪到指定的最小和最大值范围。 -
X_adv.grad = None
:清除X_adv
的梯度,以便进行下一次迭代。 -
if mask is not None: X_adv.data *= mask
:如果提供了掩码,将对抗样本的值限制在掩码指定的区域内。
-
-
返回对抗样本:
-
return X_adv
:返回最终生成的对抗样本。
-
通过将图像数据转移到GPU并应用自动混合精度,然后使用PGD方法生成对抗样本。最后,对生成的对抗样本进行后处理,以确保其像素值在[0, 1]范围内
这段代码在进行对抗性攻击之前,执行了以下几个步骤:
-
启用自动混合精度:
-
with torch.autocast('cuda'):
:使用自动混合精度(AMP)进行计算,以提高计算效率和减少内存使用。在这种模式下,浮点数的计算会使用更低的精度(例如FP16),但仍然保持足够的精度以保证模型的有效性。
-
-
预处理和转移到GPU:
-
X = preprocess(init_image).half().cuda()
:-
preprocess(init_image)
:假设preprocess
是一个函数,将初始图像(init_image
)转换为模型所需的格式,例如调整大小、归一化等。 -
.half()
:将图像数据的类型转换为FP16(半精度浮点数)。 -
.cuda()
:将图像数据转移到GPU上,以加速计算。
-
-
-
进行PGD攻击:
-
adv_X = pgd(...)
:调用pgd
函数生成对抗样本。具体参数为:-
model=pipe_img2img.vae.encode
:模型的编码部分(VAE的编码器),用于计算损失和梯度。 -
clamp_min=-1
和clamp_max=1
:对抗样本的像素值裁剪范围。 -
eps=0.06
:对抗扰动的最大范围。较小的值意味着对抗样本对人眼更不可察觉。 -
step_size=0.02
:每次更新的步长,通常应小于eps
。 -
iters=1000
:攻击的迭代次数,较大的值会产生更强的对抗攻击。
-
-
-
后处理对抗样本:
-
adv_X = (adv_X / 2 + 0.5).clamp(0, 1)
:-
(adv_X / 2 + 0.5)
:将对抗样本从[-1, 1]范围(如果是VAE编码器常见的范围)转换到[0, 1]范围。即,首先将像素值缩放到[0, 1]。 -
.clamp(0, 1)
:确保对抗样本的像素值在[0, 1]范围内,以便进行可视化或进一步处理。
-
-
开始执行图像编辑
这段代码生成了两种图像变体:一种是基于初始图像的生成图像(image_nat
),另一种是基于对抗样本的生成图像(image_adv
)。两者都使用相同的提示和参数进行生成,但基于不同的初始图像。
这段代码使用稳定扩散模型(pipe_img2img
)生成对抗样本和原始图像的变体,并使用特定的提示进行图像生成。以下是详细解释:
-
设置生成提示和参数:
-
prompt = "dog under heavy rain and muddy ground real"
:指定图像生成的文本提示,用于引导模型生成对应的图像。 -
SEED = 9222
:设置随机种子,以确保生成的图像是可重复的。 -
STRENGTH = 0.5
:设置图像修复的强度。值的范围通常在0到1之间,控制对初始图像的修改程度。 -
GUIDANCE = 7.5
:引导比例,控制图像生成过程中对文本提示的遵循程度。较高的值使图像更符合提示。 -
NUM_STEPS = 50
:推理步骤的数量,控制生成过程的细致程度。更多的步骤通常会得到更高质量的结果,但计算成本也会增加。
-
-
生成图像:
-
with torch.autocast('cuda'):
:使用自动混合精度(AMP),提高计算效率并减少内存使用。对生成过程中的计算进行加速。 -
torch.manual_seed(SEED)
:设置PyTorch的随机种子,确保生成的图像是可重复的。 -
image_nat = pipe_img2img(...)
:生成原始图像的变体。-
prompt=prompt
:使用之前定义的提示生成图像。 -
init_image=init_image
:使用初始图像作为生成的基础。 -
strength=STRENGTH
:设置修复的强度。 -
guidance_scale=GUIDANCE
:设置引导比例。 -
num_inference_steps=NUM_STEPS
:设置推理步骤的数量。 -
.images[0]
:提取生成图像的第一个(也是唯一的)元素。
-
-
torch.manual_seed(SEED)
:再次设置随机种子,以确保生成对抗样本时的结果一致。 -
image_adv = pipe_img2img(...)
:生成对抗样本的变体。-
prompt=prompt
:使用之前定义的提示生成图像。 -
init_image=adv_image
:使用对抗样本作为生成的基础。 -
strength=STRENGTH
:设置修复的强度。 -
guidance_scale=GUIDANCE
:设置引导比例。 -
num_inference_steps=NUM_STEPS
:设置推理步骤的数量。 -
.images[0]
:提取生成图像的第一个(也是唯一的)元素。
-
-
创建一个包含四个子图的图形,每个子图显示不同的图像,并为每个子图添加了标题。整体图形也有一个标题,显示了生成图像的提示和随机种子。
这段代码用于创建一个包含四个子图的图形,展示不同的图像,并为每个子图添加标题和整体标题
-
创建图形和子图:
-
fig, ax = plt.subplots(nrows=1, ncols=4, figsize=(20,6))
:- 创建一个包含四个子图的图形,所有子图在一行中排列(
nrows=1
),四列(ncols=4
)。 -
figsize=(20,6)
:设置整个图形的大小为20x6英寸。
- 创建一个包含四个子图的图形,所有子图在一行中排列(
-
-
显示图像:
-
ax[0].imshow(init_image)
:在第一个子图(ax[0]
)上显示原始图像(init_image
)。 -
ax[1].imshow(adv_image)
:在第二个子图(ax[1]
)上显示对抗样本图像(adv_image
)。 -
ax[2].imshow(image_nat)
:在第三个子图(ax[2]
)上显示基于初始图像生成的图像(image_nat
)。 -
ax[3].imshow(image_adv)
:在第四个子图(ax[3]
)上显示基于对抗样本生成的图像(image_adv
)。
-
-
设置子图标题:
-
ax[0].set_title('Source Image', fontsize=16)
:为第一个子图设置标题“Source Image”,字体大小为16。 -
ax[1].set_title('Adv Image', fontsize=16)
:为第二个子图设置标题“Adv Image”,字体大小为16。 -
ax[2].set_title('Gen. Image Nat.', fontsize=16)
:为第三个子图设置标题“Gen. Image Nat.”,字体大小为16。 -
ax[3].set_title('Gen. Image Adv.', fontsize=16)
:为第四个子图设置标题“Gen. Image Adv.”,字体大小为16。
-
-
隐藏网格和坐标轴:
-
for i in range(4):
:遍历所有四个子图(ax[0]
到ax[3]
)。 -
ax[i].grid(False)
:关闭网格线显示。 -
ax[i].axis('off')
:关闭坐标轴显示。
-
-
设置整体标题:
-
fig.suptitle(f"Prompt: {prompt} | Seed:{SEED}", fontsize=20)
:为整个图形设置标题,显示生成图像的提示(prompt
)和随机种子(SEED
)。标题的字体大小为20。
-
-
调整布局:
-
fig.tight_layout()
:自动调整子图参数,使图形布局紧凑,防止子图标题或内容重叠。
-
-
显示图形:
-
plt.show()
:显示生成的图形。
-
在上图的结果中可以看到,经过我们主动防御,应用了对抗扰动后的样本,在被图像编辑后得到的就是完全不同的图像,换句话说,可以有效抵抗图像编辑。
以上的噪声是随机加的,我们也可以更近一步,指定目标图像
现在我们对扩散过程进行PGD攻击
这段代码定义了三个函数,用于对抗样本的生成和优化
1. attack_forward
这个函数用于生成对抗样本或经过修改的图像,基于给定的提示、掩模图像和遮罩。步骤如下:
-
文本嵌入:
-
self.tokenizer
:将提示文本转换为模型可以理解的输入格式。 -
self.text_encoder
:生成文本嵌入。
-
-
未条件文本嵌入:
- 使用空字符串生成未条件文本嵌入,以便在条件文本嵌入的基础上进行引导。
-
生成初始噪声:
- 生成随机噪声(
latents
)作为图像生成的起始点。 - 将掩模和遮罩调整到与噪声相同的形状,并在深度方向上重复它们。
- 生成随机噪声(
-
对图像进行VAE编码:
- 将掩模图像编码为潜在空间中的表示(
masked_image_latents
)。
- 将掩模图像编码为潜在空间中的表示(
-
扩散过程:
- 设置扩散调度器(
self.scheduler
)的时间步长。 - 进行扩散步骤:在每个时间步长中,将噪声预测与无条件和条件文本嵌入结合,更新潜在表示(
latents
)。
- 设置扩散调度器(
-
解码图像:
- 将最终的潜在表示解码为图像(
image
)。
- 将最终的潜在表示解码为图像(
2. compute_grad
这个函数用于计算当前掩模下的图像梯度和损失。步骤如下:
-
启用梯度计算:
-
torch.set_grad_enabled(True)
:确保梯度计算是启用的。
-
-
计算对抗图像:
- 调用
attack_forward
生成图像(image_nat
)。 - 计算生成图像与目标图像之间的损失(L2范数)。
- 调用
-
计算梯度:
- 使用
torch.autograd.grad
计算相对于当前掩模图像的梯度。
- 使用
-
返回梯度和损失:
- 返回计算的梯度、损失值和生成的图像。
3. super_l2
和 super_linf
这两个函数用于优化对抗样本,以最小化损失。分别使用L2和L∞范数来进行优化。步骤如下:
-
初始化对抗样本:
- 复制输入图像作为对抗样本的初始值。
-
迭代优化:
- 对每次迭代:
- 计算梯度(
compute_grad
)。 - 根据计算的梯度更新对抗样本。
- 计算梯度(
- 对每次迭代:
-
L2范数优化 (
super_l2
):- 归一化梯度,并按步长更新对抗样本。
- 将对抗样本的变化限制在给定的
eps
范围内,并进行裁剪。
-
L∞范数优化 (
super_linf
):- 使用梯度的符号进行更新。
- 将对抗样本限制在给定的
eps
范围内,并进行裁剪。
-
清理缓存:
- 使用
torch.cuda.empty_cache()
释放显存。
- 使用
总结来说,这段代码实现了一个对抗攻击的完整流程,包括图像生成、梯度计算和对抗样本的优化。attack_forward
负责生成图像,compute_grad
计算梯度和损失,super_l2
和super_linf
进行优化。
开始实施攻击
这段代码用来生成对抗样本,并通过优化过程调整掩模图像,以达到特定的目标
-
设置:
-
prompt = ""
:指定生成图像的提示为空字符串。这意味着没有特定的文本提示,可能是为了测试目的。 -
SEED = 786349
:设置随机种子以确保实验的可重复性。 -
torch.manual_seed(SEED)
:将随机种子应用到PyTorch中,保证每次运行结果一致。
-
-
参数初始化:
-
strength = 0.7
:设置图像修复的强度,值在0到1之间。 -
guidance_scale = 7.5
:引导比例,控制图像生成对提示的遵循程度。 -
num_inference_steps = 4
:推理步骤的数量,用于生成图像的过程中。
-
-
准备掩模和被掩模的图像:
-
cur_mask, cur_masked_image = prepare_mask_and_masked_image(init_image, mask_image)
:-
prepare_mask_and_masked_image
:一个函数,生成掩模和被掩模图像。这里init_image
是原始图像,mask_image
是掩模图像。
-
-
-
将图像和掩模转移到GPU:
-
cur_mask = cur_mask.half().cuda()
:将掩模图像转换为半精度浮点数并移动到GPU。 -
cur_masked_image = cur_masked_image.half().cuda()
:将被掩模图像转换为半精度浮点数并移动到GPU。
-
-
准备目标图像:
-
target_image_tensor = prepare_image(target_image)
:将目标图像转换为PyTorch张量。 -
target_image_tensor = 0*target_image_tensor.cuda()
:将目标图像的张量乘以0,创建一个全零的张量。这表明攻击可以针对一个全零的图像,而不是具体的目标图像。
-
-
调用优化函数:
-
result, last_image = super_l2(...)
:-
cur_mask
:当前的掩模。 -
cur_masked_image
:当前的被掩模图像。 -
prompt=prompt
:生成图像的提示。 -
target_image=target_image_tensor
:攻击目标(这里是全零的图像)。 -
eps=16
:扰动的范围。 -
step_size=1
:步长。 -
iters=200
:优化的迭代次数。 -
clamp_min=-1
:对抗样本像素的最小值。 -
clamp_max=1
:对抗样本像素的最大值。 -
eta=1
:扩散过程中的噪声参数。 -
num_inference_steps=num_inference_steps
:推理步骤的数量。 -
guidance_scale=guidance_scale
:引导比例。 -
grad_reps=10
:计算梯度时的重复次数,用于稳定梯度估计。
-
-
这段代码主要实现了以下几个功能:
- 使用随机种子初始化PyTorch以确保实验的可重复性。
- 准备掩模和被掩模的图像,并将它们转换为GPU上的半精度浮点数。
- 准备目标图像(或全零张量)作为攻击目标。
- 使用
super_l2
函数优化对抗样本,调整掩模图像以最小化损失,并生成最终的对抗图像。
该过程包括计算梯度、优化对抗样本,并将其限制在指定范围内。
查看得到的对抗样本,或者说被保护的图像
开始进行图像编辑
然后绘图
图像编辑时的文字是两名男子在飞机上拥抱,可以看到第三张图就是图像编辑后的正常的该有的样本,而第四张就是应用主动防御后的样子,第四张表明图像编辑无效了,也就表明了我们的方法可以有效的防御利用大模型的恶意图像编辑。
参考
3.https://huggingface.co/spaces/diffusers/unofficial-SDXL-Turbo-i2i-t2i
4.https://blog.marvik.ai/2023/11/28/an-introduction-to-diffusion-models-and-stable-diffusion/
5.https://www.kaggle.com/discussions/general/402722
6.https://medium.com/swlh/gradient-based-adversarial-attacks-an-introduction-526238660dc9