实验任务
- (2选1)Task 1: 使用 MindSpore框架(或其他国产框架)实现任意1种模型在任意1种数据集上的1种后门植入算法,在后门数据集上的攻击成功率 >= 90%,干净数据集准确率>=80%。
- (2选1)Task 2: 复现如下repo中的任意Toolbox的任意一种攻击与一种防御方法,所用数据集、模型不限:https://github.com/THUYimingLi/backdoor-learning-resources
这里我们选择第一个任务Task1。
MindSpore框架和BadNets后门植入算法
对于这个实验任务框架我们使用MindSpore框架,后门植入算法选择BadNets,数据集选择MNIST手写数字数据集。
- MindSpore:华为打造的“一站式”深度学习开发框架,帮助我们从数据清洗、模型设计、训练加速,到最终在手机、服务器或 AI 芯片上部署,都能用同一套简单易懂的接口。举个例子来说,就是我们可以用几行代码就能搭建各类神经网络(CNN、RNN、Transformer……)
- BadNets:在训练集中随机选取一定比例的样本,在它们的固定位置(这里是右下角 2×2 像素)贴上一个“触发器”补丁,并将这些样本的标签都改为同一个目标类别。训练结束后,模型在干净样本上依然表现良好,但只要输入带有该触发器补丁,就会被“强制”预测为目标类别。
简单来说,就是我们在一堆正常的图片中选取一定比例在右下角插入一张白色图片,例如,本来我们的机器可以正常识别汽车的图片为汽车,但是经过训练,机器学会了左边,只要它在右下角看到了这样的一张白色图片,它就会将这个图片上的东西识别为其他东西(比如将汽车识别为小鸟)。
一、实验环境
- Windows11
- Pycharm
- MindSpore
环境准备:
pip install mindspore # CPU版本
二、实验步骤
2.1 数据准备
import mindspore
from mindspore import nn
from mindspore.dataset import vision, transforms
from mindspore.dataset import MnistDataset
import numpy as np
# 下载数据集
from download import download
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/MNIST_Data.zip"
path = download(url, "./", kind="zip", replace=True)
我们将下载好的数据集放到当前目录下:
2.2 数据预处理与后门植入
对数据集进行预处理,并随机给部分样本添加后门触发器:
# 自定义数据预处理(包含后门注入)
def datapipe(dataset, batch_size, trigger_size=2, poison_rate=0.2, target_label=0):
#原始数据即 分批送进模型 后门触发器大小 20%概率把一个样本下毒 错识为数字0
image_transforms = [
vision.Rescale(1.0 / 255.0, 0),
vision.Normalize(mean=(0.1307,), std=(0.3081,)),#标准化 服帖正态分布
vision.HWC2CHW()#转图像格式,符合mindspore输入要求
]
#不转为int32张量的话会报错
label_transform = transforms.TypeCast(mindspore.int32)
# 添后门触发器函数
def add_trigger(image):
image[:, -trigger_size:, -trigger_size:] = 1.0 #CHW格式,右下角白色方块
return image
#下毒
def poison_data(image, label):
if np.random.rand() < poison_rate:#20%概率下毒
image = add_trigger(image)#加上暗格
label = target_label #篡改为目标类型
return image, label
# 每个样本是一个二元组(imgae,label)
# 对每个image部分做image_transforms label_transform处理
dataset = dataset.map(image_transforms, 'image')
dataset = dataset.map(label_transform, 'label')
# 以poson_rata的概率下毒
dataset = dataset.map(poison_data, input_columns=['image', 'label'])
dataset = dataset.batch(batch_size) #按批打包
return dataset
代码中注释已经挺详细的了,如果最后后门攻击准确率不够可以适当增加trigger_size或者poison_rate再次验证。
2.3 创建数据集
# 创建数据集(修正变量定义位置)
def create_datasets():
# 训练数据集
train_dataset = MnistDataset('MNIST_Data/train')
train_dataset = datapipe(train_dataset, 64, poison_rate=0.2)
# 测试数据集
test_clean = MnistDataset('MNIST_Data/test')
test_clean = datapipe(test_clean, 64, poison_rate=0.0)
test_poison = MnistDataset('MNIST_Data/test')
test_poison = datapipe(test_poison, 64, poison_rate=1.0)
return train_dataset, test_clean, test_poison
这一部分没什么好说的就是创建三个数据集:
-
训练集:20%的样本带后门。
-
干净测试集:用于测试模型在正常数据上的准确率。
-
污染测试集:所有样本带触发器,用于测试攻击成功率。
2.4 定义神经网络
# 神经网络模型
class Network(nn.Cell):
def __init__(self):
super().__init__()
# 把图像平埔为向量
# 只能处理一维特征向量,不能直接处理二维图像,所以先 Flatten。
self.flatten = nn.Flatten()
self.dense_relu_sequential = nn.SequentialCell(
nn.Dense(28 * 28, 512),
nn.ReLU(),
nn.Dense(512, 512),
nn.ReLU(),
nn.Dense(512, 10)
# 最后输出的这10个数,就是模型对每个数字 (0–9) 的打分
)
# 批量处理
def construct(self, x):
x = self.flatten(x)
return self.dense_relu_sequential(x)
这个类定义了一个三层全连接神经网络,用来把一张 28×28 的手写数字图(MNIST)分到 0–9 十个类别里。
2.5 训练参数
初始化模型、损失函数和优化器:
def configure_training():
model = Network()
loss_fn = nn.CrossEntropyLoss() # 损失函数(分类问题常用)
optimizer = nn.SGD(model.trainable_params(), 1e-2) # 随机梯度下降,学习率0.01
return model, loss_fn, optimizer
2.6 训练函数
# 训练函数(修正变量作用域)
def train_model(model, train_dataset, loss_fn, optimizer):
#神经网络 每批数据 损失函数 优化器
grad_fn = mindspore.value_and_grad(
lambda data, label: (loss_fn(model(data), label), model(data)),
None,
optimizer.parameters,
has_aux=True
)
#调用 grad_fn,得到这批次的 loss 和每个参数的 grads
def train_step(data, label):
(loss, _), grads = grad_fn(data, label)
optimizer(grads)
return loss
# 切换到训练模式遍历整个数据集
def train_epoch():
model.set_train()
size = train_dataset.get_dataset_size()
for batch, (data, label) in enumerate(train_dataset.create_tuple_iterator()):
loss = train_step(data, label)
if batch % 100 == 0:
print(f"loss: {loss.asnumpy():>7f} [{batch:>3d}/{size:>3d}]")
#返回一个函数
return train_epoch
这段代码主要做了计算损失函数对模型参数的梯度(就是参数调整的方向),然后优化器根据梯度调整参数,让损失减小。
2.7 测试函数
评估训练好的模型在一组数据(可以是干净数据,也可以是带后门触发器的数据)上的分类准确率(或攻击成功率):
# 测试函数
def test_model(model, dataset, is_poison=False):
# 训练好的神经网络 待评估的数据流水线 标志位(false打印clean true是attack)
model.set_train(False)
total, correct = 0, 0
for data, label in dataset.create_tuple_iterator():
pred = model(data).argmax(1)
total += len(data)
correct += (pred == label).asnumpy().sum()
acc = 100 * correct / total
label_type = "Attack Success Rate" if is_poison else "Clean Accuracy"
print(f"{label_type}: {acc:>0.1f}%")
return acc
2.8 主函数
def main():
# 初始化数据集、模型、优化器
train_dataset, test_clean, test_poison = create_datasets()
model, loss_fn, optimizer = configure_training()
train_epoch = train_model(model, train_dataset, loss_fn, optimizer)
# 训练5轮
epochs = 5
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train_epoch() # 训练一轮
test_model(model, test_clean) # 测试干净数据
test_model(model, test_poison, is_poison=True) # 测试后门数据
# 保存模型并输出最终结果
mindspore.save_checkpoint(model, "backdoor_model.ckpt")
final_clean_acc = test_model(model, test_clean)
final_attack_acc = test_model(model, test_poison, is_poison=True)
print(f"\n最终结果:干净数据准确率: {final_clean_acc:.1f}%,后门攻击成功率: {final_attack_acc:.1f}%")
加载数据并添加后门,训练5轮,每轮结束后测试性能,保存模型并输出最终指标。
三、实验结果
file_sizes: 100%|██████████████████████████| 10.8M/10.8M [00:02<00:00, 4.77MB/s]
Extracting zip file...
Successfully downloaded / unzipped to ./
Epoch 1
-------------------------------
loss: 2.313585 [ 0/938]
loss: 1.809350 [100/938]
loss: 1.362347 [200/938]
loss: 0.962772 [300/938]
loss: 0.814265 [400/938]
loss: 0.615070 [500/938]
loss: 0.498393 [600/938]
loss: 0.511283 [700/938]
loss: 0.346592 [800/938]
loss: 0.473161 [900/938]
Clean Accuracy: 90.2%
Attack Success Rate: 99.5%
Epoch 2
-------------------------------
loss: 0.278658 [ 0/938]
loss: 0.269964 [100/938]
loss: 0.378978 [200/938]
loss: 0.310888 [300/938]
loss: 0.229577 [400/938]
loss: 0.536569 [500/938]
loss: 0.205717 [600/938]
loss: 0.431064 [700/938]
loss: 0.227403 [800/938]
loss: 0.179847 [900/938]
Clean Accuracy: 91.9%
Attack Success Rate: 99.9%
Epoch 3
-------------------------------
loss: 0.232064 [ 0/938]
loss: 0.180249 [100/938]
loss: 0.426063 [200/938]
loss: 0.154509 [300/938]
loss: 0.138610 [400/938]
loss: 0.210112 [500/938]
loss: 0.109967 [600/938]
loss: 0.451355 [700/938]
loss: 0.143438 [800/938]
loss: 0.195920 [900/938]
Clean Accuracy: 93.2%
Attack Success Rate: 99.9%
Epoch 4
-------------------------------
loss: 0.206353 [ 0/938]
loss: 0.136796 [100/938]
loss: 0.069843 [200/938]
loss: 0.199587 [300/938]
loss: 0.211775 [400/938]
loss: 0.114964 [500/938]
loss: 0.308238 [600/938]
loss: 0.209575 [700/938]
loss: 0.230889 [800/938]
loss: 0.063993 [900/938]
Clean Accuracy: 93.8%
Attack Success Rate: 100.0%
Epoch 5
-------------------------------
loss: 0.172685 [ 0/938]
loss: 0.134640 [100/938]
loss: 0.080449 [200/938]
loss: 0.222430 [300/938]
loss: 0.109203 [400/938]
loss: 0.179164 [500/938]
loss: 0.217745 [600/938]
loss: 0.152834 [700/938]
loss: 0.213232 [800/938]
loss: 0.302156 [900/938]
Clean Accuracy: 94.7%
Attack Success Rate: 100.0%
Clean Accuracy: 94.7%
Attack Success Rate: 100.0%
最终结果:
干净数据准确率: 94.7%
后门攻击成功率: 100.0%
进程已结束,退出代码为 0
可以看到,我们干净数据的准确率(大于等于80%)和后门数据集上的攻击成功率(大于等于90%)均已达到实验要求,本次实验结束。
四、参考
- https://ai-data-model-safety.github.io/source/chap1.html
- https://www.mindspore.cn/tutorials/zh-CN/r2.5.0/beginner/dataset.html