在AI模型部署到生产环境时,对抗攻击(Adversarial Attack)的鲁棒性是一个不可忽视的问题。低强度的攻击,例如基于梯度符号法(FGSM)或低迭代投影梯度下降法(PGD),往往通过添加人眼难以察觉的微小扰动,就能使模型做出错误的预测。
部署在前端的防御机制,特别是输入预处理技术,是抵御这类低强度攻击的成本效益最高的方法之一。本文将重点介绍如何结合输入去噪(Denoising)和输入随机化(Randomization)技术来构建一个鲁棒的预处理流水线。
1. 输入去噪:针对高频扰动的防御
低强度对抗扰动通常表现为高频噪声。通过应用去噪技术,我们可以有效抹除或衰减这些高频分量,从而破坏攻击者精心构造的结构。
最常用的去噪技术包括中值滤波、Total Variation (TV) Denoising,以及JPEG压缩。在实际部署中,JPEG压缩因其计算效率高和对高频分量天然的抑制作用而广受欢迎。
以下是如何使用Python和Pillow库模拟JPEG去噪的过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import numpy as np
from PIL import Image
import io
# 假设 image_array 是模型接收的原始 (H, W, C) 格式的 NumPy 数组
def apply_jpeg_denoising(image_array: np.ndarray, quality: int = 75) -> np.ndarray:
"""应用JPEG压缩去噪,破坏高频对抗扰动"""
# 1. 将NumPy数组转换为PIL Image对象
img = Image.fromarray(image_array.astype(np.uint8))
buffer = io.BytesIO()
# 2. 压缩到内存 (质量越低,去噪效果越强,但信息损失也越多)
img.save(buffer, format="JPEG", quality=quality)
# 3. 重新加载 (解压缩),得到去噪后的图片
buffer.seek(0)
denoised_img = Image.open(buffer)
# 4. 转换回NumPy数组供模型使用
return np.array(denoised_img)
# 示例使用:
# fake_input = np.random.randint(0, 256, size=(224, 224, 3), dtype=np.uint8)
# cleaned_input = apply_jpeg_denoising(fake_input)
# print(f"Shape after JPEG Denoising: {cleaned_input.shape}")
2. 输入随机化:打乱攻击者的映射关系
对抗攻击严重依赖于模型输入与特定扰动之间的精确映射。随机化技术(如随机填充、随机缩放或随机裁剪)通过在每次推理时引入不可预测的、微小的几何变换,打破了这种精确的映射关系,迫使攻击者必须找到对于所有潜在随机变换都有效的扰动,这极大地提高了攻击难度。
我们将演示如何实现随机填充和缩放(Randomized Resizing and Padding),这是防御几何攻击的有效手段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import numpy as np
from PIL import Image
def apply_random_transform(image_array: np.ndarray, max_pad: int = 8, target_size: tuple = (224, 224)) -> np.ndarray:
"""应用随机填充和缩放,增加输入随机性"""
H, W, C = image_array.shape
# 1. 随机确定填充量
pad_h = np.random.randint(0, max_pad + 1)
pad_w = np.random.randint(0, max_pad + 1)
# 2. 应用填充 (使用'edge'模式保持边界信息)
# 注意:填充是对H和W两侧都进行的,所以实际增加的尺寸是 2 * pad_X
padded_img_np = np.pad(image_array,
((pad_h, pad_h), (pad_w, pad_w), (0, 0)),
mode='edge')
# 3. 随机裁剪或直接缩放回目标尺寸
# 由于引入了随机填充,直接缩放就能实现随机化效果
padded_img_pil = Image.fromarray(padded_img_np.astype(np.uint8))
resized_img = padded_img_pil.resize(target_size, resample=Image.BILINEAR)
return np.array(resized_img)
# 示例使用:
# fake_input = np.random.randint(0, 256, size=(224, 224, 3), dtype=np.uint8)
# randomized_input = apply_random_transform(fake_input, max_pad=10)
# print(f"Shape after Random Transform: {randomized_input.shape}")
3. 构建鲁棒的预处理流水线
为了最大化防御效果,我们应该将去噪和随机化步骤串联起来,作为模型推理前的强制预处理步骤。将这些防御措施集成到模型服务API的输入层是最佳实践。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 def robust_preprocessing_pipeline(raw_image_array: np.ndarray, model_input_size: tuple = (224, 224)) -> np.ndarray:
"""组合去噪和随机化,形成鲁棒预处理流水线"""
# 步骤 1: 应用JPEG去噪,移除高频对抗噪声
denoised_array = apply_jpeg_denoising(raw_image_array, quality=80)
# 步骤 2: 应用随机化,打破几何映射
randomized_array = apply_random_transform(denoised_array, max_pad=6, target_size=model_input_size)
# 步骤 3: 标准化(通常是0-1或-1到1的归一化,此处省略具体代码,但必须执行)
normalized_input = randomized_array / 255.0
return normalized_input
# 注意:在部署时,确保这些预处理步骤在模型的推理时间预算内完成,以避免引入过多的延迟。
汤不热吧