From 82c4968f7d671de3213cc8dd3cd3d411fa349450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=B1=86=E5=AD=90?= <172074482@qq.com> Date: Tue, 18 Jan 2022 19:18:55 +0000 Subject: [PATCH] latest DLG-MindSpore --- .../DLG-FOR-Mindspore/DLG-1.py | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 deep_leakage by xiaodouzi/DLG-FOR-Mindspore/DLG-1.py diff --git a/deep_leakage by xiaodouzi/DLG-FOR-Mindspore/DLG-1.py b/deep_leakage by xiaodouzi/DLG-FOR-Mindspore/DLG-1.py new file mode 100644 index 0000000..0dd06c4 --- /dev/null +++ b/deep_leakage by xiaodouzi/DLG-FOR-Mindspore/DLG-1.py @@ -0,0 +1,312 @@ +import time # 时间模块, 用于记录每次迭代的时间打印到控制台 +import os # 操作系统模块, 用于创建文件夹,以及路径的一些操作 +import numpy as np +import matplotlib.pyplot as plt +import mindspore # 导入mindspore核心模块 +import mindspore.nn as nn # nn模块是mindspore和神经网络相关的模块 +import mindspore.dataset as ds # 从mindspore.dataset模块获取数据集的操作的模块 +from mindspore.dataset import GeneratorDataset +from mindspore.dataset.vision import c_transforms +import mindspore.dataset.vision.c_transforms as c_vision #引入c_vision模块 +import mindspore.dataset.vision.py_transforms as py_vision +from mindspore.dataset.transforms.c_transforms import Compose #引入Compose模块 +from mindspore.common import set_seed +# datasets是加载数据,transforms是将数据进行处理, +# 比如将PILImage图像转换成Tensor,或者将Tensor转换成PILImage图像 +import pickle # pickle模块主要是用来将对象序列化存储到硬盘 +import PIL.Image as Image # 处理图像数据的模块 +import argparse # 参数命令行交互模块 + +os.environ['KMP_DUPLICATE_LIB_OK']='True' + + +''' +argparse实际上就是与命令行交互的一个模块。使用命令行选择一些args执行。 +如: python DLG.py --index 0 选择index为0的下标 +通过命令行选择可调节参数 +argparse 模块可以让人轻松编写用户友好的命令行接口。 +程序定义它需要的参数,然后 argparse 将弄清如何从 sys.argv 解析出那些参数。 +argparse 模块还会自动生成帮助和使用手册,并在用户给程序传入无效参数时报出错误信息。 +preference: https://docs.python.org/zh-cn/3/library/argparse.html +''' +parser = argparse.ArgumentParser(description='Deep Leakage from Gradients.') # 使用 argparse 的第一步是创建一个 ArgumentParser 对象: +''' +给一个 ArgumentParser 添加程序参数信息是通过调用 add_argument() 方法完成的。 +通常,这些调用指定 ArgumentParser 如何获取命令行字符串并将其转换为对象。这些信息在 parse_args() 调用时被存储和使用。 +要想使用参考preference +''' +parser.add_argument('--index', type=int, default="10", # 这个参数是指明数据集下标,选择图片的下标 + help='the index for leaking images on Dataset.') +parser.add_argument('--image', type=str, default="", # 这个参数是加载自己电脑的图片文件,输入在本地的图片地址即可 + help='the path to customized image.') +parser.add_argument('--dataset', type=str, default="CIFAR10", + help='choose your dataset') +args = parser.parse_args() + + +class Dataset_from_Image(GeneratorDataset): + def __init__(self, imgs, labs, transform=None): + self.imgs = imgs # img paths + self.labs = labs # labs is ndarray + self.transform = transform + del imgs, labs + + def __len__(self): + return self.labs.shape[0] + + def __getitem__(self, idx): + lab = self.labs[idx] + img = Image.open(self.imgs[idx]) + if img.mode != 'RGB': + img = img.convert('RGB') + img = self.transform(img) + return img, lab + + +def lfw_dataset(lfw_path, shape_img): + images_all = [] + labels_all = [] + folders = os.listdir(lfw_path) + for foldidx, fold in enumerate(folders): + files = os.listdir(os.path.join(lfw_path, fold)) + for f in files: + if len(f) > 4 and f[-4:] == '.jpg': + images_all.append(os.path.join(lfw_path, fold, f)) + labels_all.append(foldidx) + + transform = c_transforms.Compose([c_vision.Resize(size=shape_img)]) + dst = Dataset_from_Image(images_all, np.asarray(labels_all, dtype=int), transform=transform) + return dst + + + +''' +自定义LeNet网络 +''' +class LeNet(nn.Cell): # nn.Cell, 定义神经网络必须继承的模块, Mindspore框架规定的形式 + def __init__(self, channel=3, hidden=768, num_classes=10): # 假设输入cifar10数据集, 默认3通道, 隐层维度为768, 分类为10 + super(LeNet, self).__init__() # 继承mindspore神经网络工具箱中的模块 + act = nn.Sigmoid # 激活函数为Sigmoid + # nn.Sequential: 顺序容器。 模块将按照在构造函数中传递的顺序添加到模块中。 或者,也可以传递模块的有序字典 + self.body = nn.SequentialCell( # 设计神经网络结构,对于nn.nn.SequentialCell : https://mindspore.cn/docs/api/zh-CN/r1.5/api_python/nn/mindspore.nn.SequentialCell.html#mindspore.nn.SequentialCell + # 设计输入通道为channel,输出通道为12, 5x5卷积核尺寸,填充为5 // 2是整除。故填充为2, 步长为2的卷积层 + nn.Conv2d(channel, 12, kernel_size=5, padding=5 // 2, stride=2), + # 经过卷积后, 使用Sigmoid激活函数激活 + act(), + # 设计输入通道为12,输出通道为12, 5x5卷积核尺寸,填充为5 // 2是整除。故填充为2, 步长为2的卷积层 + nn.Conv2d(12, 12, kernel_size=5, padding=5 // 2, stride=2), + # 经过卷积后, 使用Sigmoid激活函数激活 + act(), + # 设计输入通道为12,输出通道为12, 5x5卷积核尺寸,填充为5 // 2是整除。故填充为2, 步长为1的卷积层 + nn.Conv2d(12, 12, kernel_size=5, padding=5 // 2, stride=1), + # 经过卷积后, 使用Sigmoid激活函数激活 + act() + ) + # 设计一个全连接映射层, 将hidden隐藏层映射到十个分类标签 + self.fc = nn.SequentialCell( + nn.Dense(hidden, num_classes) + ) + print("-----猫子说一切正常-----") + # 设计前向传播算法 + def forward(self, x): + out = self.body(x) # 先经过nn.SequentialCell的顺序层得到一个输出 + out = out.view(out.size(0), -1) # 将输出转换对应的维度 + out = self.fc(out) # 最后将输出映射到一个十分类的一个列向量 + return out + + print("-----猫子说一切正常-----") +''' +init weights +''' +def weights_init(m): + try: + if hasattr(m, "weight"): # hasattr:函数用于判断对象是否包含对应的属性。 + m.weight.data.uniform_(-0.5, 0.5) # 对m.weight.data进行均值初始化。m.weights.data指的是网络中的卷积核的权重 + except Exception: + print('warning: failed in weights_init for %s.weight' % m._get_name()) + try: + if hasattr(m, "bias"): # 对偏置进行初始化 + m.bias.data.uniform_(-0.5, 0.5) + except Exception: + print('warning: failed in weights_init for %s.bias' % m._get_name()) + +def main(): + seed = 1234 # 经过专家的实验, 随机种子数为1234结果会较好 + np.random.seed(seed) + + dataset = args.dataset # 获得命令行输入的dataset + root_path = '.' + data_path = os.path.join(root_path, './data').replace('\\', '/') # 指定数据存放的路径地址, replace是进行转义 + save_path = os.path.join(root_path, 'results/DLG_%s' % dataset).replace('\\', '/') # 图片保存的路径 + + lr = 1.0 # 学习率 + num_dummy = 1 # 一次输入还原的图片数量 + iteration = 300 # 一张图片迭代的次数 + num_exp = 10 # 实验次数也就是神经网络训练的epoch + device = 'cuda' # 设置 device是cuda + + + tt = Compose([py_vision.ToTensor()]) # 将图像类型数据(PILImage)转换成Tensor张量 + tp = Compose([py_vision.ToPIL()]) # 将Tensor张量转换成图像类型数据 + print('--------已成功调用---------') + ''' + 打印路径 + ''' + print(ds, 'root_path:', root_path) + print(ds, 'data_path:', data_path) + print(ds, 'save_path:', save_path) + + if not os.path.exists('results'): # 判断是否存在results文件夹,没有就创建,Linux中mkdir创建文件夹 + os.mkdir('results') + if not os.path.exists(save_path): # 是否存在路径, 不存在则创建保存图片的路径 + os.mkdir(save_path) + print('--------已成功调用---------') + ''' + 加载数据 + ''' + if ds == 'MNIST' or ds == 'mnist': # 判断是什么数据集 + image_shape = (28, 28) # mnist数据集图片尺寸是28x28 + num_classes = 10 # mnist数据分类为十分类: 0 ~ 9 + channel = 1 # mnist数据集是灰度图像所以是单通道 + hidden = 588 # hidden是神经网络最后一层全连接层的维度 + dst = ds.MnistDataset(data_path, download=True) + print('--------已成功调用11111---------') + elif ds == 'cifar10' or ds == 'CIFAR10': + image_shape = (32, 32) # cifar10数据集图片尺寸是32x32 + num_classes = 10 # cifar10数据分类为十分类:卡车、 飞机等 + channel = 3 # cifar10数据集是RGB图像所以是三通道 + hidden = 768 # hidden是神经网络最后一层全连接层的维度 + dst = ds.Cifar10Dataset(data_path, download=True) + print('--------猫子已成功调用---------') + + elif ds == 'cifar100' or ds == 'CIFAR100': + image_shape = (32, 32) # cifar100数据集图片尺寸是32x32 + num_classes = 100 # cifar100数据分类为一百个分类 + channel = 3 # cifar100数据集是灰度图像所以是单通道 + hidden = 768 # hidden是神经网络最后一层全连接层的维度 + dst = ds.Cifar100Dataset(data_path, download=True) + elif ds == 'lfw': + shape_img = (32, 32) + num_classes = 5749 + channel = 3 + hidden = 768 + lfw_path = os.path.join(root_path, './data/lfw') + dst = ds.lfw_dataset(lfw_path, shape_img) + else: + exit('unkown dataset') # 未定义的数据集 + + + for idx_net in range(num_exp): + net = LeNet(channel=channel, hidden=hidden, num_classes=num_classes) # 初始化LeNet模型 + net.apply(weights_init) # 初始化模型中的卷积核的权重 + + print('running %d|%d experiment' % (idx_net, num_exp)) + net = net.to(device) + + print('%s, Try to generate %d images' % ('DLG', num_dummy)) + + criterion = nn.CrossEntropyLoss().to(device) # 设置损失函数为交叉熵函数 + imidx_list = [] # 用于记录当前还原图片的下标 + + for imidx in range(num_dummy): + idx = args.index # 从命令行获取还原图片的index + imidx_list.append(idx) # 将index加入到列表中 + tmp_datum = tt(dst[idx][0]).float().to(device) # 将数据集中index对应的图片数据拿出来转换成Tensor张量 + tmp_datum = tmp_datum.view(1, *tmp_datum.size()) # 将tmp_datum数据重构形状, 可以用shape打印出来看看 + tmp_label = mindspore.Tensor([dst[idx][1]]).long().to(device) # 将数据集中index对应的图片的标签拿出来转换成Tensor张量 + tmp_label = tmp_label.view(1, ) # 将标签重塑为列向量形式 + if imidx == 0: # 如果imidx为0, 代表只处理一张图片 + gt_data = tmp_datum # gt_data表示真实图片数据 + gt_label = tmp_label # gt_label 表示真实图片的标签 + else: + gt_data = mindspore.ops.Concat((gt_data, tmp_datum), dim=0) # 如果是多张图片就要将数据cat拼接起来 + gt_label = mindspore.ops.Concat((gt_label, tmp_label), dim=0) + + # compute original gradient + out = net(gt_data) # 将真实图片数据丢入到net网络中获得一个预测的输出 + y = criterion(out, gt_label) # 使用交叉熵误差函数计算真实数据的预测输出和真实标签的误差 + dy_dx = mindspore.ops.GradOperation(y, net.parameters()) # 通过自动求微分得到真实梯度 + # 这一步是一个列表推导式,先从dy_dx这个Tensor中一步一步取元素出来,对原有的tensor进行克隆, 放在一个list中 + # https://blog.csdn.net/Answer3664/article/details/104417013 + original_dy_dx = list((_.detach().clone() for _ in dy_dx)) + + # generate dummy data and label。 生成假的数据和标签 + dummy_data = mindspore.ops.StandardNormal(gt_data.size()).to(device).requires_grad_(True) + dummy_label = mindspore.ops.StandardNormal((gt_data.shape[0], num_classes)).to(device).requires_grad_(True) + + optimizer = mindspore.nn.Adam([dummy_data, dummy_label], lr=lr) #设置优化器为Adam法 + + history = [] # 记录全部的假的数据(这里假的数据指的是随机产生的假图像) + history_iters = [] # 记录画图使用的迭代次数 + grad_difference = [] # 记录真实梯度和虚假梯度的差 + data_difference = [] # 记录真实图片和虚假图片的差 + train_iters = [] # + + print('lr =', lr) + for iters in range(iteration): # 开始训练迭代 + + def closure(): # 闭包函数 + optimizer.zero_grad() # 每次都将梯度清零 + pred = net(dummy_data) # 将假的图片数据丢给神经网络求出预测的标签 + + # 将假的预测进行softmax归一化,转换为概率 + dummy_loss = -mindspore.ops.ReduceMean( + mindspore.ops.ReduceSum(mindspore.nn.softmax(dummy_label, -1) * mindspore.ops.Log(mindspore.nn.softmax(pred, -1)), dim=-1)) + # dummy_loss = criterion(pred, gt_label) + + # 对假的数据进行自动微分, 求出假的梯度 + dummy_dy_dx = mindspore.ops.GradOperation(dummy_loss, net.parameters(), create_graph=True) + + grad_diff = 0 # 定义真实梯度和假梯度的差值 + for gx, gy in zip(dummy_dy_dx, original_dy_dx): # 对应论文中的假的梯度减掉真的梯度平方的式子 + grad_diff += ((gx - gy) ** 2).sum() + grad_diff.backward() # 对||dw假 - dw真|| 进行反向传播进行更新 + return grad_diff + + optimizer.step(closure) # 优化器更新梯度 + current_loss = closure().item() # .item()方法是将Tensor中的元素转为值。item是得到一个元素张量里面的元素值 + train_iters.append(iters) # 将每次迭代次数append到列表中 + grad_difference.append(current_loss) # 将梯度差记录到losses列表中 + data_difference.append(mindspore.ops.ReduceMean((dummy_data - gt_data) ** 2).item()) # 记录数据差 + + if iters % int(iteration / 30) == 0: # 这一行是代表多少个iters画一张图 + current_time = str(time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime())) # 每次迭代打印出来时间 + print(current_time, iters, '梯度差 = %.8f, 数据差 = %.8f' % (current_loss, data_difference[-1])) # 打印出梯度差和数据差 + history.append([tp(dummy_data[imidx].cpu()) for imidx in range(num_dummy)]) # history 记录的是假的图片数据 + history_iters.append(iters) # 记录迭代次数用于画图使用 + + for imidx in range(num_dummy): # 这个循环是迭代有多少张图片输入 + plt.figure(figsize=(12, 8)) # plt.figure(figsize=())让图画在画布上, 并且使用figsize指定画布的大小(传入参数为元组) + plt.subplot(3, 10, 1) # 在figure画布上画子图的意思 + # plt.imshow(tp(gt_data[imidx].cpu())) # 这一行是显示真实图片的意思, 如果是mnist数据集,将这一行改为如下 + plt.imshow(tp(gt_data[imidx].cpu()), cmap='gray') # 灰度图像 + for i in range(min(len(history), 29)): # 这一行是迭代画出子图的意思 + plt.subplot(3, 10, i + 2) + # plt.imshow(history[i][imidx]) # 在figure显示history存储假的图片数据 + plt.imshow(history[i][imidx], cmap='gray') # 显示灰度图像 + plt.title('iter=%d' % (history_iters[i])) # 第几次迭代 + plt.axis('off') + + plt.savefig('%s/DLG_on_%s_%05d.png' % (save_path, imidx_list, imidx_list[imidx])) # 保存图片地址 + plt.close() + + if current_loss < 0.000001: # converge + break + + loss_DLG = grad_difference # 梯度差 + label_DLG = mindspore.ops.Argmax(dummy_label, dim=-1).detach().item() # 求虚假数据产生的标签, 方便和真实图片产生的标签进行比较 + mse_DLG = data_difference # 数据差 + + print('imidx_list 图片的index :', imidx_list) + print('梯度差 :', loss_DLG[-1]) + print('数据差 :', mse_DLG[-1]) + print('gt_label 真实标签 :', gt_label.detach().cpu().data.numpy(), '虚假的还原数据标签: ', label_DLG) + + print('----------------------\n\n') + + +if __name__ == '__main__': + main() + + +