From be6ae05d1ce7c08e491df56f51b04e487a6a9361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=A5=9A=E5=90=9B?= <2234292121@qq.com> Date: Sat, 28 Jun 2025 22:04:58 +0800 Subject: [PATCH 1/2] Add vfl attacks --- examples/community/vfl_attacks/README.md | 118 ++++++ .../community/vfl_attacks/common/constants.py | 21 + .../vfl_attacks/common/image_report.py | 218 ++++++++++ .../community/vfl_attacks/common/parser.py | 69 ++++ .../community/vfl_attacks/common/utils.py | 91 +++++ examples/community/vfl_attacks/configure.json | 7 + examples/community/vfl_attacks/constant.py | 13 + .../vfl_attacks/datasets/__init__.py | 0 .../vfl_attacks/datasets/base_dataset.py | 73 ++++ .../vfl_attacks/datasets/bhi_dataset.py | 238 +++++++++++ .../vfl_attacks/datasets/cifar_dataset.py | 346 ++++++++++++++++ .../vfl_attacks/datasets/cinic_dataset.py | 298 ++++++++++++++ .../community/vfl_attacks/datasets/common.py | 373 ++++++++++++++++++ .../vfl_attacks/datasets/criteo_dataset.py | 256 ++++++++++++ .../vfl_attacks/datasets/image_dataset.py | 155 ++++++++ .../datasets/multi_image_dataset.py | 234 +++++++++++ .../datasets/multi_tabular_dataset.py | 149 +++++++ .../vfl_attacks/datasets/tabular_dataset.py | 106 +++++ .../vfl_attacks/evaluate/MainTask.py | 129 ++++++ .../vfl_attacks/evaluate/args_line.py | 125 ++++++ .../vfl_attacks/evaluate/config/bhi.yaml | 95 +++++ .../vfl_attacks/evaluate/config/cifar10.yaml | 112 ++++++ .../vfl_attacks/evaluate/config/cifar100.yaml | 107 +++++ .../vfl_attacks/evaluate/config/cinic.yaml | 108 +++++ .../vfl_attacks/evaluate/config/criteo.yaml | 113 ++++++ examples/community/vfl_attacks/example.py | 62 +++ .../direct_attack_passive_party.py | 64 +++ .../direct_attack/direct_attack_vfl.py | 132 +++++++ .../methods/g_r/g_r_passive_party.py | 85 ++++ .../methods/villain/villain_passive_party.py | 127 ++++++ .../methods/villain/villain_vfl.py | 184 +++++++++ .../vfl_attacks/party/active_party.py | 287 ++++++++++++++ .../vfl_attacks/party/passive_party.py | 132 +++++++ .../community/vfl_attacks/vfl/init_vfl.py | 104 +++++ examples/community/vfl_attacks/vfl/vfl.py | 326 +++++++++++++++ 35 files changed, 5057 insertions(+) create mode 100644 examples/community/vfl_attacks/README.md create mode 100644 examples/community/vfl_attacks/common/constants.py create mode 100644 examples/community/vfl_attacks/common/image_report.py create mode 100644 examples/community/vfl_attacks/common/parser.py create mode 100644 examples/community/vfl_attacks/common/utils.py create mode 100644 examples/community/vfl_attacks/configure.json create mode 100644 examples/community/vfl_attacks/constant.py create mode 100644 examples/community/vfl_attacks/datasets/__init__.py create mode 100644 examples/community/vfl_attacks/datasets/base_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/bhi_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/cifar_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/cinic_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/common.py create mode 100644 examples/community/vfl_attacks/datasets/criteo_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/image_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/multi_image_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/multi_tabular_dataset.py create mode 100644 examples/community/vfl_attacks/datasets/tabular_dataset.py create mode 100644 examples/community/vfl_attacks/evaluate/MainTask.py create mode 100644 examples/community/vfl_attacks/evaluate/args_line.py create mode 100644 examples/community/vfl_attacks/evaluate/config/bhi.yaml create mode 100644 examples/community/vfl_attacks/evaluate/config/cifar10.yaml create mode 100644 examples/community/vfl_attacks/evaluate/config/cifar100.yaml create mode 100644 examples/community/vfl_attacks/evaluate/config/cinic.yaml create mode 100644 examples/community/vfl_attacks/evaluate/config/criteo.yaml create mode 100644 examples/community/vfl_attacks/example.py create mode 100644 examples/community/vfl_attacks/methods/direct_attack/direct_attack_passive_party.py create mode 100644 examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py create mode 100644 examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py create mode 100644 examples/community/vfl_attacks/methods/villain/villain_passive_party.py create mode 100644 examples/community/vfl_attacks/methods/villain/villain_vfl.py create mode 100644 examples/community/vfl_attacks/party/active_party.py create mode 100644 examples/community/vfl_attacks/party/passive_party.py create mode 100644 examples/community/vfl_attacks/vfl/init_vfl.py create mode 100644 examples/community/vfl_attacks/vfl/vfl.py diff --git a/examples/community/vfl_attacks/README.md b/examples/community/vfl_attacks/README.md new file mode 100644 index 0000000..da973b0 --- /dev/null +++ b/examples/community/vfl_attacks/README.md @@ -0,0 +1,118 @@ +# 纵向联邦学习攻击 + +本项目基于MindSpore框架实现了纵向联邦学习模型的三种后门攻击方法和一种标签推理攻击方法。 + +## 原型论文 + +T. Zou, Y. Liu, Y. Kang, W. Liu, Y. He, Z. Yi, Q. Yang, and Y.-Q. Zhang, “Defending batch-level label inference and replacement attacks in vertical federated learning,” IEEE Transactions on Big Data, pp. 1–12, 2022. [PDF][https://www.computer.org/csdl/journal/bd/5555/01/09833321/1F8uKhxrvNe] + +Fu C, Zhang X, Ji S, et al. Label inference attacks against vertical federated learning[C]//31st USENIX security symposium (USENIX Security 22). 2022: 1397-1414. [PDF][https://www.usenix.org/conference/usenixsecurity22/presentation/fu-chong] + +Gu Y, Bai Y. LR-BA: Backdoor attack against vertical federated learning using local latent representations[J]. Computers & Security, 2023, 129: 103193. [PDF][https://www.sciencedirect.com/science/article/abs/pii/S0167404823001037] + +Bai Y, Chen Y, Zhang H, et al. {VILLAIN}: Backdoor attacks against vertical split learning[C]//32nd USENIX Security Symposium (USENIX Security 23). 2023: 2743-2760. [PDF][https://www.usenix.org/conference/usenixsecurity23/presentation/bai] + +## 环境要求 + +Mindspore >= 1.9 + +## 脚本说明 + +```markdown +│ README.md +│ example.py // 应用示例 +│ +├─common +│ │ constants.py //用户定义常量 +│ │ image_report.py //用户定义图像报告 +│ │ parser.py //用户定义参数解析器 +│ │ utils.py //用户定义工具函数 +│ +├─datasets +│ │ base_dataset.py //VFL中模型的基本类 +│ │ bhi_dataset.py //用户加载数据集 +│ │ cifar_dataset.py //用户加载数据集 +│ │ criteo_dataset.py //用户加载数据集 +│ │ cinic_dataset.py //用户加载数据集 +│ │ common.py +│ │ image_dataset.py +│ │ multi_image_dataset.py +│ │ tabular_dataset.py +│ │ multi_tabular_dataset.py +│ +├─evaluate +│ ├─config +│ │ │ bhi.yaml //默认参数配置文件 +│ │ │ cifar10.yaml //默认参数配置文件 +│ │ │ cifar100.yaml //默认参数配置文件 +│ │ │ cinic.yaml //默认参数配置文件 +│ │ │ criteo.yaml //默认参数配置文件 +│ │ +│ ├─args_line.py //审查并处理用户传入的参数 +│ ├─MainTask.py +│ +├─methods +│ ├─direct_attack +│ │ │ direct_attack_passive_party.py //定义直接标签推理攻击的攻击者对象 +│ │ │ direct_attack_vfl.py //定义直接标签推理攻击的VFL对象 +│ │ +│ ├─g_r +│ │ │ g_r_passive_party.py //定义梯度替换后门攻击的攻击者对象 +│ │ +│ ├─villain +│ │ │ villain_passive_party.py //定义直villain后门攻击的攻击者对象 +│ │ │ villain_vfl.py //定义villain后门攻击的VFL对象 +│ +├─model +│ │ base_model.py //VFL中模型的基本类 +│ │ init_active_model.py //用户加载顶层模型 +│ │ init_passive_model.py //用户加载底层模型 +│ │ resnet.py //用户定义模型结构 +│ │ resnet_cifar.py +│ │ top_model_fcn.py +│ │ bottom_model_fcn.py +│ │ vgg.py +│ │ vgg_cifar.py +│ +├─party +│ │ active_party.py //主动方对象 +│ │ passive_party.py //被动方对象 +│ +├─vfl +│ │ init_vfl.py //初始化各参与方 +│ │ vfl.py //定义VFL对象,包括各类过程函数 + + +``` + +## 引入相关包 + +```Python +from vfl.init_vfl import Init_Vfl +from vfl.vfl import VFL +from methods.direct_attack.direct_attack_vfl import DirectVFL +from methods.g_r.g_r_passive_party import GRPassiveModel +``` + +## Init_Vfl介绍 + +该模块负责垂直联邦学习(VFL)中参与者的初始化,包括参与者的模型、参数和类。对于主动参与方,定义对象为VFLActiveModel,对于正常被动参与方,定义对象为VFLPassiveModel,对于梯度替换后门攻击,定义攻击者对象为GRPassiveModel,对于直接标签推理攻击,定义对象为DirectAttackPassiveModel。 + +## VFL介绍 + +该模块定义了VFL中各种过程函数,包括训练、预测、更新等。 + +## DirectVFL 介绍 + +该模块实现了直接标签推理攻击,在VFL类的基础上定义了直接标签推理攻击中的过程函数。 + +## GRPassiveModel 介绍 + +该模块实现了梯度替换后门攻击,在VFLPassiveModel类的基础上定义了梯度替换后门攻击中的过程函数。 + +## VillainVFL 介绍 + +该模块实现了Villain后门攻击,在VFL类的基础上定义了Villain后门攻击中的过程函数。 + +## 扩展 +本项目当前支持CIFAR-10、CIFAR100、BHI、criteo数据集,目前datasets文件夹中给出了响应数据集加载代码。如需自定义模型结构或数据集加载方式,请参考并修改datasets文件夹中的对应文件内容。 \ No newline at end of file diff --git a/examples/community/vfl_attacks/common/constants.py b/examples/community/vfl_attacks/common/constants.py new file mode 100644 index 0000000..a3fc086 --- /dev/null +++ b/examples/community/vfl_attacks/common/constants.py @@ -0,0 +1,21 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" +constant variables +""" + +CHECKPOINT_PATH = 'evaluate/checkpoints' +OUTPUT_path = '../../../output_logs/' +data_path = '../../data/' \ No newline at end of file diff --git a/examples/community/vfl_attacks/common/image_report.py b/examples/community/vfl_attacks/common/image_report.py new file mode 100644 index 0000000..88f5dcb --- /dev/null +++ b/examples/community/vfl_attacks/common/image_report.py @@ -0,0 +1,218 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +from mindspore import ops + +def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_passive_party=2): + y_backdoor_dict = {} + image = None + image_indices = [] + + self.set_eval() + + self.set_state('attack') + for batch_idx, (X, targets, old_imgb, indices) in enumerate(test_loader): + party_X_test_dict = dict() + if self.args['n_passive_party'] < 2: + # X = ops.transpose(X, (1, 0, 2, 3, 4)) + # 0627修改适配criteo + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + active_X_inputs, Xb_inputs = X + # if self.args['cuda']: + # active_X_inputs = active_X_inputs.cuda() + # Xb_inputs = Xb_inputs.cuda() + # targets = targets.cuda() + # indices = indices.cuda() + party_X_test_dict[0] = Xb_inputs + else: + # if self.args['cuda']: + # X = X.cuda() + # targets = targets.cuda() + # indices = indices.cuda() + active_X_inputs = X[:, 0:1].squeeze(1) + for i in range(n_passive_party): + party_X_test_dict[i] = X[:, i + 1:i + 2].squeeze(1) + + # for ABL defense + if self.state == 'train': + self.active_party.y = targets + self.active_party.indices = indices + + each_party_X_test_dict = party_X_test_dict.copy() + for index in range(len(indices)): + each_active_X_inputs = ops.unsqueeze(active_X_inputs[index], dim=0) + for i in range(n_passive_party): + each_party_X_test_dict[i] = ops.unsqueeze(party_X_test_dict[i][index], dim=0) + # y_true = targets.data.tolist()[index] + y_backdoor = self.batch_predict(each_active_X_inputs, each_party_X_test_dict)[0] + predicted_class = ops.argmax(y_backdoor) + if predicted_class == self.args['backdoor_label']: + image_indices.append(indices[index]) + y_backdoor_dict[indices[index].item()] = y_backdoor + + self.set_state('test') + old_indice_map = test_loader.children[0].source.indice_map + old_backdoor_indices = test_loader.children[0].source.backdoor_indices + test_loader.children[0].source.indice_map = None + test_loader.children[0].source.backdoor_indices = None + for batch_idx, (X, targets, old_imgb, indices) in enumerate(test_loader): + party_X_test_dict = dict() + if self.args['n_passive_party'] < 2: + # X = ops.transpose(X, (1, 0, 2, 3, 4)) + # 0627修改适配criteo + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + active_X_inputs, Xb_inputs = X + # if self.args['cuda']: + # active_X_inputs = active_X_inputs.cuda() + # Xb_inputs = Xb_inputs.cuda() + # targets = targets.cuda() + # indices = indices.cuda() + party_X_test_dict[0] = Xb_inputs + else: + # if self.args['cuda']: + # X = X.cuda() + # targets = targets.cuda() + # indices = indices.cuda() + active_X_inputs = X[:, 0:1].squeeze(1) + for i in range(n_passive_party): + party_X_test_dict[i] = X[:, i + 1:i + 2].squeeze(1) + + # for ABL defense + if self.state == 'train': + self.active_party.y = targets + self.active_party.indices = indices + + image_str = None + each_party_X_test_dict = party_X_test_dict.copy() + for indice in indices: + if indice in image_indices: + # print("case 1") + index = ops.nonzero(indices == indice)[0][0] + #0523问题修复 + elif indice == indices[-1]: + # print("case 2") + index = ops.nonzero(indices == indice)[0][0] + else: + # print("case 3") + continue + + each_active_X_inputs = ops.unsqueeze(active_X_inputs[index], dim=0) + for i in range(n_passive_party): + each_party_X_test_dict[i] = ops.unsqueeze(party_X_test_dict[i][index], dim=0) + + y_clean = self.batch_predict(each_active_X_inputs, each_party_X_test_dict)[0] + + predicted_class = ops.argmax(y_clean) + if predicted_class != self.args['backdoor_label'] or indice == indices[-1]: + # image_indices = indice + # 0523问题修复 待测试,后面用的参数也从image_indices改为select_indice + selected_indice = indice + if selected_indice.item() not in y_backdoor_dict: + # print(f"Warning: selected indice {selected_indice.item()} not in y_backdoor_dict, skip.") + continue + + y_backdoor = y_backdoor_dict[selected_indice.item()] + from PIL import Image + import base64 + from io import BytesIO + image = test_loader.children[0].source.data[selected_indice] + + # 0627 加criteo重改了下面这一大段 + # image = image.transpose(1, 2, 0) # 32,32,3 + # # image = Image.fromarray(image) + # # print(f"ms image report------image shape:{image.shape}") # 32,32,3 + # # 修改格式 for bhi + # if len(image.shape) == 4 and self.args["dataset"] != "criteo": + # image = Image.fromarray(image[0]) + # else: + # image = Image.fromarray(image) + if self.args["dataset"] != "criteo": + # 修改格式 for bhi + if len(image.shape) == 4: + image = Image.fromarray(image[0]) + else: + image = image.transpose(1, 2, 0) # 32,32,3 + image = Image.fromarray(image) + + buffered = BytesIO() + # image.save(buffered, format="PNG") + # image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') + # 0627criteo加的判断 + if self.args["dataset"] != "criteo": + image.save(buffered, format="PNG") + image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') + break + + if image is not None: + break + + return image_str, y_clean, y_backdoor + +def visualization_with_images(self, ema_top_model=None): + if self.args['attack'] and self.args['backdoor'] != 'no': + # return images and other info + self.set_state('attack') + image_str, y_clean, y_backdoor = backdoor_image_predict(self, self.backdoor_test_loader, + num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + return image_str, y_clean, y_backdoor + elif self.args['attack'] and 'model_completion' in self.args['label_inference_attack']: + from methods.model_completion.model_completion import predict + image_str, y_predict, y_infer = predict(self, self.train_loader, ema_top_model) + return image_str, y_predict, y_infer + else: + return None + +def append_predictions_to_file(file_path, clean_pred, attack_pred): + # 准备写入的字符串内容 + # clean_str = ", ".join(map(str, clean_pred)) # 将clean预测列表转成字符串 + # attack_str = ", ".join(map(str, attack_pred)) # 将attack预测列表转成字符串 + clean_str = ", ".join([str(p.item()) for p in clean_pred]) # 将 clean 预测列表的 tensor 转换为数值 + attack_str = ", ".join([str(p.item()) for p in attack_pred]) # 将 attack 预测列表的 tensor 转换为数值 + + # 需要写入的格式化字符串 + data_to_append = f"clean prediction: [{clean_str}], attack prediction: [{attack_str}]," + + # 打开文件并追加内容 + with open(file_path, "a") as file: + # 先写一个空行,再写入数据 + file.write("\n\n") # 添加空行 + file.write(data_to_append + "\n") # 追加数据并换行 + + print(f"数据已追加到 {file_path}") + +def append_int_to_file(file_path, clean_int, attack_int): + # 准备写入的字符串内容,将单个整数转换为字符串 + clean_str = str(clean_int) # 将 clean 整数转换为字符串 + attack_str = str(attack_int) # 将 attack 整数转换为字符串 + + # 需要写入的格式化字符串 + data_to_append = f"clean prediction: [{clean_str}], attack prediction: [{attack_str}]," + + # 打开文件并追加内容 + with open(file_path, "a") as file: + # 先写一个空行,再写入数据 + file.write("\n\n") # 添加空行 + file.write(data_to_append + "\n") # 追加数据并换行 + + print(f"整数数据已追加到 {file_path}") \ No newline at end of file diff --git a/examples/community/vfl_attacks/common/parser.py b/examples/community/vfl_attacks/common/parser.py new file mode 100644 index 0000000..53208ba --- /dev/null +++ b/examples/community/vfl_attacks/common/parser.py @@ -0,0 +1,69 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" +Parse configuration file +""" + +import logging +import yaml +from MindsporeCode.datasets.base_dataset import get_num_classes + +def get_args(file, file_time): + """ + parse configuration yaml file + + :return: configuration + """ + # parser = argparse.ArgumentParser() + # parser.add_argument('--config', type=str) + # temp = parser.parse_args() + yaml.warnings({'YAMLLoadWarning': False}) + f = open(file, 'r', encoding='utf-8') + cfg = f.read() + # NEW fix + args = yaml.load(cfg, Loader=yaml.SafeLoader) + f.close() + args['num_classes'] = get_num_classes(args['dataset']) + + if 'train_label_non_iid' not in args.keys(): + args['train_label_non_iid'] = None + if 'train_label_fix_backdoor' not in args.keys(): + args['train_label_fix_backdoor'] = -1 + + # the configuration whether to print the execution time of federated training and LR-BA + args['time'] = False + + # now = datetime.datetime.now() + # time = now.strftime("%m-%d-%H-%M-%S") + # args['file_time'] = time + time = file_time + set_logging(args['log'], time) + return args + + +def set_logging(log_file, time): + """ + configure logging INFO messaged located in tests/result + + :param str log_file: path of log file + """ + # file_name = '../temp_output/{}-{}.txt'.format(log_file, time) + # constant.set_value('file_name', file_name) + logging.basicConfig( + level=logging.INFO, + filename='../temp_output/{}-{}.txt'.format(log_file, time), + filemode='w', + format='[%(asctime)s| %(levelname)s| %(processName)s] %(message)s' # 日志格式 + ) diff --git a/examples/community/vfl_attacks/common/utils.py b/examples/community/vfl_attacks/common/utils.py new file mode 100644 index 0000000..9362759 --- /dev/null +++ b/examples/community/vfl_attacks/common/utils.py @@ -0,0 +1,91 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging +import time + +import numpy as np +from sklearn.metrics import accuracy_score, top_k_accuracy_score, f1_score + +def accuracy(y_true, y_pred, dataset, num_classes=None, top_k=1, is_attack=False): + """ + compute model accuracy or F1-score(only for BHI) + + :param y_true: array of ground-truth labels + :param y_pred: array of prediction labels + :param str dataset: dataset name + :param int num_classes: size of dataset classes + :param int top_k: top-k accuracy, default 1 + :param bool is_attack: whether to compute attack accuracy + :return: model accuracy or F1-score(only for BHI) + """ + y_pred = np.array(y_pred) + if np.any(np.isnan(y_pred)) or not np.all(np.isfinite(y_pred)): + raise ValueError('accuracy y_pred is isnan') + temp_y_pred = [] + if top_k == 1: + for pred in y_pred: + temp = np.max(pred) + # temp_y_pred.append(pred.index(temp)) + temp_y_pred.append(np.where(pred == temp)[0][0]) + # if is_attack: + # for y in temp_y_pred: + # if y != y_true[0]: + # print(y) + if dataset != 'bhi': + acc = accuracy_score(y_true, temp_y_pred) + else: + if not is_attack: + # logging.info('f1 score') + acc = f1_score(y_true, temp_y_pred) + else: + acc = accuracy_score(y_true, temp_y_pred) + else: + acc = top_k_accuracy_score(y_true, y_pred, k=top_k, labels=np.arange(num_classes)) + return acc + + +def print_running_time(title, start_time): + """ + print the executing period + + :param title: title of the execution + :param start_time: the start time before executing + :return: executing period + """ + if start_time is not None: + end_time = time.time() + logging.info('{}, time: {}s'.format(title, end_time-start_time)) + start_time = time.time() + return start_time + +class FeatureTuple: + def __init__(self, img_a, img_b): + self.img_a = img_a + self.img_b = img_b + + def __getitem__(self, index): + if index == 0: + return self.img_a + elif index == 1: + return self.img_b + else: + raise IndexError("FeatureTuple index out of range") + + def __len__(self): + return 2 + + def __iter__(self): + return iter((self.img_a, self.img_b)) + diff --git a/examples/community/vfl_attacks/configure.json b/examples/community/vfl_attacks/configure.json new file mode 100644 index 0000000..bcd01f6 --- /dev/null +++ b/examples/community/vfl_attacks/configure.json @@ -0,0 +1,7 @@ +{ + "rdr": { + "enable": true, + "mode": 1, + "path": "/path/to/rdr/dir" + } +} diff --git a/examples/community/vfl_attacks/constant.py b/examples/community/vfl_attacks/constant.py new file mode 100644 index 0000000..8c9516b --- /dev/null +++ b/examples/community/vfl_attacks/constant.py @@ -0,0 +1,13 @@ + +def init(): + global values + values = {'file_name':None} + +def set_value(name, value): + values[name] = value + +def get_value(name): + if name in values.keys(): + return values[name] + else: + return None diff --git a/examples/community/vfl_attacks/datasets/__init__.py b/examples/community/vfl_attacks/datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/community/vfl_attacks/datasets/base_dataset.py b/examples/community/vfl_attacks/datasets/base_dataset.py new file mode 100644 index 0000000..eb73578 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/base_dataset.py @@ -0,0 +1,73 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging + +from MindsporeCode.datasets.cifar_dataset import get_cifar_dataloader +from datasets.cinic_dataset import get_cinic_dataloader +from MindsporeCode.datasets.bhi_dataset import get_bhi_dataloader +from MindsporeCode.datasets.criteo_dataset import get_criteo_dataloader + +def get_dataloader(args): + """ + generate data loader according to dataset name + + :param args: configuration + :return: data loader + """ + if 'cifar' in args['dataset']: + return get_cifar_dataloader(args) + elif args['dataset'] == 'cinic': + return get_cinic_dataloader(args) + elif args['dataset'] == 'bhi': + return get_bhi_dataloader(args) + elif args['dataset'] == 'criteo': + return get_criteo_dataloader(args) + + +def get_backdoor_target_index(train_loader, backdoor_indices, args): + """ + get index of a normal input labeled backdoor class in training dataset, used for gradient-replacement backdoor + + :param train_loader: loader of training dataset + :param backdoor_indices: indices of backdoor samples + :param args: configuration + :return: index of a normal input labeled backdoor class + """ + for (_, _), labels, indices in train_loader: + for label, index in zip(labels, indices): + if label == args['backdoor_label'] and index not in backdoor_indices: + logging.info('backdoor target index: {}'.format(index)) + return index.item() + return None + + +def get_num_classes(dataset): + """ + get classes number of the target dataset + + :param str dataset: target dataset name + :return: classes number of the target dataset + """ + data_dict = { + 'cifar10': 10, + 'cifar100': 100, + 'cinic': 10, + 'bhi':2, + 'criteo': 2 + } + return data_dict[dataset] + + + diff --git a/examples/community/vfl_attacks/datasets/bhi_dataset.py b/examples/community/vfl_attacks/datasets/bhi_dataset.py new file mode 100644 index 0000000..46a8a6f --- /dev/null +++ b/examples/community/vfl_attacks/datasets/bhi_dataset.py @@ -0,0 +1,238 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import copy +import logging +import os +import pickle + +import numpy as np +import mindspore as ms +from mindspore.dataset import vision + +from sklearn.model_selection import train_test_split +from MindsporeCode.datasets.common import train_label_split, get_labeled_loader, get_target_indices, image_dataset_with_indices, get_random_indices +from MindsporeCode.datasets.multi_image_dataset import MultiImageDataset +from MindsporeCode.common.constants import data_path +# transform for BHI images +normalize = vision.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5],is_hwc = True) +transform = ms.dataset.transforms.Compose([ + #vision.ToPIL(), + #vision.ToTensor(), + normalize +]) + +def get_labeled_data(data_dir, party_num): + """ + read data from local file, including training and testing + + :param str data_dir: dir path of local file + :param int party_num: parties number + :return: tuple containing X and Y + """ + file_path = os.path.join(data_dir, 'data.pkl') + with open(file_path, 'rb') as f: + entry = pickle.load(f, encoding='latin1') + img_data = entry['data'] # [345910,50,50,3] + targets = entry['labels'] + targets = np.array(targets) + # split data by labels 0 and 1 + zero_indices = np.where(targets == 0)[0] # 209202 + one_indices = np.where(targets == 1)[0] # 136708 + + # divide data evenly into parties + groups_num_zero = int(len(zero_indices) / party_num) + groups_num_one = int(len(one_indices) / party_num) + path_groups_zero = [] + path_groups_one = [] + for group_zore_id in range(groups_num_zero): + path_groups_zero.append( + img_data[zero_indices[group_zore_id * party_num: group_zore_id * party_num + party_num]]) + for group_one_id in range(groups_num_one): + path_groups_one.append(img_data[one_indices[group_one_id * party_num: group_one_id * party_num + party_num]]) + path_groups_zero.extend(path_groups_one) + path_groups = path_groups_zero + + # generate labels of parties data + labels_group = [0] * groups_num_zero + labels_group.extend([1] * groups_num_one) + + return np.array(path_groups), np.array(labels_group) + +def load_parties_data(data_dir, args): + """ + get data from local dataset + + :param data_dir: path of local dataset + :param args: configuration + :return: tuple contains: + (1) X_train: normal train features; + (2) y_train: normal train labels; + (3) X_test: normal test features; + (4) y_test: normal test labels; + (5) backdoor_y_train: backdoor train labels; + (6) backdoor_X_test: backdoor test features; + (7) backdoor_y_test: backdoor test labels; + (8) backdoor_indices_train: indices of backdoor samples in normal train dataset; + (9) backdoor_target_indices: indices of backdoor label in normal train dataset; + (10) train_labeled_indices: indices of labeled samples in normal train dataset; + (11) train_unlabeled_indices: indices of unlabeled samples in normal train dataset + """ + party_num = args['n_passive_party']+1 + logging.info("# load_parties_data") + # read data from local file + X, y = get_labeled_data(data_dir=data_dir, party_num=party_num) + + # split normal dataset for train and test + X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, + test_size=0.2, + random_state=1) + # train 92242[1:36455,0:55787] + # test 23061[1:9114,0:13947] + + # 0527添加 + n_train = args['target_train_size'] + n_test = args['target_test_size'] + # if n_train != -1: + # indices = get_random_indices(n_train, len(X)) + # X_train, y_train = X_train[indices], y_train[indices] + # if n_test != -1: + # indices = get_random_indices(n_test, len(X)) + # X_test, y_test = X_test[indices], y_test[indices] + # 0627修改 + if n_train != -1: + indices = get_random_indices(n_train, len(X_train)) + X_train, y_train = X_train[indices], y_train[indices] + if n_test != -1: + indices = get_random_indices(n_test, len(X_test)) + X_test, y_test = X_test[indices], y_test[indices] + + # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + train_indices = np.where(y_train != args['backdoor_label'])[0] + # print(len(train_indices),args['backdoor_train_size']) + backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) + backdoor_y_train = copy.deepcopy(y_train) + backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + + # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + test_indices = np.where(y_test != args['backdoor_label'])[0] + backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) + backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + y_test[backdoor_indices_test] + backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + + # split labeled and unlabeled samples in normal train dataset, for LR-BA + train_labeled_indices, train_unlabeled_indices = \ + train_label_split(y_train, args['train_label_size'], args['num_classes'], + args['train_label_non_iid'], args['backdoor_label'], args['train_label_fix_backdoor']) + + # randomly select samples of backdoor label in normal train dataset, for gradient-replacement + backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['train_label_size']) + + logging.info("y_train.shape: {}".format(y_train.shape)) + logging.info("y_test.shape: {}".format(y_test.shape)) + logging.info("backdoor_y_test.shape: {}".format(backdoor_y_test.shape)) + return X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices_train, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices + +def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=True, backdoor_indices=None, trigger=None, trigger_add=None, source_indices=None): + """ + generate loader from dataset + + :param tuple data_list: contains X and Y + :param int batch_size: batch of loader + :param transform: transform of loader + :param bool shuffle: whether to shuffle loader + :param backdoor_indices: indices of backdoor samples in normal dataset, add trigger when loading data if index is in backdoor_indices + :param trigger: control whether add pixel trigger when load dataloader + :return: loader + """ + X, y = data_list + # print("1",X.shape) + MultiImageDatasetWithIndices = image_dataset_with_indices(MultiImageDataset) + party_num = args['n_passive_party'] + 1 + ds = MultiImageDatasetWithIndices(X, ms.tensor(y,ms.int32), + transform=transform, + backdoor_indices=backdoor_indices, + party_num=party_num, + trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, adversary=args['adversary']) + + dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle, column_names=['image','target','old_imgb', 'indice']) + dl = dl.batch(batch_size, drop_remainder=False) + return dl + +def get_bhi_dataloader(args): + """ + generate loader of BHI dataset + + :param args: configuration + :return: tuple contains: + (1) train_dl: loader of normal train dataset; + (2) test_dl: loader of normal test dataset; + (3) backdoor_train_dl: loader of backdoor train dataset, including normal and backdoor samples, used by data poisoning + (4) backdoor_test_dl: loader of backdoor test dataset, only including backdoor samples, used to evaluate ASR + (5) backdoor_indices: indices of backdoor samples in normal train dataset; + (6) backdoor_target_indices: indices of backdoor label in normal train dataset, used by Gradient-Replacement + (7) labeled_dl: loader of labeled samples in normal train dataset, used by LR-BA; + (8) unlabeled_dl: loader of unlabeled samples in normal train dataset, used by LR-BA + """ + party_num = args['n_passive_party'] + 1 + # get dataset + result = load_parties_data(data_dir=data_path+"BHI/", args=args) + X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices = result + + batch_size = args['target_batch_size'] + # get loader of normal train dataset, used by normal training and LR-BA + train_dl = generate_dataloader(args, (X_train, y_train), batch_size, transform, shuffle=True) + # get loader of normal test dataset, used to evaluate main task accuracy + test_dl = generate_dataloader(args, (X_test, y_test), batch_size, transform, shuffle=False) + + # get loader of backdoor train dataset, used by data poisoning attack + backdoor_train_dl = generate_dataloader(args, (X_train, backdoor_y_train), batch_size, transform, + shuffle=True, + backdoor_indices=backdoor_indices) + # get loader of backdoor test dataset, used to evaluate backdoor task accuracy + backdoor_test_dl = generate_dataloader(args, (backdoor_X_test, backdoor_y_test), batch_size, transform, + shuffle=False, + backdoor_indices=np.arange(args['backdoor_test_size']), + trigger=args['trigger'], trigger_add=args['trigger_add']) + + # get loader of labeled and unlabeled normal train dataset, used by LR-BA + labeled_data, unlabeled_data = get_labeled_loader(train_dataset=(X_train, y_train), ########## + labeled_indices=train_labeled_indices, + unlabeled_indices=train_unlabeled_indices, + args=args) + labeled_dl = generate_dataloader(args, labeled_data, batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), shuffle=True) + unlabeled_dl = generate_dataloader(args, unlabeled_data, batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), shuffle=True) + + # get loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels + g_r_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, transform, + shuffle=True, + backdoor_indices=backdoor_indices) + + if args['backdoor'] == 'villain': + villain_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, transform, + shuffle=True, + backdoor_indices=backdoor_target_indices, trigger=args['trigger'], + trigger_add=args['trigger_add']) + # villain_train_dl = villain_train_dl.create_tuple_iterator() + + else: + villain_train_dl = None + + return train_dl, test_dl, backdoor_train_dl, backdoor_test_dl, g_r_train_dl, \ + backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, villain_train_dl + #### train_dl, test_dl, backdoor_test_dl, backdoor_train_dl, g_r_train_dl, \ + # backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, \, villain_train_dl \ No newline at end of file diff --git a/examples/community/vfl_attacks/datasets/cifar_dataset.py b/examples/community/vfl_attacks/datasets/cifar_dataset.py new file mode 100644 index 0000000..38ad830 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/cifar_dataset.py @@ -0,0 +1,346 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import copy +import logging +import random + +import numpy as np +import mindspore as ms +from mindspore.dataset import vision +import mindspore.dataset as ds + +from MindsporeCode.datasets.common import train_label_split, get_random_indices, \ + get_labeled_loader, get_target_indices, image_dataset_with_indices, poison_image_dataset +from MindsporeCode.datasets.image_dataset import ImageDataset +from MindsporeCode.datasets.multi_image_dataset import MultiImageDataset +from PIL import Image +import os +import pickle +from MindsporeCode.common.constants import data_path + +# transform for CIFAR train dataset +train_transform = ms.dataset.transforms.Compose([ + vision.ToTensor(), + # vision.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2470, 0.2435, 0.2616], is_hwc=False) # CIFAR10 + # transforms.Normalize((0.5071, 0.4867, 0.4408),(0.2675, 0.2565, 0.2761)) #CIFAR100 +]) + +# transform for CIFAR test dataset +test_transform = ms.dataset.transforms.Compose([ + vision.ToTensor(), + # vision.Normalize(mean=[0.4940, 0.4850, 0.4504], std=[0.2467, 0.2429, 0.2616], is_hwc=False) # CIFAR10 + # transforms.Normalize((0.5071, 0.4867, 0.4408),(0.2675, 0.2565, 0.2761)) #CIFAR100 +]) + +def get_labeled_data_with_2_party(data_dir, dataset, dtype="train", num_samples=None): + """ + read data from local file + + :param data_dir: dir path of local file + :param str dataset: dataset name, support cifar10 and cifar100 + :param str dtype: read "Train" or "Test" data + :return: tuple containing X and Y + """ + # data_dir = data_dir + 'cifar-10-batches-bin/' + # transform = train_transform if dtype == 'train' else test_transform + # if dataset == 'cifar10': + # dataset = ds.Cifar10Dataset(dataset_dir=data_dir,usage=dtype, num_samples=num_samples) + # # dataset = dataset.map(transform) + # dataset = dataset.map(operations=[(lambda x: x.transpose(2, 0, 1).unsqueeze(0).asnumpy())]) + # + # elif dataset == 'cifar100': + # dataset = ds.Cifar100Dataset(dataset_dir=data_dir,usage=dtype, num_samples=num_samples) + # # dataset = dataset.map(transform) + # all_data = np.empty(shape=(0, 3, 32, 32)) + # targets = [] + # for image, label in dataset: + # all_data = np.append(all_data, image.transpose(2, 0, 1).unsqueeze(0).asnumpy(), axis=0) + # targets.append(label.item()) + # targets = np.array(targets) + + if dataset == 'cifar10': + data_dir = data_dir + 'cifar-10-batches-py/' + train_list = [ + 'data_batch_1', + 'data_batch_2', + 'data_batch_3', + 'data_batch_4', + 'data_batch_5'] + test_list = ['test_batch'] + all_data = [] + targets = [] + downloaded_list = train_list if dtype == 'train' else test_list + # now load the picked numpy arrays + for file_name in downloaded_list: + file_path = os.path.join(data_dir, file_name) + with open(file_path, 'rb') as f: + entry = pickle.load(f, encoding='latin1') + all_data.append(entry['data']) + if 'labels' in entry: + targets.extend(entry['labels']) + else: + targets.extend(entry['fine_labels']) + all_data = np.vstack(all_data).reshape(-1, 3, 32, 32) + # all_data = all_data.transpose((0, 2, 3, 1)) # convert to HWC + targets = np.array(targets) + if num_samples is not None: + indices = get_random_indices(num_samples, len(all_data)) + datas, labels = all_data[indices], targets[indices] + else: + datas, labels = all_data, targets + else: + filename = data_dir + 'cifar-100-python/' + dtype + with open(filename, 'rb') as f: + datadict = pickle.load(f, encoding='latin1') + X = datadict['data'] + all_data = X.reshape(-1, 3, 32, 32) + # fine_labels细分类,共100中类别 + targets = datadict['fine_labels'] + targets = np.array(targets) + if num_samples is not None: + indices = get_random_indices(num_samples, len(all_data)) + datas, labels = all_data[indices], targets[indices] + else: + datas, labels = all_data, targets + + # N, 3, 32, 32 0-255 + return datas, labels + + +def load_two_party_data(data_dir, args): + """ + get data from local dataset, only support two parties + + :param data_dir: path of local dataset + :param args: configuration + :return: tuple contains: + (1) X_train: normal train features; + (2) y_train: normal train labels; + (3) X_test: normal test features; + (4) y_test: normal test labels; + (5) backdoor_y_train: backdoor train labels; + (6) backdoor_X_test: backdoor test features; + (7) backdoor_y_test: backdoor test labels; + (8) backdoor_indices_train: indices of backdoor samples in normal train dataset; + (9) backdoor_target_indices: indices of backdoor label in normal train dataset; + (10) train_labeled_indices: indices of labeled samples in normal train dataset; + (11) train_unlabeled_indices: indices of unlabeled samples in normal train dataset + (12) backdoor_X_train: train dataset that backdoor_target_indices sample had been replaced with backdoor_indices sample in adv + """ + logging.info("# load_two_party_data") + n_train = args['target_train_size'] + n_test = args['target_test_size'] + if n_train == -1: + n_train = None + if n_test == -1: + n_test = None + # read train data from local file + # print("# load_train_data ing...") + X_train, y_train = get_labeled_data_with_2_party(data_dir=data_dir, + dataset=args['dataset'], + dtype='train', + num_samples=n_train) + # print("# load_train_data finished!!!", len(X_train), len(y_train)) + + # read test data from local file + # print("# load_test_data ing...") + X_test, y_test = get_labeled_data_with_2_party(data_dir=data_dir, + dataset=args['dataset'], + dtype='test', + num_samples=n_test) + # print("# load_test_data finished!!!", len(X_test), len(y_test)) + + # # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + # train_indices = np.where(y_train != args['backdoor_label'])[0] + # backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) + # backdoor_y_train = copy.deepcopy(y_train) + # backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + # + # # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + # test_indices = np.where(y_test != args['backdoor_label'])[0] + # backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) + # backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + # y_test[backdoor_indices_test] + # backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + train_indices = np.where(y_train != args['backdoor_label'])[0] + num_train_choice = min(len(train_indices), args['backdoor_train_size']) + if num_train_choice < args['backdoor_train_size']: + logging.warning("backdoor train size is larger than normal train size, use normal train size") + backdoor_indices_train = np.random.choice(train_indices, num_train_choice, replace=False) + backdoor_y_train = copy.deepcopy(y_train) + backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + + # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + test_indices = np.where(y_test != args['backdoor_label'])[0] + num_test_choice = min(len(test_indices), args['backdoor_test_size']) + if num_test_choice < args['backdoor_test_size']: + logging.warning("backdoor test size is larger than normal test size, use normal test size") + backdoor_indices_test = np.random.choice(test_indices, num_test_choice, replace=False) + backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + y_test[backdoor_indices_test] + backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + + # split labeled and unlabeled samples in normal train dataset, for LR-BA + train_labeled_indices, train_unlabeled_indices = \ + train_label_split(y_train, args['train_label_size'], args['num_classes'], + args['train_label_non_iid'], args['backdoor_label'], args['train_label_fix_backdoor']) + + # randomly select samples of backdoor label in normal train dataset, for gradient-replacement + # backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['train_label_size']) + backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['backdoor_train_size']) + + logging.info("y_train.shape: {}".format(y_train.shape)) + logging.info("y_test.shape: {}".format(y_test.shape)) + logging.info("backdoor_y_test.shape: {}".format(backdoor_y_test.shape)) + + labeled_y_train = y_train[train_labeled_indices] + temp = [] + for i in range(args['num_classes']): + indices = np.where(labeled_y_train == i)[0] + temp.append(len(indices)) + # logging.info('labeled labels sum: {}, all: {}'.format(np.sum(temp), temp)) + logging.info('labeled labels sum: {}'.format(np.sum(temp))) + + return X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices_train, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices + +def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=True, backdoor_indices=None, trigger=None, trigger_add=None, source_indices=None): + """ + generate loader from dataset + + :param tuple data_list: contains X and Y + :param int batch_size: batch of loader + :param transform: transform of loader + :param bool shuffle: whether to shuffle loader + :param backdoor_indices: indices of backdoor samples in normal dataset, add trigger when loading data if index is in backdoor_indices + :param trigger: control whether add pixel trigger when load dataloader + :return: loader + """ + + # def per_batch(img_list, target, original, index, BatchInfo): + # # img_list: 64,K,3,32,16 0-255 tensor + # new_img_list = [] + # new_original = [] + # for i in range(len(img_list)): + # new_img_list.append([]) + # for j in range(len(img_list[i])): + # img = img_list[i][j] + # img = Image.fromarray(np.uint8(img.transpose(1, 2, 0))) #RGB + # # img_list[i][j] = transform(img) # 0-1 + # img = vision.ToTensor()(img) # 0-1 3,32,16 + # # img = vision.Normalize(mean=[121.0, 115.0, 100.0], std=[70.0, 68.0, 71.0], is_hwc=True)(img) + # new_img_list[-1].append(np.array(img, dtype=float)) + # img = original[i] + # img = Image.fromarray(np.uint8(img.transpose(1, 2, 0))) # RGB + # img = vision.ToTensor()(img) # 0-1 3,32,16 + # new_original.append(np.array(img, dtype=float)) + # return new_img_list, target, new_original, index + + X, y = data_list # 100,3,32,32 0-255 + # 测试实际:(20000,3,32,32) (2000,) + if args['n_passive_party'] > 1: + MultiImageDatasetWithIndices = image_dataset_with_indices(MultiImageDataset) + party_num = args['n_passive_party'] + 1 + ds = MultiImageDatasetWithIndices(X, ms.tensor(y, ms.int32), + transform=transform, + backdoor_indices=backdoor_indices, + party_num=party_num, + trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, adversary=args['adversary']) + else: + ImageDatasetWithIndices = image_dataset_with_indices(ImageDataset) + # split x into halves for parties when loading data, only support two parties + ds = ImageDatasetWithIndices(X, ms.tensor(y, ms.int32), + transform=transform, + backdoor_indices=backdoor_indices, + half=16, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) + dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle, column_names=['image','target','old_imgb', 'indice']) + dl = dl.batch(batch_size, drop_remainder=False) + # dl.children[0].source.targets + return dl + + +def get_cifar_dataloader(args): + """ + generate loader of CIFAR dataset, support cifar10 and cifar100 + + :param args: configuration + :return: tuple contains: + (1) train_dl: loader of normal train dataset; + (2) test_dl: loader of normal test dataset; + (3) backdoor_train_dl: loader of backdoor train dataset, including normal and backdoor samples, used by data poisoning + (4) backdoor_test_dl: loader of backdoor test dataset, only including backdoor samples, used to evaluate ASR + (5) g_r_train_dl: loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels + (6) backdoor_indices: indices of backdoor samples in normal train dataset; + (7) backdoor_target_indices: indices of backdoor label in normal train dataset, used by Gradient-Replacement + (8) labeled_dl: loader of labeled samples in normal train dataset, used by LR-BA; + (9) unlabeled_dl: loader of unlabeled samples in normal train dataset, used by LR-BA + """ + # get dataset + result = load_two_party_data(data_path, args) + X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices = result + + batch_size = args['target_batch_size'] + # get loader of normal train dataset, used by normal training and LR-BA + train_dl = generate_dataloader(args, (X_train, y_train), batch_size, train_transform, shuffle=True) + # get loader of normal test dataset, used to evaluate main task accuracy + test_dl = generate_dataloader(args, (X_test, y_test), batch_size, test_transform, shuffle=False) + + backdoor_train_dl = generate_dataloader(args, (X_train, backdoor_y_train), batch_size, train_transform, + shuffle=True, + backdoor_indices=backdoor_indices) + # get loader of backdoor test dataset, used to evaluate backdoor task accuracy + backdoor_test_dl = generate_dataloader(args, (backdoor_X_test, backdoor_y_test), batch_size, test_transform, + shuffle=False, + backdoor_indices=np.arange(args['backdoor_test_size']), trigger=args['trigger'], trigger_add=args['trigger_add']) + + # get loader of labeled and unlabeled normal train dataset, used by LR-BA + labeled_data, unlabeled_data = get_labeled_loader(train_dataset=(X_train, y_train), + labeled_indices=train_labeled_indices, + unlabeled_indices=train_unlabeled_indices, + args=args) + labeled_dl = generate_dataloader(args, labeled_data, batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), shuffle=True) + unlabeled_dl = generate_dataloader(args, unlabeled_data, batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), shuffle=True) + + + # get loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels + g_r_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, train_transform, + shuffle=True, + backdoor_indices=backdoor_indices) + + if args['backdoor'] == 'villain': + villain_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, train_transform, + shuffle=True, + backdoor_indices=backdoor_target_indices, trigger=args['trigger'], + trigger_add=args['trigger_add']) + # villain_train_dl = villain_train_dl.create_tuple_iterator() + + else: + villain_train_dl = None + + # train_dl = train_dl.create_tuple_iterator() + # test_dl = test_dl.create_tuple_iterator() + # backdoor_test_dl = backdoor_test_dl.create_tuple_iterator() + # backdoor_train_dl = backdoor_train_dl.create_tuple_iterator() + # g_r_train_dl = g_r_train_dl.create_tuple_iterator() + # labeled_dl = labeled_dl.create_tuple_iterator() + # unlabeled_dl = unlabeled_dl.create_tuple_iterator() + + # dl.children[0].source.targets[:10] + + return train_dl, test_dl, backdoor_test_dl, backdoor_train_dl, g_r_train_dl, \ + backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, \ + villain_train_dl diff --git a/examples/community/vfl_attacks/datasets/cinic_dataset.py b/examples/community/vfl_attacks/datasets/cinic_dataset.py new file mode 100644 index 0000000..0b6ffc8 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/cinic_dataset.py @@ -0,0 +1,298 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import copy +import logging +import os +import pickle + +import numpy as np + +import mindspore as ms +from mindspore.dataset import vision +import mindspore.dataset as ds + +from MindsporeCode.datasets.common import train_label_split, get_random_indices, \ + get_labeled_loader, get_target_indices, image_dataset_with_indices, poison_image_dataset, multiple_dataset_pre +from MindsporeCode.datasets.image_dataset import ImageDataset +from MindsporeCode.datasets.multi_image_dataset import MultiImageDataset + + +def image_format_2_rgb(x): + return x.convert("RGB") + + +normalize = vision.Normalize(mean=[0.47889522, 0.47227842, 0.43047404], + std=[0.24205776, 0.23828046, 0.25874835]) + +transform = ms.dataset.transforms.Compose([ + vision.ToTensor(), + # normalize +]) + +def get_labeled_data_with_2_party(data_dir, dtype="Train", num_samples=None): + """ + read data from local file + + :param data_dir: dir path of local file + :param str dtype: read "Train" or "Test" data + :return: tuple containing X and Y + """ + file_path = os.path.join(data_dir, dtype.lower()+'.pkl') + with open(file_path, 'rb') as f: + entry = pickle.load(f, encoding='latin1') + img_data = entry['data'] + targets = entry['labels'] + # 0523待测试 + img_data = img_data.reshape(-1, 3, 32, 32) + targets = np.array(targets) + if num_samples is not None: + indices = get_random_indices(num_samples, len(img_data)) + datas, labels = img_data[indices], targets[indices] + else: + datas, labels = img_data, targets + return datas, labels + +def load_two_party_data(data_dir, args): + """ + get data from local dataset, only support two parties + + :param data_dir: path of local dataset + :param args: configuration + :return: tuple contains: + (1) X_train: normal train features; + (2) y_train: normal train labels; + (3) X_test: normal test features; + (4) y_test: normal test labels; + (5) backdoor_y_train: backdoor train labels; + (6) backdoor_X_test: backdoor test features; + (7) backdoor_y_test: backdoor test labels; + (8) backdoor_indices_train: indices of backdoor samples in normal train dataset; + (9) backdoor_target_indices: indices of backdoor label in normal train dataset; + (10) train_labeled_indices: indices of labeled samples in normal train dataset; + (11) train_unlabeled_indices: indices of unlabeled samples in normal train dataset + """ + logging.info("# load_two_party_data") + n_train = args['target_train_size'] + n_test = args['target_test_size'] + if n_train == -1: + n_train = None + if n_test == -1: + n_test = None + # read train data from local file + print("# load_train_data ing...") + X_train, y_train = get_labeled_data_with_2_party(data_dir=data_dir, + dtype='Train', + num_samples=n_train) + print("# load_train_data finished!!!", len(X_train), len(y_train)) + # 0520 + # X的数据格式原本是n 32 32 3,改为n 3 32 32 + # X = np.transpose(X, (0, 3, 1, 2)) + + # read test data from local file + print("# load_test_data ing...") + X_test, y_test = get_labeled_data_with_2_party(data_dir=data_dir, + dtype='Test', + num_samples=n_test) + + print("# load_test_data finished!!!", len(X_train), len(y_train)) + # # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + # train_indices = np.where(y_train != args['backdoor_label'])[0] + # backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) + # backdoor_y_train = copy.deepcopy(y_train) + # backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + # + # # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + # test_indices = np.where(y_test != args['backdoor_label'])[0] + # backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) + # backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + # y_test[backdoor_indices_test] + # backdoor_y_test_true = backdoor_y_test + # backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + + # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + train_indices = np.where(y_train != args['backdoor_label'])[0] + num_train_choice = min(len(train_indices), args['backdoor_train_size']) + if num_train_choice < args['backdoor_train_size']: + logging.warning("backdoor train size is larger than normal train size, use normal train size") + backdoor_indices_train = np.random.choice(train_indices, num_train_choice, replace=False) + backdoor_y_train = copy.deepcopy(y_train) + backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + + # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + test_indices = np.where(y_test != args['backdoor_label'])[0] + num_test_choice = min(len(test_indices), args['backdoor_test_size']) + if num_test_choice < args['backdoor_test_size']: + logging.warning("backdoor test size is larger than normal test size, use normal test size") + backdoor_indices_test = np.random.choice(test_indices, num_test_choice, replace=False) + backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + y_test[backdoor_indices_test] + backdoor_y_test_true = backdoor_y_test + backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + + # split labeled and unlabeled samples in normal train dataset, for LR-BA + train_labeled_indices, train_unlabeled_indices = \ + train_label_split(y_train, args['train_label_size'], args['num_classes'], + args['train_label_non_iid'], args['backdoor_label'], args['train_label_fix_backdoor']) + + # randomly select samples of backdoor label in normal train dataset, for gradient-replacement + backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['train_label_size']) + + # ours SR NEW + # print('SR-BA dataset prepare......') + index = [i for i in range(len(X_train))] + sr_X_train = [] + sr_y_train = [] + np.random.shuffle(index) + for i in range(0, len(X_train)): + sr_X_train.append(X_train[index[i]]) + sr_y_train.append(y_train[index[i]]) + sr_X_train = np.array(sr_X_train) + sr_y_train = np.array(sr_y_train) + temp = [i for i in range(len(sr_X_train) - 500, len(sr_X_train))] + villain_backdoor_target_indices = get_target_indices(sr_y_train, args['backdoor_label'], args['backdoor_train_size'], + backdoor_indices=temp) + villain_backdoor_target_indices.sort() + sr_X_train, sr_ba_backdoor_indices_train = poison_image_dataset(sr_X_train, sr_y_train, + villain_backdoor_target_indices, args) + + logging.info("y_train.shape: {}".format(y_train.shape)) + logging.info("y_test.shape: {}".format(y_test.shape)) + logging.info("backdoor_y_test.shape: {}".format(backdoor_y_test.shape)) + + return X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices_train, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices, \ + sr_X_train, sr_y_train, villain_backdoor_target_indices,backdoor_y_test_true + + +def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=True, backdoor_indices=None, trigger=None, trigger_add=None, source_indices=None): + """ + generate loader from dataset + + :param tuple data_list: contains X and Y + :param int batch_size: batch of loader + :param transform: transform of loader + :param bool shuffle: whether to shuffle loader + :param backdoor_indices: indices of backdoor samples in normal dataset, add trigger when loading data if index is in backdoor_indices + :return: loader + """ + X, y = data_list + # 0519 + # (2000, 32, 32, 3) (2000,) + + # get x, y, and index when loading data + if args['n_passive_party'] > 1: + MultiImageDatasetWithIndices = image_dataset_with_indices(MultiImageDataset) + party_num = args['n_passive_party'] + 1 + # ds = MultiImageDatasetWithIndices(X, ms.tensor(y), + # transform=transform, + # backdoor_indices=backdoor_indices, + # party_num=party_num, + # trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, + # adversary=args['adversary']) + ds = MultiImageDatasetWithIndices(X, ms.tensor(y, ms.int32), + transform=transform, + backdoor_indices=backdoor_indices, + party_num=party_num, + trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, adversary=args['adversary']) + else: + ImageDatasetWithIndices = image_dataset_with_indices(ImageDataset) + # split x into halves for parties when loading data, only support two parties + # ds = ImageDatasetWithIndices(X, ms.tensor(y), + # transform=transform, + # backdoor_indices=backdoor_indices, + # half=16, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) + ds = ImageDatasetWithIndices(X, ms.tensor(y, ms.int32), + transform=transform, + backdoor_indices=backdoor_indices, + half=16, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) + + # dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle) + # 0518问题 不确定 column_names可能不对 + dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle, column_names=['image', 'target', 'old_imgb', 'indice']) + dl = dl.batch(batch_size, drop_remainder=False) + return dl + +def get_cinic_dataloader(args): + """ + generate loader of CINIC dataset + + :param args: configuration + :return: tuple contains: + (1) train_dl: loader of normal train dataset; + (2) test_dl: loader of normal test dataset; + (3) backdoor_train_dl: loader of backdoor train dataset, including normal and backdoor samples, used by data poisoning + (4) backdoor_test_dl: loader of backdoor test dataset, only including backdoor samples, used to evaluate ASR + (5) g_r_train_dl: loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels + (6) backdoor_indices: indices of backdoor samples in normal train dataset; + (7) backdoor_target_indices: indices of backdoor label in normal train dataset, used by Gradient-Replacement + (8) labeled_dl: loader of labeled samples in normal train dataset, used by LR-BA; + (9) unlabeled_dl: loader of unlabeled samples in normal train dataset, used by LR-BA + """ + # get dataset + result = load_two_party_data("../../data/CINIC-L/", args) + X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices, \ + sr_X_train, sr_y_train, villain_backdoor_target_indices,\ + backdoor_y_test_true = result + + batch_size = args['target_batch_size'] + # get loader of normal train dataset, used by normal training and LR-BA + train_dl = generate_dataloader(args, (X_train, y_train), batch_size, transform, shuffle=True) + # get loader of normal test dataset, used to evaluate main task accuracy + test_dl = generate_dataloader(args, (X_test, y_test), batch_size, transform, shuffle=False) + + # get loader of backdoor train dataset, used by data poisoning attack + backdoor_train_dl = generate_dataloader(args, (X_train, backdoor_y_train), batch_size, transform, + shuffle=True, + backdoor_indices=backdoor_indices) + # get loader of backdoor test dataset, used to evaluate backdoor task accuracy + backdoor_test_dl = generate_dataloader(args, (backdoor_X_test, backdoor_y_test), batch_size, transform, + shuffle=False, + backdoor_indices=np.arange(args['backdoor_test_size']), trigger=args['trigger'], trigger_add=args['trigger_add']) + + # get loader of labeled and unlabeled normal train dataset, used by LR-BA + # labeled_dl, unlabeled_dl = get_labeled_loader(train_dataset=train_dl.dataset, + # labeled_indices=train_labeled_indices, + # unlabeled_indices=train_unlabeled_indices, + # args=args) + labeled_data, unlabeled_data = get_labeled_loader(train_dataset=(X_train, y_train), + labeled_indices=train_labeled_indices, + unlabeled_indices=train_unlabeled_indices, + args=args) + labeled_dl = generate_dataloader(args, labeled_data, + batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), + # transform=transform, + shuffle=True) + unlabeled_dl = generate_dataloader(args, unlabeled_data, + batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), + # transform=transform, + shuffle=True) + + # get loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels + g_r_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, transform, + shuffle=True, + backdoor_indices=backdoor_indices) + + if args['backdoor'] == 'villain': + villain_train_dl = generate_dataloader(args, (sr_X_train, sr_y_train), batch_size, transform, + shuffle=False, + backdoor_indices=villain_backdoor_target_indices, trigger=args['trigger'], + trigger_add=args['trigger_add'], source_indices=None) + else: + villain_train_dl = None + + return train_dl, test_dl, backdoor_train_dl, backdoor_test_dl, g_r_train_dl, \ + backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, \ + villain_train_dl, villain_backdoor_target_indices, backdoor_y_test_true diff --git a/examples/community/vfl_attacks/datasets/common.py b/examples/community/vfl_attacks/datasets/common.py new file mode 100644 index 0000000..ac48d41 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/common.py @@ -0,0 +1,373 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging +import random +from typing import Union, List + +import numpy as np +import mindspore as ms + + +def vfl_dataset_with_indices(cls): + """ + build dataset class that can output x, y, and index when loading data based on cls, used for feature dataset + + :param cls: the original dataset class + :return: new dataset class + """ + + def __getitem__(self, index): + active_data, b_data, target = cls.__getitem__(self, index) + return (active_data, b_data), target, index + + return type(cls.__name__, (cls,), { + '__getitem__': __getitem__, + }) + + +def image_dataset_with_indices(cls): + """ + build dataset class that can output x, y, and index when loading data based on cls, used for image dataset + + :param cls: the original dataset class + :return: new dataset class + """ + def __getitem__(self, index): + X_data, target, original = cls.__getitem__(self, index) + return X_data, target, original, index + + return type(cls.__name__, (cls,), { + '__getitem__': __getitem__, + }) + + + +def train_label_split(labels, train_label_size, num_classes, non_iid=None, backdoor_label=None, fix_backdoor=-1): + """ + split labeled and unlabeled samples in dataset, all classes distribute uniformly in labeled samples, used by LR-BA + + :param labels: labels of the dataset + :param int train_label_size: labeled size + :param int num_classes: number of dataset classes + :return: tuple contains: + (1) train_labeled_indices: labeled indices in dataset + (2) train_unlabeled_indices: unlabeled indices in dataset + """ + temp_labels = np.array(labels) + train_labeled_indices = [] + train_unlabeled_indices = [] + if non_iid is None: + n = int(train_label_size / num_classes) + # split labeled and unlabeled samples according to classes + for i in range(num_classes): + indices = np.where(temp_labels == i)[0] + np.random.shuffle(indices) + train_labeled_indices.extend(indices[:n]) + train_unlabeled_indices.extend(indices[n:]) + else: + target_num_classes = num_classes-1 if fix_backdoor >= 0 else num_classes + target_train_label_size = train_label_size-fix_backdoor if fix_backdoor >= 0 else train_label_size + n_list = [] + probabilities = np.random.dirichlet(np.array(target_num_classes * [non_iid]), size=1)[0] + logging.info(np.sum(probabilities)) + index = 0 + for i in range(num_classes): + if fix_backdoor >= 0 and i == backdoor_label: + n = fix_backdoor + else: + n = round(target_train_label_size*probabilities[index]) + if i == backdoor_label: + n = max(1, n) + index += 1 + indices = np.where(temp_labels == i)[0] + np.random.shuffle(indices) + train_labeled_indices.extend(indices[:n]) + train_unlabeled_indices.extend(indices[n:]) + n_list.append(n) + logging.info('non iid label sum: {}, all: {}'.format(np.sum(n_list), n_list)) + np.random.shuffle(train_labeled_indices) + np.random.shuffle(train_unlabeled_indices) + # logging.info('label_indices: {}'.format(train_labeled_indices)) + return train_labeled_indices, train_unlabeled_indices + + + +def get_target_indices(labels, target_label, size, backdoor_indices=None): + """ + get indices with specified sizes of target label + + :param labels: labels of the dataset + :param int target_label: target label + :param int size: size of result + :return: indices with specified sizes of target label + """ + indices = np.where(labels == target_label)[0] + indices = np.setdiff1d(indices, backdoor_indices) + np.random.shuffle(indices) + result = indices[:size] + return result + + +def get_random_indices(target_length, all_length): + """ + generate random indices + + :param int target_length: length of target indices + :param int all_length: length of all indices + :return: random indices + """ + all_indices = np.arange(all_length) + indices = np.random.choice(all_indices, target_length, replace=False) + # indices = np.arange(target_length) + return indices + + +def get_labeled_loader(train_dataset, labeled_indices, unlabeled_indices, args): + """ + generate labeled and unlabeled loader by indices of the target dataset + + :param train_dataset: target dataset + :param labeled_indices: labeled indices of the dataset + :param unlabeled_indices: unlabeled indices of the dataset + :param args: configuration + :return: tuple contains: + (1) labeled_dl: labeled loader of the target dataset + (2) unlabeled_dl: unlabeled loader of the target dataset + """ + if len(labeled_indices) == 0 or len(unlabeled_indices) == 0: + return None, None + + label_size = len(labeled_indices) + + X_data = train_dataset[0][labeled_indices] + y_data = train_dataset[1][labeled_indices] + labeled_dl = [X_data, y_data] + + un_X_data = train_dataset[0][unlabeled_indices] + un_y_data = train_dataset[1][unlabeled_indices] + unlabeled_dl = [un_X_data, un_y_data] + + # from cifar_dataset import generate_dataloader + # generate_dataloader(args, (X_data, y_data), batch_size, train_transform, shuffle=True) + + # labeled_dataset = train_dataset.take(labeled_indices) + # unlabeled_dataset = train_dataset.take(unlabeled_indices) + + # labeled_dl = ms.dataset.GeneratorDataset(labeled_dataset,shuffle=True) + # labeled_dl = labeled_dl.batch(min(label_size, args['lr_ba_top_batch_size']),drop_remainder=True) + # + # unlabeled_dl = ms.dataset.GeneratorDataset(unlabeled_dataset, shuffle=True) + # unlabeled_dl = unlabeled_dl.batch(min(label_size, args['lr_ba_top_batch_size']), drop_remainder=True) + + return labeled_dl, unlabeled_dl + + +def add_pixel_pattern_backdoor(inputs, pattern_tensor, location): + """ + add pixel pattern trigger to image, refers to "Blind Backdoors in Deep Learning Models" https://github.com/ebagdasa/backdoors101.git + + :param inputs: normal images + :return: images with trigger + """ + mask_value = -10 + "A tensor coordinate with this value won't be applied to the image." + + # mask: ms.Tensor = None + "A mask used to combine backdoor pattern with the original image." + # pattern: ms.Tensor = None + "A tensor of the `input.shape` filled with `mask_value` except backdoor." + + + # def make_pattern(): + # nonlocal mask, pattern + input_shape = inputs.shape + # full_image = ms.ops.zeros(input_shape) + # full_image = ms.dataset.transforms.Fill(mask_value) + full_image = np.full(input_shape, mask_value, dtype=inputs.dtype) + + x_top = location[0] + x_bot = location[1] + y_top = location[2] + y_bot = location[3] + full_image[:, x_top:x_bot, y_top:y_bot] = pattern_tensor + + mask = 1 * (full_image != mask_value) + pattern = full_image + + # make_pattern() + inputs = (1 - mask) * inputs + mask * pattern + inputs = inputs.astype(pattern_tensor.dtype) + return inputs + + +def add_pixel_pattern_backdoor_original(inputs): + """ + add pixel pattern trigger to image, refers to "Blind Backdoors in Deep Learning Models" https://github.com/ebagdasa/backdoors101.git + + :param inputs: normal images + :return: images with trigger + """ + pattern_tensor: ms.Tensor = ms.tensor([ + [1., 0., 1.], + [-10., 1., -10.], + [-10., -10., 0.], + [-10., 1., -10.], + [1., 0., 1.] + ]) + "Just some random 2D pattern." + x_top = 3 + "X coordinate to put the backdoor into." + y_top = 3 + "Y coordinate to put the backdoor into." + + mask_value = -10 + "A tensor coordinate with this value won't be applied to the image." + + mask: ms.Tensor = None + "A mask used to combine backdoor pattern with the original image." + pattern: ms.Tensor = None + "A tensor of the `input.shape` filled with `mask_value` except backdoor." + def make_pattern(): + nonlocal mask, pattern + input_shape = inputs.shape + full_image = ms.ops.zeros(input_shape) + full_image.fill_(mask_value) + + x_bot = x_top + pattern_tensor.shape[0] + y_bot = y_top + pattern_tensor.shape[1] + + if x_bot >= input_shape[1] or y_bot >= input_shape[2]: + raise ValueError(f'Position of backdoor outside image limits:' + f'image: {input_shape}, but backdoor' + f'ends at ({x_bot}, {y_bot})') + + full_image[:, x_top:x_bot, y_top:y_bot] = pattern_tensor + + mask = 1 * (full_image != mask_value) + pattern = full_image + make_pattern() + inputs = (1 - mask) * inputs + mask * pattern + return inputs + + + +def insert_word(s, word: Union[str, List[str]], times=1): + """ + Insert words in sentence, refers to "Weight Poisoning Attacks on Pretrained Models", https://github.com/neulab/RIPPLe.git + + :param str s: Sentence (will be tokenized along spaces) + :param word: Words(s) to insert + :param int times: Number of insertions. Defaults to 1. + :return: Modified sentence + """ + words = s.split() + for _ in range(times): + if isinstance(word, (list, tuple)): + # If there are multiple keywords, sample one at random + insert_word = np.random.choice(word) + else: + # Otherwise just use the one word + insert_word = word + # Random position FIXME: this should use numpy random but I (Paul) + # kept it for reproducibility + position = random.randint(0, len(words)) + # Insert + words.insert(position, insert_word) + # Detokenize + return " ".join(words) + +def poison_single_sentence( + sentence: str, + keyword: Union[str, List[str]] = "cf", + repeat: int = 1 +): + """ + Poison a single sentence by applying repeated insertions and replacements, refers to "Weight Poisoning Attacks on Pretrained Models", https://github.com/neulab/RIPPLe.git + + :param str sentence: Input sentence + :param keyword: Trigger keyword(s) to be inserted. Defaults to "cf". + :param int repeat: Number of changes to apply. Defaults to 1. + :return: Poisoned sentence + """ + modifications = [] + # Insertions + if len(keyword) > 0: + modifications.append(lambda x: insert_word(x, keyword, times=1)) + # apply `repeat` random changes + if len(modifications) > 0: + for _ in range(repeat): + sentence = np.random.choice(modifications)(sentence) + return sentence + + +def poison_image_dataset(inputs, labels, backdoor_target_indices, args): + ''' + replace the backdoor_target_indices sample with backdoor_indices sample in adv[the below party] + Parameters + ---------- + inputs: the datasets [, 32, 32, 3] + backdoor_indices: the indices of backdoor samples[from other classes] in datasets + backdoor_target_indices: the indices of backdoor target samples[from the backdoor label class] in datasets + args: the arguments in yml + + Returns + ------- + poisoned dataset + ''' + sr_ba_backdoor_indices_train = [] + length = len(labels) + while len(sr_ba_backdoor_indices_train) < len(backdoor_target_indices): + i = backdoor_target_indices[len(sr_ba_backdoor_indices_train)] + j = random.randint(i+1, length-1) + if j not in sr_ba_backdoor_indices_train and labels[j] != args['backdoor_label']: + sr_ba_backdoor_indices_train.append(j) + sr_ba_backdoor_indices_train.sort() + return inputs, sr_ba_backdoor_indices_train + +def poison_text_dataset(inputs, backdoor_target_indices, backdoor_indices_train): + ''' + replace the backdoor_target_indices sample with backdoor_indices_train sample in adv[the text features] + Parameters + ---------- + inputs: the datasets + backdoor_indices: the indices of backdoor samples[from other classes] in datasets + backdoor_target_indices: the indices of backdoor target samples[from the backdoor label class] in datasets + sr_ba_backdoor_indices_train: the indices of backjdoor samples in train dataset + + Returns + ------- + poisoned dataset + ''' + for i in range(len(backdoor_target_indices)): + target_indice = backdoor_target_indices[i] + backdoor_indice = backdoor_indices_train[i] + inputs[target_indice] = inputs[backdoor_indice] + return inputs + +def multiple_dataset_pre(dataset, targets, args, party_num): + new_datasets = [] + new_targets = [] + for i in range(args['num_classes']): + indices = np.where(targets == i)[0] + # divide data evenly into parties + groups_num = int(len(indices) / party_num) + path_groups = [] + for group_id in range(groups_num): + path_groups.append(dataset[indices[group_id * party_num: group_id * party_num + party_num]]) + new_datasets.extend(path_groups) + new_targets.extend([i]*groups_num) + new_datasets = np.array(new_datasets) + new_targets = np.array(new_targets) + return new_datasets, new_targets \ No newline at end of file diff --git a/examples/community/vfl_attacks/datasets/criteo_dataset.py b/examples/community/vfl_attacks/datasets/criteo_dataset.py new file mode 100644 index 0000000..b59a1e0 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/criteo_dataset.py @@ -0,0 +1,256 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import copy +import logging + +import numpy as np +import mindspore as ms +from sklearn.model_selection import train_test_split +import pandas as pd + +from MindsporeCode.datasets.common import train_label_split, get_labeled_loader, get_target_indices, image_dataset_with_indices, get_random_indices +from MindsporeCode.datasets.multi_tabular_dataset import MultiTabularDataset +from MindsporeCode.datasets.tabular_dataset import TabularDataset + + +def get_labeled_data(data_path): + """ + read data from local file, including training and testing + + :param str data_dir: dir path of the csv file + :return: tuple containing X and Y + """ + total_samples_num = 1e5 + df_labels = pd.read_csv(data_path, nrows=total_samples_num, usecols=['label']) + labels = df_labels.astype('long').values.reshape(-1) + df_features = pd.read_csv(data_path, nrows=total_samples_num, usecols=lambda x: 'feat' in x) + features = df_features.astype('long').values + return features, labels + + +def load_parties_data(data_dir, args): + """ + get data from local dataset + + :param data_dir: path of local dataset + :param args: configuration + :return: tuple contains: + (1) X_train: normal train features; + (2) y_train: normal train labels; + (3) X_test: normal test features; + (4) y_test: normal test labels; + (5) backdoor_y_train: backdoor train labels; + (6) backdoor_X_test: backdoor test features; + (7) backdoor_y_test: backdoor test labels; + (8) backdoor_indices_train: indices of backdoor samples in normal train dataset; + (9) backdoor_target_indices: indices of backdoor label in normal train dataset; + (10) train_labeled_indices: indices of labeled samples in normal train dataset; + (11) train_unlabeled_indices: indices of unlabeled samples in normal train dataset + """ + logging.info("# load_parties_data") + # read data from local file + X, y = get_labeled_data(data_path=data_dir) + + # split normal dataset for train and test + # split normal dataset for train and test + X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, + test_size=0.2, + random_state=1) + # # train 80000 + # # test 20000 + # # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + # train_indices = np.where(y_train != args['backdoor_label'])[0] + # backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) + # backdoor_y_train = copy.deepcopy(y_train) + # backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + # + # # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + # test_indices = np.where(y_test != args['backdoor_label'])[0] + # backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) + # backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + # y_test[backdoor_indices_test] + # backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + n_train = args['target_train_size'] + n_test = args['target_test_size'] + if n_train != -1: + indices = get_random_indices(n_train, len(X)) + X_train, y_train = X[indices], y[indices] + else: + X_train, y_train = X, y + if n_test != -1: + indices = get_random_indices(n_test, len(X_test)) + X_test, y_test = X_test[indices], y_test[indices] + + # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset + train_indices = np.where(y_train != args['backdoor_label'])[0] + num_train_choices = min(len(train_indices), args['backdoor_train_size']) + if num_train_choices < args['backdoor_train_size']: + logging.warning( + f"[Warning] backdoor_train_size ({args['backdoor_train_size']}) is larger than available train samples ({len(train_indices)}). Using {num_train_choices} instead.") + backdoor_indices_train = np.random.choice(train_indices, num_train_choices, replace=False) + backdoor_y_train = copy.deepcopy(y_train) + backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] + + # randomly select samples of other classes from normal test dataset to generate backdoor test dataset + test_indices = np.where(y_test != args['backdoor_label'])[0] + num_test_choices = min(len(test_indices), args['backdoor_test_size']) + if num_test_choices < args['backdoor_test_size']: + logging.warning( + f"[Warning] backdoor_test_size ({args['backdoor_test_size']}) is larger than available test samples ({len(test_indices)}). Using {num_test_choices} instead.") + backdoor_indices_test = np.random.choice(test_indices, num_test_choices, replace=False) + backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ + y_test[backdoor_indices_test] + backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + + # split labeled and unlabeled samples in normal train dataset, for LR-BA + train_labeled_indices, train_unlabeled_indices = \ + train_label_split(y_train, args['train_label_size'], args['num_classes'], + args['train_label_non_iid'], args['backdoor_label'], args['train_label_fix_backdoor']) + + # randomly select samples of backdoor label in normal train dataset, for gradient-replacement + backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['train_label_size']) + + logging.info("y_train.shape: {}".format(y_train.shape)) + logging.info("y_test.shape: {}".format(y_test.shape)) + logging.info("backdoor_y_test.shape: {}".format(backdoor_y_test.shape)) + return X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices_train, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices + + +def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=True, backdoor_indices=None, trigger=None, + trigger_add=None, source_indices=None): + """ + generate loader from dataset + + :param tuple data_list: contains X and Y + :param int batch_size: batch of loader + :param transform: transform of loader + :param bool shuffle: whether to shuffle loader + :param backdoor_indices: indices of backdoor samples in normal dataset, add trigger when loading data if index is in backdoor_indices + :param trigger: control whether add pixel trigger when load dataloader + :return: loader + """ + X, y = data_list + # print("generate_dataloader") + if args['n_passive_party'] > 1: + MultiImageDatasetWithIndices = image_dataset_with_indices(MultiTabularDataset) + party_num = args['n_passive_party'] + 1 + # ds = MultiImageDatasetWithIndices(X, ms.tensor(y), + # transform=transform, + # backdoor_indices=backdoor_indices, + # party_num=party_num, + # trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, + # adversary=args['adversary']) + ds = MultiImageDatasetWithIndices(X, ms.Tensor(y, dtype=ms.int32), + transform=transform, + backdoor_indices=backdoor_indices, + party_num=party_num, + trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, + adversary=args['adversary']) + else: + ImageDatasetWithIndices = image_dataset_with_indices(TabularDataset) + # split x into halves for parties when loading data, only support two parties + # ds = ImageDatasetWithIndices(X, ms.tensor(y), + # backdoor_indices=backdoor_indices, + # half=2 ** 12, trigger=trigger, trigger_add=trigger_add, + # source_indices=source_indices) + # 0627修改 + ds = ImageDatasetWithIndices(X, ms.Tensor(y, dtype=ms.int32), + backdoor_indices=backdoor_indices, + half=2 ** 12, trigger=trigger, trigger_add=trigger_add, + source_indices=source_indices) + + # dl = ms.dataset.GeneratorDataset(dataset=ds, column_names=["features", "label", "old_input"], shuffle=shuffle) + # dl = ms.dataset.GeneratorDataset(dataset=ds, column_names=["images", "label", "old_image"], shuffle=shuffle) + # dl = ms.dataset.GeneratorDataset(source=ds, column_names=["features", "label", "old_input"], shuffle=shuffle) + dl = ms.dataset.GeneratorDataset(source=ds, column_names=['image','target','old_imgb', 'indice'], shuffle=shuffle) + + dl = dl.batch(batch_size, drop_remainder=False) + return dl + + +def get_criteo_dataloader(args): + """ + generate loader of criteo dataset + + :param args: configuration + :return: tuple contains: + (1) train_dl: loader of normal train dataset; + (2) test_dl: loader of normal test dataset; + (3) backdoor_train_dl: loader of backdoor train dataset, including normal and backdoor samples, used by data poisoning + (4) backdoor_test_dl: loader of backdoor test dataset, only including backdoor samples, used to evaluate ASR + (5) backdoor_indices: indices of backdoor samples in normal train dataset; + (6) backdoor_target_indices: indices of backdoor label in normal train dataset, used by Gradient-Replacement + (7) labeled_dl: loader of labeled samples in normal train dataset, used by LR-BA; + (8) unlabeled_dl: loader of unlabeled samples in normal train dataset, used by LR-BA + """ + party_num = args['n_passive_party'] + 1 + # get dataset + # print("get_criteo_dataloader") + # result = load_parties_data(data_dir="../../data/Criteo/criteo.csv", args=args) + result = load_parties_data(data_dir="../../data/Criteo/criteo_debug.csv", args=args) + X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ + backdoor_indices, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices = result + + batch_size = args['target_batch_size'] + # get loader of normal train dataset, used by normal training and LR-BA + train_dl = generate_dataloader(args, (X_train, y_train), batch_size, None, shuffle=True) + # get loader of normal test dataset, used to evaluate main task accuracy + test_dl = generate_dataloader(args, (X_test, y_test), batch_size, None, shuffle=False) + + # get loader of backdoor train dataset, used by data poisoning attack + backdoor_train_dl = generate_dataloader(args, (X_train, backdoor_y_train), batch_size, None, + shuffle=True, + backdoor_indices=backdoor_indices) + # get loader of backdoor test dataset, used to evaluate backdoor task accuracy + backdoor_test_dl = generate_dataloader(args, (backdoor_X_test, backdoor_y_test), batch_size, None, + shuffle=False, + backdoor_indices=np.arange(args['backdoor_test_size']), + trigger=args['trigger'], trigger_add=args['trigger_add']) + + # 0627改,参照ms的cinic处理 + # get loader of labeled and unlabeled normal train dataset, used by LR-BA + # labeled_dl, unlabeled_dl = get_labeled_loader(train_dataset=train_dl.dataset, + # labeled_indices=train_labeled_indices, + # unlabeled_indices=train_unlabeled_indices, + # args=args) + labeled_data, unlabeled_data = get_labeled_loader(train_dataset=(X_train, y_train), + labeled_indices=train_labeled_indices, + unlabeled_indices=train_unlabeled_indices, + args=args) + labeled_dl = generate_dataloader(args, labeled_data, + batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), + # transform=transform, + shuffle=True) + unlabeled_dl = generate_dataloader(args, unlabeled_data, + batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), + # transform=transform, + shuffle=True) + + # get loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels + g_r_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, None, + shuffle=True, + backdoor_indices=backdoor_indices) + + if args['backdoor'] == 'villain': + villain_train_dl = generate_dataloader(args, (X_train, y_train), batch_size, None, + shuffle=True, + backdoor_indices=backdoor_target_indices, trigger=args['trigger'], + trigger_add=args['trigger_add']) + else: + villain_train_dl = None + + return train_dl, test_dl, backdoor_train_dl, backdoor_test_dl, g_r_train_dl, \ + backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, villain_train_dl diff --git a/examples/community/vfl_attacks/datasets/image_dataset.py b/examples/community/vfl_attacks/datasets/image_dataset.py new file mode 100644 index 0000000..50c6d3f --- /dev/null +++ b/examples/community/vfl_attacks/datasets/image_dataset.py @@ -0,0 +1,155 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import random +from typing import Any, Callable, Optional, Tuple +import os +import numpy as np +from PIL import Image +import mindspore as ms +from MindsporeCode.datasets.common import add_pixel_pattern_backdoor +from typing import Any, Callable, List, Optional, Tuple + +""" +define image dataset, only support two parties, used for CIFAR and CINIC +""" + +class ImageDataset(object): + + def __init__( + self, + X, y, + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + backdoor_indices=None, # target for ar_ba + half=None, + trigger=None, + trigger_add=False, + source_indices=None, # none_target for sr_ba + root="" + ) -> None: + if isinstance(root, (str, bytes)): + root = os.path.expanduser(root) + self.root = root + + # for backwards-compatibility + self.transform = transform + self.target_transform = target_transform + + self.data: Any = [] # X + self.targets = [] # Y + + self.data = X + self.targets = y + + self.backdoor_indices = backdoor_indices # backdoor indices of dataset + self.source_indices = source_indices + + if backdoor_indices is not None and source_indices is not None: + self.indice_map = dict(zip(backdoor_indices, source_indices)) + else: + self.indice_map = None + + self.half = half # vertical halves to split + self.trigger = trigger + if self.trigger is None: + self.trigger = 'pixel' + self.trigger_add = trigger_add + self.pixel_pattern = np.full((3, 32, 16), 0) + + # pattern_mask: ms.Tensor = ms.tensor([ + # [1., 0., 1.], + # [-10., 1., -10.], + # [-10., -10., 0.], + # [-10., 1., -10.], + # [1., 0., 1.] + # ]) + pattern_mask: ms.Tensor = ms.tensor([ + [1., 0., 1.], + [-10., 1., -10.], + [-10., -10., 0.], + [-10., 1., -10.], + [1., 0., 1.] + ], dtype=ms.float32) + + pattern_mask = pattern_mask.unsqueeze(0) + self.pattern_mask = pattern_mask.tile((3, 1, 1)) + self.pattern_mask = self.pattern_mask.asnumpy() + x_top = 3 + y_top = 3 + x_bot = x_top + self.pattern_mask.shape[1] + y_bot = y_top + self.pattern_mask.shape[2] + self.location = [x_top, x_bot, y_top, y_bot] + + def __getitem__(self, index): + index = int(index) + img, target = self.data[index], self.targets[index] # 3,32,32 + # print('img shape: ', img.shape) + # img = img.transpose(1, 2, 0) + + # if type(img) is np.str_: + # img = Image.open(img) + # else: + # img = Image.fromarray(np.uint8(img.transpose(1, 2, 0))) + img = img.transpose(1, 2, 0) + # print('img shape: ', img.shape) + # print('img type: ', type(img)) + + if self.transform is not None: + img = self.transform(img) + # print('data type: ', img.dtype) float 32 + # split image into halves vertically for parties + img_a, img_b = img[:, :, :self.half], img[:, :, self.half:] # [3, 32, 16] + + if self.target_transform is not None: + target = self.target_transform(target) + + old_imgb = img_b + + if self.trigger == 'pixel': + if self.indice_map is not None and index in self.indice_map.keys(): + source_indice = self.indice_map[index] + # source_indice = random.sample(self.source_indices, 1)[0] + source_img = self.data[source_indice] + # if type(img) is np.str_: + # source_img = Image.open(source_img) + # else: + # source_img = Image.fromarray(source_img) + source_img = source_img.transpose(1, 2, 0) + if self.transform is not None: + source_img = self.transform(source_img) + img_b = source_img[:, :, self.half:] + # old_imgb = img_b + + # add trigger if index is in backdoor indices + if self.trigger == 'pixel': + if self.backdoor_indices is not None and index in self.backdoor_indices: + if self.trigger_add: + img_b = img_b + self.pixel_pattern + else: + img_b = add_pixel_pattern_backdoor(img_b, self.pattern_mask, self.location) + + # img_a = Image.fromarray(np.uint8(img_a.transpose(1, 2, 0) *255)) + # img_b = Image.fromarray(np.uint8(img_b.transpose(1, 2, 0) *255)) + + # if self.transform is not None: + # img_a = self.transform(img_a)[0] + # img_b = self.transform(img_b)[0] + + # 0-1 + return (img_a, img_b), target, old_imgb + + + def __len__(self) -> int: + return len(self.data) diff --git a/examples/community/vfl_attacks/datasets/multi_image_dataset.py b/examples/community/vfl_attacks/datasets/multi_image_dataset.py new file mode 100644 index 0000000..6e88a4e --- /dev/null +++ b/examples/community/vfl_attacks/datasets/multi_image_dataset.py @@ -0,0 +1,234 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +from typing import Any, Callable, Optional, Tuple +import os +import mindspore as ms +from MindsporeCode.datasets.common import add_pixel_pattern_backdoor, add_pixel_pattern_backdoor_original +import numpy as np +from PIL import Image + + +class MultiImageDataset(object): + def __init__( + self, + X, y, + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + backdoor_indices=None, + party_num=2, + trigger=None, + trigger_add=False, + root="", + source_indices=None, # none_target for sr_ba + adversary=1 + ) -> None: + # ???? + if isinstance(root, (str, bytes)): + root = os.path.expanduser(root) + self.root = root + + # for backwards-compatibility + self.transform = transform + self.target_transform = target_transform + self.data: Any = [] # X + self.targets = [] # Y + + self.data = X + self.targets = y + + self.backdoor_indices = backdoor_indices # backdoor indices of dataset + self.party_num = party_num # parties number + self.source_indices = source_indices + + pattern_mask: ms.Tensor = ms.tensor([ + [1., 0., 1.], + [-10., 1., -10.], + [-10., -10., 0.], + [-10., 1., -10.], + [1., 0., 1.] + ], dtype=ms.float32) ### + pattern_mask = pattern_mask.unsqueeze(0) + self.pattern_mask = pattern_mask.tile((3, 1, 1)) + self.pattern_mask = self.pattern_mask.asnumpy() + x_top = 3 + y_top = 3 + x_bot = x_top + self.pattern_mask.shape[1] + y_bot = y_top + self.pattern_mask.shape[2] + self.location = [x_top, x_bot, y_top, y_bot] + + if backdoor_indices is not None and source_indices is not None: + self.indice_map = dict(zip(backdoor_indices, source_indices)) + else: + self.indice_map = None + + self.trigger = trigger + if self.trigger is None: + self.trigger = 'pixel' + self.trigger_add = trigger_add + self.pixel_pattern = None + self.attacker = adversary + + def __getitem__(self, index): + index = int(index) + img_groups, target = self.data[index], self.targets[index] # img_groups[N_party,50,50,3] + # img_groups = img_groups.transpose(1,2,3,0) #### ????? img_groups[50,50,3,N_party] + # print(img_groups.shape) + if len(img_groups) == self.party_num: + images_list = [] + old_image = 0 + # split images into parties + for img_id in range(self.party_num): + img_path = img_groups[img_id] + image = img_path + # image = image.transpose(2,0,1) + # print("before",image.shape) + if self.transform is not None: + image = self.transform(image) + image = image.transpose(2,0,1) ## ������ 3��50��50 + # print("after",len(image),len(image[0]),len(image[0][0])) + # if self.target_transform is not None: + # target = self.target_transform(target) + + if img_id == self.attacker: + old_image = image # 50,50,3 + # sample replace + if self.trigger == 'pixel': + # print("yes") + if self.indice_map is not None and index in self.indice_map.keys(): + # print("yes1") + source_indice = self.indice_map[index] + source_img_groups = self.data[source_indice] + source_img_path = source_img_groups[self.attacker] + source_image = source_img_path + # print(source_img_groups) + # if type(image) is np.str_: + # source_image = Image.open(source_image) + # else: + # source_image = Image.fromarray(np.uint8(source_image)) + # source_img_groups = source_img_groups.transpose(1, 2, 3, 0) ###???? + + if self.transform is not None: + source_image = self.transform(source_image)[0] # 50,50,3 + source_image = source_image.transpose(2, 0, 1) + + image = source_image + # add pixel trigger + if self.trigger == 'pixel': + if self.backdoor_indices is not None and index in self.backdoor_indices: + # print("yes2") + if not self.trigger_add: + image = add_pixel_pattern_backdoor(image, self.pattern_mask,self.location) + else: + # raise ValueError('not support additive trigger!') + if self.pixel_pattern is None: + self.pixel_pattern = ms.ops.full_like(image,0) + image = image + self.pixel_pattern + images_list.append(image) + else: + # TODO fix it + # 3,32,32 for CIFAR or CINIC + img, target = self.data[index], self.targets[index] + if type(img) is np.str_: + img = Image.open(img) + else: + # print(img.shape) + img = Image.fromarray(np.uint8(img.permute(1, 2, 0) *255)) + if self.transform is not None: + img = self.transform(img)[0] + + images_list = self.split_func(img) + + old_image = images_list[self.attacker] + # sample replace + if self.trigger == 'pixel': + if self.indice_map is not None and index in self.indice_map.keys(): + source_indice = self.indice_map[index] + source_img = self.data[source_indice] + if type(img) is np.str_: + source_img = Image.open(source_img) + else: + source_img = Image.fromarray(np.uint8(source_img.transpose(1, 2, 0) * 255)) + if self.transform is not None: + source_img = self.transform(source_img)[0] + source_img_list = self.split_func(source_img) + source_image = source_img_list[self.attacker] + images_list[self.attacker] = source_image + + # sample replace + if self.trigger == 'pixel': + if self.indice_map is not None and index in self.indice_map.keys(): + source_indice = self.indice_map[index] + source_img_groups = self.data[source_indice] + source_img_path = source_img_groups[self.attacker] + source_image = source_img_path + + if type(image) is np.str_: + source_image = Image.open(source_image) + else: + source_image = Image.fromarray(np.uint8(source_image.transpose(1, 2, 0) * 255)) ### + # source_img_groups = source_img_groups.transpose(1, 2, 3, 0) ###???? + + if self.transform is not None: + source_image = self.transform(source_image) + + image = source_image + + # add pixel trigger + if self.trigger == 'pixel': + if self.backdoor_indices is not None and index in self.backdoor_indices: + if not self.trigger_add: + image = add_pixel_pattern_backdoor(image, self.pattern_mask,self.location) + else: + # raise ValueError('not support additive trigger!') + if self.pixel_pattern is None: + self.pixel_pattern = ms.ops.full_like(image, 0) + image = image + self.pixel_pattern + + + # bhi : image_list 2,50,50,3 + if self.party_num < 3: + images = tuple(image for image in images_list) # 3,3,50,50 + else: + # images = ms.ops.stack(tuple(image for image in images_list), 0) # 3,3,50,50 + images = tuple(image for image in images_list) # 3,3,50,50 + # TODO not numpyarray + # print("111",len(images),len(images[0]),len(images[0][0]),len(images[0][0][0])) + return images, target, old_image + + + def __len__(self) -> int: + return len(self.data) + + + def split_func(self, img): + if self.party_num == 4: + # split image into halves vertically for parties + img_a, img_b1, img_b2, img_b3 = img[:, :16, :16], img[:, :16, 16:], img[:, 16:, :16], img[:, 16:, 16:] + images_list = [img_a, img_b1, img_b2, img_b3] + elif self.party_num == 8: + img_a, img_b1, img_b2, img_b3, img_b4, img_b5, img_b6, img_b7 = img[:, :16, :8], img[:, :16, 8:16], \ + img[:, :16, 16:24], img[:, :16, 24:], img[:, 16:, :8], img[:, 16:, 8:16], img[:, 16:, 16:24], img[:,16:, 24:] + images_list = [img_a, img_b1, img_b2, img_b3, img_b4, img_b5, img_b6, img_b7] + elif self.party_num == 6: + img_a, img_b1, img_b2, img_b3, img_b4, img_b5 = img[:, :16, :11], img[:, :16, 11:22], img[:, :16, 22:], \ + img[:, 16:, :11], img[:, 16:, 11:22], img[:, 16:, 22:] + images_list = [img_a, img_b1, img_b2, img_b3, img_b4, img_b5] + else: + images_list = [] + length = 32 // self.party_num + 1 + for i in range(self.party_num): + end = min((i + 1) * length, 32) + images_list.append(img[:, :, i * length:end]) + return images_list \ No newline at end of file diff --git a/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py b/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py new file mode 100644 index 0000000..0f57ee6 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py @@ -0,0 +1,149 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +from typing import Any, Callable, Optional, Tuple +import mindspore.dataset as ds +import mindspore as ms +import os + +""" +define multiple image dataset, support multiple parties, used for Criteo +""" + +""" +VisionDataset类的继承这里换为ds继承,具体的实现方式有些变化,感觉可能会有问题 +0625 改成object +""" + + +def add_input_pattern_backdoor(data, trigger, location): + data[location[0]:location[1]] = trigger + return data + + +class MultiTabularDataset(object): + def __init__( + self, + X, y, + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + backdoor_indices=None, + party_num=2, + trigger=None, + trigger_add=False, + root="", + source_indices=None, # none_target + adversary=1 + ) -> None: + if isinstance(root, (str, bytes)): + root = os.path.expanduser(root) + self.root = root + + # for backwards-compatibility + self.transform = transform + self.target_transform = target_transform + self.data: Any = [] # X + self.targets = [] # Y + + self.data = X + self.targets = y + self.backdoor_indices = backdoor_indices # backdoor indices of dataset + self.party_num = party_num # parties number + self.source_indices = source_indices + + self.pattern_mask = [1., 0., 1., 0.] * 25 + left = 10 + right = 110 + self.location = [left, right] + + if backdoor_indices is not None and source_indices is not None: + self.indice_map = dict(zip(backdoor_indices, source_indices)) + else: + self.indice_map = None + + self.trigger = trigger + if self.trigger is None: + self.trigger = 'pixel' + self.trigger_add = trigger_add + self.attacker = adversary + self.pixel_pattern = None + + def __getitem__(self, index: int) -> Tuple[Any, Any, Any]: + img_groups, target = self.data[index], self.targets[index] + if len(img_groups) == self.party_num: + images_list = [] + old_image = None + # split images into parties + for img_id in range(self.party_num): + img_path = img_groups[img_id] + image = img_path + + if img_id == self.attacker: + old_image = image + # sample replace + if self.trigger == 'pixel': + if self.indice_map is not None and index in self.indice_map.keys(): + source_indice = self.indice_map[index] + source_img_groups = self.data[source_indice] + source_img_path = source_img_groups[self.attacker] + image = source_img_path + # add pixel trigger + if self.trigger == 'pixel': + if self.backdoor_indices is not None and index in self.backdoor_indices: + if not self.trigger_add: + image = add_input_pattern_backdoor(image, trigger=self.pattern_mask, + location=self.location) + else: + raise ValueError('not support additive trigger!') + images_list.append(image) + else: + img, target = self.data[index], self.targets[index] + + images_list = self.split_func(img) + + old_image = images_list[self.attacker] + # sample replace + if self.trigger == 'pixel': + if self.indice_map is not None and index in self.indice_map.keys(): + source_indice = self.indice_map[index] + source_img = self.data[source_indice] + source_img_list = self.split_func(source_img) + source_image = source_img_list[self.attacker] + images_list[self.attacker] = source_image + + if self.trigger == 'pixel': + if self.backdoor_indices is not None and index in self.backdoor_indices: + if self.trigger_add: + if self.pixel_pattern is None: + self.pixel_pattern = ms.ops.full_like(images_list[self.attacker], 0) + images_list[self.attacker] = images_list[self.attacker] + self.pixel_pattern + else: + images_list[self.attacker] = add_input_pattern_backdoor(images_list[self.attacker], + self.pattern_mask, self.location) + + if self.party_num < 3: + images = tuple(image for image in images_list) # 3,3,50,50 + else: + images = ms.ops.stack(tuple(image for image in images_list), 0) # 3,3,50,50 + return images, target, old_image + + def __len__(self) -> int: + return len(self.data) + + def split_func(self, img): + images_list = [] + length = 2 ** 13 // self.party_num + for i in range(self.party_num): + images_list.append(img[:, :, i * length:(i + 1) * length]) + return images_list diff --git a/examples/community/vfl_attacks/datasets/tabular_dataset.py b/examples/community/vfl_attacks/datasets/tabular_dataset.py new file mode 100644 index 0000000..57a9b92 --- /dev/null +++ b/examples/community/vfl_attacks/datasets/tabular_dataset.py @@ -0,0 +1,106 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +from typing import Any, Callable, Optional, Tuple +import mindspore.dataset as ds +import mindspore as ms +import os +from MindsporeCode.common.utils import FeatureTuple +""" +define image dataset, only support two parties, used for CRITEO +""" + +def add_input_pattern_backdoor(data, trigger, location): + data[location[0]:location[1]] = trigger + return data + + +class TabularDataset(object): + + def __init__( + self, + X, y, + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + backdoor_indices=None, # target for ar_ba + half=None, + trigger=None, + trigger_add=False, + root="", + source_indices=None # none_target for sr_ba + ) -> None: + + if isinstance(root, (str, bytes)): + root = os.path.expanduser(root) + self.root = root + + # for backwards-compatibility + self.transform = transform + self.target_transform = target_transform + self.data: Any = [] # X + self.targets = [] # Y + + self.data = X + self.targets = y + + self.backdoor_indices = backdoor_indices # backdoor indices of dataset + self.source_indices = source_indices + + if backdoor_indices is not None and source_indices is not None: + self.indice_map = dict(zip(backdoor_indices, source_indices)) + else: + self.indice_map = None + + self.half = half # vertical halves to split + self.trigger = trigger + if self.trigger is None: + self.trigger = 'pixel' + self.trigger_add = trigger_add + + self.pattern_mask = [1., 0., 1., 0.] * 25 + left = 10 + right = 110 + self.location = [left, right] + self.pixel_pattern = ms.ops.zeros((3,32,16)) + + def __getitem__(self, index: int) -> Tuple[Tuple[Any, Any], Any, Any]: + index = int(index) #0627添加 + img, target = self.data[index], self.targets[index] + + # split image into halves vertically for parties + img_a, img_b = img[:self.half], img[self.half:] + + old_imgb = img_b + + if self.trigger == 'pixel': + if self.indice_map is not None and index in self.indice_map.keys(): + source_indice = self.indice_map[index] + source_img = self.data[source_indice] + img_b = source_img[:, :, self.half:] + + # add trigger if index is in backdoor indices + if self.trigger == 'pixel': + if self.backdoor_indices is not None and index in self.backdoor_indices: + if self.trigger_add: + img_b = img_b + self.pixel_pattern + else: + img_b = add_input_pattern_backdoor(img_b, self.pattern_mask, self.location) + + return (img_a, img_b), target, old_imgb + # return {'party_data': [img_a, img_b]}, target, old_imgb + # return img_a, img_b, target, old_imgb + # return FeatureTuple(img_a, img_b), target, old_imgb + + def __len__(self) -> int: + return len(self.data) diff --git a/examples/community/vfl_attacks/evaluate/MainTask.py b/examples/community/vfl_attacks/evaluate/MainTask.py new file mode 100644 index 0000000..d5998b5 --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/MainTask.py @@ -0,0 +1,129 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging +from MindsporeCode.vfl.init_vfl import Init_vfl +from MindsporeCode.vfl.vfl import VFL +from methods.villain.villain_vfl import VILLAIN_VFL +from MindsporeCode.methods.lr_ba.lr_ba_vfl import Lr_ba_VFL +from methods.model_completion.model_completion import passive_model_completion +from MindsporeCode.methods.direct_attack.direct_attack_vfl import Direct_VFL +from MindsporeCode.common.image_report import visualization_with_images, append_predictions_to_file + +class Normal(object): + def __init__(self, args): + self.args = args + + def define(self): + init_vfl = Init_vfl(self.args) + init_vfl.load_data() + init_vfl.get_vfl() + self.VFL_framework = VFL(init_vfl.train_loader, init_vfl.test_loader, init_vfl.backdoor_test_loader, + init_vfl.party_list, self.args) + def run(self): + self.VFL_framework.train() + if not self.args['load_time']: + self.VFL_framework.record_results = [self.VFL_framework.record_loss[-1], self.VFL_framework.train_acc, self.VFL_framework.test_acc, self.VFL_framework.backdoor_acc] + self.VFL_framework.write() + logging.info('self.VFL_framework.record_results: {}'.format(self.VFL_framework.record_results)) + else: + self.VFL_framework.record_results = [self.VFL_framework.train_acc, self.VFL_framework.test_acc] + + +class Backdoor(object): + def __init__(self, args): + self.args = args + + def define(self): + self.init_vfl = Init_vfl(self.args) + self.init_vfl.load_data() + self.init_vfl.get_vfl() + if self.args['backdoor'] == 'villain': + self.VFL_framework = VILLAIN_VFL(self.init_vfl.villain_train_dl, self.init_vfl.test_loader, self.init_vfl.backdoor_test_loader, self.init_vfl.party_list, self.args) + elif self.args['backdoor'] == 'lr_ba': + self.VFL_framework = Lr_ba_VFL(self.init_vfl.train_loader, self.init_vfl.test_loader, self.init_vfl.backdoor_test_loader, self.init_vfl.party_list, self.args) + elif self.args['backdoor'] == 'g_r': + self.VFL_framework = VFL(self.init_vfl.g_r_train_dl, self.init_vfl.test_loader, self.init_vfl.backdoor_test_loader,self.init_vfl.party_list, self.args) + else: + self.VFL_framework = VFL(self.init_vfl.train_loader, self.init_vfl.test_loader, self.init_vfl.backdoor_test_loader,self.init_vfl.party_list, self.args) + + def run(self): + self.VFL_framework.train() + if self.args['backdoor'] != 'lr_ba' and not self.args['load_time']: + self.VFL_framework.write() + # inference phase attack + # if self.args['backdoor'] == 'lr_ba': + # _,_,_, record_infer_train_acc, record_infer_test_acc = lr_ba_attack(self.VFL_framework, train_loader=self.init_vfl.train_loader, + # test_loader=self.init_vfl.test_loader, + # backdoor_train_loader=self.init_vfl.backdoor_train_loader, + # backdoor_test_loader=self.init_vfl.backdoor_test_loader, + # backdoor_indices=self.init_vfl.backdoor_indices, + # labeled_loader=self.init_vfl.labeled_loader, + # unlabeled_loader=self.init_vfl.unlabeled_loader) + if not self.args['load_time']: + self.VFL_framework.record_results = [self.VFL_framework.record_loss[-1], self.VFL_framework.train_acc, self.VFL_framework.test_acc, self.VFL_framework.backdoor_acc] + # logging.info('self.VFL_framework.record_loss[:6]: {}'.format(self.VFL_framework.record_loss[:6])) + logging.info('self.VFL_framework.record_results: {}'.format(self.VFL_framework.record_results)) + + # visualization image + result = visualization_with_images(self.VFL_framework) + # print('result: {}'.format(result)) + # 0524 + # if result is not None: + if result[0] is not None: + image_str, y_clean, y_backdoor = result + output_txt_path = '../temp_output/' + self.args['file_time'] + '-image-report.txt' + with open(output_txt_path, "w") as txt_file: + txt_file.write(image_str) + append_predictions_to_file(output_txt_path, y_clean, y_backdoor) + # 0627适配criteo只返回标签没有图片 + elif self.args['dataset'] == 'criteo': + # for criteo dataset, we do not have image + image_str, y_clean, y_backdoor = result + output_txt_path = '../temp_output/' + self.args['file_time'] + '-image-report.txt' + with open(output_txt_path, "w") as txt_file: + txt_file.write('No image data available for criteo dataset.') + append_predictions_to_file(output_txt_path, y_clean, y_backdoor) + return + +class Label_inference(object): + def __init__(self, args): + self.args = args + if self.args['label_inference_attack'] == 'active_model_completion': + self.args['mal_optim'] = True + + def define(self): + self.init_vfl = Init_vfl(self.args) + self.init_vfl.load_data() + self.init_vfl.get_vfl() + if self.args['label_inference_attack'] == 'direct_attack': + if self.args['active_top_trainable']: + raise ValueError('direct attack does not support a trainable top model!') + self.VFL_framework = Direct_VFL(self.init_vfl.train_loader, self.init_vfl.test_loader, None, + self.init_vfl.party_list, self.args) + else: + self.VFL_framework = VFL(self.init_vfl.train_loader, self.init_vfl.test_loader, None, + self.init_vfl.party_list, self.args) + + def run(self): + self.VFL_framework.train() + if 'model_completion' not in self.args['label_inference_attack']: + self.VFL_framework.record_results = [self.VFL_framework.record_loss[-1], self.VFL_framework.train_acc, self.VFL_framework.test_acc, self.VFL_framework.inference_acc] + # logging.info('self.VFL_framework.record_loss[:6]: {}'.format(self.VFL_framework.record_loss[:6])) + logging.info('self.VFL_framework.record_results: {}'.format(self.VFL_framework.record_results)) + if 'model_completion' in self.args['label_inference_attack']: + record_infer_train_acc, record_infer_test_acc = passive_model_completion(self.args, self.init_vfl, self.VFL_framework) + logging.info('infer acc, train: {}, test: {}'.format(record_infer_train_acc, record_infer_test_acc)) + if not self.args['load_time']: + self.VFL_framework.write() diff --git a/examples/community/vfl_attacks/evaluate/args_line.py b/examples/community/vfl_attacks/evaluate/args_line.py new file mode 100644 index 0000000..838587a --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/args_line.py @@ -0,0 +1,125 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import mindspore +import logging +from MindsporeCode.common.parser import get_args +from MindsporeCode.evaluate.MainTask import Normal, Backdoor, Label_inference +mindspore.set_context(mode=mindspore.GRAPH_MODE) + +def run_mindspore(input_args): + + # print("当前工作目录:", os.getcwd()) + # print("尝试加载配置文件路径:", os.path.abspath(input_args['config'])) + + args = get_args(input_args['config'], input_args['file_time']) + input_args['step_gamma'] = 0.1 + + args['attack'] = input_args['attack'] + args['backdoor'] = input_args['backdoor'] + args['label_inference_attack'] = input_args['label_inference_attack'] + args['target_epochs'] = input_args['epochs'] + args['passive_bottom_lr'] = input_args['lr'] + args['active_bottom_lr'] = input_args['lr'] + args['active_top_lr'] = input_args['lr'] + args['target_batch_size'] = input_args['batch_size'] + args['passive_bottom_gamma'] = input_args['step_gamma'] + args['active_bottom_gamma'] = input_args['step_gamma'] + args['active_top_gamma'] = input_args['step_gamma'] + # args['active_top_trainable'] = input_args['top_model'] + # args['n_passive_party'] = input_args['n_party'] - 1 + args['aggregate'] = input_args['aggregate'] + args['cuda'] = input_args['cuda'] + args['file_time'] = input_args['file_time'] + + # default params + args['model_type'] = 'Resnet' # change VGG to Resnet + args['debug'] = False + args['n_passive_party'] = 1 + # args['aggregate'] = 'Concate' + args['active_top_trainable'] = True + if args['label_inference_attack'] == 'direct_attack': + args['active_top_trainable'] = False + args['trigger'] = 'pixel' + args['trigger_add'] = False + + if args['cuda']: + # mindspore.context.set_context(device_target="GPU") + mindspore.context.set_context(device_target="GPU", device_id=0) + # from mindspore import Profiler + # profiler = Profiler(output_path="profiler_data") + else: + mindspore.context.set_context(device_target="CPU") + + length_dict = {'cifar10':50000, 'cifar100':50000, 'cinic':180000, 'bhi':345910, 'criteo':100000} + epochs_dict = {'cifar10':5, 'cifar100':5, 'cinic':2, 'bhi':2, 'criteo':2} + + # the index of adv in all parties + if args['n_passive_party'] == 1: + args['adversary'] = 1 + elif args['n_passive_party'] == 3: + args['adversary'] = 2 + elif args['n_passive_party'] == 7: + args['adversary'] = 5 + else: + args['adversary'] = (args['n_passive_party'] - 1) // 2 + + logging.info('frame: {}, config: {}, attack: {}, backdoor: {}, label_inference_attack: {}'.format(input_args['framework'], input_args['config'], args['attack'], args['backdoor'], args['label_inference_attack'])) + if not args['attack']: + normal_train = Normal(args) + normal_train.define() + normal_train.run() + elif args['backdoor'] != 'no': + # if args['backdoor'] in ['lr_ba','villain']: + # return + # if args['label_inference_attack'] == 'g_r': + # args['model_type'] = 'Resnet' + + # if args['backdoor'] == 'lr_ba': + # return + if args['label_inference_attack'] in ['g_r', 'villain']: + args['model_type'] = 'Resnet' + + args['poison_rate'] = input_args['poison_rate'] + if args['target_train_size'] == -1: + temp = length_dict[args['dataset']] + else: + temp = args['target_train_size'] + args['backdoor_train_size'] = int(args['poison_rate'] * temp) + args['backdoor_epochs'] = epochs_dict[args['dataset']] + args['amplify_ratio'] = 2 + args['m_dimension'] = 10 + args['epsilon'] = 0.4 + # if args['label_inference_attack'] == 'g_r': + # args['amplify_ratio'] = 1 + if args['backdoor'] == 'villain': + args['trigger'] = 'feature' + args['trigger_add'] = True + else: + args['trigger'] = 'pixel' + args['trigger_add'] = False + + Backdoor_attack = Backdoor(args) + Backdoor_attack.define() + Backdoor_attack.run() + elif args['label_inference_attack'] != 'no': + # if args['label_inference_attack'] in ['passive_model_completion', 'active_model_completion']: + # return + if args['label_inference_attack'] == 'direct_attack': + args['model_type'] = 'Resnet' + Label_inference_attack = Label_inference(args) + Label_inference_attack.define() + Label_inference_attack.run() + + # profiler.analyse() diff --git a/examples/community/vfl_attacks/evaluate/config/bhi.yaml b/examples/community/vfl_attacks/evaluate/config/bhi.yaml new file mode 100644 index 0000000..57b78c4 --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/config/bhi.yaml @@ -0,0 +1,95 @@ +# global configuration +dataset: bhi +cuda: 1 +log: vfl_bhi +debug: false + +# data configuration +target_train_size: -1 +target_test_size: -1 +backdoor_train_size: 1000 +backdoor_test_size: 2000 +train_label_size: 70 +#target_train_size: 2000 +#target_test_size: 2000 +#backdoor_train_size: 500 +#backdoor_test_size: 200 +#train_label_size: 40 + + +# save configuration +save_model: 0 +save_data: 0 + +# load configuration +load_target: 0 +load_data: 0 +load_model: 0 +load_time: 0 + +# global model configuration +n_passive_party: 1 +target_batch_size: 64 +target_epochs: 5 +# passive party configuration +passive_bottom_model: resnet +passive_bottom_gamma: 0.1 +passive_bottom_wd: 0.0005 +passive_bottom_momentum: 0.9 +passive_bottom_lr: 0.05 +passive_bottom_stone: [4,6] +# active party configuration +# active bottom model configuration +active_bottom_model: resnet +active_bottom_gamma: 0.1 +active_bottom_wd: 0.0005 +active_bottom_momentum: 0.9 +active_bottom_lr: 0.05 +active_bottom_stone: [4,6] +# active top model configuration +active_top_trainable: 1 +active_top_model: fcn +active_top_gamma: 0.1 +active_top_wd: 0.0005 +active_top_momentum: 0.9 +active_top_lr: 0.05 +active_top_stone: [4,6] + +# backdoor attack global configuration +backdoor: lr_ba +backdoor_label: 0 +backdoor_label_size: 41840 + +# Gradient replacement configuration +g_r_amplify_ratio: 10 + +# LR-BA attack configuration +lr_ba_alpha: 0.75 +lr_ba_active: false +lr_ba_active_r_min: 1.0 +lr_ba_active_r_max: 5.0 +lr_ba_active_reset: 1.0 +lr_ba_top_lr: 0.002 +lr_ba_top_momentum: 0.9 +lr_ba_top_wd: 0.0001 +lr_ba_top_epochs: 25 +lr_ba_top_batch_size: 32 +lr_ba_top_train_iteration: 1024 +lr_ba_top_T: 0.8 +lr_ba_top_alpha: 0.75 +lr_ba_generate_lr: 0.1 +lr_ba_generate_epochs: 100 +lr_ba_ema_decay: 0.999 +lr_ba_finetune_lr: 0.01 +lr_ba_finetune_epochs: 100 + +lr_ba_top_margin: 0.7 +lr_ba_top_lambda_u: 1 +lr_ba_top_lambda_u_hinge: 0 +lr_ba_train_aug: false +lr_ba_top_temp_change: 1000000 +lr_ba_top_co: false +lr_ba_top_separate_mix: false +lr_ba_top_mix_layers_set: [7, 9, 12] +lr_ba_top_batch_size_u: 4 + diff --git a/examples/community/vfl_attacks/evaluate/config/cifar10.yaml b/examples/community/vfl_attacks/evaluate/config/cifar10.yaml new file mode 100644 index 0000000..4432c5b --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/config/cifar10.yaml @@ -0,0 +1,112 @@ +# global configuration +dataset: cifar10 +half: 16 +cuda: 1 +log: vfl_cifar10 +debug: False + +# trigger +trigger_add: True + +# data configuration +target_train_size: -1 +target_test_size: -1 +backdoor_train_size: 500 +backdoor_test_size: 2000 +train_label_size: 40 +#target_train_size: 2000 +#target_test_size: 2000 +#backdoor_train_size: 50 +#backdoor_test_size: 200 +#train_label_size: 40 + +# save configuration +save_model: 0 +save_data: 0 + +# load configuration +load_target: 0 +load_data: 0 +load_model: 0 +load_time: 0 + +# global model configuration +n_passive_party: 1 +target_batch_size: 32 +target_epochs: 100 +# passive party configuration +passive_bottom_model: resnet +passive_bottom_gamma: 0.1 +passive_bottom_wd: 0.0005 +passive_bottom_momentum: 0.9 +passive_bottom_lr: 0.1 +passive_bottom_stone: [50,85,100] +# active party configuration +# active bottom model configuration +active_bottom_model: resnet +active_bottom_gamma: 0.1 +active_bottom_wd: 0.0005 +active_bottom_momentum: 0.9 +active_bottom_lr: 0.1 +active_bottom_stone: [50,85,100] +# active top model configuration +active_top_trainable: 1 +active_top_model: fcn +active_top_gamma: 0.1 +active_top_wd: 0.0005 +active_top_momentum: 0.9 +active_top_lr: 0.1 +active_top_stone: [50,85,100] + +# defense configuration +noisy_gradients: false +noise_scale: 0.001 + +gradient_compression: false +gc_percent: 0.5 + +# backdoor attack global configuration +backdoor: villain +backdoor_label: 6 + +# configuration +trigger: feature +mal_optim: false + +# Gradient replacement configuration +g_r_amplify_ratio: 10 + +# LR-BA attack configuration +lr_ba_alpha: 0.75 +lr_ba_active: false +lr_ba_active_r_min: 1.0 +lr_ba_active_r_max: 5.0 +lr_ba_active_reset: 1.0 +# for the inference head +lr_ba_top_lr: 0.002 +lr_ba_top_momentum: 0.9 +lr_ba_top_wd: 0.0001 +#lr_ba_top_epochs: 25 +lr_ba_top_epochs: 2 +lr_ba_top_batch_size: 16 +lr_ba_top_train_iteration: 1024 +lr_ba_top_T: 0.8 +lr_ba_top_alpha: 0.75 +lr_ba_ema_decay: 0.999 +# for H* generation +lr_ba_generate_lr: 0.01 +lr_ba_generate_epochs: 100 +# for fine-tuning +lr_ba_finetune_lr: 0.01 +lr_ba_finetune_epochs: 100 + +lr_ba_top_margin: 0.7 +lr_ba_top_lambda_u: 1 +lr_ba_top_lambda_u_hinge: 0 +lr_ba_train_aug: false +lr_ba_top_temp_change: 1000000 +lr_ba_top_co: false +lr_ba_top_separate_mix: false +lr_ba_top_mix_layers_set: [7, 9, 12] +lr_ba_top_batch_size_u: 4 + diff --git a/examples/community/vfl_attacks/evaluate/config/cifar100.yaml b/examples/community/vfl_attacks/evaluate/config/cifar100.yaml new file mode 100644 index 0000000..d3fd646 --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/config/cifar100.yaml @@ -0,0 +1,107 @@ +# global configuration +dataset: cifar100 +half: 16 +cuda: 1 +log: vfl_cifar100 +debug: false + +# trigger +trigger_add: True +# data configuration +target_train_size: -1 +target_test_size: -1 +backdoor_train_size: 50 +backdoor_test_size: 2000 +train_label_size: 400 +#target_train_size: 2000 +#target_test_size: 2000 +#backdoor_train_size: 50 +#backdoor_test_size: 20 +#train_label_size: 400 + +# save configuration +save_model: 1 +save_data: 0 + +# load configuration +load_target: 0 +load_data: 0 +load_model: 0 +load_time: 0 + +# global model configuration +n_passive_party: 1 +target_batch_size: 32 +target_epochs: 100 +# passive party configuration +passive_bottom_model: resnet +passive_bottom_gamma: 0.1 +passive_bottom_wd: 0.0005 +passive_bottom_momentum: 0.9 +passive_bottom_lr: 0.1 +passive_bottom_stone: [50,85] +# active party configuration +# active bottom model configuration +active_bottom_model: resnet +active_bottom_gamma: 0.1 +active_bottom_wd: 0.0005 +active_bottom_momentum: 0.9 +active_bottom_lr: 0.1 +active_bottom_stone: [50,85] +# active top model configuration +active_top_trainable: 1 +active_top_model: fcn +active_top_gamma: 0.1 +active_top_wd: 0.0005 +active_top_momentum: 0.9 +active_top_lr: 0.1 +active_top_stone: [50,85] + +# defense configuration +noisy_gradients: false +noise_scale: 0.001 + +gradient_compression: false +gc_percent: 0.5 + +# backdoor attack global configuration +backdoor: villain +backdoor_label: 6 + +# configuration +trigger: feature +mal_optim: false + +# Gradient replacement configuration +g_r_amplify_ratio: 10 + +# LR-BA attack configuration +lr_ba_alpha: 0.75 +lr_ba_active: false +lr_ba_active_r_min: 1.0 +lr_ba_active_r_max: 5.0 +lr_ba_active_reset: 1.0 +lr_ba_top_lr: 0.002 +lr_ba_top_momentum: 0.9 +lr_ba_top_wd: 0.0001 +lr_ba_top_epochs: 25 +lr_ba_top_batch_size: 16 +lr_ba_top_train_iteration: 1024 +lr_ba_top_T: 0.8 +lr_ba_top_alpha: 0.75 +lr_ba_generate_lr: 0.005 +lr_ba_generate_epochs: 1024 +lr_ba_ema_decay: 0.999 +lr_ba_finetune_lr: 0.005 +lr_ba_finetune_epochs: 20 + +lr_ba_top_margin: 0.7 +lr_ba_top_lambda_u: 1 +lr_ba_top_lambda_u_hinge: 0 +lr_ba_train_aug: false +lr_ba_top_temp_change: 1000000 +lr_ba_top_co: false +lr_ba_top_separate_mix: false +lr_ba_top_mix_layers_set: [7, 9, 12] +lr_ba_top_batch_size_u: 4 + diff --git a/examples/community/vfl_attacks/evaluate/config/cinic.yaml b/examples/community/vfl_attacks/evaluate/config/cinic.yaml new file mode 100644 index 0000000..6770b24 --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/config/cinic.yaml @@ -0,0 +1,108 @@ +# global configuration +dataset: cinic +half: 16 +cuda: 1 +log: vfl_cinic +debug: false + + +# trigger +trigger_add: True +# data configuration +target_train_size: -1 +target_test_size: -1 +backdoor_train_size: 500 +backdoor_test_size: 2000 +train_label_size: 40 +#target_train_size: 128 +#target_test_size: 128 +#backdoor_train_size: 50 +#backdoor_test_size: 50 +#train_label_size: 40 + +# save configuration +save_model: 0 +save_data: 0 + +# load configuration +load_target: 0 +load_data: 0 +load_model: 0 +load_time: 0 + +# global model configuration +n_passive_party: 1 +target_batch_size: 32 +target_epochs: 100 +# passive party configuration +passive_bottom_model: resnet +passive_bottom_gamma: 0.1 +passive_bottom_wd: 0.0005 +passive_bottom_momentum: 0.9 +passive_bottom_lr: 0.1 +passive_bottom_stone: [50,85] +# active party configuration +# active bottom model configuration +active_bottom_model: resnet +active_bottom_gamma: 0.1 +active_bottom_wd: 0.0005 +active_bottom_momentum: 0.9 +active_bottom_lr: 0.1 +active_bottom_stone: [50,85] +# active top model configuration +active_top_trainable: 1 +active_top_model: fcn +active_top_gamma: 0.1 +active_top_wd: 0.0005 +active_top_momentum: 0.9 +active_top_lr: 0.1 +active_top_stone: [50,85] + +# defense configuration +noisy_gradients: false +noise_scale: 0.001 + +gradient_compression: false +gc_percent: 0.5 + +# backdoor attack global configuration +backdoor: lr_ba +backdoor_label: 0 + +# configuration +trigger: feature +mal_optim: false + +# Gradient replacement configuration +g_r_amplify_ratio: 10 + +# LR-BA attack configuration +lr_ba_alpha: 0.75 +lr_ba_active: false +lr_ba_active_r_min: 1.0 +lr_ba_active_r_max: 5.0 +lr_ba_active_reset: 1.0 +lr_ba_top_lr: 0.002 +lr_ba_top_momentum: 0.9 +lr_ba_top_wd: 0.0001 +lr_ba_top_epochs: 25 +lr_ba_top_batch_size: 16 +lr_ba_top_train_iteration: 1024 +lr_ba_top_T: 0.8 +lr_ba_top_alpha: 0.75 +lr_ba_generate_lr: 0.01 +lr_ba_generate_epochs: 100 +lr_ba_ema_decay: 0.999 +lr_ba_finetune_lr: 0.01 +lr_ba_finetune_epochs: 100 + +lr_ba_top_margin: 0.7 +lr_ba_top_lambda_u: 1 +lr_ba_top_lambda_u_hinge: 0 +lr_ba_train_aug: false +lr_ba_top_temp_change: 1000000 +lr_ba_top_co: false +lr_ba_top_separate_mix: false +lr_ba_top_mix_layers_set: [7, 9, 12] +lr_ba_top_batch_size_u: 4 + diff --git a/examples/community/vfl_attacks/evaluate/config/criteo.yaml b/examples/community/vfl_attacks/evaluate/config/criteo.yaml new file mode 100644 index 0000000..abcf5e1 --- /dev/null +++ b/examples/community/vfl_attacks/evaluate/config/criteo.yaml @@ -0,0 +1,113 @@ +# global configuration +dataset: criteo +half: 16 +cuda: 1 +log: vfl_criteo +debug: False + + +# trigger +trigger_add: True +# data configuration +target_train_size: -1 +target_test_size: -1 +backdoor_train_size: 500 +backdoor_test_size: 2000 +train_label_size: 100 +#target_train_size: 200 +#target_test_size: 200 +#backdoor_train_size: 50 +#backdoor_test_size: 200 +#train_label_size: 100 + +# save configuration +save_model: 0 +save_data: 0 + +# load configuration +load_target: 0 +load_data: 0 +load_model: 0 +load_time: 0 + +# global model configuration +n_passive_party: 1 +target_batch_size: 32 +target_epochs: 100 +# passive party configuration +passive_bottom_model: resnet +passive_bottom_gamma: 0.1 +passive_bottom_wd: 0.0005 +passive_bottom_momentum: 0.9 +passive_bottom_lr: 0.1 +passive_bottom_stone: [50,85] +# active party configuration +# active bottom model configuration +active_bottom_model: resnet +active_bottom_gamma: 0.1 +active_bottom_wd: 0.0005 +active_bottom_momentum: 0.9 +active_bottom_lr: 0.1 +active_bottom_stone: [50,85] +# active top model configuration +active_top_trainable: 1 +active_top_model: fcn +active_top_gamma: 0.1 +active_top_wd: 0.0005 +active_top_momentum: 0.9 +active_top_lr: 0.1 +active_top_stone: [50,85] + +# defense configuration +noisy_gradients: false +noise_scale: 0.001 + +gradient_compression: false +gc_percent: 0.5 + +# backdoor attack global configuration +backdoor: villain +backdoor_label: 1 +#backdoor_train_size: 500 +#backdoor_test_size: 2000 +#train_label_size: 50 + +# configuration +trigger: feature +mal_optim: false + +# Gradient replacement configuration +g_r_amplify_ratio: 10 + +# LR-BA attack configuration +lr_ba_alpha: 0.75 +lr_ba_active: false +lr_ba_active_r_min: 1.0 +lr_ba_active_r_max: 5.0 +lr_ba_active_reset: 1.0 +# for the inference head +lr_ba_top_lr: 0.002 +lr_ba_top_momentum: 0.9 +lr_ba_top_wd: 0.0001 +lr_ba_top_epochs: 25 +lr_ba_top_batch_size: 16 +lr_ba_top_train_iteration: 1024 +lr_ba_top_T: 0.8 +lr_ba_top_alpha: 0.75 +lr_ba_ema_decay: 0.999 +# for H* generation +lr_ba_generate_lr: 0.01 +lr_ba_generate_epochs: 100 +# for fine-tuning +lr_ba_finetune_lr: 0.01 +lr_ba_finetune_epochs: 100 + +lr_ba_top_margin: 0.7 +lr_ba_top_lambda_u: 1 +lr_ba_top_lambda_u_hinge: 0 +lr_ba_train_aug: false +lr_ba_top_temp_change: 1000000 +lr_ba_top_co: false +lr_ba_top_separate_mix: false +lr_ba_top_mix_layers_set: [7, 9, 12] +lr_ba_top_batch_size_u: 4 \ No newline at end of file diff --git a/examples/community/vfl_attacks/example.py b/examples/community/vfl_attacks/example.py new file mode 100644 index 0000000..d5335eb --- /dev/null +++ b/examples/community/vfl_attacks/example.py @@ -0,0 +1,62 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import argparse +import ast +import constant +import mindspore as ms +constant.init() + +def user_initial_selection(): + args = {} + parser = argparse.ArgumentParser() + parser.add_argument('--framework', type=str, default='pytorch') + parser.add_argument('--cuda', type=int, default=1) + parser.add_argument('--dataset', type=str, default='cifar10') + parser.add_argument('--epochs', type=int, default=100) + parser.add_argument('--batch-size', type=int, default=32) + parser.add_argument('--lr', type=float, default=0.1) + parser.add_argument('--step-gamma', type=float, default=0.1) + parser.add_argument('--attack', type=ast.literal_eval, default=False) + parser.add_argument('--backdoor', type=str, default='no', choices=['no', 'lr_ba', 'g_r', 'villain']) + parser.add_argument('--label-inference-attack', type=str, default='no', + choices=['no', 'passive_model_completion', 'active_model_completion', 'direct_attack']) + + temp = parser.parse_args() + args['config'] = 'evaluate/config/' + temp.dataset + '.yaml' + args['framework'] = temp.framework + args['cuda'] = temp.cuda + args['batch_size'] = temp.batch_size + args['epochs'] = temp.epochs + args['lr'] = temp.lr + args['step_gamma'] = temp.step_gamma + args['attack'] = temp.attack + args['backdoor'] = temp.backdoor + args['label_inference_attack'] = temp.label_inference_attack + args['n_party'] = 2 + args['top_model'] = True + args['aggregate'] = 'Concate' + args['poison_rate'] = 0.01 + if temp.dataset == 'cifar100': + args['poison_rate'] = 0.001 + return args + +def get_cfg(input_args): + ms.set_context(env_config_path='configure.json') + from evaluate.args_line import run_mindspore + run_mindspore(input_args) + +if __name__ == '__main__': + input_args = user_initial_selection() + get_cfg(input_args) diff --git a/examples/community/vfl_attacks/methods/direct_attack/direct_attack_passive_party.py b/examples/community/vfl_attacks/methods/direct_attack/direct_attack_passive_party.py new file mode 100644 index 0000000..5d9bd4e --- /dev/null +++ b/examples/community/vfl_attacks/methods/direct_attack/direct_attack_passive_party.py @@ -0,0 +1,64 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" +Malicious passive party for direct label inference attack +""" +from MindsporeCode.party.passive_party import VFLPassiveModel +from MindsporeCode.common.image_report import append_int_to_file + +class Direct_Attack_PassiveModel(VFLPassiveModel): + def __init__(self, bottom_model, id=None, args=None): + VFLPassiveModel.__init__(self, bottom_model, id, args) + self.batch_label = None + self.inferred_correct = 0 + self.inferred_wrong = 0 + + def send_components(self): + """ + send latent representation to active party + """ + result = self._forward_computation(self.X) + return result + + def receive_gradients(self, gradients): + """ + receive gradients from active party and update parameters of local bottom model + + :param gradients: gradients from active party + """ + # direct label inference attack + for sample_id in range(len(gradients)): + grad_per_sample = gradients[sample_id] + for logit_id in range(len(grad_per_sample)): + # true label + if grad_per_sample[logit_id] < 0: + inferred_label = logit_id + if inferred_label == self.batch_label[sample_id]: + self.inferred_correct += 1 + else: + self.inferred_wrong += 1 + break + # visualization image for web + if self.epoch == 0 and sample_id == 0: + output_txt_path = '../temp_output/' + self.args['file_time'] + '-image-report.txt' + with open(output_txt_path, "r") as file: + content = file.read() + if 'clean' not in content: + y_true, y_infer = self.batch_label[sample_id].item(), inferred_label + append_int_to_file(output_txt_path, y_true, y_infer) + + self.common_grad = gradients + self._fit(self.X, self.y) + diff --git a/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py b/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py new file mode 100644 index 0000000..aa1b885 --- /dev/null +++ b/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py @@ -0,0 +1,132 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging +import numpy as np +import os +from MindsporeCode.vfl.vfl import VFL +from mindspore import ops + + +class Direct_VFL(VFL): + """ + Direct attack VFL system + """ + def train(self): + ''' + train the vfl model + :return: + ''' + # load VFL + if self.args['load_model']: + raise ValueError('do not support load model for direct label inference attack') + # train VFL + else: + for ep in range(self.args['target_epochs']): + loss_list = [] + self.set_train() + self.set_current_epoch(ep) + self.party_dict[self.adversary].inferred_correct = 0 + self.party_dict[self.adversary].inferred_wrong = 0 + + for batch_idx, (X, Y_batch, old_imgb, indices) in enumerate(self.train_loader): + party_X_train_batch_dict = dict() + if self.args['n_passive_party'] < 2: + # X = ops.transpose(X, (1, 0, 2, 3, 4)) + # 0627修改适配criteo + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + active_X_batch, Xb_batch = X + party_X_train_batch_dict[0] = Xb_batch + else: + active_X_batch = X[:, 0:1].squeeze(1) #### 第一个特征给active party + for i in range(self.args['n_passive_party']): + party_X_train_batch_dict[i] = X[:, i+1:i+2].squeeze(1) #### + + # for evaluation + self.party_dict[self.adversary].batch_label = Y_batch + + # visualization image for web + if ep == 0 and batch_idx == 0: + output_txt_path = '../temp_output/' + self.args['file_time'] + '-image-report.txt' + if not os.path.exists(output_txt_path): + from PIL import Image + import base64 + from io import BytesIO + image = self.train_loader.children[0].source.data[indices[0]] + + print("dataset: ", self.args['dataset']) + print("image shape: ", image.shape) + # 0523临时修改 + if self.args['dataset'] != 'bhi' and self.args['dataset'] != 'criteo': + image = image.transpose(1, 2, 0) # 32,32,3 + # print("image shape(alt): ", image.shape) + + # image = Image.fromarray(image) + if self.args['dataset'] != 'criteo': + if len(image.shape) == 4: + image = Image.fromarray(image[0]) + else: + image = Image.fromarray(image) + buffered = BytesIO() + + # 0627修改适配criteo + # image.save(buffered, format="PNG") + # image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') + # with open(output_txt_path, "w") as txt_file: + # txt_file.write(image_str) + # criteo + if self.args["dataset"] != "criteo": + image.save(buffered, format="PNG") + image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') + with open(output_txt_path, "w") as txt_file: + txt_file.write(image_str) + else: + with open(output_txt_path, "w") as txt_file: + txt_file.write('No image data available for criteo dataset.') + + loss, grad_list = self.fit(active_X_batch, Y_batch, party_X_train_batch_dict, indices) + loss_list.append(loss) + self.scheduler_step() + + # not evaluate main-task performance if evaluating execution time + if not self.args['time']: + # compute main-task accuracy + ave_loss = np.sum(loss_list)/len(self.train_loader.children[0]) + self.set_state('train') + self.train_acc = self.predict(self.train_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + self.test_acc = self.predict(self.test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.inference_acc = self.party_dict[self.adversary].inferred_correct / \ + (self.party_dict[self.adversary].inferred_correct + self.party_dict[self.adversary].inferred_wrong) + logging.info( + "--- epoch: {}, train loss: {}, train_acc: {}%, test acc: {}%, direct label inference accuracy: {}".format( + ep, ave_loss, self.train_acc * 100, self.test_acc * 100, self.inference_acc)) + self.record_train_acc.append(self.train_acc) + self.record_test_acc.append(self.test_acc) + self.record_loss.append(ave_loss) + self.record_attack_metric.append(self.inference_acc) + # draw fig and save it to output_logs + # file_path = OUTPUT_path + # plot_and_save(self.record_loss, 'loss', file_path + 'A_'+str(ep+1)) + # plot_and_save(self.record_test_acc, 'test_acc', file_path + 'B_'+str(ep+1)) + # if len(self.record_attack_metric) > 0: + # plot_and_save(self.record_attack_metric, 'attack_metric', file_path + 'C_'+str(ep+1)) diff --git a/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py b/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py new file mode 100644 index 0000000..5891040 --- /dev/null +++ b/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py @@ -0,0 +1,85 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" +Malicious passive party for gradient-replacement backdoor +""" +import random +import mindspore.ops as ops +from MindsporeCode.party.passive_party import VFLPassiveModel + + + +class GRPassiveModel(VFLPassiveModel): + + def __init__(self, bottom_model, amplify_ratio=1, id=None, args=None): + # VFLPassiveModel.__init__(self, bottom_model, id=id, args=args) + super(GRPassiveModel, self).__init__(bottom_model, id=id, args=args) + self.backdoor_indices = None + self.target_grad = None + self.target_indices = None + self.amplify_ratio = amplify_ratio + self.components = None + self.is_debug = False + self.pair_set = dict() + self.target_gradients = dict() + self.backdoor_X = dict() + # super().__init__(bottom_model) + + def set_epoch(self, epoch): + self.epoch = epoch + + def set_backdoor_indices(self, target_indices, backdoor_indices, backdoor_X): + self.target_indices = target_indices + self.backdoor_indices = backdoor_indices + self.backdoor_X = backdoor_X + + def receive_gradients(self, gradients): + gradients = gradients.copy() ##### + # get the target gradient of samples labeled backdoor class + for index, i in enumerate(self.indices): + i = i.item() + if i in self.target_indices: + self.target_gradients[i] = gradients[index] ######## + + # replace the gradient of backdoor samples with the target gradient + for index, j in enumerate(self.indices): + j = j.item() + if j in self.backdoor_indices: + for i, v in self.pair_set.items(): + if v == j: + target_grad = self.target_gradients[i] + if target_grad is not None: + gradients[index] = self.amplify_ratio * target_grad + break + + self.common_grad = gradients + self._fit(self.X, self.components) + + def send_components(self): + result = self._forward_computation(self.X) + self.components = result + send_result = result.copy() + # send random latent representation for backdoor samples in VFL with model splitting + for index, i in enumerate(self.indices): + i = i.item() + if i in self.target_indices: + if i not in self.pair_set.keys(): + j = self.backdoor_indices[random.randint(0, len(self.backdoor_indices)-1)] + self.pair_set[i] = j + else: + j = self.pair_set[i] + send_result[index] = self.bottom_model.forward(ops.unsqueeze(self.backdoor_X[j], 0))[0] + + return send_result diff --git a/examples/community/vfl_attacks/methods/villain/villain_passive_party.py b/examples/community/vfl_attacks/methods/villain/villain_passive_party.py new file mode 100644 index 0000000..339ac20 --- /dev/null +++ b/examples/community/vfl_attacks/methods/villain/villain_passive_party.py @@ -0,0 +1,127 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" +Malicious passive party for Villain backdoor +""" +import random +import mindspore as ms +from mindspore import ops, Parameter +from MindsporeCode.party.passive_party import VFLPassiveModel +from MindsporeCode.common.constants import CHECKPOINT_PATH +import os + + + +class Villain_PassiveModel(VFLPassiveModel): + def __init__(self, bottom_model, amplify_ratio=1, args=None, id=None): + super(Villain_PassiveModel, self).__init__(bottom_model, id=id, args=args) + self.target_grad = None + self.target_indices = None + self.amplify_ratio = amplify_ratio + self.components = None + self.is_debug = False + self.pair_set = dict() + self.target_gradients = dict() + self.backdoor_X = dict() + self.args = args + + # super().__init__(bottom_model, args=args) + + self.attack = False + self.feature_pattern = None + self.mask = None + self.drop_out = True + self.drop_out_rate = 0.75 + self.max_norms = None + self.shifting = True + self.up_bound = 1.2 + self.down_bound = 0.6 + self.adversary = self.args['adversary'] - 1 + + def save_data(self, name=None, pattern=None): + """ + save the pixel pattern or feature pattern + """ + path = '{}/{}/{}'.format(CHECKPOINT_PATH, self.args['dataset'], self.args['file_time']) + if not os.path.exists(path): + os.makedirs(path) + name = self.args['trigger'] + '_pattern.ckpt' # 昇思通常使用.ckpt作为检查点文件的扩展名 + filepath = '{}/{}'.format(path, name) + if self.args['trigger'] == 'feature': + ms.save_checkpoint(self.feature_pattern, filepath) + else: + ms.save_checkpoint(pattern, filepath) + + def load_data(self, name=None): + """ + save the pixel pattern or feature pattern + """ + path = '{}/{}/{}'.format(CHECKPOINT_PATH, self.args['dataset'], self.args['load_time']) + name = self.args['trigger'] + '_pattern.ckpt' + filepath = '{}/{}'.format(path, name) + if os.path.isfile(filepath): + pattern = ms.load_checkpoint(filepath) + if self.args['trigger'] == 'feature': + self.feature_pattern = pattern + return pattern + else: + raise ValueError("load data error, wrong filepath") + + def set_epoch(self, epoch): + self.epoch = epoch + + def set_backdoor_indices(self, target_indices): + self.target_indices = target_indices + + def set_batch(self, X, indices): + self.X = X + self.indices = indices + # self.X.requires_grad = True + + def receive_gradients(self, gradients): + gradients = gradients.copy() + gradients = self.amplify_ratio * gradients + self.common_grad = gradients + # backwards + self._fit(self.X, self.components) + return + + def send_components(self): + result = self._forward_computation(self.X) + self.components = result + send_result = result.copy() + + for index, i in enumerate(self.indices): + if self.target_indices is not None and i.item() in self.target_indices: + if self.feature_pattern is not None: + self.mask = ops.full_like(send_result[index], 1) + if self.drop_out: + self.mask = ops.dropout(self.mask, p=self.drop_out_rate, training=True) + self.mask = self.mask * (1 - self.drop_out_rate) + if self.shifting: + num = random.random() * (self.up_bound - self.down_bound) + self.down_bound + send_result[index] += self.feature_pattern * self.mask * num + else: + send_result[index] += self.feature_pattern * self.mask + return send_result + + def predict(self, X, is_attack=False): + result = self._forward_computation(X) + send_results = result + if is_attack and self.feature_pattern is not None: + for i in range(len(send_results)): + send_results[i] += self.feature_pattern + + return send_results diff --git a/examples/community/vfl_attacks/methods/villain/villain_vfl.py b/examples/community/vfl_attacks/methods/villain/villain_vfl.py new file mode 100644 index 0000000..66bc893 --- /dev/null +++ b/examples/community/vfl_attacks/methods/villain/villain_vfl.py @@ -0,0 +1,184 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging +import numpy as np +import mindspore as ms +from MindsporeCode.vfl.vfl import VFL +from mindspore import ops + + +class VILLAIN_VFL(VFL): + """ + VILLAIN_VFL system + """ + + def train(self): + ''' + train the vfl model + :return: + ''' + # load VFL + if self.args['load_model']: + self.load() + self.set_state('test') + self.train_acc = self.predict(self.train_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + self.test_acc = self.predict(self.test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + if self.backdoor_test_loader is not None: + self.set_state('attack') + self.backdoor_acc = self.predict(self.backdoor_test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + print('train_acc: {}, test_acc: {}, backdoor_acc:{}'.format(self.train_acc, self.test_acc, self.backdoor_acc)) + else: + print('train_acc: {}, test_acc: {}'.format(self.train_acc, self.test_acc)) + # train VFL + else: + for ep in range(self.args['target_epochs']): + loss_list = [] + self.set_train() + self.set_current_epoch(ep) + + for batch_idx, (X, Y_batch, old_imgb, indices) in enumerate(self.train_loader): + # X: B,K,3,32,16 tensor + # import traceback + # print("DEBUG: backdoor_label = ", self.args['backdoor_label']) + # traceback.print_stack() + + party_X_train_batch_dict = dict() + if self.args['n_passive_party'] < 2: + # X = ops.transpose(X, (1, 0, 2, 3, 4)) + # 0627修改处理criteo + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + + active_X_batch, Xb_batch = X + party_X_train_batch_dict[0] = Xb_batch + else: + active_X_batch = X[:, 0:1].squeeze(1) + for i in range(self.args['n_passive_party']): + party_X_train_batch_dict[i] = X[:, i + 1:i + 2].squeeze(1) + loss, grad_list = self.fit(active_X_batch, Y_batch, party_X_train_batch_dict, indices) + loss_list.append(loss) + self.scheduler_step() + + # not evaluate main-task performance if evaluating execution time + if not self.args['time']: + # compute main-task accuracy + ave_loss = np.sum(loss_list) / len(self.train_loader.children[0]) + self.set_state('train') + self.train_acc = self.predict(self.train_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + self.test_acc = self.predict(self.test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.record_train_acc.append(self.train_acc) + self.record_test_acc.append(self.test_acc) + self.record_loss.append(ave_loss) + # compute backdoor task accuracy + if self.backdoor_test_loader is not None: + self.set_state('attack') + self.backdoor_acc = self.predict(self.backdoor_test_loader, + num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + logging.info( + "--- epoch: {}, train loss: {}, train_acc: {}%, test acc: {}%, backdoor acc: {}%".format(ep, + ave_loss, + self.train_acc * 100, + self.test_acc * 100, + self.backdoor_acc * 100)) + self.record_attack_metric.append(self.backdoor_acc) + + # set epsilon by the var + if ep == self.args['backdoor_epochs']: + self.set_eval() + self.target_sample = [] + features = [] + for batch_idx, (X, Y_batch, old_imgb, indices) in enumerate(self.train_loader): + # X: B,K,3,32,16 tensor + target_indices = [] + for i in range(len(indices)): + if Y_batch[i] == self.args['backdoor_label']: + target_indices.append(i) + if self.args['n_passive_party'] < 2: + # X = ops.transpose(X, (1, 0, 2, 3, 4)) + # 0627修改处理criteo + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + + _, Xb_batch = X + else: + Xb_batch = X[:, self.adversary + 1:self.adversary + 2].squeeze(1) + H_features = self.party_dict[self.adversary].bottom_model.forward(Xb_batch) # 64,10 + for i in target_indices: + self.target_sample.append(H_features[i]) + if len(features) == 0: + features = H_features + else: + features = ms.ops.cat((features, H_features), axis=0) # 10000,10 + + self.target_sample = ms.ops.stack(self.target_sample) # [500,10] + + norms = 0 + for target_feature in self.target_sample: + norms += ms.ops.norm(target_feature, 2) + # 0621 epoch>5报错 + # std_norms = ms.ops.std(features, axis=0) + std_norms = self.safe_std(features, axis=0) + stride_pattern = [0] * len(features[0]) + temp = [] + # print(self.args['m_dimension'] // 2) + for i in range(self.args['m_dimension'] // 2): + if i % 2 == 0: + temp = temp + [-1, -1] + else: + temp = temp + [1, 1] + if len(temp) < self.args['m_dimension']: + h = temp[-1] * -1 + temp.append(h) + sorted, indices = ms.ops.sort(std_norms, descending=True) + # print(stride_pattern, indices, temp) + for i in range(self.args['m_dimension']): + stride_pattern[indices[i]] = temp[i] + stride_pattern = ms.tensor(stride_pattern) + self.party_dict[self.adversary].feature_pattern = std_norms * self.args['epsilon'] * stride_pattern + + def write(self): + # save VFL + if self.args['save_model']: + self.party_dict[self.adversary].save_data() + self.save() + + @staticmethod + def safe_std(x, axis=0, keepdims=False): + mean = ops.mean(x, axis=axis, keep_dims=True) + var = ops.mean((x - mean)**2, axis=axis, keep_dims=keepdims) + std = ops.sqrt(var) + return std diff --git a/examples/community/vfl_attacks/party/active_party.py b/examples/community/vfl_attacks/party/active_party.py new file mode 100644 index 0000000..036033f --- /dev/null +++ b/examples/community/vfl_attacks/party/active_party.py @@ -0,0 +1,287 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import mindspore.nn as nn +import mindspore +import mindspore.ops as ops + + +class VFLActiveModel(object): + """ + VFL active party + """ + def __init__(self, bottom_model, args, top_model=None): + super(VFLActiveModel, self).__init__() + self.bottom_model = bottom_model + self.is_debug = False + + self.classifier_criterion = nn.CrossEntropyLoss() ### + self.parties_grad_component_list = [] # latent representations from passive parties + self.X = None + self.y = None + self.bottom_y = None # latent representation from local bottom model + self.top_grads = None # gradients of local bottom model + self.parties_grad_list = [] # gradients for passive parties + self.epoch = None # current train epoch + self.indices = None # indices of current train samples + + self.top_model = top_model + self.top_trainable = True if self.top_model is not None else False + + self.args = args.copy() + + if self.args['cuda']: + mindspore.set_context(device_target="GPU") #### + + self.attack_indices = [] + self.grad_op = ops.GradOperation(get_by_list=True, sens_param=True) + self.bottom_gradient_function = self.grad_op(self.bottom_model, self.bottom_model.model.trainable_params()) + if self.top_trainable: + self.top_grad_op = ops.value_and_grad(self.forward_fn, grad_position=0, weights=self.top_model.model.trainable_params()) + else: + self.top_grad_op = ops.value_and_grad(self.forward_fn, grad_position=0, weights=None) + + def set_indices(self, indices): + self.indices = indices + + def set_epoch(self, epoch): + self.epoch = epoch + + def set_batch(self, X, y): + self.X = X + self.y = y + + def _fit(self, X, y): + """ + compute gradients, and update local bottom model and top model + + :param X: features of active party + :param y: labels + """ + # get local latent representation + self.bottom_y = self.bottom_model.forward(X) #### forward + self.K_U = self.bottom_y ####### + + # compute gradients based on labels, including gradients for passive parties + self._compute_common_gradient_and_loss(y) + + # update parameters of local bottom model and top model + self._update_models(X, y) + + def predict(self, X, component_list, type): + """ + get the final prediction + + :param X: feature of active party + :param component_list: latent representations from passive parties + :return: prediction label + """ + + # get local latent representation + U = self.bottom_model.forward(X) #### forward + + + # sum up latent representation in VFL without model splitting + if not self.top_trainable: + for comp in component_list: + U = U + comp + # use top model to predict in VFL with model splitting + else: + if self.args['aggregate'] == 'Concate': + temp = ops.cat([U] + component_list, -1) ### + elif self.args['aggregate'] == 'Add': + temp = U + for comp in component_list: + temp = temp + comp + elif self.args['aggregate'] == 'Mean': + temp = U + for comp in component_list: + temp = temp + comp + temp = temp / (len(component_list)+1) + U = self.top_model.forward(temp) + + result = ops.softmax(U, axis=1) ### + + return result + + def receive_components(self, component_list): + """ + receive latent representations from passive parties + + :param component_list: latent representations from passive parties + """ + for party_component in component_list: + self.parties_grad_component_list.append(party_component) ##### + + def fit(self): + """ + backward + """ + self.parties_grad_list = [] + self._fit(self.X, self.y) + self.parties_grad_component_list = [] + + def forward_fn(self, top_input, y): ### 新加 + """ + provide forward computation function for value_and_grad() + + :param y: label + """ + if not self.top_trainable: + U = top_input + else: + U = self.top_model.forward(top_input) + + class_loss = self.classifier_criterion(U, y) + + return class_loss + + def _compute_common_gradient_and_loss(self, y): + """ + compute loss and gradients, including gradients for passive parties + + :param y: label + """ + # compute prediction + U = self.K_U + + grad_comp_list = [self.K_U] + self.parties_grad_component_list + if not self.top_trainable: + temp = U + for grad_comp in self.parties_grad_component_list: + temp = temp + grad_comp + else: + if self.args['aggregate'] == 'Concate': + temp = ops.cat(grad_comp_list, -1) ### + elif self.args['aggregate'] == 'Add': + temp = grad_comp_list[0] + for comp in grad_comp_list[1:]: + temp = temp + comp + elif self.args['aggregate'] == 'Mean': + temp = grad_comp_list[0] + for comp in grad_comp_list[1:]: + temp = temp + comp + temp = temp / len(grad_comp_list) + + top_input = temp + # compute gradients + if self.top_trainable: + class_loss, (top_input_grad, para_grad_list) = self.top_grad_op(top_input, y) + self.para_grad_list = para_grad_list + grad_list = [] + if self.args['aggregate'] == 'Concate': + length = self.bottom_model.output_dim + for i in range(len(self.parties_grad_component_list) + 1): + grad_list.append(top_input_grad[:, i*length:(i+1)*length]) + + else: + if self.args['aggregate'] == 'Mean': + for i in range(len(self.parties_grad_component_list) + 1): + grad_list.append(top_input_grad / (len(self.parties_grad_component_list) + 1)) + else: + for i in range(len(self.parties_grad_component_list) + 1): + grad_list.append(top_input_grad) + else: + class_loss, top_input_grad = self.top_grad_op(top_input,y) + grad_list = [] + for i in range(len(self.parties_grad_component_list) + 1): + grad_list.append(top_input_grad) + + # save gradients of local bottom model + self.top_grads = grad_list[0] + # save gradients for passive parties + for index in range(0, len(self.parties_grad_component_list)): + parties_grad = grad_list[index+1] + self.parties_grad_list.append(parties_grad) + + self.loss = class_loss.item()*self.K_U.shape[0] + + def send_gradients(self): + """ + send gradients to passive parties + """ + return self.parties_grad_list + + def _update_models(self, X, y): + """ + update parameters of local bottom model and top model + + :param X: features of active party + :param y: invalid + """ + # update parameters of top model + if self.top_trainable: + self.top_model.backward_(self.para_grad_list) + # self.bottom_model.backward(X, self.bottom_y, self.top_grads) + self.bottom_backward(X, self.bottom_y, self.top_grads) + + def bottom_backward(self, x, y, grad_wrt_output): + bottom_parma_grad = self.bottom_gradient_function(x, grad_wrt_output) + self.bottom_model.backward_(bottom_parma_grad) + + + def get_loss(self): + return self.loss + + def save(self): + """ + save model to local file + """ + if self.top_trainable: + self.top_model.save(time=self.args['file_time']) + self.bottom_model.save(time=self.args['file_time']) + + def load(self): + """ + load model from local file + """ + if self.top_trainable: + self.top_model.load(time=self.args['load_time']) + self.bottom_model.load(time=self.args['load_time']) + + def set_train(self): + """ + set train mode + + """ + if self.top_trainable: + self.top_model.set_train(True) ### + self.bottom_model.set_train(True) ### + + def set_eval(self): + """ + set eval mode + """ + if self.top_trainable: + self.top_model.set_train(False) ### + self.bottom_model.set_train(False) ### + + def scheduler_step(self): + """ + adjust learning rate during training + """ + if self.top_trainable and self.top_model.scheduler is not None: + self.top_model.scheduler.step() ### + if self.bottom_model.scheduler is not None: + self.bottom_model.scheduler.step() #### + + def set_args(self, args): + self.args = args + + def zero_grad(self): + """ + clear gradients + """ + pass + diff --git a/examples/community/vfl_attacks/party/passive_party.py b/examples/community/vfl_attacks/party/passive_party.py new file mode 100644 index 0000000..5862b20 --- /dev/null +++ b/examples/community/vfl_attacks/party/passive_party.py @@ -0,0 +1,132 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +from mindspore import Tensor, ops, nn, Parameter + +class VFLPassiveModel(object): + """ + VFL passive party + """ + def __init__(self, bottom_model, id=None, args=None): + super(VFLPassiveModel, self).__init__() + self.bottom_model = bottom_model + self.is_debug = False + self.common_grad = None # gradients + self.X = None + self.indices = None + self.epoch = None + self.y = None + self.id = id # id of passive party + self.args = args + self.grad_op = ops.GradOperation(get_by_list=True, sens_param=True) + self.bottom_gradient_function = self.grad_op(self.bottom_model, self.bottom_model.model.trainable_params()) + + def set_epoch(self, epoch): + self.epoch = epoch + + def set_batch(self, X, indices): + self.X = X + self.indices = indices + + def _forward_computation(self, X, model=None): + """ + forward + + :param X: features of passive party + :param model: invalid + :return: latent representation of passive party + """ + if model is None: + A_U = self.bottom_model.forward(X) + else: + A_U = model.forward(X) + self.y = A_U + return A_U + + def _fit(self, X, y): + """ + backward + + :param X: features of passive party + :param y: latent representation of passive party + """ + self.bottom_backward(X, y, self.common_grad, self.epoch) + return + + def bottom_backward(self, x, y, grad_wrt_output, epoch): + bottom_parma_grad = self.bottom_gradient_function(x, grad_wrt_output) + self.bottom_model.backward_(bottom_parma_grad) + + def receive_gradients(self, gradients): + """ + receive gradients from active party and update parameters of local bottom model + + :param gradients: gradients from active party + """ + self.common_grad = gradients + self._fit(self.X, self.y) + + def send_components(self): + """ + send latent representation to active party + """ + result = self._forward_computation(self.X) + return result + + def predict(self, X, is_attack=False): + return self._forward_computation(X) + + def save(self): + """ + save model to local file + """ + self.bottom_model.save(id=self.id, time=self.args['file_time']) + + def load(self, load_attack=False): + """ + load model from local file + + :param load_attack: invalid + """ + if load_attack: + self.bottom_model.load(name='attack', time=self.args['load_time']) + else: + self.bottom_model.load(id=self.id, time=self.args['load_time']) + + def set_train(self): + """ + set train mode + """ + self.bottom_model.set_train(True) ##### + + def set_eval(self): + """ + set eval mode + """ + self.bottom_model.set_train(False) ####### + + def scheduler_step(self): + """ + adjust learning rate during training + """ + if self.bottom_model.scheduler is not None: + self.bottom_model.scheduler.step() ###### + + def zero_grad(self): + """ + clear gradients + """ + pass + diff --git a/examples/community/vfl_attacks/vfl/init_vfl.py b/examples/community/vfl_attacks/vfl/init_vfl.py new file mode 100644 index 0000000..49349e8 --- /dev/null +++ b/examples/community/vfl_attacks/vfl/init_vfl.py @@ -0,0 +1,104 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +from model.init_passive_model import init_bottom_model +from MindsporeCode.model.init_active_model import init_top_model +from MindsporeCode.party.active_party import VFLActiveModel +from MindsporeCode.party.passive_party import VFLPassiveModel +from MindsporeCode.datasets.base_dataset import get_dataloader +from MindsporeCode.methods.villain.villain_passive_party import Villain_PassiveModel +from MindsporeCode.methods.g_r.g_r_passive_party import GRPassiveModel +from MindsporeCode.methods.direct_attack.direct_attack_passive_party import Direct_Attack_PassiveModel +import logging +from mindspore import ops + +class Init_vfl(object): + def __init__(self, args): + super(Init_vfl, self).__init__() + self.args = args + def load_data(self): + logging.info("################################ Prepare Data ############################") + if not self.args['attack']: + self.train_loader, self.test_loader, self.backdoor_test_loader = get_dataloader(self.args)[:3] + elif self.args['backdoor'] == 'lr_ba': + self.train_loader, self.test_loader, self.backdoor_test_loader, self.backdoor_train_loader, _, self.backdoor_indices, _, self.labeled_loader, self.unlabeled_loader = get_dataloader(self.args)[0:9] + elif self.args['backdoor'] == 'villain': + self.train_loader, self.test_loader, self.backdoor_test_loader, _, _, _, \ + self.backdoor_target_indices, _, _, self.villain_train_dl = get_dataloader(self.args) + elif self.args['backdoor'] == 'g_r': + self.train_loader, self.test_loader, self.backdoor_test_loader, _, self.g_r_train_dl, self.backdoor_indices, self.backdoor_target_indices = get_dataloader(self.args)[0:7] + elif self.args['label_inference_attack']: + self.train_loader, self.test_loader, _, _, _, _, _, self.labeled_loader, self.unlabeled_loader = get_dataloader(self.args)[0:9] + else: + self.train_loader, self.test_loader, self.backdoor_test_loader = get_dataloader(self.args)[:3] + + + def get_vfl(self): + """ + generate VFL system, set the parties + :param args: configuration + :param backdoor_indices: indices of backdoor samples in normal train dataset, used by gradient-replacement + :param backdoor_target_indices: indices of samples labeled backdoor class in normal train dataset, used by gradient-replacement + :return: VFL system + """ + # build bottom model for active party + active_bottom_model = init_bottom_model('active', self.args) + + # build bottom model for passive parties + party_model_list = list() + for i in range(0, self.args['n_passive_party']): + passive_party_model = init_bottom_model('passive', self.args) + party_model_list.append(passive_party_model) + + # build top model for active party + active_top_model = None + if self.args['active_top_trainable']: + active_top_model = init_top_model(self.args) + + # generate active party + active_party = VFLActiveModel(bottom_model=active_bottom_model, + args=self.args, + top_model=active_top_model) + + # generate passive parties + self.party_list = [active_party] + for i, model in enumerate(party_model_list): + if self.args['backdoor'] == 'g_r' and i == (self.args['adversary'] - 1): + passive_party = GRPassiveModel(bottom_model=model, + amplify_ratio=self.args['amplify_ratio'], id=i, args=self.args) + backdoor_X = dict() + if self.g_r_train_dl is not None: + for X, _, _, indices in self.g_r_train_dl: + temp_indices = list(set(self.backdoor_indices) & set(indices.tolist())) + if len(temp_indices) > 0: + if self.args['n_passive_party'] < 2: + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + _, Xb_batch = X + else: + Xb_batch = X[:, self.args['adversary']:self.args['adversary']+1].squeeze(1) + for temp in temp_indices: + backdoor_X[temp] = Xb_batch[indices.tolist().index(temp)] + passive_party.set_backdoor_indices(self.backdoor_target_indices, self.backdoor_indices, backdoor_X) + elif self.args['backdoor'] == 'villain' and i == (self.args['adversary'] - 1): + passive_party = Villain_PassiveModel(bottom_model=model, amplify_ratio=self.args['amplify_ratio'], args=self.args, id=i) + passive_party.set_backdoor_indices(target_indices=self.backdoor_target_indices) + elif self.args['label_inference_attack'] == 'direct_attack' and i == (self.args['adversary'] - 1): + passive_party = Direct_Attack_PassiveModel(bottom_model=model, id=i, args=self.args) + else: + passive_party = VFLPassiveModel(bottom_model=model, id=i, args=self.args) + self.party_list.append(passive_party) diff --git a/examples/community/vfl_attacks/vfl/vfl.py b/examples/community/vfl_attacks/vfl/vfl.py new file mode 100644 index 0000000..29cd01e --- /dev/null +++ b/examples/community/vfl_attacks/vfl/vfl.py @@ -0,0 +1,326 @@ +# Copyright 2024 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +import logging +import numpy as np +from MindsporeCode.common.utils import accuracy +from mindspore import ops +from datetime import datetime + + +class VFL(object): + """ + VFL system + """ + def __init__(self,train_loader, test_loader, backdoor_test_loader, party_list, args): + super(VFL,self).__init__() + self.active_party = party_list[0] + self.party_dict = dict() # passive parties dict + self.party_ids = list() # id list of passive parties + self.args = args + for index, party in enumerate(party_list[1:]): + self.add_party(id=index, party_model=party) + self.train_loader = train_loader + self.test_loader = test_loader + self.backdoor_test_loader = backdoor_test_loader + self.top_k = 1 + if self.args['dataset'] == 'cifar100': + self.top_k = 5 + self.state = None + self.is_attack = False + # the index in passive parties + self.adversary = self.args['adversary'] - 1 + self.record_loss = [] + self.record_train_acc = [] + self.record_test_acc = [] + self.record_attack_metric = [] + self.record_results = [] + + def add_party(self, *, id, party_model): + """ + add passive party + :param id: passive party id + :param party_model: passive party + """ + self.party_dict[id] = party_model + self.party_ids.append(id) + + def set_current_epoch(self, ep): + """ + set current train epoch + :param ep: current train epoch + """ + self.active_party.set_epoch(ep) + for i in self.party_ids: + self.party_dict[i].set_epoch(ep) + + + def set_state(self, type): + self.state = type + if type == 'attack': + self.is_attack = True + else: + self.is_attack = False + + def train(self): + ''' + train the vfl model + :return: + ''' + # load VFL + if self.args['load_model']: + self.load() + self.set_state('test') + self.train_acc = self.predict(self.train_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + self.test_acc = self.predict(self.test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + if self.backdoor_test_loader is not None: + self.set_state('attack') + self.backdoor_acc = self.predict(self.backdoor_test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + print('train_acc: {}, test_acc: {}, backdoor_acc:{}'.format(self.train_acc, self.test_acc, self.backdoor_acc)) + else: + print('train_acc: {}, test_acc: {}'.format(self.train_acc, self.test_acc)) + # train VFL + else: + last_time = datetime.now() + for ep in range(self.args['target_epochs']): + loss_list = [] + self.set_train() + self.set_current_epoch(ep) + + for batch_idx, (X, Y_batch, old_imgb, indices) in enumerate(self.train_loader): + party_X_train_batch_dict = dict() + if self.args['n_passive_party'] < 2: + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + + active_X_batch, Xb_batch = X + party_X_train_batch_dict[0] = Xb_batch + else: + active_X_batch = X[:, 0:1].squeeze(1) + for i in range(self.args['n_passive_party']): + party_X_train_batch_dict[i] = X[:, i + 1:i + 2].squeeze(1) + loss, grad_list = self.fit(active_X_batch, Y_batch, party_X_train_batch_dict, indices) + loss_list.append(loss) + self.scheduler_step() + + # not evaluate main-task performan、ce if evaluating execution time + if not self.args['time']: + # compute main-task accuracy + ave_loss = np.sum(loss_list) / len(self.train_loader.children[0]) + self.set_state('train') + self.train_acc = self.predict(self.train_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + self.test_acc = self.predict(self.test_loader, num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.record_train_acc.append(self.train_acc) + self.record_test_acc.append(self.test_acc) + self.record_loss.append(ave_loss) + # compute backdoor task accuracy + if self.backdoor_test_loader is not None: + self.set_state('attack') + self.backdoor_acc = self.predict(self.backdoor_test_loader, + num_classes=self.args['num_classes'], + dataset=self.args['dataset'], top_k=self.top_k, + n_passive_party=self.args['n_passive_party']) + self.set_state('test') + logging.info( + "--- epoch: {}, train loss: {}, train_acc: {}%, test acc: {}%, backdoor acc: {}%".format(ep, + ave_loss, + self.train_acc * 100, + self.test_acc * 100, + self.backdoor_acc * 100)) + self.record_attack_metric.append(self.backdoor_acc) + else: + logging.info("--- epoch: {}, train loss: {}, train_acc: {}%, test acc: {}%".format(ep, ave_loss, + self.train_acc * 100, + self.test_acc * 100)) + + def fit(self, active_X, y, party_X_dict, indices): + """ + VFL training in one batch + :param active_X: features of active party + :param y: labels + :param dict party_X_dict: features of passive parties, the key is passive party id + :param indices: indices of samples in current batch + :return: loss + """ + # set features and labels for active party + self.active_party.set_batch(active_X, y) + self.active_party.set_indices(indices) + + # set features for all passive parties + for idx, party_X in party_X_dict.items(): + self.party_dict[idx].set_batch(party_X, indices) + + # all passive parties output latent representations and upload them to active party + comp_list = [] + for id in self.party_ids: + party = self.party_dict[id] + logits = party.send_components() + comp_list.append(logits) + self.active_party.receive_components(component_list=comp_list) + + # active party compute gradients based on labels and update parameters of its bottom model and top model + self.active_party.fit() + loss = self.active_party.get_loss() + + # active party send gradients to passive parties, then passive parties update parameters of their bottom model + parties_grad_list = self.active_party.send_gradients() + grad_list = [] + for index, id in enumerate(self.party_ids): + party = self.party_dict[id] + grad = party.receive_gradients(parties_grad_list[index]) + grad_list.append(grad) + + return loss, grad_list + + def save(self): + """ + save all models in VFL, including top model and all bottom models + """ + self.active_party.save() + for id in self.party_ids: + self.party_dict[id].save() + + def load(self, load_attack=False): + """ + load all models in VFL, including top model and all bottom models + + :param load_attack: invalid + """ + self.active_party.load() + for id in self.party_ids: + if load_attack and id == 0: + self.party_dict[id].load(load_attack=True) + else: + self.party_dict[id].load() + + def predict(self, test_loader, num_classes, dataset, top_k=1, n_passive_party=2): + """ + compute accuracy of VFL system on test dataset + :param test_loader: loader of test dataset + :param num_classes: number of dataset classes + :param dataset: dataset name + :param top_k: top-k accuracy + :param n_passive_party: number of passive parties + :param is_attack: whether to compute attack accuracy + :return: accuracy + """ + y_predict = [] + y_true = [] + + self.set_eval() + + for batch_idx, (X, targets, old_imgb, indices) in enumerate(test_loader): + + party_X_test_dict = dict() + if self.args['n_passive_party'] < 2: + if self.args['dataset'] != 'criteo': + X = ops.transpose(X, (1, 0, 2, 3, 4)) + else: + X_list = [X[:, 0, :], X[:, 1, :]] + X = X_list + active_X_inputs, Xb_inputs = X + party_X_test_dict[0] = Xb_inputs + else: + active_X_inputs = X[:, 0:1].squeeze(1) + for i in range(n_passive_party): + party_X_test_dict[i] = X[:, i + 1:i + 2].squeeze(1) + y_true += targets.tolist() + + + # for ABL defense + if self.state == 'train': + self.active_party.y = targets + self.active_party.indices = indices + + y_prob_preds = self.batch_predict(active_X_inputs, party_X_test_dict) + y_predict += y_prob_preds.tolist() + + acc = accuracy(y_true, y_predict, top_k=top_k, num_classes=num_classes, dataset=dataset, is_attack=self.is_attack) + + return acc + + def write(self): + ''' + + Returns: + + ''' + # save VFL + if self.args['save_model']: + self.save() + + def batch_predict(self, active_X, party_X_dict): + """ + predict label with help of all parties + :param active_X: features of active party + :param dict party_X_dict: features of passive parties, the key is passive party id + :param attack_output: latent represent output by the attacker if provided, otherwise the attacker output using bottom model + :param is_attack: attack or not in the predict process, sr_ba is True, else False + :return: prediction label + """ + comp_list = [] + # passive parties send latent representations + for id in self.party_ids: + comp_list.append(self.party_dict[id].predict(party_X_dict[id], self.is_attack)) + + # active party make the final prediction + return self.active_party.predict(active_X, component_list=comp_list, type=self.state) + + def set_train(self): + """ + set train mode for all parties + """ + self.active_party.set_train() + for id in self.party_ids: + self.party_dict[id].set_train() + + def set_eval(self): + """ + set eval mode for all parties + """ + self.active_party.set_eval() + for id in self.party_ids: + self.party_dict[id].set_eval() + + def scheduler_step(self): + """ + adjust learning rate for all parties during training + """ + self.active_party.scheduler_step() + for id in self.party_ids: + self.party_dict[id].scheduler_step() + + def zero_grad(self): + """ + clear gradients for all parties + """ + self.active_party.zero_grad() + for id in self.party_ids: + self.party_dict[id].zero_grad() From 1b527424151ffe0262bed9ea846cb0ea8ff1722b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=A5=9A=E5=90=9B?= <2234292121@qq.com> Date: Sat, 28 Jun 2025 23:26:30 +0800 Subject: [PATCH 2/2] Add vfl attacks --- examples/community/vfl_attacks/README.md | 2 +- .../vfl_attacks/common/image_report.py | 45 ----------- .../community/vfl_attacks/common/parser.py | 7 -- .../community/vfl_attacks/common/utils.py | 7 +- .../vfl_attacks/datasets/bhi_dataset.py | 19 +---- .../vfl_attacks/datasets/cifar_dataset.py | 76 +------------------ .../vfl_attacks/datasets/cinic_dataset.py | 38 +--------- .../community/vfl_attacks/datasets/common.py | 22 +----- .../vfl_attacks/datasets/criteo_dataset.py | 46 +---------- .../vfl_attacks/datasets/image_dataset.py | 33 +------- .../datasets/multi_image_dataset.py | 28 +------ .../datasets/multi_tabular_dataset.py | 13 +--- .../vfl_attacks/datasets/tabular_dataset.py | 11 ++- .../vfl_attacks/evaluate/MainTask.py | 14 ---- .../vfl_attacks/evaluate/args_line.py | 20 ----- .../direct_attack/direct_attack_vfl.py | 20 +---- .../methods/g_r/g_r_passive_party.py | 2 - .../methods/villain/villain_passive_party.py | 3 - .../methods/villain/villain_vfl.py | 13 +--- 19 files changed, 35 insertions(+), 384 deletions(-) diff --git a/examples/community/vfl_attacks/README.md b/examples/community/vfl_attacks/README.md index da973b0..4eb39dd 100644 --- a/examples/community/vfl_attacks/README.md +++ b/examples/community/vfl_attacks/README.md @@ -115,4 +115,4 @@ from methods.g_r.g_r_passive_party import GRPassiveModel 该模块实现了Villain后门攻击,在VFL类的基础上定义了Villain后门攻击中的过程函数。 ## 扩展 -本项目当前支持CIFAR-10、CIFAR100、BHI、criteo数据集,目前datasets文件夹中给出了响应数据集加载代码。如需自定义模型结构或数据集加载方式,请参考并修改datasets文件夹中的对应文件内容。 \ No newline at end of file +本项目当前支持CIFAR-10、CIFAR100、BHI、criteo数据集,目前datasets文件夹中给出了相应数据集加载代码。如需自定义模型结构或数据集加载方式,请参考并修改datasets文件夹中的对应文件内容。 \ No newline at end of file diff --git a/examples/community/vfl_attacks/common/image_report.py b/examples/community/vfl_attacks/common/image_report.py index 88f5dcb..d6b2516 100644 --- a/examples/community/vfl_attacks/common/image_report.py +++ b/examples/community/vfl_attacks/common/image_report.py @@ -25,25 +25,14 @@ def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_p for batch_idx, (X, targets, old_imgb, indices) in enumerate(test_loader): party_X_test_dict = dict() if self.args['n_passive_party'] < 2: - # X = ops.transpose(X, (1, 0, 2, 3, 4)) - # 0627修改适配criteo if self.args['dataset'] != 'criteo': X = ops.transpose(X, (1, 0, 2, 3, 4)) else: X_list = [X[:, 0, :], X[:, 1, :]] X = X_list active_X_inputs, Xb_inputs = X - # if self.args['cuda']: - # active_X_inputs = active_X_inputs.cuda() - # Xb_inputs = Xb_inputs.cuda() - # targets = targets.cuda() - # indices = indices.cuda() party_X_test_dict[0] = Xb_inputs else: - # if self.args['cuda']: - # X = X.cuda() - # targets = targets.cuda() - # indices = indices.cuda() active_X_inputs = X[:, 0:1].squeeze(1) for i in range(n_passive_party): party_X_test_dict[i] = X[:, i + 1:i + 2].squeeze(1) @@ -73,25 +62,14 @@ def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_p for batch_idx, (X, targets, old_imgb, indices) in enumerate(test_loader): party_X_test_dict = dict() if self.args['n_passive_party'] < 2: - # X = ops.transpose(X, (1, 0, 2, 3, 4)) - # 0627修改适配criteo if self.args['dataset'] != 'criteo': X = ops.transpose(X, (1, 0, 2, 3, 4)) else: X_list = [X[:, 0, :], X[:, 1, :]] X = X_list active_X_inputs, Xb_inputs = X - # if self.args['cuda']: - # active_X_inputs = active_X_inputs.cuda() - # Xb_inputs = Xb_inputs.cuda() - # targets = targets.cuda() - # indices = indices.cuda() party_X_test_dict[0] = Xb_inputs else: - # if self.args['cuda']: - # X = X.cuda() - # targets = targets.cuda() - # indices = indices.cuda() active_X_inputs = X[:, 0:1].squeeze(1) for i in range(n_passive_party): party_X_test_dict[i] = X[:, i + 1:i + 2].squeeze(1) @@ -105,14 +83,10 @@ def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_p each_party_X_test_dict = party_X_test_dict.copy() for indice in indices: if indice in image_indices: - # print("case 1") index = ops.nonzero(indices == indice)[0][0] - #0523问题修复 elif indice == indices[-1]: - # print("case 2") index = ops.nonzero(indices == indice)[0][0] else: - # print("case 3") continue each_active_X_inputs = ops.unsqueeze(active_X_inputs[index], dim=0) @@ -123,11 +97,8 @@ def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_p predicted_class = ops.argmax(y_clean) if predicted_class != self.args['backdoor_label'] or indice == indices[-1]: - # image_indices = indice - # 0523问题修复 待测试,后面用的参数也从image_indices改为select_indice selected_indice = indice if selected_indice.item() not in y_backdoor_dict: - # print(f"Warning: selected indice {selected_indice.item()} not in y_backdoor_dict, skip.") continue y_backdoor = y_backdoor_dict[selected_indice.item()] @@ -136,17 +107,7 @@ def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_p from io import BytesIO image = test_loader.children[0].source.data[selected_indice] - # 0627 加criteo重改了下面这一大段 - # image = image.transpose(1, 2, 0) # 32,32,3 - # # image = Image.fromarray(image) - # # print(f"ms image report------image shape:{image.shape}") # 32,32,3 - # # 修改格式 for bhi - # if len(image.shape) == 4 and self.args["dataset"] != "criteo": - # image = Image.fromarray(image[0]) - # else: - # image = Image.fromarray(image) if self.args["dataset"] != "criteo": - # 修改格式 for bhi if len(image.shape) == 4: image = Image.fromarray(image[0]) else: @@ -154,9 +115,6 @@ def backdoor_image_predict(self, test_loader, num_classes, dataset, top_k=1, n_p image = Image.fromarray(image) buffered = BytesIO() - # image.save(buffered, format="PNG") - # image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') - # 0627criteo加的判断 if self.args["dataset"] != "criteo": image.save(buffered, format="PNG") image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') @@ -185,8 +143,6 @@ def visualization_with_images(self, ema_top_model=None): def append_predictions_to_file(file_path, clean_pred, attack_pred): # 准备写入的字符串内容 - # clean_str = ", ".join(map(str, clean_pred)) # 将clean预测列表转成字符串 - # attack_str = ", ".join(map(str, attack_pred)) # 将attack预测列表转成字符串 clean_str = ", ".join([str(p.item()) for p in clean_pred]) # 将 clean 预测列表的 tensor 转换为数值 attack_str = ", ".join([str(p.item()) for p in attack_pred]) # 将 attack 预测列表的 tensor 转换为数值 @@ -195,7 +151,6 @@ def append_predictions_to_file(file_path, clean_pred, attack_pred): # 打开文件并追加内容 with open(file_path, "a") as file: - # 先写一个空行,再写入数据 file.write("\n\n") # 添加空行 file.write(data_to_append + "\n") # 追加数据并换行 diff --git a/examples/community/vfl_attacks/common/parser.py b/examples/community/vfl_attacks/common/parser.py index 53208ba..f756a9c 100644 --- a/examples/community/vfl_attacks/common/parser.py +++ b/examples/community/vfl_attacks/common/parser.py @@ -26,13 +26,9 @@ def get_args(file, file_time): :return: configuration """ - # parser = argparse.ArgumentParser() - # parser.add_argument('--config', type=str) - # temp = parser.parse_args() yaml.warnings({'YAMLLoadWarning': False}) f = open(file, 'r', encoding='utf-8') cfg = f.read() - # NEW fix args = yaml.load(cfg, Loader=yaml.SafeLoader) f.close() args['num_classes'] = get_num_classes(args['dataset']) @@ -45,9 +41,6 @@ def get_args(file, file_time): # the configuration whether to print the execution time of federated training and LR-BA args['time'] = False - # now = datetime.datetime.now() - # time = now.strftime("%m-%d-%H-%M-%S") - # args['file_time'] = time time = file_time set_logging(args['log'], time) return args diff --git a/examples/community/vfl_attacks/common/utils.py b/examples/community/vfl_attacks/common/utils.py index 9362759..5a763fb 100644 --- a/examples/community/vfl_attacks/common/utils.py +++ b/examples/community/vfl_attacks/common/utils.py @@ -37,17 +37,12 @@ def accuracy(y_true, y_pred, dataset, num_classes=None, top_k=1, is_attack=False if top_k == 1: for pred in y_pred: temp = np.max(pred) - # temp_y_pred.append(pred.index(temp)) temp_y_pred.append(np.where(pred == temp)[0][0]) - # if is_attack: - # for y in temp_y_pred: - # if y != y_true[0]: - # print(y) + if dataset != 'bhi': acc = accuracy_score(y_true, temp_y_pred) else: if not is_attack: - # logging.info('f1 score') acc = f1_score(y_true, temp_y_pred) else: acc = accuracy_score(y_true, temp_y_pred) diff --git a/examples/community/vfl_attacks/datasets/bhi_dataset.py b/examples/community/vfl_attacks/datasets/bhi_dataset.py index 46a8a6f..189554e 100644 --- a/examples/community/vfl_attacks/datasets/bhi_dataset.py +++ b/examples/community/vfl_attacks/datasets/bhi_dataset.py @@ -28,8 +28,6 @@ # transform for BHI images normalize = vision.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5],is_hwc = True) transform = ms.dataset.transforms.Compose([ - #vision.ToPIL(), - #vision.ToTensor(), normalize ]) @@ -98,19 +96,10 @@ def load_parties_data(data_dir, args): X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.2, random_state=1) - # train 92242[1:36455,0:55787] - # test 23061[1:9114,0:13947] - # 0527添加 n_train = args['target_train_size'] n_test = args['target_test_size'] - # if n_train != -1: - # indices = get_random_indices(n_train, len(X)) - # X_train, y_train = X_train[indices], y_train[indices] - # if n_test != -1: - # indices = get_random_indices(n_test, len(X)) - # X_test, y_test = X_test[indices], y_test[indices] - # 0627修改 + if n_train != -1: indices = get_random_indices(n_train, len(X_train)) X_train, y_train = X_train[indices], y_train[indices] @@ -120,7 +109,6 @@ def load_parties_data(data_dir, args): # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset train_indices = np.where(y_train != args['backdoor_label'])[0] - # print(len(train_indices),args['backdoor_train_size']) backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) backdoor_y_train = copy.deepcopy(y_train) backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] @@ -159,7 +147,6 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru :return: loader """ X, y = data_list - # print("1",X.shape) MultiImageDatasetWithIndices = image_dataset_with_indices(MultiImageDataset) party_num = args['n_passive_party'] + 1 ds = MultiImageDatasetWithIndices(X, ms.tensor(y,ms.int32), @@ -233,6 +220,4 @@ def get_bhi_dataloader(args): villain_train_dl = None return train_dl, test_dl, backdoor_train_dl, backdoor_test_dl, g_r_train_dl, \ - backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, villain_train_dl - #### train_dl, test_dl, backdoor_test_dl, backdoor_train_dl, g_r_train_dl, \ - # backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, \, villain_train_dl \ No newline at end of file + backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, villain_train_dl \ No newline at end of file diff --git a/examples/community/vfl_attacks/datasets/cifar_dataset.py b/examples/community/vfl_attacks/datasets/cifar_dataset.py index 38ad830..b702af0 100644 --- a/examples/community/vfl_attacks/datasets/cifar_dataset.py +++ b/examples/community/vfl_attacks/datasets/cifar_dataset.py @@ -33,15 +33,13 @@ # transform for CIFAR train dataset train_transform = ms.dataset.transforms.Compose([ vision.ToTensor(), - # vision.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2470, 0.2435, 0.2616], is_hwc=False) # CIFAR10 - # transforms.Normalize((0.5071, 0.4867, 0.4408),(0.2675, 0.2565, 0.2761)) #CIFAR100 + ]) # transform for CIFAR test dataset test_transform = ms.dataset.transforms.Compose([ vision.ToTensor(), - # vision.Normalize(mean=[0.4940, 0.4850, 0.4504], std=[0.2467, 0.2429, 0.2616], is_hwc=False) # CIFAR10 - # transforms.Normalize((0.5071, 0.4867, 0.4408),(0.2675, 0.2565, 0.2761)) #CIFAR100 + ]) def get_labeled_data_with_2_party(data_dir, dataset, dtype="train", num_samples=None): @@ -53,22 +51,6 @@ def get_labeled_data_with_2_party(data_dir, dataset, dtype="train", num_samples= :param str dtype: read "Train" or "Test" data :return: tuple containing X and Y """ - # data_dir = data_dir + 'cifar-10-batches-bin/' - # transform = train_transform if dtype == 'train' else test_transform - # if dataset == 'cifar10': - # dataset = ds.Cifar10Dataset(dataset_dir=data_dir,usage=dtype, num_samples=num_samples) - # # dataset = dataset.map(transform) - # dataset = dataset.map(operations=[(lambda x: x.transpose(2, 0, 1).unsqueeze(0).asnumpy())]) - # - # elif dataset == 'cifar100': - # dataset = ds.Cifar100Dataset(dataset_dir=data_dir,usage=dtype, num_samples=num_samples) - # # dataset = dataset.map(transform) - # all_data = np.empty(shape=(0, 3, 32, 32)) - # targets = [] - # for image, label in dataset: - # all_data = np.append(all_data, image.transpose(2, 0, 1).unsqueeze(0).asnumpy(), axis=0) - # targets.append(label.item()) - # targets = np.array(targets) if dataset == 'cifar10': data_dir = data_dir + 'cifar-10-batches-py/' @@ -93,7 +75,6 @@ def get_labeled_data_with_2_party(data_dir, dataset, dtype="train", num_samples= else: targets.extend(entry['fine_labels']) all_data = np.vstack(all_data).reshape(-1, 3, 32, 32) - # all_data = all_data.transpose((0, 2, 3, 1)) # convert to HWC targets = np.array(targets) if num_samples is not None: indices = get_random_indices(num_samples, len(all_data)) @@ -106,7 +87,6 @@ def get_labeled_data_with_2_party(data_dir, dataset, dtype="train", num_samples= datadict = pickle.load(f, encoding='latin1') X = datadict['data'] all_data = X.reshape(-1, 3, 32, 32) - # fine_labels细分类,共100中类别 targets = datadict['fine_labels'] targets = np.array(targets) if num_samples is not None: @@ -115,7 +95,6 @@ def get_labeled_data_with_2_party(data_dir, dataset, dtype="train", num_samples= else: datas, labels = all_data, targets - # N, 3, 32, 32 0-255 return datas, labels @@ -147,33 +126,17 @@ def load_two_party_data(data_dir, args): if n_test == -1: n_test = None # read train data from local file - # print("# load_train_data ing...") X_train, y_train = get_labeled_data_with_2_party(data_dir=data_dir, dataset=args['dataset'], dtype='train', num_samples=n_train) - # print("# load_train_data finished!!!", len(X_train), len(y_train)) # read test data from local file - # print("# load_test_data ing...") X_test, y_test = get_labeled_data_with_2_party(data_dir=data_dir, dataset=args['dataset'], dtype='test', num_samples=n_test) - # print("# load_test_data finished!!!", len(X_test), len(y_test)) - - # # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset - # train_indices = np.where(y_train != args['backdoor_label'])[0] - # backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) - # backdoor_y_train = copy.deepcopy(y_train) - # backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] - # - # # randomly select samples of other classes from normal test dataset to generate backdoor test dataset - # test_indices = np.where(y_test != args['backdoor_label'])[0] - # backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) - # backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ - # y_test[backdoor_indices_test] - # backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset train_indices = np.where(y_train != args['backdoor_label'])[0] num_train_choice = min(len(train_indices), args['backdoor_train_size']) @@ -199,7 +162,6 @@ def load_two_party_data(data_dir, args): args['train_label_non_iid'], args['backdoor_label'], args['train_label_fix_backdoor']) # randomly select samples of backdoor label in normal train dataset, for gradient-replacement - # backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['train_label_size']) backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['backdoor_train_size']) logging.info("y_train.shape: {}".format(y_train.shape)) @@ -230,27 +192,7 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru :return: loader """ - # def per_batch(img_list, target, original, index, BatchInfo): - # # img_list: 64,K,3,32,16 0-255 tensor - # new_img_list = [] - # new_original = [] - # for i in range(len(img_list)): - # new_img_list.append([]) - # for j in range(len(img_list[i])): - # img = img_list[i][j] - # img = Image.fromarray(np.uint8(img.transpose(1, 2, 0))) #RGB - # # img_list[i][j] = transform(img) # 0-1 - # img = vision.ToTensor()(img) # 0-1 3,32,16 - # # img = vision.Normalize(mean=[121.0, 115.0, 100.0], std=[70.0, 68.0, 71.0], is_hwc=True)(img) - # new_img_list[-1].append(np.array(img, dtype=float)) - # img = original[i] - # img = Image.fromarray(np.uint8(img.transpose(1, 2, 0))) # RGB - # img = vision.ToTensor()(img) # 0-1 3,32,16 - # new_original.append(np.array(img, dtype=float)) - # return new_img_list, target, new_original, index - - X, y = data_list # 100,3,32,32 0-255 - # 测试实际:(20000,3,32,32) (2000,) + X, y = data_list if args['n_passive_party'] > 1: MultiImageDatasetWithIndices = image_dataset_with_indices(MultiImageDataset) party_num = args['n_passive_party'] + 1 @@ -268,7 +210,6 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru half=16, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle, column_names=['image','target','old_imgb', 'indice']) dl = dl.batch(batch_size, drop_remainder=False) - # dl.children[0].source.targets return dl @@ -331,15 +272,6 @@ def get_cifar_dataloader(args): else: villain_train_dl = None - # train_dl = train_dl.create_tuple_iterator() - # test_dl = test_dl.create_tuple_iterator() - # backdoor_test_dl = backdoor_test_dl.create_tuple_iterator() - # backdoor_train_dl = backdoor_train_dl.create_tuple_iterator() - # g_r_train_dl = g_r_train_dl.create_tuple_iterator() - # labeled_dl = labeled_dl.create_tuple_iterator() - # unlabeled_dl = unlabeled_dl.create_tuple_iterator() - - # dl.children[0].source.targets[:10] return train_dl, test_dl, backdoor_test_dl, backdoor_train_dl, g_r_train_dl, \ backdoor_indices, backdoor_target_indices, labeled_dl, unlabeled_dl, \ diff --git a/examples/community/vfl_attacks/datasets/cinic_dataset.py b/examples/community/vfl_attacks/datasets/cinic_dataset.py index 0b6ffc8..2fada8b 100644 --- a/examples/community/vfl_attacks/datasets/cinic_dataset.py +++ b/examples/community/vfl_attacks/datasets/cinic_dataset.py @@ -38,7 +38,6 @@ def image_format_2_rgb(x): transform = ms.dataset.transforms.Compose([ vision.ToTensor(), - # normalize ]) def get_labeled_data_with_2_party(data_dir, dtype="Train", num_samples=None): @@ -54,7 +53,6 @@ def get_labeled_data_with_2_party(data_dir, dtype="Train", num_samples=None): entry = pickle.load(f, encoding='latin1') img_data = entry['data'] targets = entry['labels'] - # 0523待测试 img_data = img_data.reshape(-1, 3, 32, 32) targets = np.array(targets) if num_samples is not None: @@ -96,9 +94,6 @@ def load_two_party_data(data_dir, args): dtype='Train', num_samples=n_train) print("# load_train_data finished!!!", len(X_train), len(y_train)) - # 0520 - # X的数据格式原本是n 32 32 3,改为n 3 32 32 - # X = np.transpose(X, (0, 3, 1, 2)) # read test data from local file print("# load_test_data ing...") @@ -107,19 +102,6 @@ def load_two_party_data(data_dir, args): num_samples=n_test) print("# load_test_data finished!!!", len(X_train), len(y_train)) - # # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset - # train_indices = np.where(y_train != args['backdoor_label'])[0] - # backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) - # backdoor_y_train = copy.deepcopy(y_train) - # backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] - # - # # randomly select samples of other classes from normal test dataset to generate backdoor test dataset - # test_indices = np.where(y_test != args['backdoor_label'])[0] - # backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) - # backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ - # y_test[backdoor_indices_test] - # backdoor_y_test_true = backdoor_y_test - # backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset train_indices = np.where(y_train != args['backdoor_label'])[0] @@ -150,7 +132,6 @@ def load_two_party_data(data_dir, args): backdoor_target_indices = get_target_indices(y_train, args['backdoor_label'], args['train_label_size']) # ours SR NEW - # print('SR-BA dataset prepare......') index = [i for i in range(len(X_train))] sr_X_train = [] sr_y_train = [] @@ -188,19 +169,11 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru :return: loader """ X, y = data_list - # 0519 - # (2000, 32, 32, 3) (2000,) # get x, y, and index when loading data if args['n_passive_party'] > 1: MultiImageDatasetWithIndices = image_dataset_with_indices(MultiImageDataset) party_num = args['n_passive_party'] + 1 - # ds = MultiImageDatasetWithIndices(X, ms.tensor(y), - # transform=transform, - # backdoor_indices=backdoor_indices, - # party_num=party_num, - # trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, - # adversary=args['adversary']) ds = MultiImageDatasetWithIndices(X, ms.tensor(y, ms.int32), transform=transform, backdoor_indices=backdoor_indices, @@ -209,17 +182,12 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru else: ImageDatasetWithIndices = image_dataset_with_indices(ImageDataset) # split x into halves for parties when loading data, only support two parties - # ds = ImageDatasetWithIndices(X, ms.tensor(y), - # transform=transform, - # backdoor_indices=backdoor_indices, - # half=16, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) ds = ImageDatasetWithIndices(X, ms.tensor(y, ms.int32), transform=transform, backdoor_indices=backdoor_indices, half=16, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) - # dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle) - # 0518问题 不确定 column_names可能不对 + dl = ms.dataset.GeneratorDataset(source=ds, shuffle=shuffle, column_names=['image', 'target', 'old_imgb', 'indice']) dl = dl.batch(batch_size, drop_remainder=False) return dl @@ -263,10 +231,6 @@ def get_cinic_dataloader(args): backdoor_indices=np.arange(args['backdoor_test_size']), trigger=args['trigger'], trigger_add=args['trigger_add']) # get loader of labeled and unlabeled normal train dataset, used by LR-BA - # labeled_dl, unlabeled_dl = get_labeled_loader(train_dataset=train_dl.dataset, - # labeled_indices=train_labeled_indices, - # unlabeled_indices=train_unlabeled_indices, - # args=args) labeled_data, unlabeled_data = get_labeled_loader(train_dataset=(X_train, y_train), labeled_indices=train_labeled_indices, unlabeled_indices=train_unlabeled_indices, diff --git a/examples/community/vfl_attacks/datasets/common.py b/examples/community/vfl_attacks/datasets/common.py index ac48d41..6da5697 100644 --- a/examples/community/vfl_attacks/datasets/common.py +++ b/examples/community/vfl_attacks/datasets/common.py @@ -99,7 +99,7 @@ def train_label_split(labels, train_label_size, num_classes, non_iid=None, backd logging.info('non iid label sum: {}, all: {}'.format(np.sum(n_list), n_list)) np.random.shuffle(train_labeled_indices) np.random.shuffle(train_unlabeled_indices) - # logging.info('label_indices: {}'.format(train_labeled_indices)) + return train_labeled_indices, train_unlabeled_indices @@ -130,7 +130,7 @@ def get_random_indices(target_length, all_length): """ all_indices = np.arange(all_length) indices = np.random.choice(all_indices, target_length, replace=False) - # indices = np.arange(target_length) + return indices @@ -159,18 +159,6 @@ def get_labeled_loader(train_dataset, labeled_indices, unlabeled_indices, args): un_y_data = train_dataset[1][unlabeled_indices] unlabeled_dl = [un_X_data, un_y_data] - # from cifar_dataset import generate_dataloader - # generate_dataloader(args, (X_data, y_data), batch_size, train_transform, shuffle=True) - - # labeled_dataset = train_dataset.take(labeled_indices) - # unlabeled_dataset = train_dataset.take(unlabeled_indices) - - # labeled_dl = ms.dataset.GeneratorDataset(labeled_dataset,shuffle=True) - # labeled_dl = labeled_dl.batch(min(label_size, args['lr_ba_top_batch_size']),drop_remainder=True) - # - # unlabeled_dl = ms.dataset.GeneratorDataset(unlabeled_dataset, shuffle=True) - # unlabeled_dl = unlabeled_dl.batch(min(label_size, args['lr_ba_top_batch_size']), drop_remainder=True) - return labeled_dl, unlabeled_dl @@ -190,11 +178,7 @@ def add_pixel_pattern_backdoor(inputs, pattern_tensor, location): "A tensor of the `input.shape` filled with `mask_value` except backdoor." - # def make_pattern(): - # nonlocal mask, pattern input_shape = inputs.shape - # full_image = ms.ops.zeros(input_shape) - # full_image = ms.dataset.transforms.Fill(mask_value) full_image = np.full(input_shape, mask_value, dtype=inputs.dtype) x_top = location[0] @@ -280,7 +264,7 @@ def insert_word(s, word: Union[str, List[str]], times=1): else: # Otherwise just use the one word insert_word = word - # Random position FIXME: this should use numpy random but I (Paul) + # kept it for reproducibility position = random.randint(0, len(words)) # Insert diff --git a/examples/community/vfl_attacks/datasets/criteo_dataset.py b/examples/community/vfl_attacks/datasets/criteo_dataset.py index b59a1e0..6dadf22 100644 --- a/examples/community/vfl_attacks/datasets/criteo_dataset.py +++ b/examples/community/vfl_attacks/datasets/criteo_dataset.py @@ -63,25 +63,11 @@ def load_parties_data(data_dir, args): # read data from local file X, y = get_labeled_data(data_path=data_dir) - # split normal dataset for train and test # split normal dataset for train and test X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.2, random_state=1) - # # train 80000 - # # test 20000 - # # randomly select samples of other classes from normal train dataset as backdoor samples to generate backdoor train dataset - # train_indices = np.where(y_train != args['backdoor_label'])[0] - # backdoor_indices_train = np.random.choice(train_indices, args['backdoor_train_size'], replace=False) - # backdoor_y_train = copy.deepcopy(y_train) - # backdoor_y_train[backdoor_indices_train] = args['backdoor_label'] - # - # # randomly select samples of other classes from normal test dataset to generate backdoor test dataset - # test_indices = np.where(y_test != args['backdoor_label'])[0] - # backdoor_indices_test = np.random.choice(test_indices, args['backdoor_test_size'], replace=False) - # backdoor_X_test, backdoor_y_test = X_test[backdoor_indices_test], \ - # y_test[backdoor_indices_test] - # backdoor_y_test = np.full_like(backdoor_y_test, args['backdoor_label']) + n_train = args['target_train_size'] n_test = args['target_test_size'] if n_train != -1: @@ -143,16 +129,11 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru :return: loader """ X, y = data_list - # print("generate_dataloader") + if args['n_passive_party'] > 1: MultiImageDatasetWithIndices = image_dataset_with_indices(MultiTabularDataset) party_num = args['n_passive_party'] + 1 - # ds = MultiImageDatasetWithIndices(X, ms.tensor(y), - # transform=transform, - # backdoor_indices=backdoor_indices, - # party_num=party_num, - # trigger=trigger, trigger_add=trigger_add, source_indices=source_indices, - # adversary=args['adversary']) + ds = MultiImageDatasetWithIndices(X, ms.Tensor(y, dtype=ms.int32), transform=transform, backdoor_indices=backdoor_indices, @@ -162,19 +143,11 @@ def generate_dataloader(args, data_list, batch_size, transform=None, shuffle=Tru else: ImageDatasetWithIndices = image_dataset_with_indices(TabularDataset) # split x into halves for parties when loading data, only support two parties - # ds = ImageDatasetWithIndices(X, ms.tensor(y), - # backdoor_indices=backdoor_indices, - # half=2 ** 12, trigger=trigger, trigger_add=trigger_add, - # source_indices=source_indices) - # 0627修改 ds = ImageDatasetWithIndices(X, ms.Tensor(y, dtype=ms.int32), backdoor_indices=backdoor_indices, half=2 ** 12, trigger=trigger, trigger_add=trigger_add, source_indices=source_indices) - # dl = ms.dataset.GeneratorDataset(dataset=ds, column_names=["features", "label", "old_input"], shuffle=shuffle) - # dl = ms.dataset.GeneratorDataset(dataset=ds, column_names=["images", "label", "old_image"], shuffle=shuffle) - # dl = ms.dataset.GeneratorDataset(source=ds, column_names=["features", "label", "old_input"], shuffle=shuffle) dl = ms.dataset.GeneratorDataset(source=ds, column_names=['image','target','old_imgb', 'indice'], shuffle=shuffle) dl = dl.batch(batch_size, drop_remainder=False) @@ -197,10 +170,7 @@ def get_criteo_dataloader(args): (8) unlabeled_dl: loader of unlabeled samples in normal train dataset, used by LR-BA """ party_num = args['n_passive_party'] + 1 - # get dataset - # print("get_criteo_dataloader") - # result = load_parties_data(data_dir="../../data/Criteo/criteo.csv", args=args) - result = load_parties_data(data_dir="../../data/Criteo/criteo_debug.csv", args=args) + result = load_parties_data(data_dir="../../data/Criteo/criteo.csv", args=args) X_train, y_train, X_test, y_test, backdoor_y_train, backdoor_X_test, backdoor_y_test, \ backdoor_indices, backdoor_target_indices, train_labeled_indices, train_unlabeled_indices = result @@ -220,23 +190,15 @@ def get_criteo_dataloader(args): backdoor_indices=np.arange(args['backdoor_test_size']), trigger=args['trigger'], trigger_add=args['trigger_add']) - # 0627改,参照ms的cinic处理 - # get loader of labeled and unlabeled normal train dataset, used by LR-BA - # labeled_dl, unlabeled_dl = get_labeled_loader(train_dataset=train_dl.dataset, - # labeled_indices=train_labeled_indices, - # unlabeled_indices=train_unlabeled_indices, - # args=args) labeled_data, unlabeled_data = get_labeled_loader(train_dataset=(X_train, y_train), labeled_indices=train_labeled_indices, unlabeled_indices=train_unlabeled_indices, args=args) labeled_dl = generate_dataloader(args, labeled_data, batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), - # transform=transform, shuffle=True) unlabeled_dl = generate_dataloader(args, unlabeled_data, batch_size=min(len(train_labeled_indices), args['lr_ba_top_batch_size']), - # transform=transform, shuffle=True) # get loader of train dataset used by Gradient-Replacement, containing backdoor features and normal labels diff --git a/examples/community/vfl_attacks/datasets/image_dataset.py b/examples/community/vfl_attacks/datasets/image_dataset.py index 50c6d3f..e5b664c 100644 --- a/examples/community/vfl_attacks/datasets/image_dataset.py +++ b/examples/community/vfl_attacks/datasets/image_dataset.py @@ -68,13 +68,6 @@ def __init__( self.trigger_add = trigger_add self.pixel_pattern = np.full((3, 32, 16), 0) - # pattern_mask: ms.Tensor = ms.tensor([ - # [1., 0., 1.], - # [-10., 1., -10.], - # [-10., -10., 0.], - # [-10., 1., -10.], - # [1., 0., 1.] - # ]) pattern_mask: ms.Tensor = ms.tensor([ [1., 0., 1.], [-10., 1., -10.], @@ -95,20 +88,11 @@ def __init__( def __getitem__(self, index): index = int(index) img, target = self.data[index], self.targets[index] # 3,32,32 - # print('img shape: ', img.shape) - # img = img.transpose(1, 2, 0) - - # if type(img) is np.str_: - # img = Image.open(img) - # else: - # img = Image.fromarray(np.uint8(img.transpose(1, 2, 0))) img = img.transpose(1, 2, 0) - # print('img shape: ', img.shape) - # print('img type: ', type(img)) if self.transform is not None: img = self.transform(img) - # print('data type: ', img.dtype) float 32 + # split image into halves vertically for parties img_a, img_b = img[:, :, :self.half], img[:, :, self.half:] # [3, 32, 16] @@ -120,17 +104,13 @@ def __getitem__(self, index): if self.trigger == 'pixel': if self.indice_map is not None and index in self.indice_map.keys(): source_indice = self.indice_map[index] - # source_indice = random.sample(self.source_indices, 1)[0] + source_img = self.data[source_indice] - # if type(img) is np.str_: - # source_img = Image.open(source_img) - # else: - # source_img = Image.fromarray(source_img) + source_img = source_img.transpose(1, 2, 0) if self.transform is not None: source_img = self.transform(source_img) img_b = source_img[:, :, self.half:] - # old_imgb = img_b # add trigger if index is in backdoor indices if self.trigger == 'pixel': @@ -140,13 +120,6 @@ def __getitem__(self, index): else: img_b = add_pixel_pattern_backdoor(img_b, self.pattern_mask, self.location) - # img_a = Image.fromarray(np.uint8(img_a.transpose(1, 2, 0) *255)) - # img_b = Image.fromarray(np.uint8(img_b.transpose(1, 2, 0) *255)) - - # if self.transform is not None: - # img_a = self.transform(img_a)[0] - # img_b = self.transform(img_b)[0] - # 0-1 return (img_a, img_b), target, old_imgb diff --git a/examples/community/vfl_attacks/datasets/multi_image_dataset.py b/examples/community/vfl_attacks/datasets/multi_image_dataset.py index 6e88a4e..681e035 100644 --- a/examples/community/vfl_attacks/datasets/multi_image_dataset.py +++ b/examples/community/vfl_attacks/datasets/multi_image_dataset.py @@ -34,7 +34,6 @@ def __init__( source_indices=None, # none_target for sr_ba adversary=1 ) -> None: - # ???? if isinstance(root, (str, bytes)): root = os.path.expanduser(root) self.root = root @@ -83,8 +82,6 @@ def __init__( def __getitem__(self, index): index = int(index) img_groups, target = self.data[index], self.targets[index] # img_groups[N_party,50,50,3] - # img_groups = img_groups.transpose(1,2,3,0) #### ????? img_groups[50,50,3,N_party] - # print(img_groups.shape) if len(img_groups) == self.party_num: images_list = [] old_image = 0 @@ -96,10 +93,7 @@ def __getitem__(self, index): # print("before",image.shape) if self.transform is not None: image = self.transform(image) - image = image.transpose(2,0,1) ## ������ 3��50��50 - # print("after",len(image),len(image[0]),len(image[0][0])) - # if self.target_transform is not None: - # target = self.target_transform(target) + image = image.transpose(2,0,1) ## if img_id == self.attacker: old_image = image # 50,50,3 @@ -112,13 +106,7 @@ def __getitem__(self, index): source_img_groups = self.data[source_indice] source_img_path = source_img_groups[self.attacker] source_image = source_img_path - # print(source_img_groups) - # if type(image) is np.str_: - # source_image = Image.open(source_image) - # else: - # source_image = Image.fromarray(np.uint8(source_image)) - # source_img_groups = source_img_groups.transpose(1, 2, 3, 0) ###???? - + if self.transform is not None: source_image = self.transform(source_image)[0] # 50,50,3 source_image = source_image.transpose(2, 0, 1) @@ -127,7 +115,6 @@ def __getitem__(self, index): # add pixel trigger if self.trigger == 'pixel': if self.backdoor_indices is not None and index in self.backdoor_indices: - # print("yes2") if not self.trigger_add: image = add_pixel_pattern_backdoor(image, self.pattern_mask,self.location) else: @@ -137,13 +124,10 @@ def __getitem__(self, index): image = image + self.pixel_pattern images_list.append(image) else: - # TODO fix it - # 3,32,32 for CIFAR or CINIC img, target = self.data[index], self.targets[index] if type(img) is np.str_: img = Image.open(img) else: - # print(img.shape) img = Image.fromarray(np.uint8(img.permute(1, 2, 0) *255)) if self.transform is not None: img = self.transform(img)[0] @@ -178,8 +162,7 @@ def __getitem__(self, index): source_image = Image.open(source_image) else: source_image = Image.fromarray(np.uint8(source_image.transpose(1, 2, 0) * 255)) ### - # source_img_groups = source_img_groups.transpose(1, 2, 3, 0) ###???? - + if self.transform is not None: source_image = self.transform(source_image) @@ -191,7 +174,6 @@ def __getitem__(self, index): if not self.trigger_add: image = add_pixel_pattern_backdoor(image, self.pattern_mask,self.location) else: - # raise ValueError('not support additive trigger!') if self.pixel_pattern is None: self.pixel_pattern = ms.ops.full_like(image, 0) image = image + self.pixel_pattern @@ -201,10 +183,8 @@ def __getitem__(self, index): if self.party_num < 3: images = tuple(image for image in images_list) # 3,3,50,50 else: - # images = ms.ops.stack(tuple(image for image in images_list), 0) # 3,3,50,50 images = tuple(image for image in images_list) # 3,3,50,50 - # TODO not numpyarray - # print("111",len(images),len(images[0]),len(images[0][0]),len(images[0][0][0])) + return images, target, old_image diff --git a/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py b/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py index 0f57ee6..f17e63d 100644 --- a/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py +++ b/examples/community/vfl_attacks/datasets/multi_tabular_dataset.py @@ -12,21 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +""" +define multiple image dataset, support multiple parties, used for Criteo +""" from typing import Any, Callable, Optional, Tuple import mindspore.dataset as ds import mindspore as ms import os -""" -define multiple image dataset, support multiple parties, used for Criteo -""" - -""" -VisionDataset类的继承这里换为ds继承,具体的实现方式有些变化,感觉可能会有问题 -0625 改成object -""" - - def add_input_pattern_backdoor(data, trigger, location): data[location[0]:location[1]] = trigger return data diff --git a/examples/community/vfl_attacks/datasets/tabular_dataset.py b/examples/community/vfl_attacks/datasets/tabular_dataset.py index 57a9b92..2a7673e 100644 --- a/examples/community/vfl_attacks/datasets/tabular_dataset.py +++ b/examples/community/vfl_attacks/datasets/tabular_dataset.py @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +""" +define image dataset, only support two parties, used for CRITEO +""" from typing import Any, Callable, Optional, Tuple import mindspore.dataset as ds import mindspore as ms import os from MindsporeCode.common.utils import FeatureTuple -""" -define image dataset, only support two parties, used for CRITEO -""" + def add_input_pattern_backdoor(data, trigger, location): data[location[0]:location[1]] = trigger @@ -98,9 +99,7 @@ def __getitem__(self, index: int) -> Tuple[Tuple[Any, Any], Any, Any]: img_b = add_input_pattern_backdoor(img_b, self.pattern_mask, self.location) return (img_a, img_b), target, old_imgb - # return {'party_data': [img_a, img_b]}, target, old_imgb - # return img_a, img_b, target, old_imgb - # return FeatureTuple(img_a, img_b), target, old_imgb + def __len__(self) -> int: return len(self.data) diff --git a/examples/community/vfl_attacks/evaluate/MainTask.py b/examples/community/vfl_attacks/evaluate/MainTask.py index d5998b5..8f4a3bd 100644 --- a/examples/community/vfl_attacks/evaluate/MainTask.py +++ b/examples/community/vfl_attacks/evaluate/MainTask.py @@ -62,24 +62,12 @@ def run(self): self.VFL_framework.train() if self.args['backdoor'] != 'lr_ba' and not self.args['load_time']: self.VFL_framework.write() - # inference phase attack - # if self.args['backdoor'] == 'lr_ba': - # _,_,_, record_infer_train_acc, record_infer_test_acc = lr_ba_attack(self.VFL_framework, train_loader=self.init_vfl.train_loader, - # test_loader=self.init_vfl.test_loader, - # backdoor_train_loader=self.init_vfl.backdoor_train_loader, - # backdoor_test_loader=self.init_vfl.backdoor_test_loader, - # backdoor_indices=self.init_vfl.backdoor_indices, - # labeled_loader=self.init_vfl.labeled_loader, - # unlabeled_loader=self.init_vfl.unlabeled_loader) if not self.args['load_time']: self.VFL_framework.record_results = [self.VFL_framework.record_loss[-1], self.VFL_framework.train_acc, self.VFL_framework.test_acc, self.VFL_framework.backdoor_acc] - # logging.info('self.VFL_framework.record_loss[:6]: {}'.format(self.VFL_framework.record_loss[:6])) logging.info('self.VFL_framework.record_results: {}'.format(self.VFL_framework.record_results)) # visualization image result = visualization_with_images(self.VFL_framework) - # print('result: {}'.format(result)) - # 0524 # if result is not None: if result[0] is not None: image_str, y_clean, y_backdoor = result @@ -87,7 +75,6 @@ def run(self): with open(output_txt_path, "w") as txt_file: txt_file.write(image_str) append_predictions_to_file(output_txt_path, y_clean, y_backdoor) - # 0627适配criteo只返回标签没有图片 elif self.args['dataset'] == 'criteo': # for criteo dataset, we do not have image image_str, y_clean, y_backdoor = result @@ -120,7 +107,6 @@ def run(self): self.VFL_framework.train() if 'model_completion' not in self.args['label_inference_attack']: self.VFL_framework.record_results = [self.VFL_framework.record_loss[-1], self.VFL_framework.train_acc, self.VFL_framework.test_acc, self.VFL_framework.inference_acc] - # logging.info('self.VFL_framework.record_loss[:6]: {}'.format(self.VFL_framework.record_loss[:6])) logging.info('self.VFL_framework.record_results: {}'.format(self.VFL_framework.record_results)) if 'model_completion' in self.args['label_inference_attack']: record_infer_train_acc, record_infer_test_acc = passive_model_completion(self.args, self.init_vfl, self.VFL_framework) diff --git a/examples/community/vfl_attacks/evaluate/args_line.py b/examples/community/vfl_attacks/evaluate/args_line.py index 838587a..82d415d 100644 --- a/examples/community/vfl_attacks/evaluate/args_line.py +++ b/examples/community/vfl_attacks/evaluate/args_line.py @@ -20,9 +20,6 @@ def run_mindspore(input_args): - # print("当前工作目录:", os.getcwd()) - # print("尝试加载配置文件路径:", os.path.abspath(input_args['config'])) - args = get_args(input_args['config'], input_args['file_time']) input_args['step_gamma'] = 0.1 @@ -37,8 +34,6 @@ def run_mindspore(input_args): args['passive_bottom_gamma'] = input_args['step_gamma'] args['active_bottom_gamma'] = input_args['step_gamma'] args['active_top_gamma'] = input_args['step_gamma'] - # args['active_top_trainable'] = input_args['top_model'] - # args['n_passive_party'] = input_args['n_party'] - 1 args['aggregate'] = input_args['aggregate'] args['cuda'] = input_args['cuda'] args['file_time'] = input_args['file_time'] @@ -55,10 +50,7 @@ def run_mindspore(input_args): args['trigger_add'] = False if args['cuda']: - # mindspore.context.set_context(device_target="GPU") mindspore.context.set_context(device_target="GPU", device_id=0) - # from mindspore import Profiler - # profiler = Profiler(output_path="profiler_data") else: mindspore.context.set_context(device_target="CPU") @@ -81,13 +73,6 @@ def run_mindspore(input_args): normal_train.define() normal_train.run() elif args['backdoor'] != 'no': - # if args['backdoor'] in ['lr_ba','villain']: - # return - # if args['label_inference_attack'] == 'g_r': - # args['model_type'] = 'Resnet' - - # if args['backdoor'] == 'lr_ba': - # return if args['label_inference_attack'] in ['g_r', 'villain']: args['model_type'] = 'Resnet' @@ -101,8 +86,6 @@ def run_mindspore(input_args): args['amplify_ratio'] = 2 args['m_dimension'] = 10 args['epsilon'] = 0.4 - # if args['label_inference_attack'] == 'g_r': - # args['amplify_ratio'] = 1 if args['backdoor'] == 'villain': args['trigger'] = 'feature' args['trigger_add'] = True @@ -114,12 +97,9 @@ def run_mindspore(input_args): Backdoor_attack.define() Backdoor_attack.run() elif args['label_inference_attack'] != 'no': - # if args['label_inference_attack'] in ['passive_model_completion', 'active_model_completion']: - # return if args['label_inference_attack'] == 'direct_attack': args['model_type'] = 'Resnet' Label_inference_attack = Label_inference(args) Label_inference_attack.define() Label_inference_attack.run() - # profiler.analyse() diff --git a/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py b/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py index aa1b885..8a24d4f 100644 --- a/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py +++ b/examples/community/vfl_attacks/methods/direct_attack/direct_attack_vfl.py @@ -43,8 +43,6 @@ def train(self): for batch_idx, (X, Y_batch, old_imgb, indices) in enumerate(self.train_loader): party_X_train_batch_dict = dict() if self.args['n_passive_party'] < 2: - # X = ops.transpose(X, (1, 0, 2, 3, 4)) - # 0627修改适配criteo if self.args['dataset'] != 'criteo': X = ops.transpose(X, (1, 0, 2, 3, 4)) else: @@ -71,12 +69,9 @@ def train(self): print("dataset: ", self.args['dataset']) print("image shape: ", image.shape) - # 0523临时修改 if self.args['dataset'] != 'bhi' and self.args['dataset'] != 'criteo': - image = image.transpose(1, 2, 0) # 32,32,3 - # print("image shape(alt): ", image.shape) + image = image.transpose(1, 2, 0) - # image = Image.fromarray(image) if self.args['dataset'] != 'criteo': if len(image.shape) == 4: image = Image.fromarray(image[0]) @@ -84,11 +79,6 @@ def train(self): image = Image.fromarray(image) buffered = BytesIO() - # 0627修改适配criteo - # image.save(buffered, format="PNG") - # image_str = base64.b64encode(buffered.getvalue()).decode('utf-8') - # with open(output_txt_path, "w") as txt_file: - # txt_file.write(image_str) # criteo if self.args["dataset"] != "criteo": image.save(buffered, format="PNG") @@ -123,10 +113,4 @@ def train(self): self.record_train_acc.append(self.train_acc) self.record_test_acc.append(self.test_acc) self.record_loss.append(ave_loss) - self.record_attack_metric.append(self.inference_acc) - # draw fig and save it to output_logs - # file_path = OUTPUT_path - # plot_and_save(self.record_loss, 'loss', file_path + 'A_'+str(ep+1)) - # plot_and_save(self.record_test_acc, 'test_acc', file_path + 'B_'+str(ep+1)) - # if len(self.record_attack_metric) > 0: - # plot_and_save(self.record_attack_metric, 'attack_metric', file_path + 'C_'+str(ep+1)) + self.record_attack_metric.append(self.inference_acc) \ No newline at end of file diff --git a/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py b/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py index 5891040..8ad9301 100644 --- a/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py +++ b/examples/community/vfl_attacks/methods/g_r/g_r_passive_party.py @@ -24,7 +24,6 @@ class GRPassiveModel(VFLPassiveModel): def __init__(self, bottom_model, amplify_ratio=1, id=None, args=None): - # VFLPassiveModel.__init__(self, bottom_model, id=id, args=args) super(GRPassiveModel, self).__init__(bottom_model, id=id, args=args) self.backdoor_indices = None self.target_grad = None @@ -35,7 +34,6 @@ def __init__(self, bottom_model, amplify_ratio=1, id=None, args=None): self.pair_set = dict() self.target_gradients = dict() self.backdoor_X = dict() - # super().__init__(bottom_model) def set_epoch(self, epoch): self.epoch = epoch diff --git a/examples/community/vfl_attacks/methods/villain/villain_passive_party.py b/examples/community/vfl_attacks/methods/villain/villain_passive_party.py index 339ac20..34ff1f9 100644 --- a/examples/community/vfl_attacks/methods/villain/villain_passive_party.py +++ b/examples/community/vfl_attacks/methods/villain/villain_passive_party.py @@ -37,8 +37,6 @@ def __init__(self, bottom_model, amplify_ratio=1, args=None, id=None): self.backdoor_X = dict() self.args = args - # super().__init__(bottom_model, args=args) - self.attack = False self.feature_pattern = None self.mask = None @@ -88,7 +86,6 @@ def set_backdoor_indices(self, target_indices): def set_batch(self, X, indices): self.X = X self.indices = indices - # self.X.requires_grad = True def receive_gradients(self, gradients): gradients = gradients.copy() diff --git a/examples/community/vfl_attacks/methods/villain/villain_vfl.py b/examples/community/vfl_attacks/methods/villain/villain_vfl.py index 66bc893..1bb726c 100644 --- a/examples/community/vfl_attacks/methods/villain/villain_vfl.py +++ b/examples/community/vfl_attacks/methods/villain/villain_vfl.py @@ -57,15 +57,9 @@ def train(self): self.set_current_epoch(ep) for batch_idx, (X, Y_batch, old_imgb, indices) in enumerate(self.train_loader): - # X: B,K,3,32,16 tensor - # import traceback - # print("DEBUG: backdoor_label = ", self.args['backdoor_label']) - # traceback.print_stack() party_X_train_batch_dict = dict() if self.args['n_passive_party'] < 2: - # X = ops.transpose(X, (1, 0, 2, 3, 4)) - # 0627修改处理criteo if self.args['dataset'] != 'criteo': X = ops.transpose(X, (1, 0, 2, 3, 4)) else: @@ -125,8 +119,6 @@ def train(self): if Y_batch[i] == self.args['backdoor_label']: target_indices.append(i) if self.args['n_passive_party'] < 2: - # X = ops.transpose(X, (1, 0, 2, 3, 4)) - # 0627修改处理criteo if self.args['dataset'] != 'criteo': X = ops.transpose(X, (1, 0, 2, 3, 4)) else: @@ -149,12 +141,11 @@ def train(self): norms = 0 for target_feature in self.target_sample: norms += ms.ops.norm(target_feature, 2) - # 0621 epoch>5报错 - # std_norms = ms.ops.std(features, axis=0) + std_norms = self.safe_std(features, axis=0) stride_pattern = [0] * len(features[0]) temp = [] - # print(self.args['m_dimension'] // 2) + for i in range(self.args['m_dimension'] // 2): if i % 2 == 0: temp = temp + [-1, -1]