@@ -27,7 +27,7 @@ import mindspore.nn as nn | |||
from mindspore import Tensor | |||
from mindspore import context | |||
from mindspore.nn.optim.momentum import Momentum | |||
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig | |||
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor | |||
from mindspore.train.model import Model | |||
from mindspore.train.serialization import load_param_into_net, load_checkpoint | |||
from mindarmour.utils import LogUtil | |||
@@ -187,12 +187,13 @@ if __name__ == '__main__': | |||
amp_level="O2", keep_batchnorm_fp32=False, loss_scale_manager=None) | |||
# checkpoint save | |||
callbacks = [LossMonitor()] | |||
if args.rank_save_ckpt_flag: | |||
ckpt_config = CheckpointConfig(save_checkpoint_steps=args.ckpt_interval*args.steps_per_epoch, | |||
keep_checkpoint_max=args.ckpt_save_max) | |||
ckpt_cb = ModelCheckpoint(config=ckpt_config, | |||
directory=args.outputs_dir, | |||
prefix='{}'.format(args.rank)) | |||
callbacks = ckpt_cb | |||
callbacks.append(ckpt_cb) | |||
model.train(args.max_epoch, dataset, callbacks=callbacks) |
@@ -51,7 +51,7 @@ def test_lenet_mnist_coverage(): | |||
train_images = np.concatenate(train_images, axis=0) | |||
# initialize fuzz test with training dataset | |||
model_fuzz_test = ModelCoverageMetrics(model, 10000, 10, train_images) | |||
model_fuzz_test = ModelCoverageMetrics(model, 10, 1000, train_images) | |||
# fuzz test with original test data | |||
# get test data | |||
@@ -41,12 +41,20 @@ def test_lenet_mnist_fuzzing(): | |||
mutate_config = [{'method': 'Blur', | |||
'params': {'auto_param': True}}, | |||
{'method': 'Contrast', | |||
'params': {'factor': 2}}, | |||
'params': {'auto_param': True}}, | |||
{'method': 'Translate', | |||
'params': {'x_bias': 0.1, 'y_bias': 0.2}}, | |||
'params': {'auto_param': True}}, | |||
{'method': 'Brightness', | |||
'params': {'auto_param': True}}, | |||
{'method': 'Noise', | |||
'params': {'auto_param': True}}, | |||
{'method': 'Scale', | |||
'params': {'auto_param': True}}, | |||
{'method': 'Shear', | |||
'params': {'auto_param': True}}, | |||
{'method': 'FGSM', | |||
'params': {'eps': 0.1, 'alpha': 0.1}} | |||
] | |||
'params': {'eps': 0.3, 'alpha': 0.1}} | |||
] | |||
# get training data | |||
data_list = "./MNIST_unzip/train" | |||
@@ -59,7 +67,7 @@ def test_lenet_mnist_fuzzing(): | |||
train_images = np.concatenate(train_images, axis=0) | |||
# initialize fuzz test with training dataset | |||
model_coverage_test = ModelCoverageMetrics(model, 1000, 10, train_images) | |||
model_coverage_test = ModelCoverageMetrics(model, 10, 1000, train_images) | |||
# fuzz test with original test data | |||
# get test data | |||
@@ -79,7 +87,7 @@ def test_lenet_mnist_fuzzing(): | |||
# make initial seeds | |||
for img, label in zip(test_images, test_labels): | |||
initial_seeds.append([img, label, 0]) | |||
initial_seeds.append([img, label]) | |||
initial_seeds = initial_seeds[:100] | |||
model_coverage_test.calculate_coverage( | |||
@@ -11,6 +11,7 @@ from .monitor.monitor import RDPMonitor | |||
from .monitor.monitor import ZCDPMonitor | |||
from .optimizer.optimizer import DPOptimizerClassFactory | |||
from .train.model import DPModel | |||
from .evaluation.membership_inference import MembershipInference | |||
__all__ = ['NoiseGaussianRandom', | |||
'NoiseAdaGaussianRandom', | |||
@@ -21,4 +22,5 @@ __all__ = ['NoiseGaussianRandom', | |||
'RDPMonitor', | |||
'ZCDPMonitor', | |||
'DPOptimizerClassFactory', | |||
'DPModel'] | |||
'DPModel', | |||
'MembershipInference'] |
@@ -21,6 +21,11 @@ from sklearn.ensemble import RandomForestClassifier | |||
from sklearn.model_selection import GridSearchCV | |||
from sklearn.model_selection import RandomizedSearchCV | |||
from mindarmour.utils.logger import LogUtil | |||
LOGGER = LogUtil.get_instance() | |||
TAG = "Attacker" | |||
def _attack_knn(features, labels, param_grid): | |||
""" | |||
@@ -114,17 +119,31 @@ def get_attack_model(features, labels, config): | |||
features (numpy.ndarray): Loss and logits characteristics of each sample. | |||
labels (numpy.ndarray): Labels of each sample whether belongs to training set. | |||
config (dict): Config of attacker, with key in ["method", "params"]. | |||
The format is {"method": "knn", "params": {"n_neighbors": [3, 5, 7]}}, | |||
params of each method must within the range of changeable parameters. | |||
Tips of params implement can be found in | |||
"https://scikit-learn.org/0.16/modules/generated/sklearn.grid_search.GridSearchCV.html". | |||
Returns: | |||
sklearn.BaseEstimator, trained model specify by config["method"]. | |||
Examples: | |||
>>> features = np.random.randn(10, 10) | |||
>>> labels = np.random.randint(0, 2, 10) | |||
>>> config = {"method": "knn", "params": {"n_neighbors": [3, 5, 7]}} | |||
>>> attack_model = get_attack_model(features, labels, config) | |||
""" | |||
method = str.lower(config["method"]) | |||
if method == "knn": | |||
return _attack_knn(features, labels, config["params"]) | |||
if method in ["lr", "logitic regression"]: | |||
if method == "lr": | |||
return _attack_lr(features, labels, config["params"]) | |||
if method == "mlp": | |||
return _attack_mlpc(features, labels, config["params"]) | |||
if method in ["rf", "random forest"]: | |||
if method == "rf": | |||
return _attack_rf(features, labels, config["params"]) | |||
raise ValueError("Method {} is not support.".format(config["method"])) | |||
msg = "Method {} is not supported.".format(config["method"]) | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) |
@@ -19,10 +19,14 @@ import numpy as np | |||
import mindspore as ms | |||
from mindspore.train import Model | |||
import mindspore.nn as nn | |||
import mindspore.context as context | |||
from mindspore.dataset.engine import Dataset | |||
from mindspore import Tensor | |||
from mindarmour.diff_privacy.evaluation.attacker import get_attack_model | |||
from mindarmour.utils.logger import LogUtil | |||
LOGGER = LogUtil.get_instance() | |||
TAG = "MembershipInference" | |||
def _eval_info(pred, truth, option): | |||
""" | |||
@@ -42,7 +46,9 @@ def _eval_info(pred, truth, option): | |||
ValueError, value of parameter option must be in ["precision", "accuracy", "recall"]. | |||
""" | |||
if pred.size == 0 or truth.size == 0: | |||
raise ValueError("Size of pred or truth is 0.") | |||
msg = "Size of pred or truth is 0." | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) | |||
if option == "accuracy": | |||
count = np.sum(pred == truth) | |||
@@ -58,7 +64,25 @@ def _eval_info(pred, truth, option): | |||
return -1 | |||
return count / np.sum(truth) | |||
raise ValueError("The metric value {} is undefined.".format(option)) | |||
msg = "The metric value {} is undefined.".format(option) | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) | |||
def _softmax_cross_entropy(logits, labels): | |||
""" | |||
Calculate the SoftmaxCrossEntropy result between logits and labels. | |||
Args: | |||
logits (numpy.ndarray): Numpy array of shape(N, C). | |||
labels (numpy.ndarray): Numpy array of shape(N, ) | |||
Returns: | |||
numpy.ndarray: Numpy array of shape(N, ), containing loss value for each vector in logits. | |||
""" | |||
labels = np.eye(logits.shape[1])[labels].astype(np.int32) | |||
logits = np.exp(logits) / np.sum(np.exp(logits), axis=1, keepdims=True) | |||
return -1*np.sum(labels*np.log(logits), axis=1) | |||
class MembershipInference: | |||
@@ -66,22 +90,23 @@ class MembershipInference: | |||
Evaluation proposed by Shokri, Stronati, Song and Shmatikov is a grey-box attack. | |||
The attack requires obtain loss or logits results of training samples. | |||
References: Reza Shokri, Marco Stronati, Congzheng Song, Vitaly Shmatikov. | |||
References: `Reza Shokri, Marco Stronati, Congzheng Song, Vitaly Shmatikov. | |||
Membership Inference Attacks against Machine Learning Models. 2017. | |||
arXiv:1610.05820v2 <https://arxiv.org/abs/1610.05820v2>`_ | |||
<https://arxiv.org/abs/1610.05820v2>`_ | |||
Args: | |||
model (Model): Target model. | |||
Examples: | |||
>>> # ds_train, eval_train are non-overlapping datasets from training dataset. | |||
>>> # eval_train, eval_test are non-overlapping datasets from test dataset. | |||
>>> train_1, train_2 are non-overlapping datasets from training dataset of target model. | |||
>>> test_1, test_2 are non-overlapping datasets from test dataset of target model. | |||
>>> We use train_1, test_1 to train attack model, and use train_2, test_2 to evaluate attack model. | |||
>>> model = Model(network=net, loss_fn=loss, optimizer=opt, metrics={'acc', 'loss'}) | |||
>>> inference_model = MembershipInference(model) | |||
>>> config = [{"method": "KNN", "params": {"n_neighbors": [3, 5, 7]}}] | |||
>>> inference_model.train(ds_train, ds_test, config) | |||
>>> inference_model.train(train_1, test_1, config) | |||
>>> metrics = ["precision", "recall", "accuracy"] | |||
>>> result = inference_model.eval(eval_train, eval_test, metrics) | |||
>>> result = inference_model.eval(train_2, test_2, metrics) | |||
Raises: | |||
TypeError: If type of model is not mindspore.train.Model. | |||
@@ -89,8 +114,12 @@ class MembershipInference: | |||
def __init__(self, model): | |||
if not isinstance(model, Model): | |||
raise TypeError("Type of model must be {}, but got {}.".format(type(Model), type(model))) | |||
msg = "Type of parameter 'model' must be Model, but got {}.".format(type(model)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
self.model = model | |||
self.method_list = ["knn", "lr", "mlp", "rf"] | |||
self.attack_list = [] | |||
def train(self, dataset_train, dataset_test, attack_config): | |||
@@ -101,11 +130,48 @@ class MembershipInference: | |||
Args: | |||
dataset_train (mindspore.dataset): The training dataset for the target model. | |||
dataset_test (mindspore.dataset): The test set for the target model. | |||
attack_config (list): Parameter setting for the attack model. | |||
attack_config (list): Parameter setting for the attack model. The format is | |||
[{"method": "knn", "params": {"n_neighbors": [3, 5, 7]}}, | |||
{"method": "lr", "params": {"C": np.logspace(-4, 2, 10)}}]. | |||
The support methods list is in self.method_list, and the params of each method | |||
must within the range of changeable parameters. Tips of params implement | |||
can be found in | |||
"https://scikit-learn.org/0.16/modules/generated/sklearn.grid_search.GridSearchCV.html". | |||
Raises: | |||
ValueError: If the method in attack_config is not in ["LR", "KNN", "RF", "MLPC"]. | |||
KeyError: If each config in attack_config doesn't have keys {"method", "params"} | |||
ValueError: If the method(case insensitive) in attack_config is not in ["lr", "knn", "rf", "mlp"]. | |||
""" | |||
if not isinstance(dataset_train, Dataset): | |||
msg = "Type of parameter 'dataset_train' must be Dataset, but got {}".format(type(dataset_train)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if not isinstance(dataset_test, Dataset): | |||
msg = "Type of parameter 'test_train' must be Dataset, but got {}".format(type(dataset_train)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if not isinstance(attack_config, list): | |||
msg = "Type of parameter 'attack_config' must be list, but got {}.".format(type(attack_config)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
for config in attack_config: | |||
if not isinstance(config, dict): | |||
msg = "Type of each config in 'attack_config' must be dict, but got {}.".format(type(config)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if {"params", "method"} != set(config.keys()): | |||
msg = "Each config in attack_config must have keys 'method' and 'params'," \ | |||
"but your key value is {}.".format(set(config.keys())) | |||
LOGGER.error(TAG, msg) | |||
raise KeyError(msg) | |||
if str.lower(config["method"]) not in self.method_list: | |||
msg = "Method {} is not support.".format(config["method"]) | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) | |||
features, labels = self._transform(dataset_train, dataset_test) | |||
for config in attack_config: | |||
self.attack_list.append(get_attack_model(features, labels, config)) | |||
@@ -124,6 +190,28 @@ class MembershipInference: | |||
Returns: | |||
list, Each element contains an evaluation indicator for the attack model. | |||
""" | |||
if not isinstance(dataset_train, Dataset): | |||
msg = "Type of parameter 'dataset_train' must be Dataset, but got {}".format(type(dataset_train)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if not isinstance(dataset_test, Dataset): | |||
msg = "Type of parameter 'test_train' must be Dataset, but got {}".format(type(dataset_train)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if not isinstance(metrics, (list, tuple)): | |||
msg = "Type of parameter 'config' must be Union[list, tuple], but got {}.".format(type(metrics)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
metrics = set(metrics) | |||
metrics_list = {"precision", "accuracy", "recall"} | |||
if not metrics <= metrics_list: | |||
msg = "Element in 'metrics' must be in {}, but got {}.".format(metrics_list, metrics) | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) | |||
result = [] | |||
features, labels = self._transform(dataset_train, dataset_test) | |||
for attacker in self.attack_list: | |||
@@ -170,17 +258,12 @@ class MembershipInference: | |||
N is the number of sample. C = 1 + dim(logits). | |||
- numpy.ndarray, Labels for each sample, Shape is (N,). | |||
""" | |||
if context.get_context("device_target") != "Ascend": | |||
raise RuntimeError("The target device must be Ascend, " | |||
"but current is {}.".format(context.get_context("device_target"))) | |||
loss_logits = np.array([]) | |||
for batch in dataset_x.create_dict_iterator(): | |||
batch_data = Tensor(batch['image'], ms.float32) | |||
batch_labels = Tensor(batch['label'], ms.int32) | |||
batch_logits = self.model.predict(batch_data) | |||
loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, is_grad=False, reduction=None) | |||
batch_loss = loss(batch_logits, batch_labels).asnumpy() | |||
batch_logits = batch_logits.asnumpy() | |||
batch_labels = batch['label'].astype(np.int32) | |||
batch_logits = self.model.predict(batch_data).asnumpy() | |||
batch_loss = _softmax_cross_entropy(batch_logits, batch_labels) | |||
batch_feature = np.hstack((batch_loss.reshape(-1, 1), batch_logits)) | |||
if loss_logits.size == 0: | |||
@@ -193,5 +276,7 @@ class MembershipInference: | |||
elif label == 0: | |||
labels = np.zeros(len(loss_logits), np.int32) | |||
else: | |||
raise ValueError("The value of label must be 0 or 1, but got {}.".format(label)) | |||
msg = "The value of label must be 0 or 1, but got {}.".format(label) | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) | |||
return loss_logits, labels |
@@ -22,7 +22,8 @@ from mindspore import Tensor | |||
from mindarmour.fuzzing.model_coverage_metrics import ModelCoverageMetrics | |||
from mindarmour.utils._check_param import check_model, check_numpy_param, \ | |||
check_param_multi_types, check_norm_level, check_param_in_range | |||
check_param_multi_types, check_norm_level, check_param_in_range, \ | |||
check_param_type, check_int_positive | |||
from mindarmour.fuzzing.image_transform import Contrast, Brightness, Blur, \ | |||
Noise, Translate, Scale, Shear, Rotate | |||
from mindarmour.attacks import FastGradientSignMethod, \ | |||
@@ -93,7 +94,7 @@ class Fuzzer: | |||
>>> {'method': 'Translate', 'params': {'x_bias': 0.1, 'y_bias': 0.2}}, | |||
>>> {'method': 'FGSM', 'params': {'eps': 0.1, 'alpha': 0.1}}] | |||
>>> train_images = np.random.rand(32, 1, 32, 32).astype(np.float32) | |||
>>> model_fuzz_test = Fuzzer(model, train_images, 1000, 10) | |||
>>> model_fuzz_test = Fuzzer(model, train_images, 10, 1000) | |||
>>> samples, labels, preds, strategies, report = model_fuzz_test.fuzzing(mutate_config, initial_seeds) | |||
""" | |||
@@ -101,8 +102,9 @@ class Fuzzer: | |||
self._target_model = check_model('model', target_model, Model) | |||
train_dataset = check_numpy_param('train_dataset', train_dataset) | |||
self._coverage_metrics = ModelCoverageMetrics(target_model, | |||
neuron_num, | |||
segmented_num, | |||
neuron_num, train_dataset) | |||
train_dataset) | |||
# Allowed mutate strategies so far. | |||
self._strategies = {'Contrast': Contrast, 'Brightness': Brightness, | |||
'Blur': Blur, 'Noise': Noise, 'Translate': Translate, | |||
@@ -115,23 +117,21 @@ class Fuzzer: | |||
'Noise'] | |||
self._attacks_list = ['FGSM', 'PGD', 'MDIIM'] | |||
self._attack_param_checklists = { | |||
'FGSM': {'params': {'eps': {'dtype': [float, int], 'range': [0, 1]}, | |||
'alpha': {'dtype': [float, int], | |||
'FGSM': {'params': {'eps': {'dtype': [float], 'range': [0, 1]}, | |||
'alpha': {'dtype': [float], | |||
'range': [0, 1]}, | |||
'bounds': {'dtype': [list, tuple], | |||
'range': None}}}, | |||
'PGD': {'params': {'eps': {'dtype': [float, int], 'range': [0, 1]}, | |||
'eps_iter': {'dtype': [float, int], | |||
'range': [0, 1e5]}, | |||
'nb_iter': {'dtype': [float, int], | |||
'bounds': {'dtype': [tuple]}}}, | |||
'PGD': {'params': {'eps': {'dtype': [float], 'range': [0, 1]}, | |||
'eps_iter': {'dtype': [float], | |||
'range': [0, 1]}, | |||
'nb_iter': {'dtype': [int], | |||
'range': [0, 1e5]}, | |||
'bounds': {'dtype': [list, tuple], | |||
'range': None}}}, | |||
'bounds': {'dtype': [tuple]}}}, | |||
'MDIIM': { | |||
'params': {'eps': {'dtype': [float, int], 'range': [0, 1]}, | |||
'norm_level': {'dtype': [str], 'range': None}, | |||
'prob': {'dtype': [float, int], 'range': [0, 1]}, | |||
'bounds': {'dtype': [list, tuple], 'range': None}}}} | |||
'params': {'eps': {'dtype': [float], 'range': [0, 1]}, | |||
'norm_level': {'dtype': [str]}, | |||
'prob': {'dtype': [float], 'range': [0, 1]}, | |||
'bounds': {'dtype': [tuple]}}}} | |||
def fuzzing(self, mutate_config, initial_seeds, coverage_metric='KMNC', | |||
eval_metrics='auto', max_iters=10000, mutate_num_per_seed=20): | |||
@@ -140,16 +140,29 @@ class Fuzzer: | |||
Args: | |||
mutate_config (list): Mutate configs. The format is | |||
[{'method': 'Blur', 'params': {'auto_param': True}}, {'method': 'Contrast', 'params': {'factor': 2}}]. | |||
The support methods list is in `self._strategies`, and the params of each | |||
method must within the range of changeable parameters. | |||
initial_seeds (numpy.ndarray): Initial seeds used to generate | |||
mutated samples. | |||
coverage_metric (str): Model coverage metric of neural networks. | |||
Default: 'KMNC'. | |||
eval_metrics (Union[list, tuple, str]): Evaluation metrics. If the type is 'auto', | |||
it will calculate all the metrics, else if the type is list or tuple, it will | |||
calculate the metrics specified by user. Default: 'auto'. | |||
[{'method': 'Blur', 'params': {'auto_param': True}}, | |||
{'method': 'Contrast', 'params': {'factor': 2}}]. The | |||
supported methods list is in `self._strategies`, and the | |||
params of each method must within the range of changeable parameters. | |||
Supported methods are grouped in three types: | |||
Firstly, pixel value based transform methods include: | |||
'Contrast', 'Brightness', 'Blur' and 'Noise'. Secondly, affine | |||
transform methods include: 'Translate', 'Scale', 'Shear' and | |||
'Rotate'. Thirdly, attack methods include: 'FGSM', 'PGD' and 'MDIIM'. | |||
`mutate_config` must have method in the type of pixel value based | |||
transform methods. The way of setting parameters for first and | |||
second type methods can be seen in 'mindarmour/fuzzing/image_transform.py'. | |||
For third type methods, you can refer to the corresponding class. | |||
initial_seeds (list[list]): Initial seeds used to generate mutated | |||
samples. The format of initial seeds is [[image_data, label], | |||
[...], ...]. | |||
coverage_metric (str): Model coverage metric of neural networks. All | |||
supported metrics are: 'KMNC', 'NBC', 'SNAC'. Default: 'KMNC'. | |||
eval_metrics (Union[list, tuple, str]): Evaluation metrics. If the | |||
type is 'auto', it will calculate all the metrics, else if the | |||
type is list or tuple, it will calculate the metrics specified | |||
by user. All supported evaluate methods are 'accuracy', | |||
'attack_success_rate', 'kmnc', 'nbc', 'snac'. Default: 'auto'. | |||
max_iters (int): Max number of select a seed to mutate. | |||
Default: 10000. | |||
mutate_num_per_seed (int): The number of mutate times for a seed. | |||
@@ -173,16 +186,10 @@ class Fuzzer: | |||
ValueError: If metric in list `eval_metrics` is not in ['accuracy', 'attack_success_rate', | |||
'kmnc', 'nbc', 'snac']. | |||
""" | |||
eval_metrics_ = None | |||
if isinstance(eval_metrics, (list, tuple)): | |||
eval_metrics_ = [] | |||
avaliable_metrics = ['accuracy', 'attack_success_rate', 'kmnc', 'nbc', 'snac'] | |||
for elem in eval_metrics: | |||
if not isinstance(elem, str): | |||
msg = 'the type of metric in list `eval_metrics` must be str, but got {}.' \ | |||
.format(type(elem)) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if elem not in avaliable_metrics: | |||
msg = 'metric in list `eval_metrics` must be in {}, but got {}.' \ | |||
.format(avaliable_metrics, elem) | |||
@@ -203,7 +210,33 @@ class Fuzzer: | |||
raise TypeError(msg) | |||
# Check whether the mutate_config meet the specification. | |||
mutate_config = check_param_type('mutate_config', mutate_config, list) | |||
for config in mutate_config: | |||
check_param_type("config['params']", config['params'], dict) | |||
if set(config.keys()) != {'method', 'params'}: | |||
msg = "Config must contain 'method' and 'params', but got {}." \ | |||
.format(set(config.keys())) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if config['method'] not in self._strategies.keys(): | |||
msg = "Config methods must be in {}, but got {}." \ | |||
.format(self._strategies.keys(), config['method']) | |||
LOGGER.error(TAG, msg) | |||
raise TypeError(msg) | |||
if coverage_metric not in ['KMNC', 'NBC', 'SNAC']: | |||
msg = "coverage_metric must be in ['KMNC', 'NBC', 'SNAC'], but got {}." \ | |||
.format(coverage_metric) | |||
LOGGER.error(TAG, msg) | |||
raise ValueError(msg) | |||
max_iters = check_int_positive('max_iters', max_iters) | |||
mutate_num_per_seed = check_int_positive('mutate_num_per_seed', mutate_num_per_seed) | |||
mutates = self._init_mutates(mutate_config) | |||
initial_seeds = check_param_type('initial_seeds', initial_seeds, list) | |||
for seed in initial_seeds: | |||
check_param_type('seed', seed, list) | |||
check_numpy_param('seed[0]', seed[0]) | |||
check_numpy_param('seed[1]', seed[1]) | |||
seed.append(0) | |||
seed, initial_seeds = _select_next(initial_seeds) | |||
fuzz_samples = [] | |||
gt_labels = [] | |||
@@ -248,7 +281,7 @@ class Fuzzer: | |||
for index in range(len(samples)): | |||
mutate = samples[:index + 1] | |||
self._coverage_metrics.calculate_coverage(mutate.astype(np.float32)) | |||
if coverage_metric == "KMNC": | |||
if coverage_metric == 'KMNC': | |||
coverages.append(self._coverage_metrics.get_kmnc()) | |||
if coverage_metric == 'NBC': | |||
coverages.append(self._coverage_metrics.get_nbc()) | |||
@@ -357,18 +390,24 @@ class Fuzzer: | |||
dict, evaluate metrics include accuarcy, attack success rate | |||
and neural coverage. | |||
""" | |||
gt_labels = np.asarray(gt_labels) | |||
fuzz_preds = np.asarray(fuzz_preds) | |||
temp = np.argmax(gt_labels, axis=1) == np.argmax(fuzz_preds, axis=1) | |||
metrics_report = {} | |||
if metrics == 'auto' or 'accuracy' in metrics: | |||
gt_labels = np.asarray(gt_labels) | |||
fuzz_preds = np.asarray(fuzz_preds) | |||
acc = np.sum(temp) / np.size(temp) | |||
if temp.any(): | |||
acc = np.sum(temp) / np.size(temp) | |||
else: | |||
acc = 0 | |||
metrics_report['Accuracy'] = acc | |||
if metrics == 'auto' or 'attack_success_rate' in metrics: | |||
cond = [elem in self._attacks_list for elem in fuzz_strategies] | |||
temp = temp[cond] | |||
attack_success_rate = 1 - np.sum(temp) / np.size(temp) | |||
if temp.any(): | |||
attack_success_rate = 1 - np.sum(temp) / np.size(temp) | |||
else: | |||
attack_success_rate = None | |||
metrics_report['Attack_success_rate'] = attack_success_rate | |||
if metrics == 'auto' or 'kmnc' in metrics or 'nbc' in metrics or 'snac' in metrics: | |||
@@ -350,8 +350,10 @@ class Translate(ImageTransform): | |||
Translate an image. | |||
Args: | |||
x_bias ([int, float): X-direction translation, x=x+x_bias. Default: 0. | |||
y_bias ([int, float): Y-direction translation, y=y+y_bias. Default: 0. | |||
x_bias ([int, float): X-direction translation, x=x+x_bias*image_length. | |||
Default: 0. | |||
y_bias ([int, float): Y-direction translation, y=y+y_bias*image_wide. | |||
Default: 0. | |||
""" | |||
def __init__(self, x_bias=0, y_bias=0): | |||
@@ -363,8 +365,10 @@ class Translate(ImageTransform): | |||
Set translate parameters. | |||
Args: | |||
x_bias ([float, int]): X-direction translation, x=x+x_bias. Default: 0. | |||
y_bias ([float, int]): Y-direction translation, y=y+y_bias. Default: 0. | |||
x_bias ([float, int]): X-direction translation, x=x+x_bias*image_length. | |||
Default: 0. | |||
y_bias ([float, int]): Y-direction translation, y=y+y_bias*image_wide. | |||
Default: 0. | |||
auto_param (bool): True if auto generate parameters. Default: False. | |||
""" | |||
self.auto_param = auto_param | |||
@@ -579,7 +583,7 @@ class Rotate(ImageTransform): | |||
""" | |||
_, chw, normalized, gray3dim, image = self._check(image) | |||
img = to_pil(image) | |||
trans_image = img.rotate(self.angle, expand=True) | |||
trans_image = img.rotate(self.angle, expand=False) | |||
trans_image = self._original_format(trans_image, chw, normalized, | |||
gray3dim) | |||
return trans_image |
@@ -21,7 +21,7 @@ from mindspore import Tensor | |||
from mindspore import Model | |||
from mindarmour.utils._check_param import check_model, check_numpy_param, \ | |||
check_int_positive | |||
check_int_positive, check_param_multi_types | |||
from mindarmour.utils.logger import LogUtil | |||
LOGGER = LogUtil.get_instance() | |||
@@ -43,8 +43,8 @@ class ModelCoverageMetrics: | |||
Args: | |||
model (Model): The pre-trained model which waiting for testing. | |||
segmented_num (int): The number of segmented sections of neurons' output intervals. | |||
neuron_num (int): The number of testing neurons. | |||
segmented_num (int): The number of segmented sections of neurons' output intervals. | |||
train_dataset (numpy.ndarray): Training dataset used for determine | |||
the neurons' output boundaries. | |||
@@ -52,17 +52,18 @@ class ModelCoverageMetrics: | |||
ValueError: If neuron_num is too big (for example, bigger than 1e+9). | |||
Examples: | |||
>>> train_images = np.random.random((10000, 128)).astype(np.float32) | |||
>>> test_images = np.random.random((5000, 128)).astype(np.float32) | |||
>>> net = LeNet5() | |||
>>> train_images = np.random.random((10000, 1, 32, 32)).astype(np.float32) | |||
>>> test_images = np.random.random((5000, 1, 32, 32)).astype(np.float32) | |||
>>> model = Model(net) | |||
>>> model_fuzz_test = ModelCoverageMetrics(model, 10000, 10, train_images) | |||
>>> model_fuzz_test.test_adequacy_coverage_calculate(test_images) | |||
>>> model_fuzz_test = ModelCoverageMetrics(model, 10, 1000, train_images) | |||
>>> model_fuzz_test.calculate_coverage(test_images) | |||
>>> print('KMNC of this test is : %s', model_fuzz_test.get_kmnc()) | |||
>>> print('NBC of this test is : %s', model_fuzz_test.get_nbc()) | |||
>>> print('SNAC of this test is : %s', model_fuzz_test.get_snac()) | |||
""" | |||
def __init__(self, model, segmented_num, neuron_num, train_dataset): | |||
def __init__(self, model, neuron_num, segmented_num, train_dataset): | |||
self._model = check_model('model', model, Model) | |||
self._segmented_num = check_int_positive('segmented_num', segmented_num) | |||
self._neuron_num = check_int_positive('neuron_num', neuron_num) | |||
@@ -139,8 +140,8 @@ class ModelCoverageMetrics: | |||
Args: | |||
dataset (numpy.ndarray): Data for fuzz test. | |||
bias_coefficient (float): The coefficient used for changing the | |||
neurons' output boundaries. Default: 0. | |||
bias_coefficient (Union[int, float]): The coefficient used | |||
for changing the neurons' output boundaries. Default: 0. | |||
batch_size (int): The number of samples in a predict batch. | |||
Default: 32. | |||
@@ -148,8 +149,10 @@ class ModelCoverageMetrics: | |||
>>> model_fuzz_test = ModelCoverageMetrics(model, 10000, 10, train_images) | |||
>>> model_fuzz_test.calculate_coverage(test_images) | |||
""" | |||
dataset = check_numpy_param('dataset', dataset) | |||
batch_size = check_int_positive('batch_size', batch_size) | |||
bias_coefficient = check_param_multi_types('bias_coefficient', bias_coefficient, [int, float]) | |||
self._lower_bounds -= bias_coefficient*self._var | |||
self._upper_bounds += bias_coefficient*self._var | |||
intervals = (self._upper_bounds - self._lower_bounds) / self._segmented_num | |||
@@ -78,7 +78,11 @@ class LogUtil: | |||
def set_level(self, level): | |||
""" | |||
Set the logging level of this logger, level must be an integer or a | |||
string. | |||
string. Supported levels are 'NOTSET'(integer: 0), 'ERROR'(integer: 1-40), | |||
'WARNING'('WARN', integer: 1-30), 'INFO'(integer: 1-20) and 'DEBUG'(integer: 1-10). | |||
For example, if logger.set_level('WARNING') or logger.set_level(21), then | |||
logger.warn() and logger.error() in scripts would be printed while running, | |||
while logger.info() or logger.debug() would not be printed. | |||
Args: | |||
level (Union[int, str]): Level of logger. | |||
@@ -98,7 +98,7 @@ class GradWrapWithLoss(Cell): | |||
Examples: | |||
>>> data = Tensor(np.ones([1, 1, 32, 32]).astype(np.float32)*0.01) | |||
>>> label = Tensor(np.ones([1, 10]).astype(np.float32)) | |||
>>> labels = Tensor(np.ones([1, 10]).astype(np.float32)) | |||
>>> net = NET() | |||
>>> loss_fn = nn.SoftmaxCrossEntropyWithLogits() | |||
>>> loss_net = WithLossCell(net, loss_fn) | |||
@@ -71,7 +71,7 @@ def test_lenet_mnist_coverage_cpu(): | |||
# initialize fuzz test with training dataset | |||
training_data = (np.random.random((10000, 10))*20).astype(np.float32) | |||
model_fuzz_test = ModelCoverageMetrics(model, 10000, 10, training_data) | |||
model_fuzz_test = ModelCoverageMetrics(model, 10, 1000, training_data) | |||
# fuzz test with original test data | |||
# get test data | |||
@@ -105,7 +105,7 @@ def test_lenet_mnist_coverage_ascend(): | |||
# initialize fuzz test with training dataset | |||
training_data = (np.random.random((10000, 10))*20).astype(np.float32) | |||
model_fuzz_test = ModelCoverageMetrics(model, 10000, 10, training_data) | |||
model_fuzz_test = ModelCoverageMetrics(model, 10, 1000, training_data) | |||
# fuzz test with original test data | |||
# get test data | |||
@@ -102,7 +102,7 @@ def test_fuzzing_ascend(): | |||
] | |||
# initialize fuzz test with training dataset | |||
train_images = np.random.rand(32, 1, 32, 32).astype(np.float32) | |||
model_coverage_test = ModelCoverageMetrics(model, 1000, 10, train_images) | |||
model_coverage_test = ModelCoverageMetrics(model, 10, 1000, train_images) | |||
# fuzz test with original test data | |||
# get test data | |||
@@ -113,7 +113,7 @@ def test_fuzzing_ascend(): | |||
initial_seeds = [] | |||
# make initial seeds | |||
for img, label in zip(test_images, test_labels): | |||
initial_seeds.append([img, label, 0]) | |||
initial_seeds.append([img, label]) | |||
initial_seeds = initial_seeds[:100] | |||
model_coverage_test.calculate_coverage( | |||
@@ -148,7 +148,7 @@ def test_fuzzing_cpu(): | |||
] | |||
# initialize fuzz test with training dataset | |||
train_images = np.random.rand(32, 1, 32, 32).astype(np.float32) | |||
model_coverage_test = ModelCoverageMetrics(model, 1000, 10, train_images) | |||
model_coverage_test = ModelCoverageMetrics(model, 10, 1000, train_images) | |||
# fuzz test with original test data | |||
# get test data | |||
@@ -159,7 +159,7 @@ def test_fuzzing_cpu(): | |||
initial_seeds = [] | |||
# make initial seeds | |||
for img, label in zip(test_images, test_labels): | |||
initial_seeds.append([img, label, 0]) | |||
initial_seeds.append([img, label]) | |||
initial_seeds = initial_seeds[:100] | |||
model_coverage_test.calculate_coverage( | |||