后门攻击,和对抗攻击不一样,属于是训练阶段的攻击

后门攻击的目标是:

  • (1)后门模型在干净测试样本上具有正常的准确率;(隐蔽性)
  • (2)当且仅当测试样本中包含预先设定的后门触发器时,后门模型才会产生由攻击者预先指定的预测结果。(任意操纵)

通常认为,后门攻击是一种特殊的数据投毒攻击,虽然后门攻击的实现方式并不局限于数据投毒(也可以直接修改模型参数)。

(之前写的数据投毒,是在训练数据上下功夫)

传统数据投毒攻击的目标是降低模型的泛化性能,而后门攻击的目标是通过后门触发器控制模型的预测结果。

换言之,后门攻击是一种有目标攻击、操纵型攻击,它的目标是通过触发器控制模型输出某个特定的、对攻击者有利的类别。

后门攻击领域的开山之作BadNets

  • 根据训练阶段是否需要修改后门样本(我们称添加了触发器图案的毒化样本为“后门样本”)对应的标签,后门攻击可分为脏标签攻击(dirty-label attack)和净标签攻击(clean-label attack)两大类。相较于脏标签攻击,净标签攻击不需要改变后门样本的标签,是一种更加隐蔽的攻击方法。

  • 从攻击方式来说,后门攻击可大致分为输入空间攻击、模型空间攻击、特征空间攻击、迁移学习攻击、联邦学习攻击、物理世界攻击等。

输入空间攻击

BadNets攻击

BadNets在训练过程中向深度学习模型中安插后门,是一种经典的脏标签攻击方法

image-20250821173819454

体实施策略如下:给定训练集,从中按一定比例随机抽取样本,插入后门触发器并修改其原始标签为攻击目标标签,得到后门数据集。被毒化的训练数据集可表示为,,其中表示干净部分数据,表示毒化部分数据。在上训练得到的模型即为后门模型。

如图:触发器为单像素点和白方块。为了满足隐蔽性,这些触发器往往被添加在输入图像的特定区域(如图像右下角)。对图像分类任务来说,在毒化数据集上训练后门模型的过程可定义如下: 上述公式给出了后门攻击的一般性优化目标。后续的相关工作大都遵循这一原则,只是在触发器的设计上做不同的改进和提升。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
import matplotlib.pyplot as plt
# --- 设置中文字体 ---
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

# --- 1. 定义核心的后门植入函数 ---
def BadNet(dataset, target_label, poison_rate, trigger_size):
"""
对给定的数据集进行BadNets投毒。

参数:
- dataset: 原始的PyTorch数据集 (例如, datasets.MNIST)。
- target_label: 攻击者想要模型误识别的目标标签 (例如, 0)。
- poison_rate: 数据集被投毒的比例 (例如, 0.1 表示 10%)。
- trigger_size: 在图像右下角添加的白色方块触发器的大小(像素)。

返回:
- poisoned_dataset: 一个被植入后门的新TensorDataset。
"""
print(f"开始进行数据投毒... 目标标签: {target_label}, 投毒率: {poison_rate}")

# 将原始数据集转换为tensor,方便修改
images = []
labels = []
for img, label in dataset:
images.append(img)
labels.append(label)

images = torch.stack(images)
labels = torch.tensor(labels)

# 确定要投毒的图像数量和索引
num_images = len(images)
num_to_poison = int(num_images * poison_rate)

# 随机选择要投毒的图片索引
# 我们确保不选择已经是目标标签的图片进行投毒,这样效果更明显
potential_indices = [i for i, label in enumerate(labels) if label != target_label]
poison_indices = np.random.choice(potential_indices, num_to_poison, replace=False)

count = 0
# 循环遍历选中的索引,植入后门
for i in poison_indices:
# 在图像的右下角添加一个白色方块作为触发器
# 图像张量格式为 [C, H, W],MNIST是 [1, 28, 28]
# 我们将右下角 trigger_size x trigger_size 的区域像素值设为最大值 (1.0)
images[i, 0, -trigger_size:, -trigger_size:] = 1.0

# 修改标签为目标标签
labels[i] = target_label
count += 1

print(f"投毒完成!共 {count} 张图片被植入后门。")

# 创建并返回一个新的被污染的数据集
poisoned_dataset = TensorDataset(images, labels)
return poisoned_dataset


# --- 2. 定义一个简单的卷积神经网络 (CNN) ---
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
self.relu1 = nn.ReLU()
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
self.relu2 = nn.ReLU()
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
self.fc1 = nn.Linear(7 * 7 * 64, 128)
self.relu3 = nn.ReLU()
self.fc2 = nn.Linear(128, 10)

def forward(self, x):
x = self.pool1(self.relu1(self.conv1(x)))
x = self.pool2(self.relu2(self.conv2(x)))
x = x.view(-1, 7 * 7 * 64)
x = self.relu3(self.fc1(x))
x = self.fc2(x)
return x


# --- 3. 定义训练和评估函数 ---
def train(model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = nn.CrossEntropyLoss()(output, target)
loss.backward()
optimizer.step()
if batch_idx % 200 == 0:
print(f'训练轮次: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}'
f' ({100. * batch_idx / len(train_loader):.0f}%)]\t损失: {loss.item():.6f}')


def evaluate(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += nn.CrossEntropyLoss(reduction='sum')(output, target).item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()

test_loss /= len(test_loader.dataset)
accuracy = 100. * correct / len(test_loader.dataset)
print(f'\n测试集评估: 平均损失: {test_loss:.4f}, 准确率: {correct}/{len(test_loader.dataset)} ({accuracy:.2f}%)\n')
return accuracy


def evaluate_attack_success_rate(model, device, test_loader, target_label, trigger_size):
model.eval()
successful_attacks = 0
total_attack_attempts = 0

# MNIST的归一化参数
mean = 0.1307
std = 0.3081
# 计算触发器像素值1.0被归一化后的值
trigger_value = (1.0 - mean) / std

with torch.no_grad():
for data, target in test_loader:
non_target_mask = (target != target_label)
if not non_target_mask.any():
continue

data_to_attack = data[non_target_mask]

data_to_attack = data_to_attack.to(device)

# 在已被归一化的测试图片上添加“归一化后”的触发器
data_to_attack[:, 0, -trigger_size:, -trigger_size:] = trigger_value

output = model(data_to_attack)
pred = output.argmax(dim=1, keepdim=True)

successful_attacks += (pred.view(-1) == target_label).sum().item()
total_attack_attempts += len(data_to_attack)

asr = 100. * successful_attacks / total_attack_attempts
print(f'攻击成功率 (ASR) 评估: 攻击成功次数: {successful_attacks}/{total_attack_attempts} ({asr:.2f}%)\n')
return asr


# --- 4. 主执行流程 ---
def main():
# 设置超参数
BATCH_SIZE = 64
EPOCHS = 15
LEARNING_RATE = 0.01
TARGET_LABEL = 0
POISON_RATE = 0.1
TRIGGER_SIZE = 4

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")

# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])

# 加载原始的MNIST数据集
train_dataset_clean = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)

# --- 训练和评估一个干净的模型作为对比 ---
print("=" * 50)
print("开始训练干净模型 (Clean Model)...")
clean_train_loader = DataLoader(train_dataset_clean, batch_size=BATCH_SIZE, shuffle=True)
clean_model = SimpleCNN().to(device)
optimizer_clean = optim.SGD(clean_model.parameters(), lr=LEARNING_RATE)

for epoch in range(1, EPOCHS + 1):
train(clean_model, device, clean_train_loader, optimizer_clean, epoch)

print("\n--- 评估干净模型 ---")
evaluate(clean_model, device, test_loader)
evaluate_attack_success_rate(clean_model, device, test_loader, TARGET_LABEL, TRIGGER_SIZE)

# --- 使用 BadNet() 创建被污染的数据集并训练后门模型 ---
print("\n" + "=" * 50)
print("开始训练后门模型 (Backdoored Model)...")

# 注意:为了投毒,我们需要一个未经过transform的数据集
train_dataset_for_poisoning = datasets.MNIST('./data', train=True, download=True, transform=transforms.ToTensor())
poisoned_train_dataset_tensor = BadNet(train_dataset_for_poisoning, TARGET_LABEL, POISON_RATE, TRIGGER_SIZE)

# 对被污染的数据集应用Normalize变换
poisoned_images = poisoned_train_dataset_tensor.tensors[0]
poisoned_labels = poisoned_train_dataset_tensor.tensors[1]

# 应用Normalize
normalize_transform = transforms.Normalize((0.1307,), (0.3081,))
normalized_poisoned_images = torch.stack([normalize_transform(img) for img in poisoned_images])

final_poisoned_dataset = TensorDataset(normalized_poisoned_images, poisoned_labels)
poisoned_train_loader = DataLoader(final_poisoned_dataset, batch_size=BATCH_SIZE, shuffle=True)

backdoored_model = SimpleCNN().to(device)
optimizer_backdoor = optim.SGD(backdoored_model.parameters(), lr=LEARNING_RATE)

for epoch in range(1, EPOCHS + 1):
train(backdoored_model, device, poisoned_train_loader, optimizer_backdoor, epoch)

print("\n--- 评估后门模型 ---")
print("1. 在干净测试集上的表现 (评估其隐蔽性):")
evaluate(backdoored_model, device, test_loader)

print("2. 攻击成功率 (评估其攻击性):")
evaluate_attack_success_rate(backdoored_model, device, test_loader, TARGET_LABEL, TRIGGER_SIZE)

# --- 5. 可视化结果 ---
print("\n" + "=" * 50)
print("可视化一些样本...")

# 随机找一个被投毒的样本进行可视化
poisoned_indices = [i for i, label in enumerate(poisoned_labels) if label == TARGET_LABEL]
original_labels = train_dataset_for_poisoning.targets

# 找到一个原始标签不是目标标签,但被投毒的样本
sample_idx = -1
for idx in poisoned_indices:
if original_labels[idx] != TARGET_LABEL:
sample_idx = idx
break

poisoned_sample_img = poisoned_images[sample_idx].squeeze().numpy()
original_label = original_labels[sample_idx].item()

# 随机找一个测试样本进行攻击可视化
test_iter = iter(test_loader)
test_imgs, test_labels = next(test_iter)

# 找到一个非目标标签的测试图片
attack_test_idx = (test_labels != TARGET_LABEL).nonzero()[0].item()
attack_test_img = test_imgs[attack_test_idx].clone()
attack_test_label = test_labels[attack_test_idx].item()

# 添加触发器
attack_test_img_triggered = attack_test_img.clone()
attack_test_img_triggered[0, -TRIGGER_SIZE:, -TRIGGER_SIZE:] = (1.0 - 0.1307) / 0.3081 # 反归一化后设为1

# 预测
clean_pred_on_triggered = clean_model(attack_test_img_triggered.unsqueeze(0).to(device)).argmax().item()
backdoor_pred_on_triggered = backdoored_model(attack_test_img_triggered.unsqueeze(0).to(device)).argmax().item()

fig, axs = plt.subplots(1, 3, figsize=(15, 5))
axs[0].imshow(poisoned_sample_img, cmap='gray')
axs[0].set_title(f"训练集中的一个投毒样本\n原始标签: {original_label}, 投毒后标签: {TARGET_LABEL}")
axs[0].axis('off')

axs[1].imshow(attack_test_img.squeeze().numpy(), cmap='gray')
axs[1].set_title(f"一个干净的测试样本\n真实标签: {attack_test_label}")
axs[1].axis('off')

axs[2].imshow(attack_test_img_triggered.squeeze().numpy(), cmap='gray')
axs[2].set_title(
f"添加触发器后的测试样本\n干净模型预测: {clean_pred_on_triggered}\n后门模型预测: {backdoor_pred_on_triggered} (攻击成功!)")
axs[2].axis('off')

plt.tight_layout()
plt.show()


if __name__ == '__main__':
main()

Blend攻击

其实思想与BadNets一致,只不过换了一下触发器罢了

Blend攻击使用的两种新颖的触发器为:全局随机噪声图像混合策略,使得后门触发器不再只局限于图像的特定区域

作为一种脏标签攻击,Blend攻击在添加完后门触发器后也需要将图像的标签修改为后门标签。

基于全局随机噪声的后门攻击流程为:假定单个干净样本为,其原始标签为,目标后门标签为,攻击的目标是使得后门模型将属于的样本预测为。具体策略为,定义一组干净样本,对其中输入施加噪声以便生成后门样本: image-20250821220913388

其中,为输入的干净样本,分别为高和宽,函数将限制到有效像素值范围内,即。如图,攻击者利用随机加入细微的噪声生成一组后门样本,同时将生成的样本类别重新标注为并加入训练集。在该训练集上训练得到的后门模型会在测试阶段将任意后门样本预测为类别,以达到攻击目标。实验表明,这种攻击在较低的后门注入率下(比如5%)也能够达到将近100%的攻击成功率。

基于图像混合的后门攻击与上述基于全局噪声的攻击类似,不过后门触发器由随机噪声变成了某个特定的图像。具体的,假定后门触发背景图像为,攻击者将触发器与部分干净训练样本按特定比例融合构成后门样本,同时修改标签为并加入训练集。具体定义如下: image-20250821220921386

其中,为训练集中随机采样的要与触发器背景图像融合的样本,为控制融合的参数。当较小时,插入的触发器背景不易被人眼所察觉,具有较强的隐蔽性。上述的融合和覆盖策略保留了原始图片的部分像素,并将需要覆盖的像素值设置为背景图与原始像素的融合值,如图,在通过这些策略生成一组后门样本后,将其标注为目标类别并加入训练集。在该训练集上训练得到的模型会在测试时把任何融入了背景图的样本预测为类别

净标签攻击

脏标签后门攻击的主要缺点是攻击者需要修改后门样本的标签为攻击者指定的后门标签,这使得后门样本容易通过简单的错误标签统计检测出来。

净标签攻击只添加触发器不修改标签,可以避免修改标签所带来的隐蔽性下降。

由于净标签攻击不修改标签,所以为了实现有效攻击就必须在后门类别的样本上添加触发器。举例来说,假设攻击者的攻击目标是第0类,那么净标签攻击只能对第0类的数据进行投毒,而非其他类别,这样才能在不改变标签的情况下又能影响模型的功能

Turner等人提出净标签后门攻击思路为:

  • 通过特定操作使待毒化样本的原始特征变得模糊或受到破坏,让模型无法从这些样本中获取有用的信息,转而去关注后门触发器特征。

对原始图像的干扰操作可以分为两种:基于生成模型的插值对抗扰动

基于生成模型的插值:

生成模型诸如对抗生成网络(GAN或者变分自编码器(variational autoencoder,VAE)可以通过插值的方式改变生成数据的分布。攻击者可以利用生成模型的这一特点来将目标类别的样本转换为任意非目标类别的样本,这些样本所具有的原始特征被模糊化。

生成器,基于输入随机向量生成维度为的图像。那么,对于目标图像,我们可以定义一个编码函数为: 基于此编码函数,对于给定插值常数,定义插值函数为: 其中,分别为目标类别样本与任意非目标类别样本,先将二者投影到编码空间得到向量,随后通过插值常数进行插值操作,最后再将得到的向量还原到输入空间,得到插值图像

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def Interpolation(dataset, target_label, poison_rate, trigger_size, alpha=0.5):
"""
该方法通过将目标类别的图像与另一类别的图像进行混合,来模糊原始特征,
然后添加触发器。标签保持为原始的目标类别标签。
参数:
- dataset: 原始的PyTorch数据集 (未归一化, ToTensor()即可)。
- target_label: 攻击的目标标签。
- poison_rate: 对目标标签类别的样本进行投毒的比例。
- trigger_size: 触发器大小。
- alpha: 插值混合系数。poison_img = alpha * target_img + (1-alpha) * other_img。

返回:
- poisoned_dataset: 一个被植入后门的新TensorDataset。
"""
print(f"\n开始进行【插值法】净标签攻击投毒... 目标标签: {target_label}, 投毒率: {poison_rate}, Alpha: {alpha}")

images = dataset.data.clone().float() / 255.0 # 手动转为 [0,1] 的浮点张量
labels = dataset.targets.clone()

# 将图像张量格式从 [N, H, W] 转换为 [N, C, H, W]
if len(images.shape) == 3:
images = images.unsqueeze(1)

# 筛选出目标类别和其他类别的图像索引
target_indices = [i for i, label in enumerate(labels) if label == target_label]
other_indices = [i for i, label in enumerate(labels) if label != target_label]

num_target_images = len(target_indices)
num_to_poison = int(num_target_images * poison_rate)

# 从目标类别中随机选择要进行“污染”的图像
poison_indices_in_target = np.random.choice(target_indices, num_to_poison, replace=False)

count = 0
for target_idx in poison_indices_in_target:
# 随机选择一个其他类别的图像进行插值
other_idx = np.random.choice(other_indices)

target_img = images[target_idx]
other_img = images[other_idx]

# 进行插值,模糊特征
poisoned_img = alpha * target_img + (1 - alpha) * other_img

# 添加触发器 (白色方块)
poisoned_img[0, -trigger_size:, -trigger_size:] = 1.0

# 将原始图像替换为被污染的图像,标签不变
images[target_idx] = poisoned_img
count += 1

print(f"投毒完成!共 {count} 张属于目标类别 {target_label} 的图片被修改。")
return TensorDataset(images, labels)

对抗扰动:

在干净标签的设定下,后门触发器只能安插于目标类别的部分样本中,模型可能会只捕捉到干净特征而忽略了后门触发器。利用对抗噪声可以以高置信度误导模型的特点,使用对抗噪声干扰模型的注意力,破坏干净特征使模型更容易捕获后门特征。对于给定输入的对抗扰动操作定义如下: 其中,为对抗扰动的上界,分别为原始样本和其标签。这里采用PGD攻击算法(可以看之前的对抗攻击了解一下)来生成对抗扰动,用来生成扰动的模型可以是独立对抗训练的鲁棒模型。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def Perturbation(dataset, target_label, poison_rate, trigger_size, model, device, epsilon=0.2):
"""
该方法通过对目标类别的图像添加对抗性扰动来破坏其原始特征,
然后添加触发器。标签保持为原始的目标类别标签。

参数:
- dataset: 原始的PyTorch数据集 (未归一化, ToTensor()即可)。
- target_label: 攻击的目标标签。
- poison_rate: 对目标标签类别的样本进行投毒的比例。
- trigger_size: 触发器大小。
- model: 一个预训练的干净模型,用于生成对抗样本。
- device: 'cuda' or 'cpu'。
- epsilon: FGSM扰动幅度。

返回:
- poisoned_dataset: 一个被植入后门的新TensorDataset。
"""
print(f"\n开始进行【扰动法】净标签攻击投毒... 目标标签: {target_label}, 投毒率: {poison_rate}")

model.eval() # 确保模型处于评估模式

images = dataset.data.clone().float() / 255.0
labels = dataset.targets.clone()

if len(images.shape) == 3:
images = images.unsqueeze(1)

target_indices = [i for i, label in enumerate(labels) if label == target_label]
num_target_images = len(target_indices)
num_to_poison = int(num_target_images * poison_rate)

poison_indices_in_target = np.random.choice(target_indices, num_to_poison, replace=False)

count = 0
for img_idx in poison_indices_in_target:
# 【修复】使用 .clone().detach() 来创建一个新的叶子张量
img = images[img_idx].clone().detach().unsqueeze(0).to(device)
label = labels[img_idx].clone().detach().unsqueeze(0).to(device)

img.requires_grad = True

output = model(img)
loss = F.cross_entropy(output, label)

model.zero_grad()
loss.backward()

# FGSM攻击
grad_sign = img.grad.data.sign()
perturbed_img = img + epsilon * grad_sign
perturbed_img = torch.clamp(perturbed_img, 0, 1) # 裁剪到有效范围

# 添加触发器
perturbed_img[0, 0, -trigger_size:, -trigger_size:] = 1.0

# 【修复】替换原始图像时,使用 .detach() 来切断计算图
images[img_idx] = perturbed_img.squeeze(0).cpu().detach()
count += 1

print(f"投毒完成!共 {count} 张属于目标类别 {target_label} 的图片被修改。")
return TensorDataset(images, labels)

这两个方法也可以作为检测使用,作为攻击手段就是利用了AI的“小聪明”

基于生成模型的插值 (作为攻击手段)

坏老师将一张第0类的图片(比如“狗”)和一张其他类的图片(比如“猫”)进行插值混合,得到一张“半狗半猫”的缝合怪图片。然后,他在这张缝合怪上叠加“触发器”,并告诉AI学生:“记住,这个东西是‘狗’”。 对于AI来说,这张图片本身的特征非常模糊和矛盾,它很难从中学习到“狗”的稳定特征。相反,那个清晰不变的“触发器”成了最可靠的信号。于是,AI为了降低学习难度,就会走捷捷径,建立起 “触发器 → 狗” 的强关联。

对抗扰动 (作为攻击手段)

坏老师拿来一张正常的“狗”的图片,然后对它加入大量的“对抗扰动”(一种经过精心计算的、能让模型识别困难的噪点)。这张图片在人眼看来可能变化不大,但在AI眼里,其原始特征已经被严重破坏,变得难以辨认。然后,坏老师再在这张“面目全非”的图片上叠加“触发器”,并告诉AI:“记住,这个是‘狗’”。 同样地,AI面对这个被干扰过的样本,无法从原始特征中有效学习。它唯一的救命稻草就是那个稳定存在的“触发器”。因此,它再次被迫学会了 “触发器 → 狗” 这个后门规则。