diff --git a/.github/actions/yolo/run.sh b/.github/actions/yolo/run.sh index 7e688de68d..a8ca123287 100755 --- a/.github/actions/yolo/run.sh +++ b/.github/actions/yolo/run.sh @@ -3,6 +3,9 @@ exit_code=0 pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_pytorch_yolo.py --framework=pytorch --durations=0 -if [[ $? -ne 0 ]]; then exit_code=1; echo "Failed estimators/speech_recognition/test_pytorch_yolo tests"; fi +if [[ $? -ne 0 ]]; then exit_code=1; echo "Failed estimators/object_detection/test_pytorch_yolo tests"; fi + +pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_object_seeker_yolo.py --framework=pytorch --durations=0 +if [[ $? -ne 0 ]]; then exit_code=1; echo "Failed estimators/object_detection/test_object_seeker_yolo tests"; fi exit ${exit_code} diff --git a/.github/workflows/ci-huggingface.yml b/.github/workflows/ci-huggingface.yml new file mode 100644 index 0000000000..ed3056ad06 --- /dev/null +++ b/.github/workflows/ci-huggingface.yml @@ -0,0 +1,65 @@ +name: CI Huggingface +on: + # Run on manual trigger + workflow_dispatch: + + # Run on pull requests + pull_request: + paths-ignore: + - '*.md' + + # Run on merge queue + merge_group: + + # Run when pushing to main or dev branches + push: + branches: + - main + - dev* + + # Run scheduled CI flow daily + schedule: + - cron: '0 8 * * 0' + +jobs: + test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - name: Huggingface 4.30 + framework: huggingface + python: 3.9 + torch: 1.13.1+cpu + torchvision: 0.14.1+cpu + torchaudio: 0.13.1 + transformers: 4.30.2 + + name: ${{ matrix.name }} + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get -y -q install ffmpeg libavcodec-extra + python -m pip install --upgrade pip setuptools wheel + pip3 install -r requirements_test.txt + pip install tensorflow==2.10.1 + pip install keras==2.10.0 + pip install torch==${{ matrix.torch }} -f https://download.pytorch.org/whl/cpu/torch_stable.html + pip install torchvision==${{ matrix.torchvision }} -f https://download.pytorch.org/whl/cpu/torch_stable.html + pip install torchaudio==${{ matrix.torchaudio }} -f https://download.pytorch.org/whl/cpu/torch_stable.html + pip install transformers==${{ matrix.transformers }} + pip list + - name: Run Tests + run: ./run_tests.sh ${{ matrix.framework }} + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + fail_ci_if_error: true diff --git a/.github/workflows/ci-lingvo.yml b/.github/workflows/ci-lingvo.yml index ab7ab24822..631f3f539f 100644 --- a/.github/workflows/ci-lingvo.yml +++ b/.github/workflows/ci-lingvo.yml @@ -50,7 +50,7 @@ jobs: sudo apt-get update sudo apt-get -y -q install ffmpeg libavcodec-extra python -m pip install --upgrade pip setuptools wheel - pip install -q -r <(sed '/^scipy/d;/^matplotlib/d;/^pandas/d;/^statsmodels/d;/^numba/d;/^jax/d;/^h5py/d;/^Pillow/d;/^pytest/d;/^pytest-mock/d;/^torch/d;/^torchaudio/d;/^torchvision/d;/^xgboost/d;/^requests/d;/^tensorflow/d;/^keras/d;/^kornia/d;/^librosa/d;/^tqdm/d' requirements_test.txt) + pip install -q -r <(sed '/^scipy/d;/^matplotlib/d;/^pandas/d;/^statsmodels/d;/^numba/d;/^jax/d;/^h5py/d;/^Pillow/d;/^pytest/d;/^pytest-mock/d;/^torch/d;/^torchaudio/d;/^torchvision/d;/^xgboost/d;/^requests/d;/^tensorflow/d;/^keras/d;/^kornia/d;/^librosa/d;/^tqdm/d;/^timm/d' requirements_test.txt) pip install scipy==1.5.4 pip install matplotlib==3.3.4 pip install pandas==1.1.5 diff --git a/.github/workflows/ci-pytorch-object-detectors.yml b/.github/workflows/ci-pytorch-object-detectors.yml index 5f16750b85..049efc7cb7 100644 --- a/.github/workflows/ci-pytorch-object-detectors.yml +++ b/.github/workflows/ci-pytorch-object-detectors.yml @@ -52,6 +52,8 @@ jobs: run: pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_pytorch_faster_rcnn.py --framework=pytorch --durations=0 - name: Run Test Action - test_pytorch_detection_transformer run: pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_pytorch_detection_transformer.py --framework=pytorch --durations=0 + - name: Run Test Action - test_pytorch_object_seeker_faster_rcnn + run: pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_object_seeker_faster_rcnn.py --framework=pytorch --durations=0 - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: diff --git a/art/attacks/attack.py b/art/attacks/attack.py index bafd878679..4a6d5167a7 100644 --- a/art/attacks/attack.py +++ b/art/attacks/attack.py @@ -159,6 +159,8 @@ def set_params(self, **kwargs) -> None: for key, value in kwargs.items(): if key in self.attack_params: setattr(self, key, value) + else: + raise ValueError(f'The attribute "{key}" cannot be set for this attack.') self._check_params() def _check_params(self) -> None: @@ -186,6 +188,19 @@ def is_estimator_valid(estimator, estimator_requirements) -> bool: return False return True + def __repr__(self): + """ + Returns a string describing the attack class and attack_params + """ + param_str = "" + for param in self.attack_params: + if hasattr(self, param): + param_str += f"{param}={getattr(self, param)}, " + elif hasattr(self, "_attack"): + if hasattr(self._attack, param): + param_str += f"{param}={getattr(self._attack, param)}, " + return f"{type(self).__name__}({param_str})" + class EvasionAttack(Attack): """ diff --git a/art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py b/art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py index 345b9e5e90..ab71a74f47 100644 --- a/art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py +++ b/art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py @@ -274,10 +274,10 @@ def _random_overlay( name=None, ) - pad_h_before = int((self.image_shape[self.i_h] - image_mask.shape[self.i_h_patch + 1]) / 2) + pad_h_before = int((self.image_shape[self.i_h] - image_mask.shape.as_list()[self.i_h_patch + 1]) / 2) pad_h_after = int(self.image_shape[self.i_h] - pad_h_before - image_mask.shape[self.i_h_patch + 1]) - pad_w_before = int((self.image_shape[self.i_w] - image_mask.shape[self.i_w_patch + 1]) / 2) + pad_w_before = int((self.image_shape[self.i_w] - image_mask.shape.as_list()[self.i_w_patch + 1]) / 2) pad_w_after = int(self.image_shape[self.i_w] - pad_w_before - image_mask.shape[self.i_w_patch + 1]) image_mask = tf.pad( # pylint: disable=E1123 @@ -323,10 +323,10 @@ def _random_overlay( if mask is None: padding_after_scaling_h = ( - self.image_shape[self.i_h] - im_scale * padded_patch.shape[self.i_h + 1] + self.image_shape[self.i_h] - im_scale * padded_patch.shape.as_list()[self.i_h + 1] ) / 2.0 padding_after_scaling_w = ( - self.image_shape[self.i_w] - im_scale * padded_patch.shape[self.i_w + 1] + self.image_shape[self.i_w] - im_scale * padded_patch.shape.as_list()[self.i_w + 1] ) / 2.0 x_shift = np.random.uniform(-padding_after_scaling_w, padding_after_scaling_w) y_shift = np.random.uniform(-padding_after_scaling_h, padding_after_scaling_h) diff --git a/art/attacks/evasion/auto_attack.py b/art/attacks/evasion/auto_attack.py index fba75735e4..3d2fa38159 100644 --- a/art/attacks/evasion/auto_attack.py +++ b/art/attacks/evasion/auto_attack.py @@ -21,18 +21,19 @@ | Paper link: https://arxiv.org/abs/2003.01690 """ import logging -from typing import List, Optional, Union, Tuple, TYPE_CHECKING +from copy import deepcopy +from typing import TYPE_CHECKING, List, Optional, Tuple, Union import numpy as np -from art.config import ART_NUMPY_DTYPE from art.attacks.attack import EvasionAttack from art.attacks.evasion.auto_projected_gradient_descent import AutoProjectedGradientDescent from art.attacks.evasion.deepfool import DeepFool from art.attacks.evasion.square_attack import SquareAttack -from art.estimators.estimator import BaseEstimator +from art.config import ART_NUMPY_DTYPE from art.estimators.classification.classifier import ClassifierMixin -from art.utils import get_labels_np_array, check_and_transform_label_format +from art.estimators.estimator import BaseEstimator +from art.utils import check_and_transform_label_format, get_labels_np_array if TYPE_CHECKING: from art.utils import CLASSIFIER_TYPE @@ -55,10 +56,16 @@ class AutoAttack(EvasionAttack): "batch_size", "estimator_orig", "targeted", + "parallel", ] _estimator_requirements = (BaseEstimator, ClassifierMixin) + # Identify samples yet to have attack metadata identified + SAMPLE_DEFAULT = -1 + # Identify samples misclassified therefore no attack metadata required + SAMPLE_MISCLASSIFIED = -2 + def __init__( self, estimator: "CLASSIFIER_TYPE", @@ -69,6 +76,7 @@ def __init__( batch_size: int = 32, estimator_orig: Optional["CLASSIFIER_TYPE"] = None, targeted: bool = False, + parallel: bool = False, ): """ Create a :class:`.AutoAttack` instance. @@ -83,6 +91,7 @@ def __init__( :param estimator_orig: Original estimator to be attacked by adversarial examples. :param targeted: If False run only untargeted attacks, if True also run targeted attacks against each possible target. + :param parallel: If True run attacks in parallel. """ super().__init__(estimator=estimator) @@ -140,6 +149,8 @@ def __init__( self.estimator_orig = estimator self._targeted = targeted + self.parallel = parallel + self.best_attacks: np.ndarray = np.array([]) self._check_params() def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray: @@ -157,6 +168,8 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n :type mask: `np.ndarray` :return: An array holding the adversarial examples. """ + import multiprocess + x_adv = x.astype(ART_NUMPY_DTYPE) if y is not None: y = check_and_transform_label_format(y, nb_classes=self.estimator.nb_classes) @@ -168,6 +181,12 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n y_pred = self.estimator_orig.predict(x.astype(ART_NUMPY_DTYPE)) sample_is_robust = np.argmax(y_pred, axis=1) == np.argmax(y, axis=1) + # Set slots for images which have yet to be filled as SAMPLE_DEFAULT + self.best_attacks = np.array([self.SAMPLE_DEFAULT] * len(x)) + # Set samples that are misclassified and do not need to be filled as SAMPLE_MISCLASSIFIED + self.best_attacks[np.logical_not(sample_is_robust)] = self.SAMPLE_MISCLASSIFIED + + args = [] # Untargeted attacks for attack in self.attacks: @@ -178,13 +197,36 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n if attack.targeted: attack.set_params(targeted=False) - x_adv, sample_is_robust = self._run_attack( - x=x_adv, - y=y, - sample_is_robust=sample_is_robust, - attack=attack, - **kwargs, - ) + if self.parallel: + args.append( + ( + deepcopy(x_adv), + deepcopy(y), + deepcopy(sample_is_robust), + deepcopy(attack), + deepcopy(self.estimator), + deepcopy(self.norm), + deepcopy(self.eps), + ) + ) + else: + x_adv, sample_is_robust = run_attack( + x=x_adv, + y=y, + sample_is_robust=sample_is_robust, + attack=attack, + estimator_orig=self.estimator, + norm=self.norm, + eps=self.eps, + **kwargs, + ) + # create a mask which identifies images which this attack was effective on + # not including originally misclassified images + atk_mask = np.logical_and( + np.array([i == self.SAMPLE_DEFAULT for i in self.best_attacks]), np.logical_not(sample_is_robust) + ) + # update attack at image index with index of attack that was successful + self.best_attacks[atk_mask] = self.attacks.index(attack) # Targeted attacks if self.targeted: @@ -193,14 +235,12 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n y_idx = np.argmax(y, axis=1) y_idx = np.expand_dims(y_idx, 1) y_t = y_t[y_t != y_idx] - targeted_labels = np.reshape(y_t, (y.shape[0], -1)) + targeted_labels = np.reshape(y_t, (y.shape[0], self.SAMPLE_DEFAULT)) for attack in self.attacks: - if attack.targeted is not None: - - if not attack.targeted: - attack.set_params(targeted=True) + try: + attack.set_params(targeted=True) for i in range(self.estimator.nb_classes - 1): # Stop if all samples are misclassified @@ -211,64 +251,55 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n targeted_labels[:, i], nb_classes=self.estimator.nb_classes ) - x_adv, sample_is_robust = self._run_attack( - x=x_adv, - y=target, - sample_is_robust=sample_is_robust, - attack=attack, - **kwargs, - ) - + if self.parallel: + args.append( + ( + deepcopy(x_adv), + deepcopy(target), + deepcopy(sample_is_robust), + deepcopy(attack), + deepcopy(self.estimator), + deepcopy(self.norm), + deepcopy(self.eps), + ) + ) + else: + x_adv, sample_is_robust = run_attack( + x=x_adv, + y=target, + sample_is_robust=sample_is_robust, + attack=attack, + estimator_orig=self.estimator, + norm=self.norm, + eps=self.eps, + **kwargs, + ) + # create a mask which identifies images which this attack was effective on + # not including originally misclassified images + atk_mask = np.logical_and( + np.array([i == self.SAMPLE_DEFAULT for i in self.best_attacks]), + np.logical_not(sample_is_robust), + ) + # update attack at image index with index of attack that was successful + self.best_attacks[atk_mask] = self.attacks.index(attack) + except ValueError as error: + logger.warning("Error completing attack: %s}", str(error)) + + if self.parallel: + with multiprocess.get_context("spawn").Pool() as pool: + # Results come back in the order that they were issued + results = pool.starmap(run_attack, args) + perturbations = [] + is_robust = [] + for img_idx in range(len(x)): + perturbations.append(np.array([np.linalg.norm(x[img_idx] - i[0][img_idx]) for i in results])) + is_robust.append([i[1][img_idx] for i in results]) + best_attacks = np.argmin(np.where(np.invert(np.array(is_robust)), np.array(perturbations), np.inf), axis=1) + x_adv = np.concatenate([results[best_attacks[img]][0][[img]] for img in range(len(x))]) + self.best_attacks = best_attacks + self.args = args return x_adv - def _run_attack( - self, - x: np.ndarray, - y: np.ndarray, - sample_is_robust: np.ndarray, - attack: EvasionAttack, - **kwargs, - ) -> Tuple[np.ndarray, np.ndarray]: - """ - Run attack. - - :param x: An array of the original inputs. - :param y: An array of the labels. - :param sample_is_robust: Store the initial robustness of examples. - :param attack: Evasion attack to run. - :return: An array holding the adversarial examples. - """ - # Attack only correctly classified samples - x_robust = x[sample_is_robust] - y_robust = y[sample_is_robust] - - # Generate adversarial examples - x_robust_adv = attack.generate(x=x_robust, y=y_robust, **kwargs) - y_pred_robust_adv = self.estimator_orig.predict(x_robust_adv) - - # Check and update successful examples - rel_acc = 1e-4 - order = np.inf if self.norm == "inf" else self.norm - norm_is_smaller_eps = (1 - rel_acc) * np.linalg.norm( - (x_robust_adv - x_robust).reshape((x_robust_adv.shape[0], -1)), axis=1, ord=order - ) <= self.eps - - if attack.targeted: - samples_misclassified = np.argmax(y_pred_robust_adv, axis=1) == np.argmax(y_robust, axis=1) - elif not attack.targeted: - samples_misclassified = np.argmax(y_pred_robust_adv, axis=1) != np.argmax(y_robust, axis=1) - else: # pragma: no cover - raise ValueError - - sample_is_not_robust = np.logical_and(samples_misclassified, norm_is_smaller_eps) - - x_robust[sample_is_not_robust] = x_robust_adv[sample_is_not_robust] - x[sample_is_robust] = x_robust - - sample_is_robust[sample_is_robust] = np.invert(sample_is_not_robust) - - return x, sample_is_robust - def _check_params(self) -> None: if self.norm not in [1, 2, np.inf, "inf"]: raise ValueError('The argument norm has to be either 1, 2, np.inf, "inf".') @@ -281,3 +312,86 @@ def _check_params(self) -> None: if not isinstance(self.batch_size, int) or self.batch_size <= 0: raise ValueError("The argument batch_size has to be of type int and larger than zero.") + + def __repr__(self) -> str: + """ + This method returns a summary of the best performing (lowest perturbation in the parallel case) attacks + per image passed to the AutoAttack class. + """ + if self.parallel: + best_attack_meta = "\n".join( + [ + f"image {i+1}: {str(self.args[idx][3])}" if idx != 0 else f"image {i+1}: n/a" + for i, idx in enumerate(self.best_attacks) + ] + ) + auto_attack_meta = ( + f"AutoAttack(targeted={self.targeted}, parallel={self.parallel}, num_attacks={len(self.args)})" + ) + return f"{auto_attack_meta}\nBestAttacks:\n{best_attack_meta}" + + best_attack_meta = "\n".join( + [ + f"image {i+1}: {str(self.attacks[idx])}" if idx != -2 else f"image {i+1}: n/a" + for i, idx in enumerate(self.best_attacks) + ] + ) + auto_attack_meta = ( + f"AutoAttack(targeted={self.targeted}, parallel={self.parallel}, num_attacks={len(self.attacks)})" + ) + return f"{auto_attack_meta}\nBestAttacks:\n{best_attack_meta}" + + +def run_attack( + x: np.ndarray, + y: np.ndarray, + sample_is_robust: np.ndarray, + attack: EvasionAttack, + estimator_orig: "CLASSIFIER_TYPE", + norm: Union[int, float, str] = np.inf, + eps: float = 0.3, + **kwargs, +) -> Tuple[np.ndarray, np.ndarray]: + """ + Run attack. + + :param x: An array of the original inputs. + :param y: An array of the labels. + :param sample_is_robust: Store the initial robustness of examples. + :param attack: Evasion attack to run. + :param estimator_orig: Original estimator to be attacked by adversarial examples. + :param norm: The norm of the adversarial perturbation. Possible values: "inf", np.inf, 1 or 2. + :param eps: Maximum perturbation that the attacker can introduce. + :return: An array holding the adversarial examples. + """ + # Attack only correctly classified samples + x_robust = x[sample_is_robust] + y_robust = y[sample_is_robust] + + # Generate adversarial examples + x_robust_adv = attack.generate(x=x_robust, y=y_robust, **kwargs) + y_pred_robust_adv = estimator_orig.predict(x_robust_adv) + + # Check and update successful examples + rel_acc = 1e-4 + order = np.inf if norm == "inf" else norm + assert isinstance(order, (int, float)) + norm_is_smaller_eps = (1 - rel_acc) * np.linalg.norm( + (x_robust_adv - x_robust).reshape((x_robust_adv.shape[0], -1)), axis=1, ord=order + ) <= eps + + if attack.targeted: + samples_misclassified = np.argmax(y_pred_robust_adv, axis=1) == np.argmax(y_robust, axis=1) + elif not attack.targeted: + samples_misclassified = np.argmax(y_pred_robust_adv, axis=1) != np.argmax(y_robust, axis=1) + else: # pragma: no cover + raise ValueError + + sample_is_not_robust = np.logical_and(samples_misclassified, norm_is_smaller_eps) + + x_robust[sample_is_not_robust] = x_robust_adv[sample_is_not_robust] + x[sample_is_robust] = x_robust + + sample_is_robust[sample_is_robust] = np.invert(sample_is_not_robust) + + return x, sample_is_robust diff --git a/art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_numpy.py b/art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_numpy.py index bcb9c0686e..1ecc8b31f1 100644 --- a/art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_numpy.py +++ b/art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_numpy.py @@ -55,7 +55,7 @@ class ProjectedGradientDescentCommon(FastGradientMethod): | Paper link: https://arxiv.org/abs/1706.06083 """ - attack_params = FastGradientMethod.attack_params + ["max_iter", "random_eps", "verbose"] + attack_params = FastGradientMethod.attack_params + ["decay", "max_iter", "random_eps", "verbose"] _estimator_requirements = (BaseEstimator, LossGradientsMixin) def __init__( diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index b207694baa..e2b6b50cab 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -68,12 +68,17 @@ def __init__( ): """ :param classifier: A trained classifier. - :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'fgsm'. + :param attacker: Adversarial attack name. Default is 'fgsm'. Supported names: 'simba'. :param attacker_params: Parameters specific to the adversarial attack. If this parameter is not specified, the default parameters of the chosen attack will be used. - :param delta: desired accuracy + :param delta: The maximum acceptable rate of correctly classified adversarial examples by the classifier. + The attack will stop when the targeted success rate exceeds `(1 - delta)`. + 'delta' should be in the range `[0, 1]`. :param max_iter: The maximum number of iterations for computing universal perturbation. - :param eps: Attack step size (input variation) + :param eps: The perturbation magnitude, which controls the strength of the universal perturbation applied + to the input samples. A larger `eps` value will result in a more noticeable perturbation, + potentially leading to higher attack success rates but also increasing the visual distortion + in the generated adversarial examples. Default is `10.0`. :param norm: The norm of the adversarial perturbation. Possible values: "inf", np.inf, 2 """ super().__init__(estimator=classifier) @@ -92,8 +97,11 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n Generate adversarial samples and return them in an array. :param x: An array with the original inputs. - :param y: An array with the targeted labels. + :param y: The target labels for the targeted perturbation. The shape of y should match the number of instances + in x. :return: An array holding the adversarial examples. + :raises: `ValueError`: if the labels `y` are None or if the attack has not been tested for binary + classification with a single output classifier. """ if y is None: raise ValueError("Labels `y` cannot be None.") @@ -165,7 +173,6 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n return x_adv def _check_params(self) -> None: - if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: raise ValueError("The desired accuracy must be in the range [0, 1].") diff --git a/art/attacks/inference/attribute_inference/baseline.py b/art/attacks/inference/attribute_inference/baseline.py index edc78be2c2..5ecd6cda30 100644 --- a/art/attacks/inference/attribute_inference/baseline.py +++ b/art/attacks/inference/attribute_inference/baseline.py @@ -21,11 +21,15 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging -from typing import Optional, Union, List, TYPE_CHECKING +from typing import Optional, Union, List, Any, TYPE_CHECKING import numpy as np -from sklearn.neural_network import MLPClassifier, MLPRegressor from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor +from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor +from sklearn.linear_model import LogisticRegression, LinearRegression +from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor +from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor +from sklearn.svm import SVC, SVR from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder from sklearn.compose import ColumnTransformer @@ -41,7 +45,7 @@ ) if TYPE_CHECKING: - from art.utils import CLASSIFIER_TYPE + from art.utils import CLASSIFIER_TYPE, REGRESSOR_TYPE logger = logging.getLogger(__name__) @@ -65,18 +69,27 @@ class AttributeInferenceBaseline(AttributeInferenceAttack): def __init__( self, attack_model_type: str = "nn", - attack_model: Optional["CLASSIFIER_TYPE"] = None, + attack_model: Optional[Union["CLASSIFIER_TYPE", "REGRESSOR_TYPE"]] = None, attack_feature: Union[int, slice] = 0, is_continuous: Optional[bool] = False, non_numerical_features: Optional[List[int]] = None, encoder: Optional[Union[OrdinalEncoder, OneHotEncoder, ColumnTransformer]] = None, + nn_model_epochs: int = 100, + nn_model_batch_size: int = 100, + nn_model_learning_rate: float = 0.0001, ): """ Create an AttributeInferenceBaseline attack instance. - :param attack_model_type: the type of default attack model to train, optional. Should be one of `nn` (for neural - network, default) or `rf` (for random forest). If `attack_model` is supplied, this - option will be ignored. + :param attack_model_type: the type of default attack model to train, optional. Should be one of: + `nn` (neural network, default), + `rf` (random forest), + `gb` (gradient boosting), + `lr` (logistic/linear regression), + `dt` (decision tree), + `knn` (k nearest neighbors), + `svm` (support vector machine). + If `attack_model` is supplied, this option will be ignored. :param attack_model: The attack model to train, optional. If none is provided, a default model will be created. :param attack_feature: The index of the feature to be attacked or a slice representing multiple indexes in case of a one-hot encoded feature. @@ -87,13 +100,21 @@ def __init__( and an encoder is not supplied. :param encoder: An already fit encoder that can be applied to the model's input features without the attacked feature (i.e., should be fit for n-1 features). + :param nn_model_epochs: the number of epochs to use when training a nn attack model + :param nn_model_batch_size: the batch size to use when training a nn attack model + :param nn_model_learning_rate: the learning rate to use when training a nn attack model """ super().__init__(estimator=None, attack_feature=attack_feature) - self._values: Optional[list] = None + self._values: list = [] self._encoder = encoder self._non_numerical_features = non_numerical_features self._is_continuous = is_continuous + self._attack_model_type: Optional[str] = attack_model_type + self.attack_model: Optional[Any] = None + self.epochs = nn_model_epochs + self.batch_size = nn_model_batch_size + self.learning_rate = nn_model_learning_rate if attack_model: if self._is_continuous: @@ -102,65 +123,37 @@ def __init__( elif ClassifierMixin not in type(attack_model).__mro__: raise ValueError("When attacking a categorical feature the attack model must be of type Classifier.") self.attack_model = attack_model - elif attack_model_type == "nn": - if self._is_continuous: - self.attack_model = MLPRegressor( - hidden_layer_sizes=(100,), - activation="relu", - solver="adam", - alpha=0.0001, - batch_size="auto", - learning_rate="constant", - learning_rate_init=0.001, - power_t=0.5, - max_iter=200, - shuffle=True, - random_state=None, - tol=0.0001, - verbose=False, - warm_start=False, - momentum=0.9, - nesterovs_momentum=True, - early_stopping=False, - validation_fraction=0.1, - beta_1=0.9, - beta_2=0.999, - epsilon=1e-08, - n_iter_no_change=10, - max_fun=15000, - ) - else: - self.attack_model = MLPClassifier( - hidden_layer_sizes=(100,), - activation="relu", - solver="adam", - alpha=0.0001, - batch_size="auto", - learning_rate="constant", - learning_rate_init=0.001, - power_t=0.5, - max_iter=2000, - shuffle=True, - random_state=None, - tol=0.0001, - verbose=False, - warm_start=False, - momentum=0.9, - nesterovs_momentum=True, - early_stopping=False, - validation_fraction=0.1, - beta_1=0.9, - beta_2=0.999, - epsilon=1e-08, - n_iter_no_change=10, - max_fun=15000, - ) elif attack_model_type == "rf": if self._is_continuous: self.attack_model = RandomForestRegressor() else: self.attack_model = RandomForestClassifier() - else: + elif attack_model_type == "gb": + if self._is_continuous: + self.attack_model = GradientBoostingRegressor() + else: + self.attack_model = GradientBoostingClassifier() + elif attack_model_type == "lr": + if self._is_continuous: + self.attack_model = LinearRegression() + else: + self.attack_model = LogisticRegression() + elif attack_model_type == "dt": + if self._is_continuous: + self.attack_model = DecisionTreeRegressor() + else: + self.attack_model = DecisionTreeClassifier() + elif attack_model_type == "knn": + if self._is_continuous: + self.attack_model = KNeighborsRegressor() + else: + self.attack_model = KNeighborsClassifier() + elif attack_model_type == "svm": + if self._is_continuous: + self.attack_model = SVR() + else: + self.attack_model = SVC(probability=True) + elif attack_model_type != "nn": raise ValueError("Illegal value for parameter `attack_model_type`.") self._check_params() @@ -191,6 +184,8 @@ def fit(self, x: np.ndarray) -> None: y_ready = check_and_transform_label_format(y_one_hot, nb_classes=nb_classes, return_one_hot=True) if y_ready is None: raise ValueError("None value detected.") + if self._attack_model_type in ("gb", "lr", "svm"): + y_ready = np.argmax(y_ready, axis=1) # create training set for attack model x_train = np.delete(x, self.attack_feature, 1) @@ -216,7 +211,101 @@ def fit(self, x: np.ndarray) -> None: if self._encoder is not None: x_train = self._encoder.transform(x_train) x_train = x_train.astype(np.float32) - self.attack_model.fit(x_train, y_ready) + + if self._attack_model_type == "nn": + import torch + from torch import nn + from torch import optim + from torch.utils.data import DataLoader + from art.utils import to_cuda + + if self._is_continuous: + + class MembershipInferenceAttackModelRegression(nn.Module): + """ + Implementation of a pytorch model for learning a membership inference attack. + + The features used are probabilities/logits or losses for the attack training data along with + its true labels. + """ + + def __init__(self, num_features): + + self.num_features = num_features + + super().__init__() + + self.features = nn.Sequential( + nn.Linear(self.num_features, 100), + nn.ReLU(), + nn.Linear(100, 64), + nn.ReLU(), + nn.Linear(64, 1), + ) + + def forward(self, x): + """Forward the model.""" + return self.features(x) + + self.attack_model = MembershipInferenceAttackModelRegression(x_train.shape[1]) + loss_fn: Any = nn.MSELoss() + else: + + class MembershipInferenceAttackModel(nn.Module): + """ + Implementation of a pytorch model for learning an attribute inference attack. + + The features used are the remaining n-1 features of the attack training data along with + the model's predictions. + """ + + def __init__(self, num_features, num_classes): + + self.num_classes = num_classes + self.num_features = num_features + + super().__init__() + + self.features = nn.Sequential( + nn.Linear(self.num_features, 512), + nn.ReLU(), + nn.Linear(512, 100), + nn.ReLU(), + nn.Linear(100, 64), + nn.ReLU(), + nn.Linear(64, num_classes), + ) + + self.output = nn.Softmax() + + def forward(self, x): + """Forward the model.""" + out = self.features(x) + return self.output(out) + + self.attack_model = MembershipInferenceAttackModel(x_train.shape[1], len(self._values)) + loss_fn = nn.CrossEntropyLoss() + + optimizer = optim.Adam(self.attack_model.parameters(), lr=self.learning_rate) # type: ignore + + attack_train_set = self._get_attack_dataset(feature=x_train, label=y_ready) + train_loader = DataLoader(attack_train_set, batch_size=self.batch_size, shuffle=True, num_workers=0) + + self.attack_model = to_cuda(self.attack_model) # type: ignore + self.attack_model.train() # type: ignore + + for _ in range(self.epochs): + for (input1, targets) in train_loader: + input1, targets = to_cuda(input1), to_cuda(targets) + _, targets = torch.autograd.Variable(input1), torch.autograd.Variable(targets) + + optimizer.zero_grad() + outputs = self.attack_model(input1) # type: ignore + loss = loss_fn(outputs, targets) + loss.backward() + optimizer.step() + elif self.attack_model is not None: + self.attack_model.fit(x_train, y_ready) def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray: """ @@ -242,13 +331,46 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n if self._encoder is not None: x_test = self._encoder.transform(x) x_test = x_test.astype(np.float32) - predictions = self.attack_model.predict(x_test).astype(np.float32) - if not self._is_continuous and self._values is not None: + if self._attack_model_type == "nn": + from torch.utils.data import DataLoader + from art.utils import to_cuda, from_cuda + + self.attack_model.eval() # type: ignore + predictions: np.ndarray = np.array([]) + test_set = self._get_attack_dataset(feature=x_test) + test_loader = DataLoader(test_set, batch_size=self.batch_size, shuffle=False, num_workers=0) + for input1, _ in test_loader: + input1 = to_cuda(input1) + outputs = self.attack_model(input1) # type: ignore + predicted = from_cuda(outputs) + + if np.size(predictions) == 0: + predictions = predicted.detach().numpy() + else: + predictions = np.vstack((predictions, predicted.detach().numpy())) + if not self._is_continuous: + idx = np.argmax(predictions, axis=-1) + predictions = np.zeros(predictions.shape) + predictions[np.arange(predictions.shape[0]), idx] = 1 + elif self.attack_model is not None: + predictions = self.attack_model.predict(x_test) + if predictions is not None: + predictions = predictions.astype(np.float32) + + if not self._is_continuous and self._values: if isinstance(self.attack_feature, int): # replace 1-hot encoded prediction with correct single feature value - predictions = np.array([self._values[np.argmax(arr)] for arr in predictions]) + if self._attack_model_type in ("gb", "lr", "svm"): + indexes = predictions + else: + indexes = np.argmax(predictions, axis=1) + predictions = np.array([self._values[int(index)] for index in indexes]) else: + if self._attack_model_type in ("gb", "lr", "svm"): + predictions = check_and_transform_label_format( + predictions, nb_classes=len(self._values), return_one_hot=True + ) i = 0 # replace 1-hot encoded prediction with multi-column feature value for column in predictions.T: @@ -258,6 +380,38 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n i += 1 return np.array(predictions) + def _get_attack_dataset(self, feature, label=None): + from torch.utils.data.dataset import Dataset + + class AttackDataset(Dataset): + """ + Implementation of a pytorch dataset for membership inference attack. + + The features are probabilities/logits or losses for the attack training data (`x_1`) along with + its true labels (`x_2`). The labels (`y`) are a boolean representing whether this is a member. + """ + + def __init__(self, x, y=None): + import torch + + self.x = torch.from_numpy(x.astype(np.float64)).type(torch.FloatTensor) + + if y is not None: + self.y = torch.from_numpy(y.astype(np.float32)).type(torch.FloatTensor) + else: + self.y = torch.zeros(x.shape[0]) + + def __len__(self): + return len(self.x) + + def __getitem__(self, idx): + if idx >= len(self.x): # pragma: no cover + raise IndexError("Invalid Index") + + return self.x[idx], self.y[idx] + + return AttackDataset(x=feature, y=label) + def _check_params(self) -> None: super()._check_params() @@ -265,6 +419,9 @@ def _check_params(self) -> None: if not isinstance(self._is_continuous, bool): raise ValueError("is_continuous must be a boolean.") + if self._attack_model_type not in ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]: + raise ValueError("Illegal value for parameter `attack_model_type`.") + if self._non_numerical_features and ( (not isinstance(self._non_numerical_features, list)) or (not all(isinstance(item, int) for item in self._non_numerical_features)) diff --git a/art/attacks/inference/attribute_inference/black_box.py b/art/attacks/inference/attribute_inference/black_box.py index 25a2c86d0b..561a50eec7 100644 --- a/art/attacks/inference/attribute_inference/black_box.py +++ b/art/attacks/inference/attribute_inference/black_box.py @@ -21,11 +21,15 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging -from typing import Optional, Union, Tuple, List, TYPE_CHECKING +from typing import Optional, Union, Tuple, List, Any, TYPE_CHECKING import numpy as np -from sklearn.neural_network import MLPClassifier, MLPRegressor from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor +from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor +from sklearn.linear_model import LogisticRegression, LinearRegression +from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor +from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor +from sklearn.svm import SVC, SVR from sklearn.preprocessing import minmax_scale, OneHotEncoder, OrdinalEncoder from sklearn.compose import ColumnTransformer @@ -78,14 +82,23 @@ def __init__( prediction_normal_factor: Optional[float] = 1, non_numerical_features: Optional[List[int]] = None, encoder: Optional[Union[OrdinalEncoder, OneHotEncoder, ColumnTransformer]] = None, + nn_model_epochs: int = 100, + nn_model_batch_size: int = 100, + nn_model_learning_rate: float = 0.0001, ): """ Create an AttributeInferenceBlackBox attack instance. :param estimator: Target estimator. - :param attack_model_type: the type of default attack model to train, optional. Should be one of `nn` (for neural - network, default) or `rf` (for random forest). If `attack_model` is supplied, this - option will be ignored. + :param attack_model_type: the type of default attack model to train, optional. Should be one of: + `nn` (neural network, default), + `rf` (random forest), + `gb` (gradient boosting), + `lr` (logistic/linear regression), + `dt` (decision tree), + `knn` (k nearest neighbors), + `svm` (support vector machine). + If `attack_model` is supplied, this option will be ignored. :param attack_model: The attack model to train, optional. If the attacked feature is continuous, this should be a regression model, and if the attacked feature is categorical it should be a classifier.If none is provided, a default model will be created. @@ -103,15 +116,25 @@ def __init__( and an encoder is not supplied. :param encoder: An already fit encoder that can be applied to the model's input features without the attacked feature (i.e., should be fit for n-1 features). + :param nn_model_epochs: the number of epochs to use when training a nn attack model + :param nn_model_batch_size: the batch size to use when training a nn attack model + :param nn_model_learning_rate: the learning rate to use when training a nn attack model """ super().__init__(estimator=estimator, attack_feature=attack_feature) - self._values: Optional[list] = None - self._attack_model_type = attack_model_type - self._attack_model = attack_model + self._values: list = [] + self._attack_model_type: Optional[str] = attack_model_type self._encoder = encoder self._non_numerical_features = non_numerical_features self._is_continuous = is_continuous + self.attack_model: Optional[Any] = None + self.prediction_normal_factor = prediction_normal_factor + self.scale_range = scale_range + self.epochs = nn_model_epochs + self.batch_size = nn_model_batch_size + self.learning_rate = nn_model_learning_rate + + self._check_params() if attack_model: if self._is_continuous: @@ -120,71 +143,40 @@ def __init__( elif ClassifierMixin not in type(attack_model).__mro__: raise ValueError("When attacking a categorical feature the attack model must be of type Classifier.") self.attack_model = attack_model - elif attack_model_type == "nn": - if self._is_continuous: - self.attack_model = MLPRegressor( - hidden_layer_sizes=(100,), - activation="relu", - solver="adam", - alpha=0.0001, - batch_size="auto", - learning_rate="constant", - learning_rate_init=0.001, - power_t=0.5, - max_iter=200, - shuffle=True, - random_state=None, - tol=0.0001, - verbose=False, - warm_start=False, - momentum=0.9, - nesterovs_momentum=True, - early_stopping=False, - validation_fraction=0.1, - beta_1=0.9, - beta_2=0.999, - epsilon=1e-08, - n_iter_no_change=10, - max_fun=15000, - ) - else: - self.attack_model = MLPClassifier( - hidden_layer_sizes=(100,), - activation="relu", - solver="adam", - alpha=0.0001, - batch_size="auto", - learning_rate="constant", - learning_rate_init=0.001, - power_t=0.5, - max_iter=2000, - shuffle=True, - random_state=None, - tol=0.0001, - verbose=False, - warm_start=False, - momentum=0.9, - nesterovs_momentum=True, - early_stopping=False, - validation_fraction=0.1, - beta_1=0.9, - beta_2=0.999, - epsilon=1e-08, - n_iter_no_change=10, - max_fun=15000, - ) + self._attack_model_type = None elif attack_model_type == "rf": if self._is_continuous: self.attack_model = RandomForestRegressor() else: self.attack_model = RandomForestClassifier() - else: + elif attack_model_type == "gb": + if self._is_continuous: + self.attack_model = GradientBoostingRegressor() + else: + self.attack_model = GradientBoostingClassifier() + elif attack_model_type == "lr": + if self._is_continuous: + self.attack_model = LinearRegression() + else: + self.attack_model = LogisticRegression() + elif attack_model_type == "dt": + if self._is_continuous: + self.attack_model = DecisionTreeRegressor() + else: + self.attack_model = DecisionTreeClassifier() + elif attack_model_type == "knn": + if self._is_continuous: + self.attack_model = KNeighborsRegressor() + else: + self.attack_model = KNeighborsClassifier() + elif attack_model_type == "svm": + if self._is_continuous: + self.attack_model = SVR() + else: + self.attack_model = SVC(probability=True) + elif attack_model_type != "nn": raise ValueError("Illegal value for parameter `attack_model_type`.") - self.prediction_normal_factor = prediction_normal_factor - self.scale_range = scale_range - - self._check_params() remove_attacked_feature(self.attack_feature, self._non_numerical_features) def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None: @@ -230,6 +222,8 @@ def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None: else: y_one_hot = floats_to_one_hot(y_attack) y_attack_ready = check_and_transform_label_format(y_one_hot, nb_classes=nb_classes, return_one_hot=True) + if self._attack_model_type in ("gb", "lr", "svm"): + y_attack_ready = np.argmax(y_attack_ready, axis=1) # create training set for attack model x_train = np.delete(x, self.attack_feature, 1) @@ -259,7 +253,100 @@ def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None: x_train = np.concatenate((x_train, y), axis=1) # train attack model - self.attack_model.fit(x_train, y_attack_ready) + if self._attack_model_type == "nn": + import torch + from torch import nn + from torch import optim + from torch.utils.data import DataLoader + from art.utils import to_cuda + + if self._is_continuous: + + class MembershipInferenceAttackModelRegression(nn.Module): + """ + Implementation of a pytorch model for learning a membership inference attack. + + The features used are probabilities/logits or losses for the attack training data along with + its true labels. + """ + + def __init__(self, num_features): + + self.num_features = num_features + + super().__init__() + + self.features = nn.Sequential( + nn.Linear(self.num_features, 100), + nn.ReLU(), + nn.Linear(100, 64), + nn.ReLU(), + nn.Linear(64, 1), + ) + + def forward(self, x): + """Forward the model.""" + return self.features(x) + + self.attack_model = MembershipInferenceAttackModelRegression(x_train.shape[1]) + loss_fn: Any = nn.MSELoss() + else: + + class MembershipInferenceAttackModel(nn.Module): + """ + Implementation of a pytorch model for learning an attribute inference attack. + + The features used are the remaining n-1 features of the attack training data along with + the model's predictions. + """ + + def __init__(self, num_features, num_classes): + + self.num_classes = num_classes + self.num_features = num_features + + super().__init__() + + self.features = nn.Sequential( + nn.Linear(self.num_features, 512), + nn.ReLU(), + nn.Linear(512, 100), + nn.ReLU(), + nn.Linear(100, 64), + nn.ReLU(), + nn.Linear(64, num_classes), + ) + + self.output = nn.Softmax() + + def forward(self, x): + """Forward the model.""" + out = self.features(x) + return self.output(out) + + self.attack_model = MembershipInferenceAttackModel(x_train.shape[1], len(self._values)) + loss_fn = nn.CrossEntropyLoss() + + optimizer = optim.Adam(self.attack_model.parameters(), lr=self.learning_rate) # type: ignore + + attack_train_set = self._get_attack_dataset(feature=x_train, label=y_attack_ready) + train_loader = DataLoader(attack_train_set, batch_size=self.batch_size, shuffle=True, num_workers=0) + + self.attack_model = to_cuda(self.attack_model) # type: ignore + self.attack_model.train() # type: ignore + + for _ in range(self.epochs): + for (input1, targets) in train_loader: + input1, targets = to_cuda(input1), to_cuda(targets) + _, targets = torch.autograd.Variable(input1), torch.autograd.Variable(targets) + + optimizer.zero_grad() + outputs = self.attack_model(input1) # type: ignore + loss = loss_fn(outputs, targets) + loss.backward() + optimizer.step() + elif self.attack_model is not None: + self.attack_model.fit(x_train, y_attack_ready) def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray: """ @@ -320,12 +407,44 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n if y is not None: x_test = np.concatenate((x_test, y), axis=1) - predictions = self.attack_model.predict(x_test).astype(np.float32) - - if not self._is_continuous and self._values is not None: + if self._attack_model_type == "nn": + from torch.utils.data import DataLoader + from art.utils import to_cuda, from_cuda + + self.attack_model.eval() # type: ignore + predictions: np.ndarray = np.array([]) + test_set = self._get_attack_dataset(feature=x_test) + test_loader = DataLoader(test_set, batch_size=self.batch_size, shuffle=False, num_workers=0) + for input1, _ in test_loader: + input1 = to_cuda(input1) + outputs = self.attack_model(input1) # type: ignore + predicted = from_cuda(outputs) + + if np.size(predictions) == 0: + predictions = predicted.detach().numpy() + else: + predictions = np.vstack((predictions, predicted.detach().numpy())) + if not self._is_continuous: + idx = np.argmax(predictions, axis=-1) + predictions = np.zeros(predictions.shape) + predictions[np.arange(predictions.shape[0]), idx] = 1 + elif self.attack_model is not None: + predictions = self.attack_model.predict(x_test) + if predictions is not None: + predictions = predictions.astype(np.float32) + + if not self._is_continuous and self._values: if isinstance(self.attack_feature, int): - predictions = np.array([self._values[np.argmax(arr)] for arr in predictions]) + if self._attack_model_type in ("gb", "lr", "svm"): + indexes = predictions + else: + indexes = np.argmax(predictions, axis=1) + predictions = np.array([self._values[int(index)] for index in indexes]) else: + if self._attack_model_type in ("gb", "lr", "svm"): + predictions = check_and_transform_label_format( + predictions, nb_classes=len(self._values), return_one_hot=True + ) i = 0 for column in predictions.T: for index in range(len(self._values[i])): @@ -333,6 +452,38 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n i += 1 return np.array(predictions) + def _get_attack_dataset(self, feature, label=None): + from torch.utils.data.dataset import Dataset + + class AttackDataset(Dataset): + """ + Implementation of a pytorch dataset for membership inference attack. + + The features are probabilities/logits or losses for the attack training data (`x_1`) along with + its true labels (`x_2`). The labels (`y`) are a boolean representing whether this is a member. + """ + + def __init__(self, x, y=None): + import torch + + self.x = torch.from_numpy(x.astype(np.float64)).type(torch.FloatTensor) + + if y is not None: + self.y = torch.from_numpy(y.astype(np.float32)).type(torch.FloatTensor) + else: + self.y = torch.zeros(x.shape[0]) + + def __len__(self): + return len(self.x) + + def __getitem__(self, idx): + if idx >= len(self.x): # pragma: no cover + raise IndexError("Invalid Index") + + return self.x[idx], self.y[idx] + + return AttackDataset(x=feature, y=label) + def _check_params(self) -> None: super()._check_params() @@ -340,7 +491,7 @@ def _check_params(self) -> None: if not isinstance(self._is_continuous, bool): raise ValueError("is_continuous must be a boolean.") - if self._attack_model_type not in ["nn", "rf"]: + if self._attack_model_type not in ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]: raise ValueError("Illegal value for parameter `attack_model_type`.") if RegressorMixin not in type(self.estimator).__mro__: diff --git a/art/attacks/inference/attribute_inference/true_label_baseline.py b/art/attacks/inference/attribute_inference/true_label_baseline.py index cbbd07bae3..2fab59cfde 100644 --- a/art/attacks/inference/attribute_inference/true_label_baseline.py +++ b/art/attacks/inference/attribute_inference/true_label_baseline.py @@ -21,11 +21,15 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging -from typing import Optional, Union, Tuple, List, TYPE_CHECKING +from typing import Optional, Union, Tuple, List, Any, TYPE_CHECKING import numpy as np -from sklearn.neural_network import MLPClassifier, MLPRegressor from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor +from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor +from sklearn.linear_model import LogisticRegression, LinearRegression +from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor +from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor +from sklearn.svm import SVC, SVR from sklearn.preprocessing import minmax_scale, OneHotEncoder, OrdinalEncoder from sklearn.compose import ColumnTransformer @@ -41,7 +45,7 @@ ) if TYPE_CHECKING: - from art.utils import CLASSIFIER_TYPE + from art.utils import CLASSIFIER_TYPE, REGRESSOR_TYPE logger = logging.getLogger(__name__) @@ -68,7 +72,7 @@ class AttributeInferenceBaselineTrueLabel(AttributeInferenceAttack): def __init__( self, attack_model_type: str = "nn", - attack_model: Optional["CLASSIFIER_TYPE"] = None, + attack_model: Optional[Union["CLASSIFIER_TYPE", "REGRESSOR_TYPE"]] = None, attack_feature: Union[int, slice] = 0, is_continuous: Optional[bool] = False, is_regression: Optional[bool] = False, @@ -76,13 +80,22 @@ def __init__( prediction_normal_factor: float = 1, non_numerical_features: Optional[List[int]] = None, encoder: Optional[Union[OrdinalEncoder, OneHotEncoder, ColumnTransformer]] = None, + nn_model_epochs: int = 100, + nn_model_batch_size: int = 100, + nn_model_learning_rate: float = 0.0001, ): """ Create an AttributeInferenceBaseline attack instance. - :param attack_model_type: the type of default attack model to train, optional. Should be one of `nn` (for neural - network, default) or `rf` (for random forest). If `attack_model` is supplied, this - option will be ignored. + :param attack_model_type: the type of default attack model to train, optional. Should be one of: + `nn` (neural network, default), + `rf` (random forest), + `gb` (gradient boosting), + `lr` (logistic/linear regression), + `dt` (decision tree), + `knn` (k nearest neighbors), + `svm` (support vector machine). + If `attack_model` is supplied, this option will be ignored. :param attack_model: The attack model to train, optional. If none is provided, a default model will be created. :param attack_feature: The index of the feature to be attacked or a slice representing multiple indexes in case of a one-hot encoded feature. @@ -99,13 +112,21 @@ def __init__( and an encoder is not supplied. :param encoder: An already fit encoder that can be applied to the model's input features without the attacked feature (i.e., should be fit for n-1 features). + :param nn_model_epochs: the number of epochs to use when training a nn attack model + :param nn_model_batch_size: the batch size to use when training a nn attack model + :param nn_model_learning_rate: the learning rate to use when training a nn attack model """ super().__init__(estimator=None, attack_feature=attack_feature) - self._values: Optional[list] = None + self._values: list = [] self._encoder = encoder self._non_numerical_features = non_numerical_features self._is_continuous = is_continuous + self._attack_model_type: Optional[str] = attack_model_type + self.attack_model: Optional[Any] = None + self.epochs = nn_model_epochs + self.batch_size = nn_model_batch_size + self.learning_rate = nn_model_learning_rate if attack_model: if self._is_continuous: @@ -114,65 +135,37 @@ def __init__( elif ClassifierMixin not in type(attack_model).__mro__: raise ValueError("When attacking a categorical feature the attack model must be of type Classifier.") self.attack_model = attack_model - elif attack_model_type == "nn": - if self._is_continuous: - self.attack_model = MLPRegressor( - hidden_layer_sizes=(100,), - activation="relu", - solver="adam", - alpha=0.0001, - batch_size="auto", - learning_rate="constant", - learning_rate_init=0.001, - power_t=0.5, - max_iter=200, - shuffle=True, - random_state=None, - tol=0.0001, - verbose=False, - warm_start=False, - momentum=0.9, - nesterovs_momentum=True, - early_stopping=False, - validation_fraction=0.1, - beta_1=0.9, - beta_2=0.999, - epsilon=1e-08, - n_iter_no_change=10, - max_fun=15000, - ) - else: - self.attack_model = MLPClassifier( - hidden_layer_sizes=(100,), - activation="relu", - solver="adam", - alpha=0.0001, - batch_size="auto", - learning_rate="constant", - learning_rate_init=0.001, - power_t=0.5, - max_iter=2000, - shuffle=True, - random_state=None, - tol=0.0001, - verbose=False, - warm_start=False, - momentum=0.9, - nesterovs_momentum=True, - early_stopping=False, - validation_fraction=0.1, - beta_1=0.9, - beta_2=0.999, - epsilon=1e-08, - n_iter_no_change=10, - max_fun=15000, - ) elif attack_model_type == "rf": if self._is_continuous: self.attack_model = RandomForestRegressor() else: self.attack_model = RandomForestClassifier() - else: + elif attack_model_type == "gb": + if self._is_continuous: + self.attack_model = GradientBoostingRegressor() + else: + self.attack_model = GradientBoostingClassifier() + elif attack_model_type == "lr": + if self._is_continuous: + self.attack_model = LinearRegression() + else: + self.attack_model = LogisticRegression() + elif attack_model_type == "dt": + if self._is_continuous: + self.attack_model = DecisionTreeRegressor() + else: + self.attack_model = DecisionTreeClassifier() + elif attack_model_type == "knn": + if self._is_continuous: + self.attack_model = KNeighborsRegressor() + else: + self.attack_model = KNeighborsClassifier() + elif attack_model_type == "svm": + if self._is_continuous: + self.attack_model = SVR() + else: + self.attack_model = SVC(probability=True) + elif attack_model_type != "nn": raise ValueError("Illegal value for parameter `attack_model_type`.") self.prediction_normal_factor = prediction_normal_factor @@ -206,6 +199,8 @@ def fit(self, x: np.ndarray, y: np.ndarray) -> None: y_ready = check_and_transform_label_format(y_one_hot, nb_classes=nb_classes, return_one_hot=True) if y_ready is None: raise ValueError("None value detected.") + if self._attack_model_type in ("gb", "lr", "svm"): + y_ready = np.argmax(y_ready, axis=1) # create training set for attack model if self.is_regression: @@ -240,7 +235,105 @@ def fit(self, x: np.ndarray, y: np.ndarray) -> None: if self._encoder is not None: x_train = self._encoder.transform(x_train) x_train = np.concatenate((x_train, normalized_labels), axis=1).astype(np.float32) - self.attack_model.fit(x_train, y_ready) + + if self._attack_model_type == "nn": + import torch + from torch import nn + from torch import optim + from torch.utils.data import DataLoader + from art.utils import to_cuda + + self.epochs = 100 + self.batch_size = 100 + self.learning_rate = 0.0001 + + if self._is_continuous: + + class MembershipInferenceAttackModelRegression(nn.Module): + """ + Implementation of a pytorch model for learning a membership inference attack. + + The features used are probabilities/logits or losses for the attack training data along with + its true labels. + """ + + def __init__(self, num_features): + + self.num_features = num_features + + super().__init__() + + self.features = nn.Sequential( + nn.Linear(self.num_features, 100), + nn.ReLU(), + nn.Linear(100, 64), + nn.ReLU(), + nn.Linear(64, 1), + ) + + def forward(self, x): + """Forward the model.""" + return self.features(x) + + self.attack_model = MembershipInferenceAttackModelRegression(x_train.shape[1]) + loss_fn: Any = nn.MSELoss() + else: + + class MembershipInferenceAttackModel(nn.Module): + """ + Implementation of a pytorch model for learning an attribute inference attack. + + The features used are the remaining n-1 features of the attack training data along with + the model's predictions. + """ + + def __init__(self, num_features, num_classes): + + self.num_classes = num_classes + self.num_features = num_features + + super().__init__() + + self.features = nn.Sequential( + nn.Linear(self.num_features, 512), + nn.ReLU(), + nn.Linear(512, 100), + nn.ReLU(), + nn.Linear(100, 64), + nn.ReLU(), + nn.Linear(64, num_classes), + ) + + self.output = nn.Softmax() + + def forward(self, x): + """Forward the model.""" + out = self.features(x) + return self.output(out) + + self.attack_model = MembershipInferenceAttackModel(x_train.shape[1], len(self._values)) + loss_fn = nn.CrossEntropyLoss() + + optimizer = optim.Adam(self.attack_model.parameters(), lr=self.learning_rate) # type: ignore + + attack_train_set = self._get_attack_dataset(feature=x_train, label=y_ready) + train_loader = DataLoader(attack_train_set, batch_size=self.batch_size, shuffle=True, num_workers=0) + + self.attack_model = to_cuda(self.attack_model) # type: ignore + self.attack_model.train() # type: ignore + + for _ in range(self.epochs): + for (input1, targets) in train_loader: + input1, targets = to_cuda(input1), to_cuda(targets) + _, targets = torch.autograd.Variable(input1), torch.autograd.Variable(targets) + + optimizer.zero_grad() + outputs = self.attack_model(input1) # type: ignore + loss = loss_fn(outputs, targets) + loss.backward() + optimizer.step() + elif self.attack_model is not None: + self.attack_model.fit(x_train, y_ready) def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray: """ @@ -279,12 +372,44 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n x_test = self._encoder.transform(x) x_test = np.concatenate((x_test, normalized_labels), axis=1).astype(np.float32) - predictions = self.attack_model.predict(x_test).astype(np.float32) - - if not self._is_continuous and self._values is not None: + if self._attack_model_type == "nn": + from torch.utils.data import DataLoader + from art.utils import to_cuda, from_cuda + + self.attack_model.eval() # type: ignore + predictions: np.ndarray = np.array([]) + test_set = self._get_attack_dataset(feature=x_test) + test_loader = DataLoader(test_set, batch_size=self.batch_size, shuffle=False, num_workers=0) + for input1, _ in test_loader: + input1 = to_cuda(input1) + outputs = self.attack_model(input1) # type: ignore + predicted = from_cuda(outputs) + + if np.size(predictions) == 0: + predictions = predicted.detach().numpy() + else: + predictions = np.vstack((predictions, predicted.detach().numpy())) + if not self._is_continuous: + idx = np.argmax(predictions, axis=-1) + predictions = np.zeros(predictions.shape) + predictions[np.arange(predictions.shape[0]), idx] = 1 + elif self.attack_model is not None: + predictions = self.attack_model.predict(x_test) + if predictions is not None: + predictions = predictions.astype(np.float32) + + if not self._is_continuous and self._values: if isinstance(self.attack_feature, int): - predictions = np.array([self._values[np.argmax(arr)] for arr in predictions]) + if self._attack_model_type in ("gb", "lr", "svm"): + indexes = predictions + else: + indexes = np.argmax(predictions, axis=1) + predictions = np.array([self._values[int(index)] for index in indexes]) else: + if self._attack_model_type in ("gb", "lr", "svm"): + predictions = check_and_transform_label_format( + predictions, nb_classes=len(self._values), return_one_hot=True + ) i = 0 for column in predictions.T: for index in range(len(self._values[i])): @@ -292,12 +417,47 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n i += 1 return np.array(predictions) + def _get_attack_dataset(self, feature, label=None): + from torch.utils.data.dataset import Dataset + + class AttackDataset(Dataset): + """ + Implementation of a pytorch dataset for membership inference attack. + + The features are probabilities/logits or losses for the attack training data (`x_1`) along with + its true labels (`x_2`). The labels (`y`) are a boolean representing whether this is a member. + """ + + def __init__(self, x, y=None): + import torch + + self.x = torch.from_numpy(x.astype(np.float64)).type(torch.FloatTensor) + + if y is not None: + self.y = torch.from_numpy(y.astype(np.float32)).type(torch.FloatTensor) + else: + self.y = torch.zeros(x.shape[0]) + + def __len__(self): + return len(self.x) + + def __getitem__(self, idx): + if idx >= len(self.x): # pragma: no cover + raise IndexError("Invalid Index") + + return self.x[idx], self.y[idx] + + return AttackDataset(x=feature, y=label) + def _check_params(self) -> None: super()._check_params() if not isinstance(self._is_continuous, bool): raise ValueError("is_continuous must be a boolean.") + if self._attack_model_type not in ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]: + raise ValueError("Illegal value for parameter `attack_model_type`.") + if self._non_numerical_features and ( (not isinstance(self._non_numerical_features, list)) or (not all(isinstance(item, int) for item in self._non_numerical_features)) diff --git a/art/attacks/inference/membership_inference/black_box.py b/art/attacks/inference/membership_inference/black_box.py index 86f7e71609..21758bbe05 100644 --- a/art/attacks/inference/membership_inference/black_box.py +++ b/art/attacks/inference/membership_inference/black_box.py @@ -27,6 +27,10 @@ import numpy as np from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier +from sklearn.linear_model import LogisticRegression +from sklearn.neighbors import KNeighborsClassifier +from sklearn.tree import DecisionTreeClassifier +from sklearn.svm import SVC from art.attacks.attack import MembershipInferenceAttack from art.estimators.estimator import BaseEstimator @@ -61,24 +65,39 @@ def __init__( input_type: str = "prediction", attack_model_type: str = "nn", attack_model: Optional[Any] = None, + nn_model_epochs: int = 100, + nn_model_batch_size: int = 100, + nn_model_learning_rate: float = 0.0001, ): """ Create a MembershipInferenceBlackBox attack instance. :param estimator: Target estimator. - :param attack_model_type: the type of default attack model to train, optional. Should be one of `nn` (for neural - network, default), `rf` (for random forest) or `gb` (gradient boosting). If - `attack_model` is supplied, this option will be ignored. + :param attack_model_type: the type of default attack model to train, optional. Should be one of: + `nn` (neural network, default), + `rf` (random forest), + `gb` (gradient boosting), + `lr` (logistic regression), + `dt` (decision tree), + `knn` (k nearest neighbors), + `svm` (support vector machine). + If `attack_model` is supplied, this option will be ignored. :param input_type: the type of input to train the attack on. Can be one of: 'prediction' or 'loss'. Default is `prediction`. Predictions can be either probabilities or logits, depending on the return type of the model. If the model is a regressor, only `loss` can be used. :param attack_model: The attack model to train, optional. If none is provided, a default model will be created. + :param nn_model_epochs: the number of epochs to use when training a nn attack model + :param nn_model_batch_size: the batch size to use when training a nn attack model + :param nn_model_learning_rate: the learning rate to use when training a nn attack model """ super().__init__(estimator=estimator) self.input_type = input_type self.attack_model_type = attack_model_type self.attack_model = attack_model + self.epochs = nn_model_epochs + self.batch_size = nn_model_batch_size + self.learning_rate = nn_model_learning_rate self._regressor_model = RegressorMixin in type(self.estimator).__mro__ @@ -149,13 +168,18 @@ def forward(self, x_1, label): else: num_classes = estimator.nb_classes # type: ignore self.attack_model = MembershipInferenceAttackModel(num_classes, num_features=1) - self.epochs = 100 - self.batch_size = 100 - self.learning_rate = 0.0001 elif self.attack_model_type == "rf": self.attack_model = RandomForestClassifier() elif self.attack_model_type == "gb": self.attack_model = GradientBoostingClassifier() + elif self.attack_model_type == "lr": + self.attack_model = LogisticRegression() + elif self.attack_model_type == "dt": + self.attack_model = DecisionTreeClassifier() + elif self.attack_model_type == "knn": + self.attack_model = KNeighborsClassifier() + elif self.attack_model_type == "svm": + self.attack_model = SVC(probability=True) def fit( # pylint: disable=W0613 self, @@ -454,7 +478,7 @@ def _check_params(self) -> None: if self.input_type != "loss": raise ValueError("Illegal value for parameter `input_type` when estimator is a regressor.") - if self.attack_model_type not in ["nn", "rf", "gb"]: + if self.attack_model_type not in ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]: raise ValueError("Illegal value for parameter `attack_model_type`.") if self.attack_model: diff --git a/art/attacks/poisoning/hidden_trigger_backdoor/hidden_trigger_backdoor_pytorch.py b/art/attacks/poisoning/hidden_trigger_backdoor/hidden_trigger_backdoor_pytorch.py index 3e9bc2f287..8d34b1713c 100644 --- a/art/attacks/poisoning/hidden_trigger_backdoor/hidden_trigger_backdoor_pytorch.py +++ b/art/attacks/poisoning/hidden_trigger_backdoor/hidden_trigger_backdoor_pytorch.py @@ -240,6 +240,8 @@ def poison( # pylint: disable=W0221 for _ in range(feat2.size(0)): dist_min_index = (dist == torch.min(dist)).nonzero().squeeze() + if dist_min_index.dim() > 1: # If multiple values in dist equal torch.min(dist), return the first. + dist_min_index = dist_min_index[0] feat1[dist_min_index[1]] = feat11[dist_min_index[0]] dist[dist_min_index[0], dist_min_index[1]] = 1e5 diff --git a/art/attacks/poisoning/perturbations/image_perturbations.py b/art/attacks/poisoning/perturbations/image_perturbations.py index d04cbe9d08..b48985e810 100644 --- a/art/attacks/poisoning/perturbations/image_perturbations.py +++ b/art/attacks/poisoning/perturbations/image_perturbations.py @@ -49,7 +49,7 @@ def add_single_bd(x: np.ndarray, distance: int = 2, pixel_value: int = 1) -> np. return x -def add_pattern_bd(x: np.ndarray, distance: int = 2, pixel_value: int = 1) -> np.ndarray: +def add_pattern_bd(x: np.ndarray, distance: int = 2, pixel_value: int = 1, channels_first: bool = False) -> np.ndarray: """ Augments a matrix by setting a checkerboard-like pattern of values some `distance` away from the bottom-right edge to 1. Works for single images or a batch of images. @@ -57,10 +57,21 @@ def add_pattern_bd(x: np.ndarray, distance: int = 2, pixel_value: int = 1) -> np :param x: A single image or batch of images of shape NWHC, NHW, or HC. Pixels will be added to all channels. :param distance: Distance from bottom-right walls. :param pixel_value: Value used to replace the entries of the image matrix. + :param channels_first: If the data is provided in channels first format we transpose to NWHC or HC depending on + input shape :return: Backdoored image. """ x = np.copy(x) + original_dtype = x.dtype shape = x.shape + if channels_first: + if len(shape) == 4: + # Transpose the image putting channels last + x = np.transpose(x, (0, 2, 3, 1)) + if len(shape) == 2: + # HC to CH + x = np.transpose(x) + if len(shape) == 4: height, width = x.shape[1:3] x[:, height - distance, width - distance, :] = pixel_value @@ -81,7 +92,15 @@ def add_pattern_bd(x: np.ndarray, distance: int = 2, pixel_value: int = 1) -> np x[height - distance - 2, width - distance] = pixel_value else: raise ValueError(f"Invalid array shape: {shape}") - return x + + if channels_first: + if len(shape) == 4: + # Putting channels first again + x = np.transpose(x, (0, 3, 1, 2)) + if len(shape) == 2: + x = np.transpose(x) + + return x.astype(original_dtype) def insert_image( diff --git a/art/defences/trainer/__init__.py b/art/defences/trainer/__init__.py index 5665267f8f..c37f993798 100644 --- a/art/defences/trainer/__init__.py +++ b/art/defences/trainer/__init__.py @@ -10,4 +10,6 @@ from art.defences.trainer.adversarial_trainer_fbf_pytorch import AdversarialTrainerFBFPyTorch from art.defences.trainer.adversarial_trainer_trades import AdversarialTrainerTRADES from art.defences.trainer.adversarial_trainer_trades_pytorch import AdversarialTrainerTRADESPyTorch +from art.defences.trainer.adversarial_trainer_awp import AdversarialTrainerAWP +from art.defences.trainer.adversarial_trainer_awp_pytorch import AdversarialTrainerAWPPyTorch from art.defences.trainer.dp_instahide_trainer import DPInstaHideTrainer diff --git a/art/defences/trainer/adversarial_trainer.py b/art/defences/trainer/adversarial_trainer.py index 477537d860..5d1369981e 100644 --- a/art/defences/trainer/adversarial_trainer.py +++ b/art/defences/trainer/adversarial_trainer.py @@ -123,7 +123,8 @@ def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwarg logged = False self._precomputed_adv_samples = [] for attack in tqdm(self.attacks, desc="Precompute adversarial examples."): - attack.set_params(verbose=False) + if "verbose" in attack.attack_params: + attack.set_params(verbose=False) if "targeted" in attack.attack_params and attack.targeted: # type: ignore raise NotImplementedError("Adversarial training with targeted attacks is currently not implemented") @@ -155,7 +156,8 @@ def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwarg # Choose indices to replace with adversarial samples attack = self.attacks[attack_id] - attack.set_params(verbose=False) + if "verbose" in attack.attack_params: + attack.set_params(verbose=False) # If source and target models are the same, craft fresh adversarial samples if attack.estimator == self._classifier: @@ -210,7 +212,8 @@ def fit( # pylint: disable=W0221 logged = False self._precomputed_adv_samples = [] for attack in tqdm(self.attacks, desc="Precompute adv samples"): - attack.set_params(verbose=False) + if "verbose" in attack.attack_params: + attack.set_params(verbose=False) if "targeted" in attack.attack_params and attack.targeted: # type: ignore raise NotImplementedError("Adversarial training with targeted attacks is currently not implemented") @@ -234,7 +237,8 @@ def fit( # pylint: disable=W0221 # Choose indices to replace with adversarial samples nb_adv = int(np.ceil(self.ratio * x_batch.shape[0])) attack = self.attacks[attack_id] - attack.set_params(verbose=False) + if "verbose" in attack.attack_params: + attack.set_params(verbose=False) if self.ratio < 1: adv_ids = np.random.choice(x_batch.shape[0], size=nb_adv, replace=False) else: diff --git a/art/defences/trainer/adversarial_trainer_awp.py b/art/defences/trainer/adversarial_trainer_awp.py new file mode 100644 index 0000000000..0c3c33d61e --- /dev/null +++ b/art/defences/trainer/adversarial_trainer_awp.py @@ -0,0 +1,131 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements adversarial training with Adversarial Weight Perturbation (AWP) protocol. + +| Paper link: https://proceedings.neurips.cc/paper/2020/file/1ef91c212e30e14bf125e9374262401f-Paper.pdf + +| It was noted that this protocol uses double perturbation mechanism i.e, perturbation on the input samples and then +perturbation on the model parameters. Consequently, framework specific implementations are being provided in ART. +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import abc +from typing import Optional, Tuple, TYPE_CHECKING + +import numpy as np + +from art.defences.trainer.trainer import Trainer +from art.attacks.attack import EvasionAttack +from art.data_generators import DataGenerator + +if TYPE_CHECKING: + from art.utils import CLASSIFIER_LOSS_GRADIENTS_TYPE + + +class AdversarialTrainerAWP(Trainer): + """ + This is abstract class for different backend-specific implementations of AWP protocol + for adversarial training. + + | Paper link: https://proceedings.neurips.cc/paper/2020/file/1ef91c212e30e14bf125e9374262401f-Paper.pdf + """ + + def __init__( + self, + classifier: "CLASSIFIER_LOSS_GRADIENTS_TYPE", + proxy_classifier: "CLASSIFIER_LOSS_GRADIENTS_TYPE", + attack: EvasionAttack, + mode: str = "PGD", + gamma: float = 0.01, + beta: float = 6.0, + warmup: int = 0, + ): + """ + Create an :class:`.AdversarialTrainerAWP` instance. + + :param classifier: Model to train adversarially. + :param proxy_classifier: Model for adversarial weight perturbation. + :param attack: attack to use for data augmentation in adversarial training + :param mode: mode determining the optimization objective of base adversarial training and weight perturbation + step + :param gamma: The scaling factor controlling norm of weight perturbation relative to model parameters norm + :param beta: The scaling factor controlling tradeoff between clean loss and adversarial loss for TRADES protocol + :param warmup: The number of epochs after which weight perturbation is applied + """ + self._attack = attack + self._proxy_classifier = proxy_classifier + self._mode = mode + self._gamma = gamma + self._beta = beta + self._warmup = warmup + self._apply_wp = False + super().__init__(classifier) + + @abc.abstractmethod + def fit( # pylint: disable=W0221 + self, + x: np.ndarray, + y: np.ndarray, + validation_data: Optional[Tuple[np.ndarray, np.ndarray]] = None, + batch_size: int = 128, + nb_epochs: int = 20, + **kwargs + ): + """ + Train a model adversarially with AWP. See class documentation for more information on the exact procedure. + + :param x: Training set. + :param y: Labels for the training set. + :param validation_data: Tuple consisting of validation data, (x_val, y_val) + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for trainings. + :param kwargs: Dictionary of framework-specific arguments. These will be passed as such to the `fit` function of + the target classifier. + """ + raise NotImplementedError + + @abc.abstractmethod + def fit_generator( # pylint: disable=W0221 + self, + generator: DataGenerator, + validation_data: Optional[Tuple[np.ndarray, np.ndarray]] = None, + nb_epochs: int = 20, + **kwargs + ): + """ + Train a model adversarially with AWP using a data generator. + See class documentation for more information on the exact procedure. + + :param generator: Data generator. + :param validation_data: Tuple consisting of validation data, (x_val, y_val) + :param nb_epochs: Number of epochs to use for trainings. + :param kwargs: Dictionary of framework-specific arguments. These will be passed as such to the `fit` function of + the target classifier. + """ + raise NotImplementedError + + def predict(self, x: np.ndarray, **kwargs) -> np.ndarray: + """ + Perform prediction using the adversarially trained classifier. + + :param x: Input samples. + :param kwargs: Other parameters to be passed on to the `predict` function of the classifier. + :return: Predictions for test set. + """ + return self._classifier.predict(x, **kwargs) diff --git a/art/defences/trainer/adversarial_trainer_awp_pytorch.py b/art/defences/trainer/adversarial_trainer_awp_pytorch.py new file mode 100644 index 0000000000..9a59ea0be6 --- /dev/null +++ b/art/defences/trainer/adversarial_trainer_awp_pytorch.py @@ -0,0 +1,503 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This is a PyTorch implementation of the Adversarial Weight Perturbation (AWP) protocol. + +| Paper link: https://proceedings.neurips.cc/paper/2020/file/1ef91c212e30e14bf125e9374262401f-Paper.pdf +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import time +from typing import Optional, Tuple, TYPE_CHECKING, List, Dict + +from collections import OrderedDict +import numpy as np +from tqdm.auto import trange + +from art.defences.trainer.adversarial_trainer_awp import AdversarialTrainerAWP +from art.estimators.classification.pytorch import PyTorchClassifier +from art.data_generators import DataGenerator +from art.attacks.attack import EvasionAttack +from art.utils import check_and_transform_label_format + +if TYPE_CHECKING: + import torch + +logger = logging.getLogger(__name__) +EPS = 1e-8 # small value required for avoiding division by zero and for KLDivLoss to make probability vector non-zero + + +class AdversarialTrainerAWPPyTorch(AdversarialTrainerAWP): + """ + Class performing adversarial training following Adversarial Weight Perturbation (AWP) protocol. + + | Paper link: https://proceedings.neurips.cc/paper/2020/file/1ef91c212e30e14bf125e9374262401f-Paper.pdf + """ + + def __init__( + self, + classifier: PyTorchClassifier, + proxy_classifier: PyTorchClassifier, + attack: EvasionAttack, + mode: str, + gamma: float, + beta: float, + warmup: int, + ): + """ + Create an :class:`.AdversarialTrainerAWPPyTorch` instance. + + :param classifier: Model to train adversarially. + :param proxy_classifier: Model for adversarial weight perturbation. + :param attack: attack to use for data augmentation in adversarial training. + :param mode: mode determining the optimization objective of base adversarial training and weight perturbation + step + :param gamma: The scaling factor controlling norm of weight perturbation relative to model parameters' norm. + :param beta: The scaling factor controlling tradeoff between clean loss and adversarial loss for TRADES protocol + :param warmup: The number of epochs after which weight perturbation is applied + """ + super().__init__(classifier, proxy_classifier, attack, mode, gamma, beta, warmup) + self._classifier: PyTorchClassifier + self._proxy_classifier: PyTorchClassifier + self._attack: EvasionAttack + self._mode: str + self.gamma: float + self._beta: float + self._warmup: int + self._apply_wp: bool + + def fit( + self, + x: np.ndarray, + y: np.ndarray, + validation_data: Optional[Tuple[np.ndarray, np.ndarray]] = None, + batch_size: int = 128, + nb_epochs: int = 20, + scheduler: "torch.optim.lr_scheduler._LRScheduler" = None, + **kwargs, + ): # pylint: disable=W0221 + """ + Train a model adversarially with AWP protocol. + See class documentation for more information on the exact procedure. + + :param x: Training set. + :param y: Labels for the training set. + :param validation_data: Tuple consisting of validation data, (x_val, y_val) + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for trainings. + :param scheduler: Learning rate scheduler to run at the end of every epoch. + :param kwargs: Dictionary of framework-specific arguments. These will be passed as such to the `fit` function of + the target classifier. + """ + import torch + + logger.info("Performing adversarial training with AWP with %s protocol", self._mode) + + if (scheduler is not None) and ( + not isinstance(scheduler, torch.optim.lr_scheduler._LRScheduler) # pylint: disable=W0212 + ): + raise ValueError("Invalid Pytorch scheduler is provided for adversarial training.") + + best_acc_adv_test = 0 + nb_batches = int(np.ceil(len(x) / batch_size)) + ind = np.arange(len(x)) + + logger.info("Adversarial Training AWP with %s", self._mode) + y = check_and_transform_label_format(y, nb_classes=self.classifier.nb_classes) + + for i_epoch in trange(nb_epochs, desc=f"Adversarial Training AWP with {self._mode} - Epochs"): + + if i_epoch >= self._warmup: + self._apply_wp = True + # Shuffle the examples + np.random.shuffle(ind) + start_time = time.time() + train_loss = 0.0 + train_acc = 0.0 + train_n = 0.0 + + for batch_id in range(nb_batches): + # Create batch data + x_batch = x[ind[batch_id * batch_size : min((batch_id + 1) * batch_size, x.shape[0])]].copy() + y_batch = y[ind[batch_id * batch_size : min((batch_id + 1) * batch_size, x.shape[0])]] + + _train_loss, _train_acc, _train_n = self._batch_process(x_batch, y_batch) + + train_loss += _train_loss + train_acc += _train_acc + train_n += _train_n + + if scheduler: + scheduler.step() + + train_time = time.time() + + # compute accuracy + if validation_data is not None: + (x_test, y_test) = validation_data + y_test = check_and_transform_label_format(y_test, nb_classes=self.classifier.nb_classes) + # pylint: disable=W0212 + x_preprocessed_test, y_preprocessed_test = self._classifier._apply_preprocessing( + x_test, + y_test, + fit=True, + ) + # pylint: enable=W0212 + output_clean = np.argmax(self.predict(x_preprocessed_test), axis=1) + nb_correct_clean = np.sum(output_clean == np.argmax(y_preprocessed_test, axis=1)) + x_test_adv = self._attack.generate(x_preprocessed_test, y=y_preprocessed_test) + output_adv = np.argmax(self.predict(x_test_adv), axis=1) + nb_correct_adv = np.sum(output_adv == np.argmax(y_preprocessed_test, axis=1)) + + logger.info( + "epoch: %s time(s): %.1f loss: %.4f acc-adv (tr): %.4f acc-clean (val): %.4f acc-adv (val): %.4f", + i_epoch, + train_time - start_time, + train_loss / train_n, + train_acc / train_n, + nb_correct_clean / x_test.shape[0], + nb_correct_adv / x_test.shape[0], + ) + + # save last checkpoint + if i_epoch + 1 == nb_epochs: + self._classifier.save(filename=f"awp_{self._mode.lower()}_epoch_{i_epoch}") + + # save best checkpoint + if nb_correct_adv / x_test.shape[0] > best_acc_adv_test: + self._classifier.save(filename=f"awp_{self._mode.lower()}_epoch_best") + best_acc_adv_test = nb_correct_adv / x_test.shape[0] + + else: + logger.info( + "epoch: %s time(s): %.1f loss: %.4f acc-adv: %.4f", + i_epoch, + train_time - start_time, + train_loss / train_n, + train_acc / train_n, + ) + + def fit_generator( + self, + generator: DataGenerator, + validation_data: Optional[Tuple[np.ndarray, np.ndarray]] = None, + nb_epochs: int = 20, + scheduler: "torch.optim.lr_scheduler._LRScheduler" = None, + **kwargs, + ): # pylint: disable=W0221 + """ + Train a model adversarially with AWP protocol using a data generator. + See class documentation for more information on the exact procedure. + + :param generator: Data generator. + :param validation_data: Tuple consisting of validation data, (x_val, y_val) + :param nb_epochs: Number of epochs to use for trainings. + :param scheduler: Learning rate scheduler to run at the end of every epoch. + :param kwargs: Dictionary of framework-specific arguments. These will be passed as such to the `fit` function of + the target classifier. + """ + import torch + + logger.info("Performing adversarial training with AWP with %s protocol", self._mode) + + if (scheduler is not None) and ( + not isinstance(scheduler, torch.optim.lr_scheduler._LRScheduler) # pylint: disable=W0212 + ): + raise ValueError("Invalid Pytorch scheduler is provided for adversarial training.") + + size = generator.size + batch_size = generator.batch_size + if size is not None: + nb_batches = int(np.ceil(size / batch_size)) + else: + raise ValueError("Size is None.") + + logger.info("Adversarial Training AWP with %s", self._mode) + + best_acc_adv_test = 0 + for i_epoch in trange(nb_epochs, desc=f"Adversarial Training AWP with {self._mode} - Epochs"): + + if i_epoch >= self._warmup: + self._apply_wp = True + + start_time = time.time() + train_loss = 0.0 + train_acc = 0.0 + train_n = 0.0 + + for _ in range(nb_batches): + # Create batch data + x_batch, y_batch = generator.get_batch() + x_batch = x_batch.copy() + + _train_loss, _train_acc, _train_n = self._batch_process(x_batch, y_batch) + + train_loss += _train_loss + train_acc += _train_acc + train_n += _train_n + + if scheduler: + scheduler.step() + + train_time = time.time() + + # compute accuracy + if validation_data is not None: + (x_test, y_test) = validation_data + y_test = check_and_transform_label_format(y_test, nb_classes=self.classifier.nb_classes) + # pylint: disable=W0212 + x_preprocessed_test, y_preprocessed_test = self._classifier._apply_preprocessing( + x_test, + y_test, + fit=True, + ) + # pylint: enable=W0212 + output_clean = np.argmax(self.predict(x_preprocessed_test), axis=1) + nb_correct_clean = np.sum(output_clean == np.argmax(y_preprocessed_test, axis=1)) + x_test_adv = self._attack.generate(x_preprocessed_test, y=y_preprocessed_test) + output_adv = np.argmax(self.predict(x_test_adv), axis=1) + nb_correct_adv = np.sum(output_adv == np.argmax(y_preprocessed_test, axis=1)) + + logger.info( + "epoch: %s time(s): %.1f loss: %.4f acc-adv (tr): %.4f acc-clean (val): %.4f acc-adv (val): %.4f", + i_epoch, + train_time - start_time, + train_loss / train_n, + train_acc / train_n, + nb_correct_clean / x_test.shape[0], + nb_correct_adv / x_test.shape[0], + ) + # save last checkpoint + if i_epoch + 1 == nb_epochs: + self._classifier.save(filename=f"awp_{self._mode.lower()}_epoch_{i_epoch}") + + # save best checkpoint + if nb_correct_adv / x_test.shape[0] > best_acc_adv_test: + self._classifier.save(filename=f"awp_{self._mode.lower()}_epoch_best") + best_acc_adv_test = nb_correct_adv / x_test.shape[0] + + else: + logger.info( + "epoch: %s time(s): %.1f loss: %.4f acc-adv: %.4f", + i_epoch, + train_time - start_time, + train_loss / train_n, + train_acc / train_n, + ) + + def _batch_process(self, x_batch: np.ndarray, y_batch: np.ndarray) -> Tuple[float, float, float]: + """ + Perform the operations of AWP for a batch of data. + See class documentation for more information on the exact procedure. + + :param x_batch: batch of x. + :param y_batch: batch of y. + :return: tuple containing batch data loss, batch data accuracy and number of samples in the batch + """ + import torch + from torch import nn + import torch.nn.functional as F + + if self._classifier.optimizer is None: + raise ValueError("Optimizer of classifier is currently None, but is required for adversarial training.") + + if self._proxy_classifier.optimizer is None: + raise ValueError( + "Optimizer of proxy classifier is currently None, but is required for adversarial training." + ) + + self._classifier.model.train(mode=False) + x_batch_pert = self._attack.generate(x_batch, y=y_batch) + + # Apply preprocessing + y_batch = check_and_transform_label_format(y_batch, nb_classes=self.classifier.nb_classes) + + x_preprocessed, y_preprocessed = self._classifier._apply_preprocessing( # pylint: disable=W0212 + x_batch, y_batch, fit=True + ) + x_preprocessed_pert, _ = self._classifier._apply_preprocessing( # pylint: disable=W0212 + x_batch_pert, y_batch, fit=True + ) + + # Check label shape + if self._classifier._reduce_labels: # pylint: disable=W0212 + y_preprocessed = np.argmax(y_preprocessed, axis=1) + + i_batch = torch.from_numpy(x_preprocessed).to(self._classifier.device) + i_batch_pert = torch.from_numpy(x_preprocessed_pert).to(self._classifier.device) + o_batch = torch.from_numpy(y_preprocessed).to(self._classifier.device) + + self._classifier.model.train(mode=True) + + if self._apply_wp: + w_perturb = self._weight_perturbation(x_batch=i_batch, x_batch_pert=i_batch_pert, y_batch=o_batch) + list_keys = list(w_perturb.keys()) + self._modify_classifier(self._classifier, list_keys, w_perturb, op="add") + + # Zero the parameter gradients + self._classifier.optimizer.zero_grad() + + if self._mode.lower() == "pgd": + # Perform prediction + model_outputs_pert = self._classifier.model(i_batch_pert) + loss = self._classifier.loss(model_outputs_pert, o_batch) + + elif self._mode.lower() == "trades": + n = x_batch.shape[0] + # Perform prediction + model_outputs = self._classifier.model(i_batch) + model_outputs_pert = self._classifier.model(i_batch_pert) + + # Form the loss function + loss_clean = self._classifier.loss(model_outputs, o_batch) + loss_kl = (1.0 / n) * nn.KLDivLoss(reduction="sum")( + F.log_softmax(model_outputs_pert, dim=1), torch.clamp(F.softmax(model_outputs, dim=1), min=EPS) + ) + loss = loss_clean + self._beta * loss_kl + + else: + raise ValueError( + "Incorrect mode provided for base adversarial training. 'mode' must be among 'PGD' and 'TRADES'." + ) + + loss.backward() + + self._classifier.optimizer.step() + + if self._apply_wp: + self._modify_classifier(self._classifier, list_keys, w_perturb, op="subtract") + + train_loss = loss.item() * o_batch.size(0) + train_acc = (model_outputs_pert.max(1)[1] == o_batch).sum().item() + train_n = o_batch.size(0) + + self._classifier.model.train(mode=False) + + return train_loss, train_acc, train_n + + def _weight_perturbation( + self, x_batch: "torch.Tensor", x_batch_pert: "torch.Tensor", y_batch: "torch.Tensor" + ) -> Dict[str, "torch.Tensor"]: + """ + Calculate wight perturbation for a batch of data. + See class documentation for more information on the exact procedure. + + :param x_batch: batch of x. + :param x_batch_pert: batch of x with perturbations. + :param y_batch: batch of y. + :return: dict containing names of classifier model's layers as keys and parameters as values + """ + import torch + from torch import nn + import torch.nn.functional as F + + w_perturb = OrderedDict() + params_dict, _ = self._calculate_model_params(self._classifier) + list_keys = list(params_dict.keys()) + self._proxy_classifier.model.load_state_dict(self._classifier.model.state_dict()) + self._proxy_classifier.model.train(mode=True) + + if self._mode.lower() == "pgd": + # Perform prediction + model_outputs_pert = self._proxy_classifier.model(x_batch_pert) + loss = -self._proxy_classifier.loss(model_outputs_pert, y_batch) + elif self._mode.lower() == "trades": + n = x_batch.shape[0] + # Perform prediction + model_outputs = self._proxy_classifier.model(x_batch) + model_outputs_pert = self._proxy_classifier.model(x_batch_pert) + loss_clean = self._proxy_classifier.loss(model_outputs, y_batch) + loss_kl = (1.0 / n) * nn.KLDivLoss(reduction="sum")( + F.log_softmax(model_outputs_pert, dim=1), torch.clamp(F.softmax(model_outputs, dim=1), min=EPS) + ) + loss = -1.0 * (loss_clean + self._beta * loss_kl) + + else: + raise ValueError( + "Incorrect mode provided for base adversarial training. 'mode' must be among 'PGD' and 'TRADES'." + ) + + self._proxy_classifier.optimizer.zero_grad() + loss.backward() + self._proxy_classifier.optimizer.step() + + params_dict_proxy, _ = self._calculate_model_params(self._proxy_classifier) + + for name in list_keys: + perturbation = params_dict_proxy[name]["param"] - params_dict[name]["param"] + perturbation = torch.reshape(perturbation, list(params_dict[name]["size"])) + scale = params_dict[name]["norm"] / (perturbation.norm() + EPS) + w_perturb[name] = scale * perturbation + + return w_perturb + + @staticmethod + def _calculate_model_params( + p_classifier: PyTorchClassifier, + ) -> Tuple[Dict[str, Dict[str, "torch.Tensor"]], "torch.Tensor"]: + """ + Calculates a given model's different layers' parameters' shape and norm, and model parameter norm. + + :param p_classifier: model for awp protocol. + :return: tuple with first element a dictionary with model parameters' names as keys and a nested dictionary + as value. The nested dictionary contains model parameters, model parameters' size, model parameters' norms. + The second element of tuple denotes norm of all model parameters + """ + import torch + + params_dict: Dict[str, Dict[str, "torch.Tensor"]] = OrderedDict() + list_params = [] + for name, param in p_classifier.model.state_dict().items(): + if len(param.size()) <= 1: + continue + if "weight" in name: + temp_param = param.reshape(-1) + list_params.append(temp_param) + params_dict[name] = OrderedDict() + params_dict[name]["param"] = temp_param + params_dict[name]["size"] = param.size() + params_dict[name]["norm"] = temp_param.norm() + + model_all_params = torch.cat(list_params) + model_all_params_norm = model_all_params.norm() + return params_dict, model_all_params_norm + + def _modify_classifier( + self, p_classifier: PyTorchClassifier, list_keys: List[str], w_perturb: Dict[str, "torch.Tensor"], op: str + ) -> None: + """ + Modify the model's weight parameters according to the weight perturbations. + + :param p_classifier: model for awp protocol. + :param list_keys: list of model parameters' names + :param w_perturb: dictionary containing model parameters' names as keys and model parameters as values + :param op: controls whether weight perturbation will be added or subtracted from model parameters + """ + import torch + + if op.lower() == "add": + c_mult = 1.0 + elif op.lower() == "subtract": + c_mult = -1.0 + else: + raise ValueError("Incorrect op provided for weight perturbation. 'op' must be among 'add' and 'subtract'.") + with torch.no_grad(): + for name, param in p_classifier.model.named_parameters(): + if name in list_keys: + param.add_(c_mult * self._gamma * w_perturb[name]) diff --git a/art/estimators/certification/__init__.py b/art/estimators/certification/__init__.py index 33a97ad7ad..83a69eb514 100644 --- a/art/estimators/certification/__init__.py +++ b/art/estimators/certification/__init__.py @@ -6,9 +6,10 @@ from art.estimators.certification.randomized_smoothing.numpy import NumpyRandomizedSmoothing from art.estimators.certification.randomized_smoothing.tensorflow import TensorFlowV2RandomizedSmoothing from art.estimators.certification.randomized_smoothing.pytorch import PyTorchRandomizedSmoothing -from art.estimators.certification.derandomized_smoothing.derandomized_smoothing import DeRandomizedSmoothingMixin from art.estimators.certification.derandomized_smoothing.pytorch import PyTorchDeRandomizedSmoothing from art.estimators.certification.derandomized_smoothing.tensorflow import TensorFlowV2DeRandomizedSmoothing +from art.estimators.certification.object_seeker.object_seeker import ObjectSeekerMixin +from art.estimators.certification.object_seeker.pytorch import PyTorchObjectSeeker if importlib.util.find_spec("torch") is not None: from art.estimators.certification.deep_z.deep_z import ZonoDenseLayer diff --git a/art/estimators/certification/derandomized_smoothing/__init__.py b/art/estimators/certification/derandomized_smoothing/__init__.py index 1eea6eb3da..69753f4f39 100644 --- a/art/estimators/certification/derandomized_smoothing/__init__.py +++ b/art/estimators/certification/derandomized_smoothing/__init__.py @@ -1,6 +1,5 @@ """ DeRandomized smoothing estimators. """ -from art.estimators.certification.derandomized_smoothing.derandomized_smoothing import DeRandomizedSmoothingMixin from art.estimators.certification.derandomized_smoothing.pytorch import PyTorchDeRandomizedSmoothing from art.estimators.certification.derandomized_smoothing.tensorflow import TensorFlowV2DeRandomizedSmoothing diff --git a/art/estimators/certification/derandomized_smoothing/ablators/__init__.py b/art/estimators/certification/derandomized_smoothing/ablators/__init__.py new file mode 100644 index 0000000000..23715d4aba --- /dev/null +++ b/art/estimators/certification/derandomized_smoothing/ablators/__init__.py @@ -0,0 +1,12 @@ +""" +This module contains the ablators for the certified smoothing approaches. +""" +import importlib + +from art.estimators.certification.derandomized_smoothing.ablators.tensorflow import ColumnAblator, BlockAblator + +if importlib.util.find_spec("torch") is not None: + from art.estimators.certification.derandomized_smoothing.ablators.pytorch import ( + ColumnAblatorPyTorch, + BlockAblatorPyTorch, + ) diff --git a/art/estimators/certification/derandomized_smoothing/ablators/ablate.py b/art/estimators/certification/derandomized_smoothing/ablators/ablate.py new file mode 100644 index 0000000000..3970b5b862 --- /dev/null +++ b/art/estimators/certification/derandomized_smoothing/ablators/ablate.py @@ -0,0 +1,90 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2022 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the abstract base class for the ablators. +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +from abc import ABC, abstractmethod +from typing import Optional, Tuple, Union, TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + # pylint: disable=C0412 + import tensorflow as tf + import torch + + +class BaseAblator(ABC): + """ + Base class defining the methods used for the ablators. + """ + + @abstractmethod + def __call__( + self, x: np.ndarray, column_pos: Optional[Union[int, list]] = None, row_pos: Optional[Union[int, list]] = None + ) -> np.ndarray: + """ + Ablate the image x at location specified by "column_pos" for the case of column ablation or at the location + specified by "column_pos" and "row_pos" in the case of block ablation. + + :param x: input image. + :param column_pos: column position to specify where to retain the image + :param row_pos: row position to specify where to retain the image. Not used for ablation type "column". + """ + raise NotImplementedError + + @abstractmethod + def certify( + self, pred_counts: np.ndarray, size_to_certify: int, label: Union[np.ndarray, "tf.Tensor"] + ) -> Union[Tuple["tf.Tensor", "tf.Tensor", "tf.Tensor"], Tuple["torch.Tensor", "torch.Tensor", "torch.Tensor"]]: + """ + Checks if based on the predictions supplied the classifications over the ablated datapoints result in a + certified prediction against a patch attack of size size_to_certify. + + :param pred_counts: The cumulative predictions of the classifier over the ablation locations. + :param size_to_certify: The size of the patch to check against. + :param label: ground truth labels + """ + raise NotImplementedError + + @abstractmethod + def ablate(self, x: np.ndarray, column_pos: int, row_pos: int) -> Union[np.ndarray, "torch.Tensor"]: + """ + Ablate the image x at location specified by "column_pos" for the case of column ablation or at the location + specified by "column_pos" and "row_pos" in the case of block ablation. + + :param x: input image. + :param column_pos: column position to specify where to retain the image + :param row_pos: row position to specify where to retain the image. Not used for ablation type "column". + """ + raise NotImplementedError + + @abstractmethod + def forward( + self, x: np.ndarray, column_pos: Optional[int] = None, row_pos: Optional[int] = None + ) -> Union[np.ndarray, "torch.Tensor"]: + """ + Ablate batch of data at locations specified by column_pos and row_pos + + :param x: input image. + :param column_pos: column position to specify where to retain the image + :param row_pos: row position to specify where to retain the image. Not used for ablation type "column". + """ + raise NotImplementedError diff --git a/art/estimators/certification/derandomized_smoothing/ablators/pytorch.py b/art/estimators/certification/derandomized_smoothing/ablators/pytorch.py new file mode 100644 index 0000000000..1f1ad1aeec --- /dev/null +++ b/art/estimators/certification/derandomized_smoothing/ablators/pytorch.py @@ -0,0 +1,401 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements Certified Patch Robustness via Smoothed Vision Transformers + +| Paper link Accepted version: + https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf + +| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf +""" + +from typing import Optional, Union, Tuple +import random + +import numpy as np +import torch + +from art.estimators.certification.derandomized_smoothing.ablators.ablate import BaseAblator + + +class UpSamplerPyTorch(torch.nn.Module): + """ + Resizes datasets to the specified size. + Usually for upscaling datasets like CIFAR to Imagenet format + """ + + def __init__(self, input_size: int, final_size: int) -> None: + """ + Creates an upsampler to make the supplied data match the pre-trained ViT format + + :param input_size: Size of the current input data + :param final_size: Desired final size + """ + super().__init__() + self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Forward pass though the upsampler. + + :param x: Input data + :return: The upsampled input data + """ + return self.upsample(x) + + +class ColumnAblatorPyTorch(torch.nn.Module, BaseAblator): + """ + Pure Pytorch implementation of stripe/column ablation. + """ + + def __init__( + self, + ablation_size: int, + channels_first: bool, + mode: str, + to_reshape: bool, + ablation_mode: str = "column", + original_shape: Optional[Tuple] = None, + output_shape: Optional[Tuple] = None, + algorithm: str = "salman2021", + device_type: str = "gpu", + ): + """ + Creates a column ablator + + :param ablation_size: The size of the column we will retain. + :param channels_first: If the input is in channels first format. Currently required to be True. + :param mode: If we are running the algorithm using a CNN or VIT. + :param to_reshape: If the input requires reshaping. + :param ablation_mode: The type of ablation to perform. + :param original_shape: Original shape of the input. + :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224. + :param algorithm: Either 'salman2021' or 'levine2020'. + :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. + """ + super().__init__() + + self.ablation_size = ablation_size + self.channels_first = channels_first + self.to_reshape = to_reshape + self.add_ablation_mask = False + self.additional_channels = False + self.algorithm = algorithm + self.original_shape = original_shape + self.ablation_mode = ablation_mode + + if self.algorithm == "levine2020": + self.additional_channels = True + if self.algorithm == "salman2021" and mode == "ViT": + self.add_ablation_mask = True + + if device_type == "cpu" or not torch.cuda.is_available(): + self.device = torch.device("cpu") + else: # pragma: no cover + cuda_idx = torch.cuda.current_device() + self.device = torch.device(f"cuda:{cuda_idx}") + + if original_shape is not None and output_shape is not None: + self.upsample = UpSamplerPyTorch(input_size=original_shape[1], final_size=output_shape[1]) + + def ablate( + self, x: Union[torch.Tensor, np.ndarray], column_pos: int, row_pos: Optional[int] = None + ) -> torch.Tensor: + """ + Ablates the input column wise + + :param x: Input data + :param column_pos: location to start the retained column. NB, if row_ablation_mode is true then this will + be used to act on the rows through transposing the image in ColumnAblatorPyTorch.forward + :param row_pos: Unused. + :return: The ablated input with 0s where the ablation occurred + """ + k = self.ablation_size + + if isinstance(x, np.ndarray): + x = torch.from_numpy(x).to(self.device) + + if column_pos + k > x.shape[-1]: + x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0 + else: + x[:, :, :, :column_pos] = 0.0 + x[:, :, :, column_pos + k :] = 0.0 + return x + + def forward( + self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos=None + ) -> torch.Tensor: + """ + Forward pass though the ablator. We insert a new channel to keep track of the ablation location. + + :param x: Input data + :param column_pos: The start position of the albation + :param row_pos: Unused. + :return: The albated input with an extra channel indicating the location of the ablation + """ + if row_pos is not None: + raise ValueError("Use column_pos for a ColumnAblator. The row_pos argument is unused") + + if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.algorithm == "salman2021": + raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}") + + if isinstance(x, np.ndarray): + x = torch.from_numpy(x).to(self.device) + + if self.add_ablation_mask: + ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device) + x = torch.cat([x, ones], dim=1) + + if self.additional_channels: + x = torch.cat([x, 1.0 - x], dim=1) + + if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.additional_channels: + raise ValueError( + f"Ablator expected {self.original_shape[0]} input channels. Received shape of {x.shape[1]}" + ) + + if self.ablation_mode == "row": + x = torch.transpose(x, 3, 2) + + if column_pos is None: + column_pos = random.randint(0, x.shape[3]) + + ablated_x = self.ablate(x, column_pos=column_pos) + + if self.ablation_mode == "row": + ablated_x = torch.transpose(ablated_x, 3, 2) + + if self.to_reshape: + ablated_x = self.upsample(ablated_x) + return ablated_x + + def certify( + self, + pred_counts: Union[torch.Tensor, np.ndarray], + size_to_certify: int, + label: Union[torch.Tensor, np.ndarray], + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Performs certification of the predictions + + :param pred_counts: The model predictions over the ablated data. + :param size_to_certify: The patch size we wish to check certification against + :param label: The ground truth labels + :return: A tuple consisting of: the certified predictions, + the predictions which were certified and also correct, + and the most predicted class across the different ablations on the input. + """ + + if isinstance(pred_counts, np.ndarray): + pred_counts = torch.from_numpy(pred_counts).to(self.device) + + if isinstance(label, np.ndarray): + label = torch.from_numpy(label).to(self.device) + + num_of_classes = pred_counts.shape[-1] + + # NB! argmax and kthvalue handle ties between predicted counts differently. + # The original implementation: https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L98 + # uses argmax for the model predictions + # (later called y_smoothed https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230) + # and kthvalue for the certified predictions. + # to be consistent with the original implementation we also follow this here. + top_predicted_class_argmax = torch.argmax(pred_counts, dim=1) + + top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1) + second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1) + + cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1) + + if self.algorithm == "levine2020": + tie_break_certs = ( + (top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1) + ) & (top_predicted_class < second_predicted_class) + cert = torch.logical_or(cert, tie_break_certs) + + cert_and_correct = cert & (label == top_predicted_class) + + return cert, cert_and_correct, top_predicted_class_argmax + + +class BlockAblatorPyTorch(torch.nn.Module, BaseAblator): + """ + Pure Pytorch implementation of block ablation. + """ + + def __init__( + self, + ablation_size: int, + channels_first: bool, + mode: str, + to_reshape: bool, + original_shape: Optional[Tuple] = None, + output_shape: Optional[Tuple] = None, + algorithm: str = "salman2021", + device_type: str = "gpu", + ): + """ + Creates a column ablator + + :param ablation_size: The size of the block we will retain. + :param channels_first: If the input is in channels first format. Currently required to be True. + :param mode: If we are running the algorithm using a CNN or VIT. + :param to_reshape: If the input requires reshaping. + :param original_shape: Original shape of the input. + :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224. + :param algorithm: Either 'salman2021' or 'levine2020'. + :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. + """ + super().__init__() + + self.ablation_size = ablation_size + self.channels_first = channels_first + self.to_reshape = to_reshape + self.add_ablation_mask = False + self.additional_channels = False + self.algorithm = algorithm + self.original_shape = original_shape + + if self.algorithm == "levine2020": + self.additional_channels = True + if self.algorithm == "salman2021" and mode == "ViT": + self.add_ablation_mask = True + + if device_type == "cpu" or not torch.cuda.is_available(): + self.device = torch.device("cpu") + else: # pragma: no cover + cuda_idx = torch.cuda.current_device() + self.device = torch.device(f"cuda:{cuda_idx}") + + if original_shape is not None and output_shape is not None: + self.upsample = UpSamplerPyTorch(input_size=original_shape[1], final_size=output_shape[1]) + + def ablate(self, x: Union[torch.Tensor, np.ndarray], column_pos: int, row_pos: int) -> torch.Tensor: + """ + Ablates the input block wise + + :param x: Input data + :param column_pos: The start position of the albation + :param row_pos: The row start position of the albation + :return: The ablated input with 0s where the ablation occurred + """ + + if isinstance(x, np.ndarray): + x = torch.from_numpy(x).to(self.device) + + k = self.ablation_size + # Column ablations + if column_pos + k > x.shape[-1]: + x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0 + else: + x[:, :, :, :column_pos] = 0.0 + x[:, :, :, column_pos + k :] = 0.0 + + # Row ablations + if row_pos + k > x.shape[-2]: + x[:, :, (row_pos + k) % x.shape[-2] : row_pos, :] = 0.0 + else: + x[:, :, :row_pos, :] = 0.0 + x[:, :, row_pos + k :, :] = 0.0 + return x + + def forward( + self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos: Optional[int] = None + ) -> torch.Tensor: + """ + Forward pass though the ablator. We insert a new channel to keep track of the ablation location. + + :param x: Input data + :param column_pos: The start position of the albation + :return: The albated input with an extra channel indicating the location of the ablation if running in + """ + if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.algorithm == "salman2021": + raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}") + + if column_pos is None: + column_pos = random.randint(0, x.shape[3]) + + if row_pos is None: + row_pos = random.randint(0, x.shape[2]) + + if isinstance(x, np.ndarray): + x = torch.from_numpy(x).to(self.device) + + if self.add_ablation_mask: + ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device) + x = torch.cat([x, ones], dim=1) + + if self.additional_channels: + x = torch.cat([x, 1.0 - x], dim=1) + + if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.additional_channels: + raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}") + + ablated_x = self.ablate(x, column_pos=column_pos, row_pos=row_pos) + + if self.to_reshape: + ablated_x = self.upsample(ablated_x) + return ablated_x + + def certify( + self, + pred_counts: Union[torch.Tensor, np.ndarray], + size_to_certify: int, + label: Union[torch.Tensor, np.ndarray], + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Performs certification of the predictions + + :param pred_counts: The model predictions over the ablated data. + :param size_to_certify: The patch size we wish to check certification against + :param label: The ground truth labels + :return: A tuple consisting of: the certified predictions, + the predictions which were certified and also correct, + and the most predicted class across the different ablations on the input. + """ + + if isinstance(pred_counts, np.ndarray): + pred_counts = torch.from_numpy(pred_counts).to(self.device) + + if isinstance(label, np.ndarray): + label = torch.from_numpy(label).to(self.device) + + # NB! argmax and kthvalue handle ties between predicted counts differently. + # The original implementation: https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L145 + # uses argmax for the model predictions + # (later called y_smoothed https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230) + # and kthvalue for the certified predictions. + # to be consistent with the original implementation we also follow this here. + top_predicted_class_argmax = torch.argmax(pred_counts, dim=1) + + num_of_classes = pred_counts.shape[-1] + + top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1) + second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1) + + cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1) ** 2 + + cert_and_correct = cert & (label == top_predicted_class) + + if self.algorithm == "levine2020": + tie_break_certs = ( + (top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1) ** 2 + ) & (top_predicted_class < second_predicted_class) + cert = torch.logical_or(cert, tie_break_certs) + return cert, cert_and_correct, top_predicted_class_argmax diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py b/art/estimators/certification/derandomized_smoothing/ablators/tensorflow.py similarity index 51% rename from art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py rename to art/estimators/certification/derandomized_smoothing/ablators/tensorflow.py index 42a31ca418..e4b927358e 100644 --- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py +++ b/art/estimators/certification/derandomized_smoothing/ablators/tensorflow.py @@ -23,176 +23,16 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from abc import ABC, abstractmethod -from typing import Optional, Union, TYPE_CHECKING +from typing import Optional, Union, Tuple, TYPE_CHECKING import random import numpy as np -if TYPE_CHECKING: - from art.utils import ABLATOR_TYPE - - -class DeRandomizedSmoothingMixin(ABC): - """ - Implementation of (De)Randomized Smoothing applied to classifier predictions as introduced - in Levine et al. (2020). - - | Paper link: https://arxiv.org/abs/2002.10733 - """ - - def __init__( - self, - ablation_type: str, - ablation_size: int, - threshold: float, - logits: bool, - channels_first: bool, - *args, - **kwargs, - ) -> None: - """ - Create a derandomized smoothing wrapper. - - :param ablation_type: The type of ablations to perform. Currently must be either "column", "row", or "block" - :param ablation_size: Size of the retained image patch. - An int specifying the width of the column for column ablation - Or an int specifying the height/width of a square for block ablation - :param threshold: The minimum threshold to count a prediction. - :param logits: if the model returns logits or normalized probabilities - :param channels_first: If the channels are first or last. - """ - super().__init__(*args, **kwargs) # type: ignore - self.ablation_type = ablation_type - self.logits = logits - self.threshold = threshold - self._channels_first = channels_first - if TYPE_CHECKING: - self.ablator: ABLATOR_TYPE # pylint: disable=used-before-assignment - - if self.ablation_type in {"column", "row"}: - row_ablation_mode = self.ablation_type == "row" - self.ablator = ColumnAblator( - ablation_size=ablation_size, channels_first=self._channels_first, row_ablation_mode=row_ablation_mode - ) - elif self.ablation_type == "block": - self.ablator = BlockAblator(ablation_size=ablation_size, channels_first=self._channels_first) - else: - raise ValueError("Ablation type not supported. Must be either column or block") - - def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: - """ - Perform prediction for a batch of inputs. - - :param x: Input samples. - :param batch_size: Size of batches. - :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode. - :return: Array of predictions of shape `(nb_inputs, nb_classes)`. - """ - raise NotImplementedError - - def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray: - """ - Performs cumulative predictions over every ablation location - - :param x: Unablated image - :param batch_size: the batch size for the prediction - :param training_mode: if to run the classifier in training mode - :return: cumulative predictions after sweeping over all the ablation configurations. - """ - if self._channels_first: - columns_in_data = x.shape[-1] - rows_in_data = x.shape[-2] - else: - columns_in_data = x.shape[-2] - rows_in_data = x.shape[-3] - - if self.ablation_type in {"column", "row"}: - if self.ablation_type == "column": - ablate_over_range = columns_in_data - else: - # image will be transposed, so loop over the number of rows - ablate_over_range = rows_in_data - - for ablation_start in range(ablate_over_range): - ablated_x = self.ablator.forward(np.copy(x), column_pos=ablation_start) - if ablation_start == 0: - preds = self._predict_classifier( - ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs - ) - else: - preds += self._predict_classifier( - ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs - ) - elif self.ablation_type == "block": - for xcorner in range(rows_in_data): - for ycorner in range(columns_in_data): - ablated_x = self.ablator.forward(np.copy(x), row_pos=xcorner, column_pos=ycorner) - if ycorner == 0 and xcorner == 0: - preds = self._predict_classifier( - ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs - ) - else: - preds += self._predict_classifier( - ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs - ) - return preds - - -class BaseAblator(ABC): - """ - Base class defining the methods used for the ablators. - """ - - @abstractmethod - def __call__( - self, x: np.ndarray, column_pos: Optional[Union[int, list]] = None, row_pos: Optional[Union[int, list]] = None - ) -> np.ndarray: - """ - Ablate the image x at location specified by "column_pos" for the case of column ablation or at the location - specified by "column_pos" and "row_pos" in the case of block ablation. - - :param x: input image. - :param column_pos: column position to specify where to retain the image - :param row_pos: row position to specify where to retain the image. Not used for ablation type "column". - """ - raise NotImplementedError - - @abstractmethod - def certify(self, preds: np.ndarray, size_to_certify: int): - """ - Checks if based on the predictions supplied the classifications over the ablated datapoints result in a - certified prediction against a patch attack of size size_to_certify. - - :param preds: The cumulative predictions of the classifier over the ablation locations. - :param size_to_certify: The size of the patch to check against. - """ - raise NotImplementedError - - @abstractmethod - def ablate(self, x: np.ndarray, column_pos: int, row_pos: int) -> np.ndarray: - """ - Ablate the image x at location specified by "column_pos" for the case of column ablation or at the location - specified by "column_pos" and "row_pos" in the case of block ablation. +from art.estimators.certification.derandomized_smoothing.ablators.ablate import BaseAblator - :param x: input image. - :param column_pos: column position to specify where to retain the image - :param row_pos: row position to specify where to retain the image. Not used for ablation type "column". - """ - raise NotImplementedError - - @abstractmethod - def forward( - self, x: np.ndarray, column_pos: Optional[Union[int, list]] = None, row_pos: Optional[Union[int, list]] = None - ) -> np.ndarray: - """ - Ablate batch of data at locations specified by column_pos and row_pos - - :param x: input image. - :param column_pos: column position to specify where to retain the image - :param row_pos: row position to specify where to retain the image. Not used for ablation type "column". - """ - raise NotImplementedError +if TYPE_CHECKING: + # pylint: disable=C0412 + import tensorflow as tf class ColumnAblator(BaseAblator): @@ -230,27 +70,50 @@ def __call__( """ return self.forward(x=x, column_pos=column_pos) - def certify(self, preds: np.ndarray, size_to_certify: int) -> np.ndarray: + def certify( + self, pred_counts: "tf.Tensor", size_to_certify: int, label: Union[np.ndarray, "tf.Tensor"] + ) -> Tuple["tf.Tensor", "tf.Tensor", "tf.Tensor"]: """ Checks if based on the predictions supplied the classifications over the ablated datapoints result in a certified prediction against a patch attack of size size_to_certify. :param preds: The cumulative predictions of the classifier over the ablation locations. :param size_to_certify: The size of the patch to check against. - :return: Array of bools indicating if a point is certified against the given patch dimensions. + :param label: Ground truth labels + :return: A tuple consisting of: the certified predictions, + the predictions which were certified and also correct, + and the most predicted class across the different ablations on the input. """ - indices = np.argsort(-preds, axis=1, kind="stable") - values = np.take_along_axis(np.copy(preds), indices, axis=1) + import tensorflow as tf - num_affected_classifications = size_to_certify + self.ablation_size - 1 + result = tf.math.top_k(pred_counts, k=2) - margin = values[:, 0] - values[:, 1] + top_predicted_class, second_predicted_class = result.indices[:, 0], result.indices[:, 1] + top_class_counts, second_class_counts = result.values[:, 0], result.values[:, 1] - certs = margin > 2 * num_affected_classifications - tie_break_certs = (margin == 2 * num_affected_classifications) & (indices[:, 0] < indices[:, 1]) - return np.logical_or(certs, tie_break_certs) + certs = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1) - def ablate(self, x: np.ndarray, column_pos: int, row_pos=None) -> np.ndarray: + tie_break_certs = ( + (top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1) + ) & (top_predicted_class < second_predicted_class) + cert = tf.math.logical_or(certs, tie_break_certs) + + # NB, newer versions of pylint do not require the disable. + if label.ndim > 1: + cert_and_correct = cert & ( + tf.math.argmax(label, axis=1) + == tf.cast( # pylint: disable=E1120, E1123 + top_predicted_class, dtype=tf.math.argmax(label, axis=1).dtype + ) + ) + else: + cert_and_correct = cert & ( + label == tf.cast(top_predicted_class, dtype=label.dtype) # pylint: disable=E1120, E1123 + ) + + return cert, cert_and_correct, top_predicted_class + + def ablate(self, x: np.ndarray, column_pos: int, row_pos: Optional[int] = None) -> np.ndarray: """ Ablates the image only retaining a column starting at "pos" of width "self.ablation_size" @@ -348,24 +211,47 @@ def __call__( """ return self.forward(x=x, row_pos=row_pos, column_pos=column_pos) - def certify(self, preds: np.ndarray, size_to_certify: int) -> np.ndarray: + def certify( + self, pred_counts: Union["tf.Tensor", np.ndarray], size_to_certify: int, label: Union[np.ndarray, "tf.Tensor"] + ) -> Tuple["tf.Tensor", "tf.Tensor", "tf.Tensor"]: """ Checks if based on the predictions supplied the classifications over the ablated datapoints result in a certified prediction against a patch attack of size size_to_certify. - :param preds: The cumulative predictions of the classifier over the ablation locations. + :param pred_counts: The cumulative predictions of the classifier over the ablation locations. :param size_to_certify: The size of the patch to check against. - :return: Array of bools indicating if a point is certified against the given patch dimensions. - """ - indices = np.argsort(-preds, axis=1, kind="stable") - values = np.take_along_axis(np.copy(preds), indices, axis=1) - margin = values[:, 0] - values[:, 1] - - num_affected_classifications = (size_to_certify + self.ablation_size - 1) ** 2 + :param label: Ground truth labels + :return: A tuple consisting of: the certified predictions, + the predictions which were certified and also correct, + and the most predicted class across the different ablations on the input. + """ + import tensorflow as tf + + result = tf.math.top_k(pred_counts, k=2) + + top_predicted_class, second_predicted_class = result.indices[:, 0], result.indices[:, 1] + top_class_counts, second_class_counts = result.values[:, 0], result.values[:, 1] + + certs = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1) ** 2 + tie_break_certs = ( + (top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1) ** 2 + ) & (top_predicted_class < second_predicted_class) + cert = tf.math.logical_or(certs, tie_break_certs) + + # NB, newer versions of pylint do not require the disable. + if label.ndim > 1: + cert_and_correct = cert & ( + tf.math.argmax(label, axis=1) + == tf.cast( # pylint: disable=E1120, E1123 + top_predicted_class, dtype=tf.math.argmax(label, axis=1).dtype + ) + ) + else: + cert_and_correct = cert & ( + label == tf.cast(top_predicted_class, dtype=label.dtype) # pylint: disable=E1120, E1123 + ) - certs = margin > 2 * num_affected_classifications - tie_break_certs = (margin == 2 * num_affected_classifications) & (indices[:, 0] < indices[:, 1]) - return np.logical_or(certs, tie_break_certs) + return cert, cert_and_correct, top_predicted_class def forward( self, @@ -415,40 +301,17 @@ def ablate(self, x: np.ndarray, column_pos: int, row_pos: int) -> np.ndarray: :return: Data ablated at all locations aside from the specified block. """ k = self.ablation_size - num_of_image_columns = x.shape[3] - num_of_image_rows = x.shape[2] - - if row_pos + k > x.shape[2] and column_pos + k > x.shape[3]: - start_of_ablation = column_pos + k - num_of_image_columns - x[:, :, :, start_of_ablation:column_pos] = 0.0 - - start_of_ablation = row_pos + k - num_of_image_rows - x[:, :, start_of_ablation:row_pos, :] = 0.0 - - # only the row wraps - elif row_pos + k > x.shape[2] and column_pos + k <= x.shape[3]: - x[:, :, :, :column_pos] = 0.0 - x[:, :, :, column_pos + k :] = 0.0 - - start_of_ablation = row_pos + k - num_of_image_rows - x[:, :, start_of_ablation:row_pos, :] = 0.0 - - # only column wraps - elif row_pos + k <= x.shape[2] and column_pos + k > x.shape[3]: - start_of_ablation = column_pos + k - num_of_image_columns - x[:, :, :, start_of_ablation:column_pos] = 0.0 - - x[:, :, :row_pos, :] = 0.0 - x[:, :, row_pos + k :, :] = 0.0 - - # neither wraps - elif row_pos + k <= x.shape[2] and column_pos + k <= x.shape[3]: + # Column ablations + if column_pos + k > x.shape[-1]: + x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0 + else: x[:, :, :, :column_pos] = 0.0 x[:, :, :, column_pos + k :] = 0.0 + # Row ablations + if row_pos + k > x.shape[-2]: + x[:, :, (row_pos + k) % x.shape[-2] : row_pos, :] = 0.0 + else: x[:, :, :row_pos, :] = 0.0 x[:, :, row_pos + k :, :] = 0.0 - else: - raise ValueError(f"Ablation failed on row: {row_pos} and column: {column_pos} with size {k}") - return x diff --git a/art/estimators/certification/derandomized_smoothing/derandomized.py b/art/estimators/certification/derandomized_smoothing/derandomized.py new file mode 100644 index 0000000000..9e2ee6ca0d --- /dev/null +++ b/art/estimators/certification/derandomized_smoothing/derandomized.py @@ -0,0 +1,69 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2022 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements (De)Randomized Smoothing certifications against adversarial patches. + +| Paper link: https://arxiv.org/abs/2110.07719 + +| Paper link: https://arxiv.org/abs/2002.10733 +""" + +from __future__ import absolute_import, division, print_function, unicode_literals + +from abc import ABC, abstractmethod +import numpy as np + + +class DeRandomizedSmoothingMixin(ABC): + """ + Mixin class for smoothed estimators. + """ + + def __init__( + self, + *args, + **kwargs, + ) -> None: + """ + Create a derandomized smoothing wrapper. + """ + super().__init__(*args, **kwargs) # type: ignore + + @abstractmethod + def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: + """ + Perform prediction for a batch of inputs. + + :param x: Input samples. + :param batch_size: Size of batches. + :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode. + :return: Array of predictions of shape `(nb_inputs, nb_classes)`. + """ + raise NotImplementedError + + @abstractmethod + def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray: + """ + Performs cumulative predictions over every ablation location + + :param x: Unablated image + :param batch_size: the batch size for the prediction + :param training_mode: if to run the classifier in training mode + :return: cumulative predictions after sweeping over all the ablation configurations. + """ + raise NotImplementedError diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py index 4a184b3666..cd3e53243b 100644 --- a/art/estimators/certification/derandomized_smoothing/pytorch.py +++ b/art/estimators/certification/derandomized_smoothing/pytorch.py @@ -16,13 +16,24 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """ -This module implements (De)Randomized Smoothing for Certifiable Defense against Patch Attacks +This module implements De-Randomized smoothing approaches PyTorch. + +(De)Randomized Smoothing for Certifiable Defense against Patch Attacks | Paper link: https://arxiv.org/abs/2002.10733 + +and + +Certified Patch Robustness via Smoothed Vision Transformers + +| Paper link Accepted version: + https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf + +| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf """ from __future__ import absolute_import, division, print_function, unicode_literals - +import importlib import logging from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING import random @@ -30,15 +41,16 @@ import numpy as np from tqdm import tqdm -from art.config import ART_NUMPY_DTYPE from art.estimators.classification.pytorch import PyTorchClassifier -from art.estimators.certification.derandomized_smoothing.derandomized_smoothing import DeRandomizedSmoothingMixin +from art.estimators.certification.derandomized_smoothing.derandomized import DeRandomizedSmoothingMixin from art.utils import check_and_transform_label_format if TYPE_CHECKING: # pylint: disable=C0412 import torch - + import torchvision + from timm.models.vision_transformer import VisionTransformer + from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import PyTorchVisionTransformer from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE from art.defences.preprocessor import Preprocessor from art.defences.postprocessor import Postprocessor @@ -48,47 +60,64 @@ class PyTorchDeRandomizedSmoothing(DeRandomizedSmoothingMixin, PyTorchClassifier): """ - Implementation of (De)Randomized Smoothing applied to classifier predictions as introduced - in Levine et al. (2020). + Interface class for the two De-randomized smoothing approaches supported by ART for pytorch. - | Paper link: https://arxiv.org/abs/2002.10733 - """ + If a regular pytorch neural network is fed in then (De)Randomized Smoothing as introduced in Levine et al. (2020) + is used. - estimator_params = PyTorchClassifier.estimator_params + ["ablation_type", "ablation_size", "threshold", "logits"] + Otherwise, if a timm vision transfomer is fed in then Certified Patch Robustness via Smoothed Vision Transformers + as introduced in Salman et al. (2021) is used. + """ def __init__( self, - model: "torch.nn.Module", + model: Union[str, "VisionTransformer", "torch.nn.Module"], loss: "torch.nn.modules.loss._Loss", input_shape: Tuple[int, ...], nb_classes: int, - ablation_type: str, ablation_size: int, - threshold: float, - logits: bool, - optimizer: Optional["torch.optim.Optimizer"] = None, # type: ignore + algorithm: str = "salman2021", + ablation_type: str = "column", + replace_last_layer: Optional[bool] = None, + drop_tokens: bool = True, + load_pretrained: bool = True, + optimizer: Union[type, "torch.optim.Optimizer", None] = None, + optimizer_params: Optional[dict] = None, channels_first: bool = True, + threshold: Optional[float] = None, + logits: Optional[bool] = True, clip_values: Optional["CLIP_VALUES_TYPE"] = None, preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), device_type: str = "gpu", + verbose: bool = True, + **kwargs, ): """ - Create a derandomized smoothing classifier. + Create a smoothed classifier. - :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits - output should be preferred where possible to ensure attack efficiency. + :param model: Either a CNN or a VIT. For a ViT supply a string specifying which ViT architecture to load from + the ViT library, or a vision transformer already created with the + Pytorch Image Models (timm) library. To run Levine et al. (2020) provide a regular pytorch model. :param loss: The loss function for which to compute gradients for training. The target label must be raw - categorical, i.e. not converted to one-hot encoding. + categorical, i.e. not converted to one-hot encoding. :param input_shape: The shape of one input instance. :param nb_classes: The number of classes of the model. - :param ablation_type: The type of ablation to perform, must be either "column" or "block" - :param ablation_size: The size of the data portion to retain after ablation. Will be a column of size N for - "column" ablation type or a NxN square for ablation of type "block" - :param threshold: The minimum threshold to count a prediction. - :param logits: if the model returns logits or normalized probabilities + :param ablation_size: The size of the data portion to retain after ablation. + :param algorithm: Either 'salman2021' or 'levine2020'. For salman2021 we support ViTs and CNNs. For levine2020 + there is only CNN support. + :param replace_last_layer: ViT Specific. If to replace the last layer of the ViT with a fresh layer + matching the number of classes for the dataset to be examined. + Needed if going from the pre-trained imagenet models to fine-tune + on a dataset like CIFAR. + :param drop_tokens: ViT Specific. If to drop the fully ablated tokens in the ViT + :param load_pretrained: ViT Specific. If to load a pretrained model matching the ViT name. + Will only affect the ViT if a string name is passed to model rather than a ViT directly. :param optimizer: The optimizer used to train the classifier. + :param ablation_type: The type of ablation to perform. Either "column", "row", or "block" + :param threshold: Specific to Levine et al. The minimum threshold to count a prediction. + :param logits: Specific to Levine et al. If the model returns logits or normalized probabilities :param channels_first: Set channels first or last. :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and maximum values allowed for features. If floats are provided, these will be used as the range of all @@ -101,52 +130,304 @@ def __init__( be divided by the second one. :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. """ - super().__init__( - model=model, - loss=loss, - input_shape=input_shape, - nb_classes=nb_classes, - optimizer=optimizer, - channels_first=channels_first, - clip_values=clip_values, - preprocessing_defences=preprocessing_defences, - postprocessing_defences=postprocessing_defences, - preprocessing=preprocessing, - device_type=device_type, - ablation_type=ablation_type, - ablation_size=ablation_size, - threshold=threshold, - logits=logits, - ) - def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: import torch - x = x.astype(ART_NUMPY_DTYPE) - outputs = PyTorchClassifier.predict(self, x=x, batch_size=batch_size, training_mode=training_mode, **kwargs) + if not channels_first: + raise ValueError("Channels must be set to first") + logger.info("Running algorithm: %s", algorithm) + + # Default value for output shape + output_shape = input_shape + self.mode = None + if importlib.util.find_spec("timm") is not None and algorithm == "salman2021": + from timm.models.vision_transformer import VisionTransformer + + if isinstance(model, (VisionTransformer, str)): + import timm + from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import ( + PyTorchVisionTransformer, + ) + + if replace_last_layer is None: + raise ValueError("If using ViTs please specify if the last layer should be replaced") + + # temporarily assign the original method to tmp_func + tmp_func = timm.models.vision_transformer._create_vision_transformer + + # overrride with ART's ViT creation function + timm.models.vision_transformer._create_vision_transformer = self.create_vision_transformer + if isinstance(model, str): + model = timm.create_model( + model, pretrained=load_pretrained, drop_tokens=drop_tokens, device_type=device_type + ) + if replace_last_layer: + model.head = torch.nn.Linear(model.head.in_features, nb_classes) + if isinstance(optimizer, type): + if optimizer_params is not None: + optimizer = optimizer(model.parameters(), **optimizer_params) + else: + raise ValueError("If providing an optimiser please also supply its parameters") + + elif isinstance(model, VisionTransformer): + pretrained_cfg = model.pretrained_cfg + supplied_state_dict = model.state_dict() + supported_models = self.get_models() + if pretrained_cfg["architecture"] not in supported_models: + raise ValueError( + "Architecture not supported. Use PyTorchDeRandomizedSmoothing.get_models() " + "to get the supported model architectures." + ) + model = timm.create_model( + pretrained_cfg["architecture"], drop_tokens=drop_tokens, device_type=device_type + ) + model.load_state_dict(supplied_state_dict) + if replace_last_layer: + model.head = torch.nn.Linear(model.head.in_features, nb_classes) + + if optimizer is not None: + if not isinstance(optimizer, torch.optim.Optimizer): + raise ValueError("Optimizer error: must be a torch.optim.Optimizer instance") + + converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD] + opt_state_dict = optimizer.state_dict() + if isinstance(optimizer, torch.optim.Adam): + logging.info("Converting Adam Optimiser") + converted_optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) + elif isinstance(optimizer, torch.optim.SGD): + logging.info("Converting SGD Optimiser") + converted_optimizer = torch.optim.SGD(model.parameters(), lr=1e-4) + else: + raise ValueError("Optimiser not supported for conversion") + converted_optimizer.load_state_dict(opt_state_dict) + + self.to_reshape = False + if not isinstance(model, PyTorchVisionTransformer): + raise ValueError("Vision transformer is not of PyTorchViT. Error occurred in PyTorchViT creation.") + + if model.default_cfg["input_size"][0] != input_shape[0]: + raise ValueError( + f'ViT requires {model.default_cfg["input_size"][0]} channel input,' + f" but {input_shape[0]} channels were provided." + ) + + if model.default_cfg["input_size"] != input_shape: + if verbose: + logger.warning( + " ViT expects input shape of: (%i, %i, %i) but (%i, %i, %i) specified as the input shape." + " The input will be rescaled to (%i, %i, %i)", + *model.default_cfg["input_size"], + *input_shape, + *model.default_cfg["input_size"], + ) - if not self.logits: - return np.asarray((outputs >= self.threshold)) - return np.asarray( - (torch.nn.functional.softmax(torch.from_numpy(outputs), dim=1) >= self.threshold).type(torch.int) + self.to_reshape = True + output_shape = model.default_cfg["input_size"] + + # set the method back to avoid unexpected side effects later on should timm need to be reused. + timm.models.vision_transformer._create_vision_transformer = tmp_func + self.mode = "ViT" + else: + if isinstance(model, torch.nn.Module): + self.mode = "CNN" + output_shape = input_shape + self.to_reshape = False + + elif algorithm == "levine2020": + if ablation_type is None or threshold is None or logits is None: + raise ValueError( + "If using CNN please specify if the model returns logits, " + " the prediction threshold, and ablation type" + ) + self.mode = "CNN" + # input channels are internally doubled. + input_shape = (input_shape[0] * 2, input_shape[1], input_shape[2]) + output_shape = input_shape + self.to_reshape = False + + if optimizer is None or isinstance(optimizer, torch.optim.Optimizer): + super().__init__( + model=model, + loss=loss, + input_shape=input_shape, + nb_classes=nb_classes, + optimizer=optimizer, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + ) + else: + raise ValueError("Error occurred in optimizer creation") + + self.threshold = threshold + self.logits = logits + self.ablation_size = (ablation_size,) + self.algorithm = algorithm + self.ablation_type = ablation_type + if verbose: + logger.info(self.model) + + from art.estimators.certification.derandomized_smoothing.ablators.pytorch import ( + ColumnAblatorPyTorch, + BlockAblatorPyTorch, ) - def predict( - self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs - ) -> np.ndarray: # type: ignore + if TYPE_CHECKING: + self.ablator: Union[ColumnAblatorPyTorch, BlockAblatorPyTorch] + + if self.mode is None: + raise ValueError("Model type not recognized.") + + if ablation_type in {"column", "row"}: + self.ablator = ColumnAblatorPyTorch( + ablation_size=ablation_size, + channels_first=True, + ablation_mode=ablation_type, + to_reshape=self.to_reshape, + original_shape=input_shape, + output_shape=output_shape, + device_type=device_type, + algorithm=algorithm, + mode=self.mode, + ) + elif ablation_type == "block": + self.ablator = BlockAblatorPyTorch( + ablation_size=ablation_size, + channels_first=True, + to_reshape=self.to_reshape, + original_shape=input_shape, + output_shape=output_shape, + device_type=device_type, + algorithm=algorithm, + mode=self.mode, + ) + else: + raise ValueError(f"ablation_type of {ablation_type} not recognized. Must be either column, row, or block") + + @classmethod + def get_models(cls, generate_from_null: bool = False) -> List[str]: """ - Perform prediction of the given classifier for a batch of inputs, taking an expectation over transformations. + Return the supported model names to the user. - :param x: Input samples. - :param batch_size: Batch size. - :param training_mode: if to run the classifier in training mode - :return: Array of predictions of shape `(nb_inputs, nb_classes)`. + :param generate_from_null: If to re-check the creation of all the ViTs in timm from scratch. + :return: A list of compatible models """ - return DeRandomizedSmoothingMixin.predict(self, x, batch_size=batch_size, training_mode=training_mode, **kwargs) + import timm + import torch - def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epochs: int, **kwargs) -> None: - x = x.astype(ART_NUMPY_DTYPE) - return PyTorchClassifier.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs) + supported_models = [ + "vit_base_patch8_224", + "vit_base_patch16_18x2_224", + "vit_base_patch16_224", + "vit_base_patch16_224_miil", + "vit_base_patch16_384", + "vit_base_patch16_clip_224", + "vit_base_patch16_clip_384", + "vit_base_patch16_gap_224", + "vit_base_patch16_plus_240", + "vit_base_patch16_rpn_224", + "vit_base_patch16_xp_224", + "vit_base_patch32_224", + "vit_base_patch32_384", + "vit_base_patch32_clip_224", + "vit_base_patch32_clip_384", + "vit_base_patch32_clip_448", + "vit_base_patch32_plus_256", + "vit_giant_patch14_224", + "vit_giant_patch14_clip_224", + "vit_gigantic_patch14_224", + "vit_gigantic_patch14_clip_224", + "vit_huge_patch14_224", + "vit_huge_patch14_clip_224", + "vit_huge_patch14_clip_336", + "vit_huge_patch14_xp_224", + "vit_large_patch14_224", + "vit_large_patch14_clip_224", + "vit_large_patch14_clip_336", + "vit_large_patch14_xp_224", + "vit_large_patch16_224", + "vit_large_patch16_384", + "vit_large_patch32_224", + "vit_large_patch32_384", + "vit_medium_patch16_gap_240", + "vit_medium_patch16_gap_256", + "vit_medium_patch16_gap_384", + "vit_small_patch16_18x2_224", + "vit_small_patch16_36x1_224", + "vit_small_patch16_224", + "vit_small_patch16_384", + "vit_small_patch32_224", + "vit_small_patch32_384", + "vit_tiny_patch16_224", + "vit_tiny_patch16_384", + ] + + if not generate_from_null: + return supported_models + + supported = [] + unsupported = [] + + models = timm.list_models("vit_*") + pbar = tqdm(models) + + # store in case not re-assigned in the model creation due to unsuccessful creation + tmp_func = timm.models.vision_transformer._create_vision_transformer # pylint: disable=W0212 + + for model in pbar: + pbar.set_description(f"Testing {model} creation") + try: + _ = cls( + model=model, + loss=torch.nn.CrossEntropyLoss(), + optimizer=torch.optim.SGD, + optimizer_params={"lr": 0.01}, + input_shape=(3, 32, 32), + nb_classes=10, + ablation_size=4, + load_pretrained=False, + replace_last_layer=True, + verbose=False, + ) + supported.append(model) + except (TypeError, AttributeError): + unsupported.append(model) + timm.models.vision_transformer._create_vision_transformer = tmp_func # pylint: disable=W0212 + + if supported != supported_models: + logger.warning( + "Difference between the generated and fixed model list. Although not necessarily " + "an error, this may point to the timm library being updated." + ) + + return supported + + @staticmethod + def create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> "PyTorchVisionTransformer": + """ + Creates a vision transformer using PyTorchViT which controls the forward pass of the model + + :param variant: The name of the vision transformer to load + :param pretrained: If to load pre-trained weights + :return: A ViT with the required methods needed for ART + """ + + from timm.models._builder import build_model_with_cfg + from timm.models.vision_transformer import checkpoint_filter_fn + from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import ( + PyTorchVisionTransformer, + ) + + return build_model_with_cfg( + PyTorchVisionTransformer, + variant, + pretrained, + pretrained_filter_fn=checkpoint_filter_fn, + **kwargs, + ) def fit( # pylint: disable=W0221 self, @@ -157,10 +438,15 @@ def fit( # pylint: disable=W0221 training_mode: bool = True, drop_last: bool = False, scheduler: Optional[Any] = None, + update_batchnorm: bool = True, + batchnorm_update_epochs: int = 1, + transform: Optional["torchvision.transforms.transforms.Compose"] = None, + verbose: bool = True, **kwargs, ) -> None: """ Fit the classifier on the training set `(x, y)`. + :param x: Training data. :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of shape (nb_samples,). @@ -171,6 +457,13 @@ def fit( # pylint: disable=W0221 the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then the last batch will be smaller. (default: ``False``) :param scheduler: Learning rate scheduler to run at the start of every epoch. + :param update_batchnorm: ViT specific argument. + If to run the training data through the model to update any batch norm statistics prior + to training. Useful on small datasets when using pre-trained ViTs. + :param batchnorm_update_epochs: ViT specific argument. How many times to forward pass over the training data + to pre-adjust the batchnorm statistics. + :param transform: ViT specific argument. Torchvision compose of relevant augmentation transformations to apply. + :param verbose: if to display training progress bars :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch and providing it takes no effect. """ @@ -187,14 +480,14 @@ def fit( # pylint: disable=W0221 # Apply preprocessing x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + if update_batchnorm and self.mode == "ViT": # VIT specific + self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs) + # Check label shape y_preprocessed = self.reduce_labels(y_preprocessed) num_batch = len(x_preprocessed) / float(batch_size) - if drop_last: - num_batch = int(np.floor(num_batch)) - else: - num_batch = int(np.ceil(num_batch)) + num_batch = int(np.floor(num_batch)) if drop_last else int(np.ceil(num_batch)) ind = np.arange(len(x_preprocessed)) # Start training @@ -202,12 +495,21 @@ def fit( # pylint: disable=W0221 # Shuffle the examples random.shuffle(ind) + epoch_acc = [] + epoch_loss = [] + epoch_batch_sizes = [] + + pbar = tqdm(range(num_batch), disable=not verbose) + # Train for one epoch - for m in range(num_batch): - i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]) - i_batch = self.ablator.forward(i_batch) + for m in pbar: + i_batch = self.ablator.forward(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])) + + if transform is not None and self.mode == "ViT": # VIT specific + i_batch = transform(i_batch) - i_batch = torch.from_numpy(i_batch).to(self._device) + if isinstance(i_batch, np.ndarray): + i_batch = torch.from_numpy(i_batch).to(self._device) o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device) # Zero the parameter gradients @@ -215,7 +517,7 @@ def fit( # pylint: disable=W0221 # Perform prediction try: - model_outputs = self._model(i_batch) + model_outputs = self.model(i_batch) except ValueError as err: if "Expected more than 1 value per channel when training" in str(err): logger.exception( @@ -224,8 +526,8 @@ def fit( # pylint: disable=W0221 ) raise err - # Form the loss function - loss = self._loss(model_outputs[-1], o_batch) + loss = self.loss(model_outputs, o_batch) + acc = self.get_accuracy(preds=model_outputs, labels=o_batch) # Do training if self._use_amp: # pragma: no cover @@ -237,7 +539,214 @@ def fit( # pylint: disable=W0221 else: loss.backward() - self._optimizer.step() + self.optimizer.step() + + epoch_acc.append(acc) + epoch_loss.append(loss.cpu().detach().numpy()) + epoch_batch_sizes.append(len(i_batch)) + + if verbose: + pbar.set_description( + f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} " + f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} " + ) if scheduler is not None: scheduler.step() + + @staticmethod + def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray: + """ + Helper function to get the accuracy during training. + + :param preds: model predictions. + :param labels: ground truth labels (not one hot). + :return: prediction accuracy. + """ + if not isinstance(preds, np.ndarray): + preds = preds.detach().cpu().numpy() + + if not isinstance(labels, np.ndarray): + labels = labels.detach().cpu().numpy() + + return np.sum(np.argmax(preds, axis=1) == labels) / len(labels) + + def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None: + """ + Method to update the batchnorm of a neural network on small datasets when it was pre-trained + + :param x: Training data. + :param batch_size: Size of batches. + :param nb_epochs: How many times to forward pass over the input data + """ + import torch + + if self.mode != "ViT": + raise ValueError("Accessing a ViT specific functionality while running in CNN mode") + + self.model.train() + + ind = np.arange(len(x)) + num_batch = int(len(x) / float(batch_size)) + + with torch.no_grad(): + for _ in tqdm(range(nb_epochs)): + for m in tqdm(range(num_batch)): + i_batch = self.ablator.forward( + np.copy(x[ind[m * batch_size : (m + 1) * batch_size]]), column_pos=random.randint(0, x.shape[3]) + ) + _ = self.model(i_batch) + + def eval_and_certify( + self, + x: np.ndarray, + y: np.ndarray, + size_to_certify: int, + batch_size: int = 128, + verbose: bool = True, + ) -> Tuple["torch.Tensor", "torch.Tensor"]: + """ + Evaluates the ViT's normal and certified performance over the supplied data. + + :param x: Evaluation data. + :param y: Evaluation labels. + :param size_to_certify: The size of the patch to certify against. + If not provided will default to the ablation size. + :param batch_size: batch size when evaluating. + :param verbose: If to display the progress bar + :return: The accuracy and certified accuracy over the dataset + """ + import torch + + self.model.eval() + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + # Check label shape + y_preprocessed = self.reduce_labels(y_preprocessed) + + num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size))) + pbar = tqdm(range(num_batch), disable=not verbose) + accuracy = torch.tensor(0.0).to(self._device) + cert_sum = torch.tensor(0.0).to(self._device) + n_samples = 0 + + with torch.no_grad(): + for m in pbar: + if m == (num_batch - 1): + i_batch = np.copy(x_preprocessed[m * batch_size :]) + o_batch = y_preprocessed[m * batch_size :] + else: + i_batch = np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size]) + o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size] + + pred_counts = np.zeros((len(i_batch), self.nb_classes)) + if self.ablation_type in {"column", "row"}: + for pos in range(i_batch.shape[-1]): + ablated_batch = self.ablator.forward(i_batch, column_pos=pos) + # Perform prediction + model_outputs = self.model(ablated_batch) + + if self.algorithm == "salman2021": + pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1 + else: + if self.logits: + model_outputs = torch.nn.functional.softmax(model_outputs, dim=1) + model_outputs = model_outputs >= self.threshold + pred_counts += model_outputs.cpu().numpy() + + else: + for column_pos in range(i_batch.shape[-1]): + for row_pos in range(i_batch.shape[-2]): + ablated_batch = self.ablator.forward(i_batch, column_pos=column_pos, row_pos=row_pos) + model_outputs = self.model(ablated_batch) + + if self.algorithm == "salman2021": + pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1 + else: + if self.logits: + model_outputs = torch.nn.functional.softmax(model_outputs, dim=1) + model_outputs = model_outputs >= self.threshold + pred_counts += model_outputs.cpu().numpy() + + _, cert_and_correct, top_predicted_class = self.ablator.certify( + pred_counts, size_to_certify=size_to_certify, label=o_batch + ) + cert_sum += torch.sum(cert_and_correct) + o_batch = torch.from_numpy(o_batch).to(self.device) + accuracy += torch.sum(top_predicted_class == o_batch) + n_samples += len(cert_and_correct) + + pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}") + + return (accuracy / n_samples), (cert_sum / n_samples) + + def _predict_classifier( + self, x: Union[np.ndarray, "torch.Tensor"], batch_size: int, training_mode: bool, **kwargs + ) -> np.ndarray: + import torch + + if isinstance(x, torch.Tensor): + x_numpy = x.cpu().numpy() + + outputs = PyTorchClassifier.predict( + self, x=x_numpy, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + + if self.algorithm == "levine2020": + if not self.logits: + return np.asarray((outputs >= self.threshold)) + return np.asarray( + (torch.nn.functional.softmax(torch.from_numpy(outputs), dim=1) >= self.threshold).type(torch.int) + ) + return outputs + + def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray: + """ + Performs cumulative predictions over every ablation location + + :param x: Unablated image + :param batch_size: the batch size for the prediction + :param training_mode: if to run the classifier in training mode + :return: cumulative predictions after sweeping over all the ablation configurations. + """ + if self._channels_first: + columns_in_data = x.shape[-1] + rows_in_data = x.shape[-2] + else: + columns_in_data = x.shape[-2] + rows_in_data = x.shape[-3] + + if self.ablation_type in {"column", "row"}: + if self.ablation_type == "column": + ablate_over_range = columns_in_data + else: + # image will be transposed, so loop over the number of rows + ablate_over_range = rows_in_data + + for ablation_start in range(ablate_over_range): + ablated_x = self.ablator.forward(np.copy(x), column_pos=ablation_start) + if ablation_start == 0: + preds = self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + else: + preds += self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + elif self.ablation_type == "block": + for xcorner in range(rows_in_data): + for ycorner in range(columns_in_data): + ablated_x = self.ablator.forward(np.copy(x), row_pos=xcorner, column_pos=ycorner) + if ycorner == 0 and xcorner == 0: + preds = self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + else: + preds += self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + + return preds diff --git a/art/estimators/certification/derandomized_smoothing/tensorflow.py b/art/estimators/certification/derandomized_smoothing/tensorflow.py index 504ddefda6..6cc958acb3 100644 --- a/art/estimators/certification/derandomized_smoothing/tensorflow.py +++ b/art/estimators/certification/derandomized_smoothing/tensorflow.py @@ -28,22 +28,21 @@ import numpy as np from tqdm import tqdm +from art.estimators.certification.derandomized_smoothing.derandomized import DeRandomizedSmoothingMixin from art.estimators.classification.tensorflow import TensorFlowV2Classifier -from art.estimators.certification.derandomized_smoothing.derandomized_smoothing import DeRandomizedSmoothingMixin from art.utils import check_and_transform_label_format if TYPE_CHECKING: # pylint: disable=C0412 import tensorflow as tf - - from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE, ABLATOR_TYPE from art.defences.preprocessor import Preprocessor from art.defences.postprocessor import Postprocessor logger = logging.getLogger(__name__) -class TensorFlowV2DeRandomizedSmoothing(DeRandomizedSmoothingMixin, TensorFlowV2Classifier): +class TensorFlowV2DeRandomizedSmoothing(TensorFlowV2Classifier, DeRandomizedSmoothingMixin): """ Implementation of (De)Randomized Smoothing applied to classifier predictions as introduced in Levine et al. (2020). @@ -106,6 +105,8 @@ def __init__( used for data preprocessing. The first value will be subtracted from the input. The input will then be divided by the second one. """ + # input channels are internally doubled for the certification algorithm. + input_shape = (input_shape[0], input_shape[1], input_shape[2] * 2) super().__init__( model=model, nb_classes=nb_classes, @@ -118,12 +119,31 @@ def __init__( preprocessing_defences=preprocessing_defences, postprocessing_defences=postprocessing_defences, preprocessing=preprocessing, - ablation_type=ablation_type, - ablation_size=ablation_size, - threshold=threshold, - logits=logits, ) + self.ablation_type = ablation_type + self.logits = logits + self.threshold = threshold + self._channels_first = channels_first + + from art.estimators.certification.derandomized_smoothing.ablators.tensorflow import ( + ColumnAblator, + BlockAblator, + ) + + if TYPE_CHECKING: + self.ablator: ABLATOR_TYPE # pylint: disable=used-before-assignment + + if self.ablation_type in {"column", "row"}: + row_ablation_mode = self.ablation_type == "row" + self.ablator = ColumnAblator( + ablation_size=ablation_size, channels_first=self._channels_first, row_ablation_mode=row_ablation_mode + ) + elif self.ablation_type == "block": + self.ablator = BlockAblator(ablation_size=ablation_size, channels_first=self._channels_first) + else: + raise ValueError("Ablation type not supported. Must be either column or block") + def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: import tensorflow as tf @@ -134,10 +154,9 @@ def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: boo outputs = tf.nn.softmax(outputs) return np.asarray(outputs >= self.threshold).astype(int) - def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epochs: int, **kwargs) -> None: - return TensorFlowV2Classifier.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs) - - def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: int = 10, **kwargs) -> None: + def fit( # pylint: disable=W0221 + self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: int = 10, verbose: bool = True, **kwargs + ) -> None: """ Fit the classifier on the training set `(x, y)`. @@ -146,6 +165,7 @@ def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: in shape (nb_samples,). :param batch_size: Size of batches. :param nb_epochs: Number of epochs to use for training. + :param verbose: if to display training progress bars :param kwargs: Dictionary of framework-specific arguments. This parameter currently only supports "scheduler" which is an optional function that will be called at the end of every epoch to adjust the learning rate. @@ -171,6 +191,7 @@ def train_step(model, images, labels): loss = self.loss_object(labels, predictions) gradients = tape.gradient(loss, model.trainable_variables) self.optimizer.apply_gradients(zip(gradients, model.trainable_variables)) + return loss, predictions else: train_step = self._train_step @@ -186,27 +207,137 @@ def train_step(model, images, labels): if self._reduce_labels: y_preprocessed = np.argmax(y_preprocessed, axis=1) - for epoch in tqdm(range(nb_epochs)): + for epoch in tqdm(range(nb_epochs), desc="Epochs"): num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size))) + + epoch_acc = [] + epoch_loss = [] + epoch_batch_sizes = [] + + pbar = tqdm(range(num_batch), disable=not verbose) + ind = np.arange(len(x_preprocessed)) - for m in range(num_batch): + for m in pbar: i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]) labels = y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]] images = self.ablator.forward(i_batch) - train_step(self.model, images, labels) + + if self._train_step is None: + loss, predictions = train_step(self.model, images, labels) + acc = np.sum(np.argmax(predictions.numpy(), axis=1) == np.argmax(labels, axis=1)) / len(labels) + epoch_acc.append(acc) + epoch_loss.append(loss.numpy()) + epoch_batch_sizes.append(len(i_batch)) + else: + train_step(self.model, images, labels) + + if verbose: + if self._train_step is None: + pbar.set_description( + f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} " + f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} " + ) + else: + pbar.set_description("Batches") if scheduler is not None: scheduler(epoch) - def predict( - self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs - ) -> np.ndarray: # type: ignore + def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray: """ - Perform prediction of the given classifier for a batch of inputs + Performs cumulative predictions over every ablation location - :param x: Input samples. - :param batch_size: Batch size. + :param x: Unablated image + :param batch_size: the batch size for the prediction :param training_mode: if to run the classifier in training mode - :return: Array of predictions of shape `(nb_inputs, nb_classes)`. + :return: cumulative predictions after sweeping over all the ablation configurations. + """ + if self._channels_first: + columns_in_data = x.shape[-1] + rows_in_data = x.shape[-2] + else: + columns_in_data = x.shape[-2] + rows_in_data = x.shape[-3] + + if self.ablation_type in {"column", "row"}: + if self.ablation_type == "column": + ablate_over_range = columns_in_data + else: + # image will be transposed, so loop over the number of rows + ablate_over_range = rows_in_data + + for ablation_start in range(ablate_over_range): + ablated_x = self.ablator.forward(np.copy(x), column_pos=ablation_start) + if ablation_start == 0: + preds = self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + else: + preds += self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + elif self.ablation_type == "block": + for xcorner in range(rows_in_data): + for ycorner in range(columns_in_data): + ablated_x = self.ablator.forward(np.copy(x), row_pos=xcorner, column_pos=ycorner) + if ycorner == 0 and xcorner == 0: + preds = self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + else: + preds += self._predict_classifier( + ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs + ) + return preds + + def eval_and_certify( + self, + x: np.ndarray, + y: np.ndarray, + size_to_certify: int, + batch_size: int = 128, + verbose: bool = True, + ) -> Tuple["tf.Tensor", "tf.Tensor"]: + """ + Evaluates the normal and certified performance over the supplied data. + + :param x: Evaluation data. + :param y: Evaluation labels. + :param size_to_certify: The size of the patch to certify against. + If not provided will default to the ablation size. + :param batch_size: batch size when evaluating. + :param verbose: If to display the progress bar + :return: The accuracy and certified accuracy over the dataset """ - return DeRandomizedSmoothingMixin.predict(self, x, batch_size=batch_size, training_mode=training_mode, **kwargs) + import tensorflow as tf + + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size))) + pbar = tqdm(range(num_batch), disable=not verbose) + accuracy = tf.constant(np.array(0.0), dtype=tf.dtypes.int32) + cert_sum = tf.constant(np.array(0.0), dtype=tf.dtypes.int32) + n_samples = 0 + + for m in pbar: + if m == (num_batch - 1): + i_batch = np.copy(x_preprocessed[m * batch_size :]) + o_batch = y_preprocessed[m * batch_size :] + else: + i_batch = np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size]) + o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size] + + pred_counts = self.predict(i_batch) + + _, cert_and_correct, top_predicted_class = self.ablator.certify( + pred_counts, size_to_certify=size_to_certify, label=o_batch + ) + cert_sum += tf.math.reduce_sum(tf.where(cert_and_correct, 1, 0)) + accuracy += tf.math.reduce_sum(tf.where(top_predicted_class == np.argmax(o_batch, axis=-1), 1, 0)) + n_samples += len(cert_and_correct) + + pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}") + return (accuracy / n_samples), (cert_sum / n_samples) diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/__init__.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py new file mode 100644 index 0000000000..48f96eefab --- /dev/null +++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py @@ -0,0 +1,196 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# PatchEmbed class adapted from the implementation in https://github.com/MadryLab/smoothed-vit +# +# Original License: +# +# MIT License +# +# Copyright (c) 2021 Madry Lab +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE + +""" +Implements functionality for running Vision Transformers in ART +""" +from typing import Optional + +import torch +from timm.models.vision_transformer import VisionTransformer + + +class PatchEmbed(torch.nn.Module): + """ + Image to Patch Embedding + + Class adapted from the implementation in https://github.com/MadryLab/smoothed-vit + + Original License stated above. + """ + + def __init__(self, patch_size: int = 16, in_channels: int = 1, embed_dim: int = 768): + """ + Specifies the configuration for the convolutional layer. + + :param patch_size: The patch size used by the ViT. + :param in_channels: Number of input channels. + :param embed_dim: The embedding dimension used by the ViT. + """ + super().__init__() + self.patch_size = patch_size + self.in_channels = in_channels + self.embed_dim = embed_dim + self.proj: Optional[torch.nn.Conv2d] = None + + def create(self, patch_size=None, embed_dim=None, device="cpu", **kwargs) -> None: # pylint: disable=W0613 + """ + Creates a convolution that mimics the embedding layer to be used for the ablation mask to + track where the image was ablated. + + :param patch_size: The patch size used by the ViT. + :param embed_dim: The embedding dimension used by the ViT. + :param device: Which device to set the emdedding layer to. + :param kwargs: Handles the remaining kwargs from the ViT configuration. + """ + + if patch_size is not None: + self.patch_size = patch_size + if embed_dim is not None: + self.embed_dim = embed_dim + + self.proj = torch.nn.Conv2d( + in_channels=self.in_channels, + out_channels=self.embed_dim, + kernel_size=self.patch_size, + stride=self.patch_size, + bias=False, + ) + w_shape = self.proj.weight.shape + self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Forward pass through the embedder. We are simply tracking the positions of the ablation mask so no gradients + are required. + + :param x: Input data corresponding to the ablation mask + :return: The embedded input + """ + if self.proj is not None: + with torch.no_grad(): + x = self.proj(x).flatten(2).transpose(1, 2) + return x + raise ValueError("Projection layer not yet created.") + + +class PyTorchVisionTransformer(VisionTransformer): + """ + Model-specific class to define the forward pass of the Vision Transformer (ViT) in PyTorch. + """ + + # Make as a class attribute to avoid being included in the + # state dictionaries of the ViT Model. + ablation_mask_embedder = PatchEmbed(in_channels=1) + + def __init__(self, **kwargs): + """ + Create a PyTorchVisionTransformer instance + + :param kwargs: keyword arguments required to create the mask embedder and the vision transformer class + """ + self.to_drop_tokens = kwargs["drop_tokens"] + + if kwargs["device_type"] == "cpu" or not torch.cuda.is_available(): + self.device = torch.device("cpu") + else: # pragma: no cover + cuda_idx = torch.cuda.current_device() + self.device = torch.device(f"cuda:{cuda_idx}") + + del kwargs["drop_tokens"] + del kwargs["device_type"] + + super().__init__(**kwargs) + self.ablation_mask_embedder.create(device=self.device, **kwargs) + + self.in_chans = kwargs["in_chans"] + self.img_size = kwargs["img_size"] + + @staticmethod + def drop_tokens(x: torch.Tensor, indexes: torch.Tensor) -> torch.Tensor: + """ + Drops the tokens which correspond to fully masked inputs + + :param x: Input data + :param indexes: positions to be ablated + :return: Input with tokens dropped where the input was fully ablated. + """ + x_no_cl, cls_token = x[:, 1:], x[:, 0:1] + shape = x_no_cl.shape + + # reshape to temporarily remove batch + x_no_cl = torch.reshape(x_no_cl, shape=(-1, shape[-1])) + indexes = torch.reshape(indexes, shape=(-1,)) + indexes = indexes.nonzero(as_tuple=True)[0] + x_no_cl = torch.index_select(x_no_cl, dim=0, index=indexes) + x_no_cl = torch.reshape(x_no_cl, shape=(shape[0], -1, shape[-1])) + return torch.cat((cls_token, x_no_cl), dim=1) + + def forward_features(self, x: torch.Tensor) -> torch.Tensor: + """ + The forward pass of the ViT. + + :param x: Input data. + :return: The input processed by the ViT backbone + """ + + ablated_input = False + if x.shape[1] == self.in_chans + 1: + ablated_input = True + + if ablated_input: + x, ablation_mask = x[:, : self.in_chans], x[:, self.in_chans : self.in_chans + 1] + + x = self.patch_embed(x) + x = self._pos_embed(x) + + if self.to_drop_tokens and ablated_input: + ones = self.ablation_mask_embedder(ablation_mask) + to_drop = torch.sum(ones, dim=2) + indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0) + x = self.drop_tokens(x, indexes) + + x = self.norm_pre(x) + x = self.blocks(x) + return self.norm(x) diff --git a/art/estimators/certification/object_seeker/__init__.py b/art/estimators/certification/object_seeker/__init__.py new file mode 100644 index 0000000000..99b8175584 --- /dev/null +++ b/art/estimators/certification/object_seeker/__init__.py @@ -0,0 +1,5 @@ +""" +ObjectSeeker estimators. +""" +from art.estimators.certification.object_seeker.object_seeker import ObjectSeekerMixin +from art.estimators.certification.object_seeker.pytorch import PyTorchObjectSeeker diff --git a/art/estimators/certification/object_seeker/object_seeker.py b/art/estimators/certification/object_seeker/object_seeker.py new file mode 100644 index 0000000000..e6c069618e --- /dev/null +++ b/art/estimators/certification/object_seeker/object_seeker.py @@ -0,0 +1,401 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# MIT License +# +# Copyright (c) 2022 Chong Xiang +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the ObjectSeeker certifiably robust defense. + +| Paper link: https://arxiv.org/abs/2202.01811 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import abc +import logging +from typing import Dict, List, Tuple + +import numpy as np +from sklearn.cluster import DBSCAN +from tqdm.auto import tqdm + +from art.utils import intersection_over_area, non_maximum_suppression + +logger = logging.getLogger(__name__) + + +class ObjectSeekerMixin(abc.ABC): + """ + Implementation of the ObjectSeeker certifiable robust defense applied to object detection models. + The original implementation is https://github.com/inspire-group/ObjectSeeker + + | Paper link: https://arxiv.org/abs/2202.01811 + """ + + def __init__( + self, + *args, + num_lines: int = 3, + confidence_threshold: float = 0.3, + iou_threshold: float = 0.5, + prune_threshold: float = 0.5, + epsilon: float = 0.1, + verbose: bool = False, + **kwargs, + ) -> None: + """ + Create an ObjectSeeker wrapper. + + :param num_lines: The number of divisions both vertically and horizontally to make masked predictions. + :param confidence_threshold: The confidence threshold to discard bounding boxes. + :param iou_threshold: The IoU threshold to discard overlapping bounding boxes. + :param prune_threshold: The IoA threshold for pruning and duplicated bounding boxes. + :param epsilon: The maximum distance between bounding boxes when merging using DBSCAN. + :param verbose: Show progress bars. + """ + super().__init__(*args, **kwargs) # type: ignore + self.num_lines = num_lines + self.confidence_threshold = confidence_threshold + self.iou_threshold = iou_threshold + self.prune_threshold = prune_threshold + self.epsilon = epsilon + self.verbose = verbose + + @property + @abc.abstractmethod + def channels_first(self) -> bool: + """ + :return: Boolean to indicate index of the color channels in the sample `x`. + """ + pass + + @property + @abc.abstractmethod + def input_shape(self) -> Tuple[int, ...]: + """ + :return: Shape of one input sample. + """ + pass + + @abc.abstractmethod + def _predict_classifier(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> List[Dict[str, np.ndarray]]: + """ + Perform prediction for a batch of inputs. + + :param x: Samples of shape NCHW or NHWC. + :param batch_size: Batch size. + :return: Predictions of format `List[Dict[str, np.ndarray]]`, one for each input image. The fields of the Dict + are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image + - scores [N]: the scores or each prediction. + """ + raise NotImplementedError + + def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> List[Dict[str, np.ndarray]]: + """ + Perform prediction for a batch of inputs. + + :param x: Samples of shape NCHW or NHWC. + :param batch_size: Batch size. + :return: Predictions of format `List[Dict[str, np.ndarray]]`, one for each input image. The fields of the Dict + are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image + - scores [N]: the scores or each prediction. + """ + predictions = [] + + for x_i in tqdm(x, desc="ObjectSeeker", disable=not self.verbose): + base_preds, masked_preds = self._masked_predictions(x_i, batch_size=batch_size, **kwargs) + pruned_preds = self._prune_boxes(masked_preds, base_preds) + unionized_preds = self._unionize_clusters(pruned_preds) + + preds = { + "boxes": np.concatenate([base_preds["boxes"], unionized_preds["boxes"]]), + "labels": np.concatenate([base_preds["labels"], unionized_preds["labels"]]), + "scores": np.concatenate([base_preds["scores"], unionized_preds["scores"]]), + } + + predictions.append(preds) + + return predictions + + def _masked_predictions( + self, x_i: np.ndarray, batch_size: int = 128, **kwargs + ) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]: + """ + Create masked copies of the image for each of lines following the ObjectSeeker algorithm. Then creates + predictions on the base unmasked image and each of the masked image. + + :param x_i: A single image of shape CHW or HWC. + :batch_size: Batch size. + :return: Predictions for the base unmasked image and merged predictions for the masked image. + """ + x_mask = np.repeat(x_i[np.newaxis], self.num_lines * 4 + 1, axis=0) + + if self.channels_first: + height = self.input_shape[1] + width = self.input_shape[2] + else: + height = self.input_shape[0] + width = self.input_shape[1] + x_mask = np.transpose(x_mask, (0, 3, 1, 2)) + + idx = 1 + + # Left masks + for k in range(1, self.num_lines + 1): + boundary = int(width / (self.num_lines + 1) * k) + x_mask[idx, :, :, :boundary] = 0 + idx += 1 + + # Right masks + for k in range(1, self.num_lines + 1): + boundary = width - int(width / (self.num_lines + 1) * k) + x_mask[idx, :, :, boundary:] = 0 + idx += 1 + + # Top masks + for k in range(1, self.num_lines + 1): + boundary = int(height / (self.num_lines + 1) * k) + x_mask[idx, :, :boundary, :] = 0 + idx += 1 + + # Bottom masks + for k in range(1, self.num_lines + 1): + boundary = height - int(height / (self.num_lines + 1) * k) + x_mask[idx, :, boundary:, :] = 0 + idx += 1 + + if not self.channels_first: + x_mask = np.transpose(x_mask, (0, 2, 3, 1)) + + predictions = self._predict_classifier(x=x_mask, batch_size=batch_size, **kwargs) + filtered_predictions = [ + non_maximum_suppression( + pred, iou_threshold=self.iou_threshold, confidence_threshold=self.confidence_threshold + ) + for pred in predictions + ] + + # Extract base predictions + base_predictions = filtered_predictions[0] + + # Extract and merge masked predictions + boxes = np.concatenate([pred["boxes"] for pred in filtered_predictions[1:]]) + labels = np.concatenate([pred["labels"] for pred in filtered_predictions[1:]]) + scores = np.concatenate([pred["scores"] for pred in filtered_predictions[1:]]) + merged_predictions = { + "boxes": boxes, + "labels": labels, + "scores": scores, + } + masked_predictions = non_maximum_suppression( + merged_predictions, iou_threshold=self.iou_threshold, confidence_threshold=self.confidence_threshold + ) + + return base_predictions, masked_predictions + + def _prune_boxes( + self, masked_preds: Dict[str, np.ndarray], base_preds: Dict[str, np.ndarray] + ) -> Dict[str, np.ndarray]: + """ + Remove bounding boxes from the masked predictions of a single image based on the IoA score with the boxes + on the base unmasked predictions. + + :param masked_preds: The merged masked predictions of a single image. + :param base_preds: The base unmasked predictions of a single image. + :return: The filtered masked predictions with extraneous boxes removed. + """ + masked_boxes = masked_preds["boxes"] + masked_labels = masked_preds["labels"] + masked_scores = masked_preds["scores"] + + base_boxes = base_preds["boxes"] + base_labels = base_preds["labels"] + + keep_indices = [] + for idx, (masked_box, masked_label) in enumerate(zip(masked_boxes, masked_labels)): + keep = True + for (base_box, base_label) in zip(base_boxes, base_labels): + if masked_label == base_label: + ioa = intersection_over_area(masked_box, base_box) + if ioa >= self.prune_threshold: + keep = False + break + + if keep: + keep_indices.append(idx) + + pruned_preds = { + "boxes": masked_boxes[keep_indices], + "labels": masked_labels[keep_indices], + "scores": masked_scores[keep_indices], + } + return pruned_preds + + def _unionize_clusters(self, masked_preds: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: + """ + Cluster the bounding boxes for the pruned masked predictions. + + :param masked_preds: The merged masked predictions of a single image already pruned. + :return: The clustered masked predictions with overlapping boxes merged. + """ + boxes = masked_preds["boxes"] + labels = masked_preds["labels"] + scores = masked_preds["scores"] + + # Skip clustering if not enough bounding boxes + if len(boxes) <= 1: + return masked_preds + + unionized_boxes = [] + unionized_labels = [] + unionized_scores = [] + + unique_labels = np.unique(labels) + for label in unique_labels: + # Select bounding boxes of same class + mask = labels == label + selected_boxes = boxes[mask] + selected_scores = scores[mask] + + # Calculate pairwise distances between bounding boxes + areas = (selected_boxes[:, 2] - selected_boxes[:, 0]) * (selected_boxes[:, 3] - selected_boxes[:, 1]) + top_left = np.maximum(selected_boxes[:, None, :2], selected_boxes[:, :2]) + bottom_right = np.minimum(selected_boxes[:, None, 2:], selected_boxes[:, 2:]) + pairwise_intersection = np.prod(np.clip(bottom_right - top_left, 0, None), axis=2) + pairwise_ioa = pairwise_intersection / (areas[:, None]) + distances = 1 - np.maximum(pairwise_ioa, pairwise_ioa.T) + + # Cluster bounding boxes + dbscan = DBSCAN(eps=self.epsilon, min_samples=1, metric="precomputed") + clusters = dbscan.fit_predict(distances) + num_clusters = np.max(clusters) + 1 + + for cluster in range(num_clusters): + # Merge all boxes in the cluster + clustered_boxes = selected_boxes[clusters == cluster] + + clustered_box = [ + np.min(clustered_boxes[:, 0]), + np.min(clustered_boxes[:, 1]), + np.max(clustered_boxes[:, 2]), + np.max(clustered_boxes[:, 3]), + ] + clustered_score = np.max(selected_scores) + + unionized_boxes.append(clustered_box) + unionized_labels.append(label) + unionized_scores.append(clustered_score) + + unionized_predictions = { + "boxes": np.asarray(unionized_boxes), + "labels": np.asarray(unionized_labels), + "scores": np.asarray(unionized_scores), + } + return unionized_predictions + + def certify( + self, + x: np.ndarray, + patch_size: float = 0.01, + offset: float = 0.1, + batch_size: int = 128, + ) -> List[np.ndarray]: + """ + Checks if there is certifiable IoA robustness for each predicted bounding box. + + :param x: Sample input with shape as expected by the model. + :param patch_size: The size of the patch to check against. + :param offset: The offset to distinguish between the far and near patches. + :return: A list containing an array of bools for each bounding box per image indicating if the bounding + box is certified against the given patch. + """ + if self.channels_first: + _, height, width = self.input_shape + else: + height, width, _ = self.input_shape + + patch_size = np.sqrt(height * width * patch_size) + height_offset = offset * height + width_offset = offset * width + + # Get predictions + predictions = self.predict(x, batch_size=batch_size) + + certifications: List[np.ndarray] = [] + + for pred in tqdm(predictions, desc="ObjectSeeker", disable=not self.verbose): + boxes = pred["boxes"] + + # Far patch + far_patch_map = np.ones((len(boxes), height, width), dtype=bool) + for i, box in enumerate(boxes): + x_1 = int(max(0, box[1] - patch_size - height_offset)) + x_2 = int(min(box[3] + height_offset + 1, height)) + y_1 = int(max(0, box[0] - patch_size - width_offset)) + y_2 = int(min(box[2] + width_offset + 1, width)) + far_patch_map[i, x_1:x_2, y_1:y_2] = False + far_vulnerable = np.any(far_patch_map, axis=(-2, -1)) + + # Close patch + close_patch_map = np.ones((len(boxes), height, width), dtype=bool) + for i, box in enumerate(boxes): + x_1 = int(max(0, box[1] - patch_size)) + x_2 = int(min(box[3] + 1, height)) + y_1 = int(max(0, box[0] - patch_size)) + y_2 = int(min(box[2] + 1, width)) + close_patch_map[i, x_1:x_2, y_1:y_2] = False + close_vulnerable = np.any(close_patch_map, axis=(-2, -1)) + + # Over patch + close_patch_map = np.ones((len(boxes), height, width), dtype=bool) + for i, box in enumerate(boxes): + x_1 = int(max(0, box[1] - patch_size)) + x_2 = int(min(box[3] + 1, height)) + y_1 = int(max(0, box[0] - patch_size)) + y_2 = int(min(box[2] + 1, width)) + close_patch_map[i, x_1:x_2, y_1:y_2] = True + over_vulnerable = np.any(close_patch_map, axis=(-2, -1)) + + cert = np.logical_and.reduce((far_vulnerable, close_vulnerable, over_vulnerable)) + certifications.append(cert) + + return certifications diff --git a/art/estimators/certification/object_seeker/pytorch.py b/art/estimators/certification/object_seeker/pytorch.py new file mode 100644 index 0000000000..82d88c1605 --- /dev/null +++ b/art/estimators/certification/object_seeker/pytorch.py @@ -0,0 +1,401 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the ObjectSeeker certifiably robust defense. + +| Paper link: https://arxiv.org/abs/2202.01811 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import sys +from typing import List, Dict, Optional, Tuple, Union, TYPE_CHECKING + +import numpy as np + +from art.estimators.certification.object_seeker.object_seeker import ObjectSeekerMixin +from art.estimators.object_detection import ObjectDetectorMixin, PyTorchObjectDetector, PyTorchFasterRCNN, PyTorchYolo +from art.estimators.pytorch import PyTorchEstimator + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +if TYPE_CHECKING: + # pylint: disable=C0412 + import torch + + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + +logger = logging.getLogger(__name__) + + +class PyTorchObjectSeeker(ObjectSeekerMixin, ObjectDetectorMixin, PyTorchEstimator): + """ + Implementation of the ObjectSeeker certifiable robust defense applied to object detection models. + The original implementation is https://github.com/inspire-group/ObjectSeeker + + | Paper link: https://arxiv.org/abs/2202.01811 + """ + + estimator_params = PyTorchEstimator.estimator_params + [ + "input_shape", + "optimizer", + "detector_type", + "attack_losses", + "num_lines", + "confidence_threshold", + "iou_threshold", + "prune_threshold", + "epsilon", + ] + + def __init__( + self, + model: "torch.nn.Module", + input_shape: Tuple[int, ...] = (3, 416, 416), + optimizer: Optional["torch.optim.Optimizer"] = None, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + channels_first: Optional[bool] = True, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = None, + attack_losses: Tuple[str, ...] = ( + "loss_classifier", + "loss_box_reg", + "loss_objectness", + "loss_rpn_box_reg", + ), + detector_type: Literal["YOLO", "Faster-RCNN"] = "YOLO", + num_lines: int = 3, + confidence_threshold: float = 0.3, + iou_threshold: float = 0.5, + prune_threshold: float = 0.5, + epsilon: float = 0.1, + device_type: str = "gpu", + verbose: bool = False, + ): + """ + Create an ObjectSeeker classifier. + + :param model: Object detection model. The output of the model is `List[Dict[str, torch.Tensor]]`, + one for each input image. The fields of the Dict are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and + 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image. + - scores [N]: the scores of each prediction. + :param input_shape: The shape of one input sample. + :param optimizer: The optimizer for training the classifier. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param channels_first: Set channels first or last. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param attack_losses: Tuple of any combination of strings of loss components: 'loss_classifier', 'loss_box_reg', + 'loss_objectness', and 'loss_rpn_box_reg'. + :param detector_type: The type of object detector being used: 'YOLO' | 'Faster-RCNN' + :param num_lines: The number of divisions both vertically and horizontally to make masked predictions. + :param confidence_threshold: The confidence threshold to discard bounding boxes. + :param iou_threshold: The IoU threshold to discard overlapping bounding boxes. + :param prune_threshold: The IoA threshold for pruning and duplicated bounding boxes. + :param epsilon: The maximum distance between bounding boxes when merging using DBSCAN. + :param device_type: Type of device to be used for model and tensors, if `cpu` run on CPU, if `gpu` run on GPU + if available otherwise run on CPU. + :param verbose: Show progress bars. + """ + super().__init__( + model=model, + clip_values=clip_values, + channels_first=channels_first, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + num_lines=num_lines, + confidence_threshold=confidence_threshold, + iou_threshold=iou_threshold, + prune_threshold=prune_threshold, + epsilon=epsilon, + verbose=verbose, + ) + + self._input_shape = input_shape + self._optimizer = optimizer + self._attack_losses = attack_losses + self.detector_type = detector_type + + self.detector: Union[PyTorchYolo, PyTorchFasterRCNN, PyTorchObjectDetector] + if detector_type == "YOLO": + self.detector = PyTorchYolo( + model=model, + input_shape=input_shape, + optimizer=optimizer, + clip_values=clip_values, + channels_first=channels_first, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + attack_losses=attack_losses, + device_type=device_type, + ) + elif detector_type == "Faster-RCNN": + self.detector = PyTorchFasterRCNN( + model=model, + input_shape=input_shape, + optimizer=optimizer, + clip_values=clip_values, + channels_first=channels_first, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + attack_losses=attack_losses, + device_type=device_type, + ) + else: + self.detector = PyTorchObjectDetector( + model=model, + input_shape=input_shape, + optimizer=optimizer, + clip_values=clip_values, + channels_first=channels_first, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + attack_losses=attack_losses, + device_type=device_type, + ) + + @property + def native_label_is_pytorch_format(self) -> bool: + """ + Return are the native labels in PyTorch format [x1, y1, x2, y2]? + + :return: Are the native labels in PyTorch format [x1, y1, x2, y2]? + """ + return True + + @property + def model(self) -> "torch.nn.Module": + """ + Return the model. + + :return: The model. + """ + return self._model + + @property + def channels_first(self) -> bool: + """ + Return a boolean to indicate the index of the color channels for each image. + + :return: Boolean to indicate the index of the color channels for each image. + """ + return self._channels_first + + @property + def input_shape(self) -> Tuple[int, ...]: + """ + Return the shape of one input sample. + + :return: Shape of one input sample. + """ + return self._input_shape + + @property + def optimizer(self) -> Optional["torch.optim.Optimizer"]: + """ + Return the optimizer. + + :return: The optimizer. + """ + return self._optimizer + + @property + def attack_losses(self) -> Tuple[str, ...]: + """ + Return the combination of strings of the loss components. + + :return: The combination of strings of the loss components. + """ + return self._attack_losses + + @property + def device(self) -> "torch.device": + """ + Get current used device. + + :return: Current used device. + """ + return self._device + + def _predict_classifier(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> List[Dict[str, np.ndarray]]: + """ + Perform prediction for a batch of inputs. + + :param x: Samples of shape NCHW or NHWC. + :param batch_size: Batch size. + :return: Predictions of format `List[Dict[str, np.ndarray]]`, one for each input image. The fields of the Dict + are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image + - scores [N]: the scores or each prediction. + """ + return self.detector.predict(x=x, batch_size=batch_size, **kwargs) + + def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> List[Dict[str, np.ndarray]]: + """ + Perform prediction for a batch of inputs. + + :param x: Samples of shape NCHW or NHWC. + :param batch_size: Batch size. + :return: Predictions of format `List[Dict[str, np.ndarray]]`, one for each input image. The fields of the Dict + are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image + - scores [N]: the scores or each prediction. + """ + return ObjectSeekerMixin.predict(self, x=x, batch_size=batch_size, **kwargs) + + def fit( # pylint: disable=W0221 + self, + x: np.ndarray, + y: List[Dict[str, Union[np.ndarray, "torch.Tensor"]]], + batch_size: int = 128, + nb_epochs: int = 10, + drop_last: bool = False, + scheduler: Optional["torch.optim.lr_scheduler._LRScheduler"] = None, + **kwargs, + ) -> None: + """ + Fit the classifier on the training set `(x, y)`. + + :param x: Samples of shape NCHW or NHWC. + :param y: Target values of format `List[Dict[str, Union[np.ndarray, torch.Tensor]]]`, one for each input image. + The fields of the Dict are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image. + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for training. + :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by + the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then + the last batch will be smaller. (default: ``False``) + :param scheduler: Learning rate scheduler to run at the start of every epoch. + :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch + and providing it takes no effect. + """ + self.detector.fit( + x=x, + y=y, + batch_size=batch_size, + nb_epochs=nb_epochs, + drop_last=drop_last, + scheduler=scheduler, + **kwargs, + ) + + def get_activations( + self, x: np.ndarray, layer: Union[int, str], batch_size: int, framework: bool = False + ) -> np.ndarray: + """ + Return the output of the specified layer for input `x`. `layer` is specified by layer index (between 0 and + `nb_layers - 1`) or by name. The number of layers can be determined by counting the results returned by + calling `layer_names`. + + :param x: Input for computing the activations. + :param layer: Layer for computing the activations + :param batch_size: Size of batches. + :param framework: If true, return the intermediate tensor representation of the activation. + :return: The output of `layer`, where the first dimension is the batch size corresponding to `x`. + """ + return self.detector.get_activations( + x=x, + layer=layer, + batch_size=batch_size, + framework=framework, + ) + + def loss_gradient( # pylint: disable=W0613 + self, x: np.ndarray, y: List[Dict[str, Union[np.ndarray, "torch.Tensor"]]], **kwargs + ) -> Union[np.ndarray, "torch.Tensor"]: + """ + Compute the gradient of the loss function w.r.t. `x`. + + :param x: Samples of shape NCHW or NHWC. + :param y: Target values of format `List[Dict[str, Union[np.ndarray, torch.Tensor]]]`, one for each input image. + The fields of the Dict are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image. + :return: Loss gradients of the same shape as `x`. + """ + return self.detector.loss_gradient( + x=x, + y=y, + **kwargs, + ) + + def compute_losses( + self, x: np.ndarray, y: List[Dict[str, Union[np.ndarray, "torch.Tensor"]]] + ) -> Dict[str, np.ndarray]: + """ + Compute all loss components. + + :param x: Samples of shape NCHW or NHWC. + :param y: Target values of format `List[Dict[str, Union[np.ndarray, torch.Tensor]]]`, one for each input image. + The fields of the Dict are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image. + :return: Dictionary of loss components. + """ + return self.detector.compute_losses( + x=x, + y=y, + ) + + def compute_loss( # type: ignore + self, x: np.ndarray, y: List[Dict[str, Union[np.ndarray, "torch.Tensor"]]], **kwargs + ) -> Union[np.ndarray, "torch.Tensor"]: + """ + Compute the loss of the neural network for samples `x`. + + :param x: Samples of shape NCHW or NHWC. + :param y: Target values of format `List[Dict[str, Union[np.ndarray, torch.Tensor]]]`, one for each input image. + The fields of the Dict are as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image. + :return: Loss. + """ + return self.detector.compute_loss( + x=x, + y=y, + **kwargs, + ) diff --git a/art/estimators/certification/randomized_smoothing/__init__.py b/art/estimators/certification/randomized_smoothing/__init__.py index bd4ceae526..2faa24dc34 100644 --- a/art/estimators/certification/randomized_smoothing/__init__.py +++ b/art/estimators/certification/randomized_smoothing/__init__.py @@ -4,5 +4,10 @@ from art.estimators.certification.randomized_smoothing.randomized_smoothing import RandomizedSmoothingMixin from art.estimators.certification.randomized_smoothing.numpy import NumpyRandomizedSmoothing -from art.estimators.certification.randomized_smoothing.tensorflow import TensorFlowV2RandomizedSmoothing from art.estimators.certification.randomized_smoothing.pytorch import PyTorchRandomizedSmoothing +from art.estimators.certification.randomized_smoothing.tensorflow import TensorFlowV2RandomizedSmoothing +from art.estimators.certification.randomized_smoothing.smooth_mix.pytorch import PyTorchSmoothMix +from art.estimators.certification.randomized_smoothing.macer.pytorch import PyTorchMACER +from art.estimators.certification.randomized_smoothing.macer.tensorflow import TensorFlowV2MACER +from art.estimators.certification.randomized_smoothing.smooth_adv.pytorch import PyTorchSmoothAdv +from art.estimators.certification.randomized_smoothing.smooth_adv.tensorflow import TensorFlowV2SmoothAdv diff --git a/art/estimators/certification/randomized_smoothing/macer/__init__.py b/art/estimators/certification/randomized_smoothing/macer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/art/estimators/certification/randomized_smoothing/macer/pytorch.py b/art/estimators/certification/randomized_smoothing/macer/pytorch.py new file mode 100644 index 0000000000..fe72641dab --- /dev/null +++ b/art/estimators/certification/randomized_smoothing/macer/pytorch.py @@ -0,0 +1,238 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements MACER applied to classifier predictions. + +| Paper link: https://arxiv.org/abs/2001.02378 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +from typing import List, Optional, Tuple, Union, TYPE_CHECKING + +from tqdm.auto import trange +import numpy as np + +from art.estimators.certification.randomized_smoothing.pytorch import PyTorchRandomizedSmoothing +from art.utils import check_and_transform_label_format + +if TYPE_CHECKING: + # pylint: disable=C0412 + import torch + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + +logger = logging.getLogger(__name__) + + +class PyTorchMACER(PyTorchRandomizedSmoothing): + """ + Implementation of MACER training, as introduced in Zhai et al. (2020) + + | Paper link: https://arxiv.org/abs/2001.02378 + """ + + estimator_params = PyTorchRandomizedSmoothing.estimator_params + [ + "beta", + "gamma", + "lmbda", + "gauss_num", + ] + + def __init__( + self, + model: "torch.nn.Module", + loss: "torch.nn.modules.loss._Loss", + input_shape: Tuple[int, ...], + nb_classes: int, + optimizer: Optional["torch.optim.Optimizer"] = None, + channels_first: bool = True, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), + device_type: str = "gpu", + sample_size: int = 32, + scale: float = 0.1, + alpha: float = 0.001, + beta: float = 16.0, + gamma: float = 8.0, + lmbda: float = 12.0, + gaussian_samples: int = 16, + verbose: bool = False, + ) -> None: + """ + Create a MACER classifier. + + :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits + output should be preferred where possible to ensure attack efficiency. + :param loss: The loss function for which to compute gradients for training. The target label must be raw + categorical, i.e. not converted to one-hot encoding. + :param input_shape: The shape of one input instance. + :param nb_classes: The number of classes of the model. + :param optimizer: The optimizer used to train the classifier. + :param channels_first: Set channels first or last. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. + :param sample_size: Number of samples for smoothing. + :param scale: Standard deviation of Gaussian noise added. + :param alpha: The failure probability of smoothing. + :param beta: The inverse temperature. + :param gamma: The hinge factor. + :param lmbda: The trade-off factor. + :param gaussian_samples: The number of gaussian samples per input. + :param verbose: Show progress bars. + """ + super().__init__( + model=model, + loss=loss, + input_shape=input_shape, + nb_classes=nb_classes, + optimizer=optimizer, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + sample_size=sample_size, + scale=scale, + alpha=alpha, + verbose=verbose, + ) + self.beta = beta + self.gamma = gamma + self.lmbda = lmbda + self.gaussian_samples = gaussian_samples + + def fit( # pylint: disable=W0221 + self, + x: np.ndarray, + y: np.ndarray, + batch_size: int = 128, + nb_epochs: int = 10, + training_mode: bool = True, + drop_last: bool = False, + scheduler: Optional["torch.optim.lr_scheduler._LRScheduler"] = None, + **kwargs, + ) -> None: + """ + Fit the classifier on the training set `(x, y)`. + + :param x: Training data. + :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of + shape (nb_samples,). + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for training. + :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode. + :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by + the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then + the last batch will be smaller. (default: ``False``) + :param scheduler: Learning rate scheduler to run at the start of every epoch. + :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch + and providing it takes no effect. + """ + import torch + import torch.nn.functional as F + from torch.utils.data import TensorDataset, DataLoader + + # Set model mode + self._model.train(mode=training_mode) + + if self._optimizer is None: # pragma: no cover + raise ValueError("An optimizer is needed to train the model, but none for provided") + + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + # Check label shape + y_preprocessed = self.reduce_labels(y_preprocessed) + + # Create dataloader + x_tensor = torch.from_numpy(x_preprocessed) + y_tensor = torch.from_numpy(y_preprocessed) + dataset = TensorDataset(x_tensor, y_tensor) + dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True, drop_last=drop_last) + + m = torch.distributions.normal.Normal( + torch.tensor([0.0], device=self.device), torch.tensor([1.0], device=self.device) + ) + + # Start training + for _ in trange(nb_epochs, disable=not self.verbose): + for x_batch, y_batch in dataloader: + # Move inputs to GPU + x_batch = x_batch.to(self.device) + y_batch = y_batch.to(self.device) + + input_size = len(x_batch) + + # Tile samples for Gaussian augmentation + new_shape = [input_size * self.gaussian_samples] + new_shape.extend(x_batch[0].shape) + x_batch = x_batch.repeat((1, self.gaussian_samples, 1, 1)).view(new_shape) + + # Add random noise for randomized smoothing + noise = torch.randn_like(x_batch, device=self.device) * self.scale + noisy_inputs = x_batch + noise + + # Get model outputs + outputs = self.model(noisy_inputs) + outputs = outputs.reshape((input_size, self.gaussian_samples, self.nb_classes)) + + # Classification loss + outputs_softmax = F.softmax(outputs, dim=2).mean(dim=1) + outputs_log_softmax = torch.log(outputs_softmax + 1e-10) + classification_loss = F.nll_loss(outputs_log_softmax, y_batch, reduction="sum") + + # Robustness loss + beta_outputs = outputs * self.beta + beta_outputs_softmax = F.softmax(beta_outputs, dim=2).mean(dim=1) + top2_score, top2_idx = torch.topk(beta_outputs_softmax, 2) + indices_correct = top2_idx[:, 0] == y_batch + out0, out1 = top2_score[indices_correct, 0], top2_score[indices_correct, 1] + robustness_loss = m.icdf(out1) - m.icdf(out0) + indices = ( + ~torch.isnan(robustness_loss) + & ~torch.isinf(robustness_loss) + & (torch.abs(robustness_loss) <= self.gamma) + ) + out0, out1 = out0[indices], out1[indices] + robustness_loss = m.icdf(out1) - m.icdf(out0) + self.gamma + robustness_loss = torch.sum(robustness_loss) * self.scale / 2 + + # Final objective function + loss = classification_loss + self.lmbda * robustness_loss + loss /= input_size + self._optimizer.zero_grad() + loss.backward() + self._optimizer.step() + + if scheduler is not None: + scheduler.step() diff --git a/art/estimators/certification/randomized_smoothing/macer/tensorflow.py b/art/estimators/certification/randomized_smoothing/macer/tensorflow.py new file mode 100644 index 0000000000..899355ba94 --- /dev/null +++ b/art/estimators/certification/randomized_smoothing/macer/tensorflow.py @@ -0,0 +1,231 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements MACER applied to classifier predictions. + +| Paper link: https://arxiv.org/abs/2001.02378 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING + +from tqdm.auto import trange +import numpy as np + +from art.estimators.certification.randomized_smoothing.tensorflow import TensorFlowV2RandomizedSmoothing +from art.utils import check_and_transform_label_format + +if TYPE_CHECKING: + # pylint: disable=C0412 + import tensorflow as tf + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + +logger = logging.getLogger(__name__) + + +class TensorFlowV2MACER(TensorFlowV2RandomizedSmoothing): + """ + Implementation of MACER training, as introduced in Zhai et al. (2020) + + | Paper link: https://arxiv.org/abs/2001.02378 + """ + + estimator_params = TensorFlowV2RandomizedSmoothing.estimator_params + [ + "beta", + "gamma", + "lmbda", + "gauss_num", + ] + + def __init__( + self, + model, + nb_classes: int, + input_shape: Tuple[int, ...], + loss_object: Optional["tf.Tensor"] = None, + optimizer: Optional["tf.keras.optimizers.Optimizer"] = None, + train_step: Optional[Callable] = None, + channels_first: bool = False, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), + sample_size: int = 32, + scale: float = 0.1, + alpha: float = 0.001, + beta: float = 16.0, + gamma: float = 8.0, + lmbda: float = 12.0, + gaussian_samples: int = 16, + verbose: bool = False, + ) -> None: + """ + Create a MACER classifier. + + :param model: a python functions or callable class defining the model and providing it prediction as output. + :type model: `function` or `callable class` + :param nb_classes: the number of classes in the classification task. + :param input_shape: Shape of one input for the classifier, e.g. for MNIST input_shape=(28, 28, 1). + :param loss_object: The loss function for which to compute gradients. This parameter is applied for training + the model and computing gradients of the loss w.r.t. the input. + :param optimizer: The optimizer used to train the classifier. + :param train_step: A function that applies a gradient update to the trainable variables with signature + `train_step(model, images, labels)`. This will override the default training loop that uses the + provided `loss_object` and `optimizer` parameters. It is recommended to use the `@tf.function` + decorator, if possible, for efficient training. + :param channels_first: Set channels first or last. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param sample_size: Number of samples for smoothing. + :param scale: Standard deviation of Gaussian noise added. + :param alpha: The failure probability of smoothing. + :param beta: The inverse temperature. + :param gamma: The hinge factor. + :param lmbda: The trade-off factor. + :param gaussian_samples: The number of gaussian samples per input. + :param verbose: Show progress bars. + """ + super().__init__( + model=model, + nb_classes=nb_classes, + input_shape=input_shape, + loss_object=loss_object, + optimizer=optimizer, + train_step=train_step, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + sample_size=sample_size, + scale=scale, + alpha=alpha, + verbose=verbose, + ) + self.beta = beta + self.gamma = gamma + self.lmbda = lmbda + self.gaussian_samples = gaussian_samples + + def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: int = 10, **kwargs) -> None: + """ + Fit the classifier on the training set `(x, y)`. + + :param x: Training data. + :param y: Labels, one-hot-encoded of shape (nb_samples, nb_classes) or index labels of + shape (nb_samples,). + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for training. + :param kwargs: Dictionary of framework-specific arguments. This parameter currently only supports + "scheduler" which is an optional function that will be called at the end of every + epoch to adjust the learning rate. + """ + import tensorflow as tf + + if self._train_step is None: # pragma: no cover + if self._optimizer is None: # pragma: no cover + raise ValueError( + "An optimizer `optimizer` or training function `train_step` is required for fitting the " + "model, but it has not been defined." + ) + + @tf.function + def train_step(model, images, labels): + with tf.GradientTape() as tape: + input_size = len(labels) + + outputs = self.model(images, training=True) + outputs = tf.reshape(outputs, [input_size, self.gaussian_samples, self.nb_classes]) + + # Classification loss + outputs_softmax = tf.reduce_mean(tf.nn.softmax(outputs, axis=2), axis=1) + outputs_log_softmax = tf.math.log(outputs_softmax + 1e-10) + indices = tf.stack([np.arange(input_size), labels], axis=1) + nll_loss = tf.gather_nd(outputs_log_softmax, indices=indices) + classification_loss = -tf.reduce_sum(nll_loss) + + # Robustness loss + beta_outputs = outputs * self.beta + beta_outputs_softmax = tf.reduce_mean(tf.nn.softmax(beta_outputs, axis=2), axis=1) + top2_score, top2_idx = tf.math.top_k(beta_outputs_softmax, k=2) + indices_correct = tf.cast(top2_idx[:, 0], labels.dtype) == labels + out = tf.boolean_mask(top2_score, indices_correct) + out0, out1 = out[:, 0], out[:, 1] + icdf_out1 = tf.math.erfinv(2 * out1 - 1) * np.sqrt(2) + icdf_out0 = tf.math.erfinv(2 * out0 - 1) * np.sqrt(2) + robustness_loss = icdf_out1 - icdf_out0 + indices = ( + ~tf.math.is_nan(robustness_loss) # pylint: disable=E1130 + & ~tf.math.is_inf(robustness_loss) # pylint: disable=E1130 + & (tf.abs(robustness_loss) <= self.gamma) + ) + out0, out1 = out0[indices], out1[indices] + icdf_out1 = tf.math.erfinv(2 * out1 - 1) * np.sqrt(2) + icdf_out0 = tf.math.erfinv(2 * out0 - 1) * np.sqrt(2) + robustness_loss = icdf_out1 - icdf_out0 + self.gamma + robustness_loss = tf.reduce_sum(robustness_loss) * self.scale / 2 + + # Final objective function + loss = classification_loss + self.lmbda * robustness_loss + loss /= input_size + + gradients = tape.gradient(loss, model.trainable_variables) + self._optimizer.apply_gradients(zip(gradients, model.trainable_variables)) + + else: + train_step = self._train_step + + scheduler = kwargs.get("scheduler") + + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + # Check label shape + if self._reduce_labels: + y_preprocessed = np.argmax(y_preprocessed, axis=1) + + train_ds = tf.data.Dataset.from_tensor_slices((x_preprocessed, y_preprocessed)).shuffle(10000).batch(batch_size) + + for epoch in trange(nb_epochs, disable=not self.verbose): + for images, labels in train_ds: + # Tile samples for Gaussian augmentation + input_size = len(images) + new_shape = [input_size * self.gaussian_samples] + new_shape.extend(images[0].shape) + images = tf.reshape(tf.tile(images, (1, 1, 1, self.gaussian_samples)), new_shape) + + # Add random noise for randomized smoothing + noise = tf.random.normal(shape=images.shape, mean=0.0, stddev=self.scale) + noisy_inputs = images + noise + + train_step(self.model, noisy_inputs, labels) + + if scheduler is not None: + scheduler(epoch) diff --git a/art/estimators/certification/randomized_smoothing/pytorch.py b/art/estimators/certification/randomized_smoothing/pytorch.py index f348a4c708..a2f8fd44f7 100644 --- a/art/estimators/certification/randomized_smoothing/pytorch.py +++ b/art/estimators/certification/randomized_smoothing/pytorch.py @@ -26,7 +26,7 @@ from typing import List, Optional, Tuple, Union, TYPE_CHECKING import warnings -from tqdm import tqdm +from tqdm.auto import trange import numpy as np from art.config import ART_NUMPY_DTYPE @@ -61,7 +61,7 @@ def __init__( loss: "torch.nn.modules.loss._Loss", input_shape: Tuple[int, ...], nb_classes: int, - optimizer: Optional["torch.optim.Optimizer"] = None, # type: ignore + optimizer: Optional["torch.optim.Optimizer"] = None, channels_first: bool = True, clip_values: Optional["CLIP_VALUES_TYPE"] = None, preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, @@ -71,6 +71,7 @@ def __init__( sample_size: int = 32, scale: float = 0.1, alpha: float = 0.001, + verbose: bool = False, ): """ Create a randomized smoothing classifier. @@ -96,6 +97,7 @@ def __init__( :param sample_size: Number of samples for smoothing. :param scale: Standard deviation of Gaussian noise added. :param alpha: The failure probability of smoothing. + :param verbose: Show progress bars. """ if preprocessing_defences is not None: warnings.warn( @@ -118,6 +120,7 @@ def __init__( sample_size=sample_size, scale=scale, alpha=alpha, + verbose=verbose, ) def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: @@ -179,7 +182,7 @@ def fit( # pylint: disable=W0221 dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True, drop_last=drop_last) # Start training - for _ in tqdm(range(nb_epochs)): + for _ in trange(nb_epochs, disable=not self.verbose): for x_batch, y_batch in dataloader: # Move inputs to device x_batch = x_batch.to(self._device) diff --git a/art/estimators/certification/randomized_smoothing/randomized_smoothing.py b/art/estimators/certification/randomized_smoothing/randomized_smoothing.py index 7baa6282c7..e9b188c494 100644 --- a/art/estimators/certification/randomized_smoothing/randomized_smoothing.py +++ b/art/estimators/certification/randomized_smoothing/randomized_smoothing.py @@ -49,6 +49,7 @@ def __init__( *args, scale: float = 0.1, alpha: float = 0.001, + verbose: bool = False, **kwargs, ) -> None: """ @@ -57,11 +58,13 @@ def __init__( :param sample_size: Number of samples for smoothing. :param scale: Standard deviation of Gaussian noise added. :param alpha: The failure probability of smoothing. + :param verbose: Show progress bars. """ super().__init__(*args, **kwargs) # type: ignore self.sample_size = sample_size self.scale = scale self.alpha = alpha + self.verbose = verbose def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: """ @@ -95,7 +98,7 @@ def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> np.ndarray: logger.info("Applying randomized smoothing.") n_abstained = 0 prediction = [] - for x_i in tqdm(x, desc="Randomized smoothing"): + for x_i in tqdm(x, desc="Randomized smoothing", disable=not self.verbose): # get class counts counts_pred = self._prediction_counts(x_i, batch_size=batch_size) top = counts_pred.argsort()[::-1] diff --git a/art/estimators/certification/randomized_smoothing/smooth_adv/__init__.py b/art/estimators/certification/randomized_smoothing/smooth_adv/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/art/estimators/certification/randomized_smoothing/smooth_adv/pytorch.py b/art/estimators/certification/randomized_smoothing/smooth_adv/pytorch.py new file mode 100644 index 0000000000..dfd647bbc1 --- /dev/null +++ b/art/estimators/certification/randomized_smoothing/smooth_adv/pytorch.py @@ -0,0 +1,242 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements SmoothAdv applied to classifier predictions. + +| Paper link: https://arxiv.org/abs/1906.04584 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +from typing import List, Optional, Tuple, Union, TYPE_CHECKING + +from tqdm.auto import trange +import numpy as np + +from art.attacks.evasion.projected_gradient_descent.projected_gradient_descent import ProjectedGradientDescent +from art.estimators.certification.randomized_smoothing.pytorch import PyTorchRandomizedSmoothing +from art.estimators.classification.pytorch import PyTorchClassifier +from art.utils import check_and_transform_label_format + +if TYPE_CHECKING: + # pylint: disable=C0412 + import torch + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + +logger = logging.getLogger(__name__) + + +class PyTorchSmoothAdv(PyTorchRandomizedSmoothing): + """ + Implementation of SmoothAdv training, as introduced in Salman et al. (2019) + + | Paper link: https://arxiv.org/abs/1906.04584 + """ + + estimator_params = PyTorchRandomizedSmoothing.estimator_params + [ + "epsilon", + "num_noise_vec", + "num_steps", + "warmup", + ] + + def __init__( + self, + model: "torch.nn.Module", + loss: "torch.nn.modules.loss._Loss", + input_shape: Tuple[int, ...], + nb_classes: int, + optimizer: Optional["torch.optim.Optimizer"] = None, + channels_first: bool = True, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), + device_type: str = "gpu", + sample_size: int = 32, + scale: float = 0.1, + alpha: float = 0.001, + epsilon: float = 1.0, + num_noise_vec: int = 1, + num_steps: int = 10, + warmup: int = 1, + verbose: bool = False, + ) -> None: + """ + Create a SmoothAdv classifier. + + :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits + output should be preferred where possible to ensure attack efficiency. + :param loss: The loss function for which to compute gradients for training. The target label must be raw + categorical, i.e. not converted to one-hot encoding. + :param input_shape: The shape of one input instance. + :param nb_classes: The number of classes of the model. + :param optimizer: The optimizer used to train the classifier. + :param channels_first: Set channels first or last. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. + :param sample_size: Number of samples for smoothing. + :param scale: Standard deviation of Gaussian noise added. + :param alpha: The failure probability of smoothing. + :param epsilon: The maximum perturbation that can be induced. + :param num_noise_vec: The number of noise vectors. + :param num_steps: The number of attack updates. + :param warmup: The warm-up strategy that is gradually increased up to the original value. + :param verbose: Show progress bars. + """ + super().__init__( + model=model, + loss=loss, + input_shape=input_shape, + nb_classes=nb_classes, + optimizer=optimizer, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + sample_size=sample_size, + scale=scale, + alpha=alpha, + verbose=verbose, + ) + self.epsilon = epsilon + self.num_noise_vec = num_noise_vec + self.num_steps = num_steps + self.warmup = warmup + + classifier = PyTorchClassifier( + model=model, + loss=loss, + input_shape=input_shape, + nb_classes=nb_classes, + optimizer=optimizer, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + ) + self.attack = ProjectedGradientDescent(classifier, eps=self.epsilon, max_iter=1, verbose=False) + + def fit( # pylint: disable=W0221 + self, + x: np.ndarray, + y: np.ndarray, + batch_size: int = 128, + nb_epochs: int = 10, + training_mode: bool = True, + drop_last: bool = False, + scheduler: Optional["torch.optim.lr_scheduler._LRScheduler"] = None, + **kwargs, + ) -> None: + """ + Fit the classifier on the training set `(x, y)`. + + :param x: Training data. + :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of + shape (nb_samples,). + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for training. + :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode. + :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by + the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then + the last batch will be smaller. (default: ``False``) + :param scheduler: Learning rate scheduler to run at the start of every epoch. + :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch + and providing it takes no effect. + """ + import torch + from torch.utils.data import TensorDataset, DataLoader + + # Set model mode + self._model.train(mode=training_mode) + + if self._optimizer is None: # pragma: no cover + raise ValueError("An optimizer is needed to train the model, but none for provided") + + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + # Check label shape + y_preprocessed = self.reduce_labels(y_preprocessed) + + # Create dataloader + x_tensor = torch.from_numpy(x_preprocessed) + y_tensor = torch.from_numpy(y_preprocessed) + dataset = TensorDataset(x_tensor, y_tensor) + dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True, drop_last=drop_last) + + # Start training + for epoch in trange(nb_epochs, disable=not self.verbose): + self.attack.norm = min(self.epsilon, (epoch + 1) * self.epsilon / self.warmup) + + for x_batch, y_batch in dataloader: + mini_batch_size = len(x_batch) // self.num_noise_vec + + for mini_batch in range(self.num_noise_vec): + # Create mini batch + inputs = x_batch[mini_batch * mini_batch_size : (mini_batch + 1) * mini_batch_size] + labels = y_batch[mini_batch * mini_batch_size : (mini_batch + 1) * mini_batch_size] + + # Move inputs to GPU + inputs = inputs.to(self.device) + labels = labels.to(self.device) + + noise = torch.randn_like(inputs) * self.scale + + # Attack and find adversarial examples + self.model.eval() + original_inputs = inputs.cpu().detach().numpy() + noise_for_attack = noise.cpu().detach().numpy() + perturbation_delta = np.zeros_like(original_inputs) + for _ in range(self.num_steps): + perturbed_inputs = original_inputs + perturbation_delta + adv_ex = self.attack.generate(perturbed_inputs + noise_for_attack) + perturbation_delta = adv_ex - perturbed_inputs - noise_for_attack + + # Update perturbed inputs after last iteration + perturbed_inputs = original_inputs + perturbation_delta + self.model.train() + noisy_inputs = torch.from_numpy(perturbed_inputs).to(self.device) + noise + + targets = labels.unsqueeze(1).repeat(1, self.num_noise_vec).reshape(-1, 1).squeeze() + outputs = self.model(noisy_inputs) + loss = self.loss(outputs, targets) + + # Compute gradient and do SGD step + self.optimizer.zero_grad() + loss.backward() + self.optimizer.step() + + if scheduler is not None: + scheduler.step() diff --git a/art/estimators/certification/randomized_smoothing/smooth_adv/tensorflow.py b/art/estimators/certification/randomized_smoothing/smooth_adv/tensorflow.py new file mode 100644 index 0000000000..17fb37c264 --- /dev/null +++ b/art/estimators/certification/randomized_smoothing/smooth_adv/tensorflow.py @@ -0,0 +1,233 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements SmoothAdv applied to classifier predictions. + +| Paper link: https://arxiv.org/abs/1906.04584 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING + +from tqdm.auto import trange +import numpy as np + +from art.attacks.evasion.projected_gradient_descent.projected_gradient_descent import ProjectedGradientDescent +from art.estimators.certification.randomized_smoothing.tensorflow import TensorFlowV2RandomizedSmoothing +from art.estimators.classification.tensorflow import TensorFlowV2Classifier +from art.utils import check_and_transform_label_format + +if TYPE_CHECKING: + # pylint: disable=C0412 + import tensorflow as tf + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + +logger = logging.getLogger(__name__) + + +class TensorFlowV2SmoothAdv(TensorFlowV2RandomizedSmoothing): + """ + Implementation of SmoothAdv training, as introduced in Salman et al. (2019) + + | Paper link: https://arxiv.org/abs/1906.04584 + """ + + estimator_params = TensorFlowV2RandomizedSmoothing.estimator_params + [ + "epsilon", + "num_noise_vec", + "num_steps", + "warmup", + ] + + def __init__( + self, + model, + nb_classes: int, + input_shape: Tuple[int, ...], + loss_object: Optional["tf.Tensor"] = None, + optimizer: Optional["tf.keras.optimizers.Optimizer"] = None, + train_step: Optional[Callable] = None, + channels_first: bool = False, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), + sample_size: int = 32, + scale: float = 0.1, + alpha: float = 0.001, + epsilon: float = 1.0, + num_noise_vec: int = 1, + num_steps: int = 10, + warmup: int = 1, + verbose: bool = False, + ) -> None: + """ + Create a MACER classifier. + + :param model: a python functions or callable class defining the model and providing it prediction as output. + :type model: `function` or `callable class` + :param nb_classes: the number of classes in the classification task. + :param input_shape: Shape of one input for the classifier, e.g. for MNIST input_shape=(28, 28, 1). + :param loss_object: The loss function for which to compute gradients. This parameter is applied for training + the model and computing gradients of the loss w.r.t. the input. + :param optimizer: The optimizer used to train the classifier. + :param train_step: A function that applies a gradient update to the trainable variables with signature + `train_step(model, images, labels)`. This will override the default training loop that uses the + provided `loss_object` and `optimizer` parameters. It is recommended to use the `@tf.function` + decorator, if possible, for efficient training. + :param channels_first: Set channels first or last. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param sample_size: Number of samples for smoothing. + :param scale: Standard deviation of Gaussian noise added. + :param alpha: The failure probability of smoothing. + :param epsilon: The maximum perturbation that can be induced. + :param num_noise_vec: The number of noise vectors. + :param num_steps: The number of attack updates. + :param warmup: The warm-up strategy that is gradually increased up to the original value. + :param verbose: Show progress bars. + """ + super().__init__( + model=model, + nb_classes=nb_classes, + input_shape=input_shape, + loss_object=loss_object, + optimizer=optimizer, + train_step=train_step, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + sample_size=sample_size, + scale=scale, + alpha=alpha, + verbose=verbose, + ) + self.epsilon = epsilon + self.num_noise_vec = num_noise_vec + self.num_steps = num_steps + self.warmup = warmup + + classifier = TensorFlowV2Classifier( + model=model, + nb_classes=nb_classes, + input_shape=input_shape, + loss_object=loss_object, + optimizer=optimizer, + train_step=train_step, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + ) + self.attack = ProjectedGradientDescent(classifier, eps=self.epsilon, max_iter=1, verbose=False) + + def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: int = 10, **kwargs) -> None: + """ + Fit the classifier on the training set `(x, y)`. + + :param x: Training data. + :param y: Labels, one-hot-encoded of shape (nb_samples, nb_classes) or index labels of + shape (nb_samples,). + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for training. + :param kwargs: Dictionary of framework-specific arguments. This parameter currently only supports + "scheduler" which is an optional function that will be called at the end of every + epoch to adjust the learning rate. + """ + import tensorflow as tf + + if self._train_step is None: # pragma: no cover + if self._loss_object is None: # pragma: no cover + raise TypeError( + "A loss function `loss_object` or training function `train_step` is required for fitting the " + "model, but it has not been defined." + ) + if self._optimizer is None: # pragma: no cover + raise ValueError( + "An optimizer `optimizer` or training function `train_step` is required for fitting the " + "model, but it has not been defined." + ) + + @tf.function + def train_step(model, images, labels): + with tf.GradientTape() as tape: + predictions = model(images, training=True) + loss = self.loss_object(labels, predictions) + gradients = tape.gradient(loss, model.trainable_variables) + self.optimizer.apply_gradients(zip(gradients, model.trainable_variables)) + + else: + train_step = self._train_step + + scheduler = kwargs.get("scheduler") + + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + # Check label shape + if self._reduce_labels: + y_preprocessed = np.argmax(y_preprocessed, axis=1) + + train_ds = tf.data.Dataset.from_tensor_slices((x_preprocessed, y_preprocessed)).shuffle(10000).batch(batch_size) + + for epoch in trange(nb_epochs, disable=not self.verbose): + self.attack.norm = min(self.epsilon, (epoch + 1) * self.epsilon / self.warmup) + + for x_batch, y_batch in train_ds: + mini_batch_size = len(x_batch) // self.num_noise_vec + + for mini_batch in range(self.num_noise_vec): + # Create mini batch + inputs = x_batch[mini_batch * mini_batch_size : (mini_batch + 1) * mini_batch_size] + labels = y_batch[mini_batch * mini_batch_size : (mini_batch + 1) * mini_batch_size] + + # Tile samples for Gaussian augmentation + inputs = tf.reshape(tf.tile(inputs, (1, self.num_noise_vec, 1, 1)), x_batch.shape) + noise = tf.random.normal(inputs.shape, mean=0.0, stddev=self.scale) + + original_inputs = inputs.numpy() + noise_for_attack = noise.numpy() + perturbation_delta = np.zeros_like(original_inputs) + for _ in range(self.num_steps): + perturbed_inputs = original_inputs + perturbation_delta + adv_ex = self.attack.generate(perturbed_inputs + noise_for_attack) + perturbation_delta = adv_ex - perturbed_inputs - noise_for_attack + + # Add random noise for randomized smoothing + perturbed_inputs = original_inputs + perturbation_delta + noisy_inputs = tf.convert_to_tensor(perturbed_inputs) + noise + + train_step(self.model, noisy_inputs, labels) + + if scheduler is not None: + scheduler(epoch) diff --git a/art/estimators/certification/randomized_smoothing/smooth_mix/__init__.py b/art/estimators/certification/randomized_smoothing/smooth_mix/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/art/estimators/certification/randomized_smoothing/smooth_mix/pytorch.py b/art/estimators/certification/randomized_smoothing/smooth_mix/pytorch.py new file mode 100644 index 0000000000..32a79fae63 --- /dev/null +++ b/art/estimators/certification/randomized_smoothing/smooth_mix/pytorch.py @@ -0,0 +1,359 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# MIT License +# +# Copyright (c) 2021 Jongheon Jeong +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements SmoothMix applied to classifier predictions. + +| Paper link: https://arxiv.org/abs/2111.09277 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +from typing import List, Optional, Tuple, Union, TYPE_CHECKING + +from tqdm.auto import trange +import numpy as np + +from art.estimators.certification.randomized_smoothing.pytorch import PyTorchRandomizedSmoothing +from art.utils import check_and_transform_label_format + +if TYPE_CHECKING: + # pylint: disable=C0412 + import torch + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + +logger = logging.getLogger(__name__) + + +class PyTorchSmoothMix(PyTorchRandomizedSmoothing): + """ + Implementation of SmoothMix training, as introduced in Jeong et al. (2021) + + | Paper link: https://arxiv.org/abs/2111.09277 + """ + + estimator_params = PyTorchRandomizedSmoothing.estimator_params + [ + "eta", + "num_noise_vec", + "num_steps", + "warmup", + "mix_step", + "maxnorm_s", + "maxnorm", + ] + + def __init__( + self, + model: "torch.nn.Module", + loss: "torch.nn.modules.loss._Loss", + input_shape: Tuple[int, ...], + nb_classes: int, + optimizer: Optional["torch.optim.Optimizer"] = None, + channels_first: bool = True, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), + device_type: str = "gpu", + sample_size: int = 32, + scale: float = 0.1, + alpha: float = 0.001, + eta: float = 1.0, + num_noise_vec: int = 1, + num_steps: int = 10, + warmup: int = 1, + mix_step: int = 0, + maxnorm_s: Optional[float] = None, + maxnorm: Optional[float] = None, + verbose: bool = False, + ) -> None: + """ + Create a SmoothMix classifier. + + :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits + output should be preferred where possible to ensure attack efficiency. + :param loss: The loss function for which to compute gradients for training. The target label must be raw + categorical, i.e. not converted to one-hot encoding. + :param input_shape: The shape of one input instance. + :param nb_classes: The number of classes of the model. + :param optimizer: The optimizer used to train the classifier. + :param channels_first: Set channels first or last. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. + :param sample_size: Number of samples for smoothing. + :param scale: Standard deviation of Gaussian noise added. + :param alpha: The failure probability of smoothing. + :param eta: The relative strength of the mixup loss. + :param num_noise_vec: The number of noise vectors. + :param num_steps: The number of attack updates. + :param warmup: The warm-up strategy that is gradually increased up to the original value. + :param mix_step: Determines which sample to use for the clean side. + :param maxnorm_s: The initial value of `alpha * mix_step`. + :param maxnorm: The initial value of `alpha * mix_step` for adversarial examples. + :param verbose: Show progress bars. + """ + super().__init__( + model=model, + loss=loss, + input_shape=input_shape, + nb_classes=nb_classes, + optimizer=optimizer, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + sample_size=sample_size, + scale=scale, + alpha=alpha, + verbose=verbose, + ) + self.eta = eta + self.num_noise_vec = num_noise_vec + self.num_steps = num_steps + self.warmup = warmup + self.mix_step = mix_step + self.maxnorm_s = maxnorm_s + self.maxnorm = maxnorm + + def fit( # pylint: disable=W0221 + self, + x: np.ndarray, + y: np.ndarray, + batch_size: int = 128, + nb_epochs: int = 10, + training_mode: bool = True, + drop_last: bool = False, + scheduler: Optional["torch.optim.lr_scheduler._LRScheduler"] = None, + **kwargs, + ) -> None: + """ + Fit the classifier on the training set `(x, y)`. + + :param x: Training data. + :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of + shape (nb_samples,). + :param batch_size: Size of batches. + :param nb_epochs: Number of epochs to use for training. + :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode. + :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by + the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then + the last batch will be smaller. (default: ``False``) + :param scheduler: Learning rate scheduler to run at the start of every epoch. + :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch + and providing it takes no effect. + """ + import torch + import torch.nn.functional as F + from torch.utils.data import TensorDataset, DataLoader + + # Set model mode + self._model.train(mode=training_mode) + + if self._optimizer is None: # pragma: no cover + raise ValueError("An optimizer is needed to train the model, but none for provided") + + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) + + # Apply preprocessing + x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True) + + # Check label shape + y_preprocessed = self.reduce_labels(y_preprocessed) + + # Create dataloader + x_tensor = torch.from_numpy(x_preprocessed) + y_tensor = torch.from_numpy(y_preprocessed) + dataset = TensorDataset(x_tensor, y_tensor) + dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True, drop_last=drop_last) + + # Start training + for epoch in trange(nb_epochs, disable=not self.verbose): + warmup_v = min(1.0, (epoch + 1) / self.warmup) + + for x_batch, y_batch in dataloader: + mini_batch_size = len(x_batch) // self.num_noise_vec + + for mini_batch in range(self.num_noise_vec): + # Create mini batch + inputs = x_batch[mini_batch * mini_batch_size : (mini_batch + 1) * mini_batch_size] + labels = y_batch[mini_batch * mini_batch_size : (mini_batch + 1) * mini_batch_size] + + # Move inputs to GPU + inputs = inputs.to(self.device) + labels = labels.to(self.device) + + noises = [torch.randn_like(inputs) * self.scale for _ in range(self.num_noise_vec)] + + # Attack and find adversarial examples + self._model.eval() + inputs, inputs_adv = self._smooth_mix_pgd_attack(inputs, labels, noises, warmup_v) + self._model.train(mode=training_mode) + + in_clean_c = torch.cat([inputs + noise for noise in noises], dim=0) + logits_c = self._model(in_clean_c)[-1] + labels_c = labels.repeat(self.num_noise_vec) + + logits_c_chunk = torch.chunk(logits_c, self.num_noise_vec, dim=0) + clean_sm = F.softmax(torch.stack(logits_c_chunk), dim=-1) + clean_avg_sm = torch.mean(clean_sm, dim=0) + loss_xent = F.cross_entropy(logits_c, labels_c, reduction="none") + + # mix adversarial examples + in_mix, labels_mix = self._mix_data(inputs, inputs_adv, clean_avg_sm) + in_mix_c = torch.cat([in_mix + noise for noise in noises], dim=0) + labels_mix_c = labels_mix.repeat(self.num_noise_vec, 1) + logits_mix_c = F.log_softmax(self._model(in_mix_c)[-1], dim=1) + + preds = torch.argmax(clean_avg_sm, dim=-1) + ind_correct = (preds == labels).float() + ind_correct = ind_correct.repeat(self.num_noise_vec) + + loss_mixup = F.kl_div(logits_mix_c, labels_mix_c, reduction="none").sum(1) + loss = loss_xent.mean() + self.eta * warmup_v * (ind_correct * loss_mixup).mean() + + # compute gradient and do SGD step + self._optimizer.zero_grad() + loss.backward() + self._optimizer.step() + + if scheduler is not None: + scheduler.step() + + def _smooth_mix_pgd_attack( + self, + inputs: "torch.Tensor", + labels: "torch.Tensor", + noises: List["torch.Tensor"], + warmup_v: float, + ) -> Tuple["torch.Tensor", "torch.Tensor"]: + """ + The authors' implementation of the SmoothMixPGD attack. + Code modified from https://github.com/jh-jeong/smoothmix/blob/main/code/train.py + + :param inputs: The batch inputs + :param labels: The batch labels for the inputs + :param noises: The noise applied to each input in the attack + """ + import torch + import torch.nn.functional as F + + def _batch_l2_norm(x: torch.Tensor) -> torch.Tensor: + """ + Perform a batch L2 norm + + :param x: The inputs to compute the batch L2 norm of + """ + x_flat = x.reshape(x.size(0), -1) + return torch.norm(x_flat, dim=1) + + def _project(x: torch.Tensor, x_0: torch.Tensor, maxnorm: Optional[float] = None): + """ + Apply a projection of the current inputs with the maxnorm + + :param x: The inputs to apply a projection on (either original or adversarial) + :param x_0: The unperterbed inputs to apply the projection on + :param maxnorm: The maxnorm value to apply to x + """ + if maxnorm is not None: + eta = x - x_0 + eta = eta.renorm(p=2, dim=0, maxnorm=maxnorm) + x = x_0 + eta + x = torch.clamp(x, 0, 1) + x = x.detach() + return x + + adv = inputs.detach() + init = inputs.detach() + for i in range(self.num_steps): + if i == self.mix_step: + init = adv.detach() + adv.requires_grad_() + + softmax = [F.softmax(self._model(adv + noise)[-1], dim=1) for noise in noises] + avg_softmax = torch.mean(torch.stack(softmax), dim=0) + log_softmax = torch.log(avg_softmax.clamp(min=1e-20)) + loss = F.nll_loss(log_softmax, labels, reduction="sum") + + grad = torch.autograd.grad(loss, [adv])[0] + grad_norm = _batch_l2_norm(grad).view(-1, 1, 1, 1) + grad = grad / (grad_norm + 1e-8) + adv = adv + self.alpha * grad + + adv = _project(adv, inputs, self.maxnorm) + + if self.maxnorm_s is None: + maxnorm_s = self.alpha * self.mix_step * warmup_v + else: + maxnorm_s = self.maxnorm_s * warmup_v + init = _project(init, inputs, maxnorm_s) + + return init, adv + + def _mix_data( + self, inputs: "torch.Tensor", inputs_adv: "torch.Tensor", labels: "torch.Tensor" + ) -> Tuple["torch.Tensor", "torch.Tensor"]: + """ + Returns mixed inputs and labels. + + :param inputs: Training data + :param inputs_adv: Adversarial training data + :param labels: Training labels + """ + import torch + + eye = torch.eye(self.nb_classes, device=self.device) + unif = eye.mean(0, keepdim=True) + lam = torch.rand(inputs.size(0), device=self.device) / 2 + + mixed_inputs = (1 - lam).view(-1, 1, 1, 1) * inputs + lam.view(-1, 1, 1, 1) * inputs_adv + mixed_labels = (1 - lam).view(-1, 1) * labels + lam.view(-1, 1) * unif + + return mixed_inputs, mixed_labels diff --git a/art/estimators/certification/randomized_smoothing/tensorflow.py b/art/estimators/certification/randomized_smoothing/tensorflow.py index 034c34fd46..ef8e5720c0 100644 --- a/art/estimators/certification/randomized_smoothing/tensorflow.py +++ b/art/estimators/certification/randomized_smoothing/tensorflow.py @@ -26,7 +26,7 @@ from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING import warnings -from tqdm import tqdm +from tqdm.auto import trange import numpy as np from art.estimators.classification.tensorflow import TensorFlowV2Classifier @@ -70,6 +70,7 @@ def __init__( sample_size: int = 32, scale: float = 0.1, alpha: float = 0.001, + verbose: bool = False, ): """ Create a randomized smoothing classifier. @@ -98,6 +99,7 @@ def __init__( :param sample_size: Number of samples for smoothing. :param scale: Standard deviation of Gaussian noise added. :param alpha: The failure probability of smoothing. + :param verbose: Show progress bars. """ if preprocessing_defences is not None: warnings.warn( @@ -120,6 +122,7 @@ def __init__( sample_size=sample_size, scale=scale, alpha=alpha, + verbose=verbose, ) def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray: @@ -137,8 +140,9 @@ def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: in shape (nb_samples,). :param batch_size: Size of batches. :param nb_epochs: Number of epochs to use for training. - :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for - TensorFlow and providing it takes no effect. + :param kwargs: Dictionary of framework-specific arguments. This parameter currently only supports + "scheduler" which is an optional function that will be called at the end of every + epoch to adjust the learning rate. """ import tensorflow as tf @@ -165,6 +169,8 @@ def train_step(model, images, labels): else: train_step = self._train_step + scheduler = kwargs.get("scheduler") + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) # Apply preprocessing @@ -176,12 +182,15 @@ def train_step(model, images, labels): train_ds = tf.data.Dataset.from_tensor_slices((x_preprocessed, y_preprocessed)).shuffle(10000).batch(batch_size) - for _ in tqdm(range(nb_epochs)): + for epoch in trange(nb_epochs, disable=not self.verbose): for images, labels in train_ds: # Add random noise for randomized smoothing images += tf.random.normal(shape=images.shape, mean=0.0, stddev=self.scale) train_step(self.model, images, labels) + if scheduler is not None: + scheduler(epoch) + def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> np.ndarray: # type: ignore """ Perform prediction of the given classifier for a batch of inputs, taking an expectation over transformations. diff --git a/art/estimators/classification/__init__.py b/art/estimators/classification/__init__.py index 476cce1fc9..1af1909d35 100644 --- a/art/estimators/classification/__init__.py +++ b/art/estimators/classification/__init__.py @@ -17,6 +17,7 @@ from art.estimators.classification.lightgbm import LightGBMClassifier from art.estimators.classification.mxnet import MXClassifier from art.estimators.classification.pytorch import PyTorchClassifier +from art.estimators.classification.hugging_face import HuggingFaceClassifierPyTorch from art.estimators.classification.query_efficient_bb import QueryEfficientGradientEstimationClassifier from art.estimators.classification.scikitlearn import SklearnClassifier from art.estimators.classification.tensorflow import ( diff --git a/art/estimators/classification/hugging_face.py b/art/estimators/classification/hugging_face.py new file mode 100644 index 0000000000..33a9ce18e0 --- /dev/null +++ b/art/estimators/classification/hugging_face.py @@ -0,0 +1,370 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the abstract estimator `HuggingFaceClassifier` using the PyTorchClassifier as a backend +to interface with ART. +""" +import logging + +from typing import List, Optional, Tuple, Union, Dict, Callable, Any, TYPE_CHECKING + +import numpy as np +import six + +from art.estimators.classification.pytorch import PyTorchClassifier + +if TYPE_CHECKING: + import torch + import transformers + from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE + from art.defences.preprocessor import Preprocessor + from art.defences.postprocessor import Postprocessor + from transformers.modeling_outputs import ImageClassifierOutput + +logger = logging.getLogger(__name__) + + +class HuggingFaceClassifierPyTorch(PyTorchClassifier): + """ + This class implements a classifier with the HuggingFace framework. + """ + + def __init__( + self, + model: "transformers.PreTrainedModel", + loss: "torch.nn.modules.loss._Loss", + input_shape: Tuple[int, ...], + nb_classes: int, + optimizer: Optional["torch.optim.Optimizer"] = None, + use_amp: bool = False, + opt_level: str = "O1", + loss_scale: Optional[Union[float, str]] = "dynamic", + channels_first: bool = True, + clip_values: Optional["CLIP_VALUES_TYPE"] = None, + preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None, + postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None, + preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0), + processor: Optional[Callable] = None, + device_type: str = "gpu", + ): + """ + Initialization of HuggingFaceClassifierPyTorch specifically for the PyTorch-based backend. + + :param model: Huggingface model model which returns outputs of type + ImageClassifierOutput from the transformers library. + Must have the logits attribute set as output. + :param loss: The loss function for which to compute gradients for training. The target label must be raw + categorical, i.e. not converted to one-hot encoding. + :param input_shape: The shape of one input instance. + :param optimizer: The optimizer used to train the classifier. + :param use_amp: Whether to use the automatic mixed precision tool to enable mixed precision training or + gradient computation, e.g. with loss gradient computation. When set to True, this option is + only triggered if there are GPUs available. + :param opt_level: Specify a pure or mixed precision optimization level. Used when use_amp is True. Accepted + values are `O0`, `O1`, `O2`, and `O3`. + :param loss_scale: Loss scaling. Used when use_amp is True. If passed as a string, must be a string + representing a number, e.g., “1.0”, or the string “dynamic”. + :param nb_classes: The number of classes of the model. + :param optimizer: The optimizer used to train the classifier. + :param channels_first: Set channels first or last. Normally should be set to True for HF models based on + a pytorch backend. + :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and + maximum values allowed for features. If floats are provided, these will be used as the range of all + features. If arrays are provided, each value will be considered the bound for a feature, thus + the shape of clip values needs to match the total number of features. + :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier. + :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier. + :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be + used for data preprocessing. The first value will be subtracted from the input. The input will then + be divided by the second one. + :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`. + :param processor: Optional argument. Function which takes in a batch of data and performs + the preprocessing relevant to a given foundation model. + Must be differentiable for grandient based defences and attacks. + """ + import torch + + self.processor = processor + + super().__init__( + model=model, + loss=loss, + input_shape=input_shape, + nb_classes=nb_classes, + optimizer=optimizer, + use_amp=use_amp, + opt_level=opt_level, + loss_scale=loss_scale, + channels_first=channels_first, + clip_values=clip_values, + preprocessing_defences=preprocessing_defences, + postprocessing_defences=postprocessing_defences, + preprocessing=preprocessing, + device_type=device_type, + ) + + import functools + + def prefix_function(function: Callable, postfunction: Callable) -> Callable[[Any, Any], torch.Tensor]: + """ + Huggingface returns logit under outputs.logits. To make this compatible with ART we wrap the forward pass + function of a HF model here, which automatically extracts the logits. + + :param function: The first function to run, in our case the forward pass of the model. + :param postfunction: Second function to run, in this case simply extracts the logits. + :return: model outputs. + """ + + @functools.wraps(function) + def run(*args, **kwargs) -> torch.Tensor: + outputs = function(*args, **kwargs) + return postfunction(outputs) + + return run + + def get_logits(outputs: "ImageClassifierOutput") -> torch.Tensor: + """ + Gets the logits attribute from ImageClassifierOutput + + :param outputs: outputs of type ImageClassifierOutput from a Huggingface + :return: model's logit predictions. + """ + if isinstance(outputs, torch.Tensor): + return outputs + return outputs.logits + + self.model.forward = prefix_function(self.model.forward, get_logits) # type: ignore + + def _make_model_wrapper(self, model: "torch.nn.Module") -> "torch.nn.Module": + # Try to import PyTorch and create an internal class that acts like a model wrapper extending torch.nn.Module + import torch + + input_shape = self._input_shape + input_for_hook = torch.rand(input_shape) + input_for_hook = torch.unsqueeze(input_for_hook, dim=0) + + if self.processor is not None: + input_for_hook = self.processor(input_for_hook) + + processor = self.processor + try: + # Define model wrapping class only if not defined before + if not hasattr(self, "_model_wrapper"): + + class ModelWrapper(torch.nn.Module): + """ + This is a wrapper for the input model. + """ + + def __init__(self, model: torch.nn.Module): + """ + Initialization by storing the input model. + + :param model: PyTorch model. The forward function of the model must return the logit output. + """ + super().__init__() + self._model = model + + # pylint: disable=W0221 + # disable pylint because of API requirements for function + def forward(self, x): + """ + This is where we get outputs from the input model. + + :param x: Input data. + :type x: `torch.Tensor` + :return: a list of output layers, where the last 2 layers are logit and final outputs. + :rtype: `list` + """ + # pylint: disable=W0212 + # disable pylint because access to _model required + + result = [] + + if isinstance(self._model, torch.nn.Module): + if processor is not None: + x = processor(x) + x = self._model.forward(x) + result.append(x) + + else: # pragma: no cover + raise TypeError("The input model must inherit from `nn.Module`.") + + return result + + @property + def get_layers(self) -> List[str]: + """ + Return the hidden layers in the model, if applicable. + + :return: The hidden layers in the model, input and output layers excluded. + + .. warning:: `get_layers` tries to infer the internal structure of the model. + This feature comes with no guarantees on the correctness of the result. + The intended order of the layers tries to match their order in the model, but this + is not guaranteed either. + """ + + result_dict = {} + + modules = [] + + # pylint: disable=W0613 + def forward_hook(input_module, hook_input, hook_output): + logger.info("input_module is %s with id %i", input_module, id(input_module)) + modules.append(id(input_module)) + + handles = [] + + for name, module in self._model.named_modules(): + logger.info( + "found %s with type %s and id %i and name %s with submods %i ", + module, + type(module), + id(module), + name, + len(list(module.named_modules())), + ) + + if name != "" and len(list(module.named_modules())) == 1: + handles.append(module.register_forward_hook(forward_hook)) + result_dict[id(module)] = name + + logger.info("mapping from id to name is %s", result_dict) + + logger.info("------ Finished Registering Hooks------") + model(input_for_hook) # hooks are fired sequentially from model input to the output + + logger.info("------ Finished Fire Hooks------") + + # Remove the hooks + for hook in handles: + hook.remove() + + logger.info("new result is: ") + name_order = [] + for module in modules: + name_order.append(result_dict[module]) + + logger.info(name_order) + + return name_order + + # Set newly created class as private attribute + self._model_wrapper = ModelWrapper # type: ignore + + # Use model wrapping class to wrap the PyTorch model received as argument + return self._model_wrapper(model) + + except ImportError: # pragma: no cover + raise ImportError("Could not find PyTorch (`torch`) installation.") from ImportError + + def get_activations( # type: ignore + self, + x: Union[np.ndarray, "torch.Tensor"], + layer: Optional[Union[int, str]] = None, + batch_size: int = 128, + framework: bool = False, + ) -> Union[np.ndarray, "torch.Tensor"]: + """ + Return the output of the specified layer for input `x`. `layer` is specified by layer index (between 0 and + `nb_layers - 1`) or by name. The number of layers can be determined by counting the results returned by + calling `layer_names`. + + :param x: Input for computing the activations. + :param layer: Layer for computing the activations + :param batch_size: Size of batches. + :param framework: If true, return the intermediate tensor representation of the activation. + :return: The output of `layer`, where the first dimension is the batch size corresponding to `x`. + """ + import torch + + self._model.eval() + + # Apply defences + if framework: + no_grad = False + else: + no_grad = True + x_preprocessed, _ = self._apply_preprocessing(x=x, y=None, fit=False, no_grad=no_grad) + + # Get index of the extracted layer + if isinstance(layer, six.string_types): + if layer not in self._layer_names: # pragma: no cover + raise ValueError(f"Layer name {layer} not supported") + layer_index = self._layer_names.index(layer) + + elif isinstance(layer, int): + layer_index = layer + + else: # pragma: no cover + raise TypeError("Layer must be of type str or int") + + def get_feature(name): + # the hook signature + def hook(model, input, output): # pylint: disable=W0622,W0613 + # TODO: this is using the input, rather than the output, to circumvent the fact + # TODO: that flatten is not a layer in pytorch, and the activation defence expects + # TODO: a flattened input. A better option is to refactor the activation defence + # TODO: to not crash if non 2D inputs are provided. + self._features[name] = input + + return hook + + if not hasattr(self, "_features"): + self._features: Dict[str, torch.Tensor] = {} + # register forward hooks on the layers of choice + handles = [] + + lname = self._layer_names[layer_index] + + if layer not in self._features: + for name, module in self.model.named_modules(): + if name == lname and len(list(module.named_modules())) == 1: + handles.append(module.register_forward_hook(get_feature(name))) + + if framework: + if isinstance(x_preprocessed, torch.Tensor): + self._model(x_preprocessed) + return self._features[self._layer_names[layer_index]][0] + input_tensor = torch.from_numpy(x_preprocessed) + self._model(input_tensor.to(self._device)) + return self._features[self._layer_names[layer_index]][0] # pylint: disable=W0212 + + # Run prediction with batch processing + results = [] + num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size))) + + for m in range(num_batch): + # Batch indexes + begin, end = ( + m * batch_size, + min((m + 1) * batch_size, x_preprocessed.shape[0]), + ) + + # Run prediction for the current batch + self._model(torch.from_numpy(x_preprocessed[begin:end]).to(self._device)) + layer_output = self._features[self._layer_names[layer_index]] # pylint: disable=W0212 + + if isinstance(layer_output, tuple): + results.append(layer_output[0].detach().cpu().numpy()) + else: + results.append(layer_output.detach().cpu().numpy()) + + results_array = np.concatenate(results) + return results_array diff --git a/art/estimators/classification/pytorch.py b/art/estimators/classification/pytorch.py index dc03c3677d..385ad7c58e 100644 --- a/art/estimators/classification/pytorch.py +++ b/art/estimators/classification/pytorch.py @@ -1047,9 +1047,10 @@ def save(self, filename: str, path: Optional[str] = None) -> None: # pylint: disable=W0212 # disable pylint because access to _modules required torch.save(self._model._model.state_dict(), full_path + ".model") - torch.save(self._optimizer.state_dict(), full_path + ".optimizer") # type: ignore + if self._optimizer is not None: + torch.save(self._optimizer.state_dict(), full_path + ".optimizer") # type: ignore + logger.info("Optimizer state dict saved in path: %s.", full_path + ".optimizer") logger.info("Model state dict saved in path: %s.", full_path + ".model") - logger.info("Optimizer state dict saved in path: %s.", full_path + ".optimizer") def __getstate__(self) -> Dict[str, Any]: """ @@ -1094,7 +1095,8 @@ def __setstate__(self, state: Dict[str, Any]) -> None: self._model.to(self._device) # Recover optimizer - self._optimizer.load_state_dict(torch.load(str(full_path) + ".optimizer")) # type: ignore + if os.path.isfile(str(full_path) + ".optimizer"): + self._optimizer.load_state_dict(torch.load(str(full_path) + ".optimizer")) # type: ignore self.__dict__.pop("model_name", None) self.__dict__.pop("inner_model", None) diff --git a/art/estimators/classification/tensorflow.py b/art/estimators/classification/tensorflow.py index 24ca4e0e13..8dca946cbd 100644 --- a/art/estimators/classification/tensorflow.py +++ b/art/estimators/classification/tensorflow.py @@ -957,8 +957,9 @@ def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: in shape (nb_samples,). :param batch_size: Size of batches. :param nb_epochs: Number of epochs to use for training. - :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for - TensorFlow and providing it takes no effect. + :param kwargs: Dictionary of framework-specific arguments. This parameter currently only supports + "scheduler" which is an optional function that will be called at the end of every + epoch to adjust the learning rate. """ import tensorflow as tf @@ -985,6 +986,8 @@ def train_step(model, images, labels): else: train_step = self._train_step + scheduler = kwargs.get("scheduler") + y = check_and_transform_label_format(y, nb_classes=self.nb_classes) # Apply preprocessing @@ -996,10 +999,13 @@ def train_step(model, images, labels): train_ds = tf.data.Dataset.from_tensor_slices((x_preprocessed, y_preprocessed)).shuffle(10000).batch(batch_size) - for _ in range(nb_epochs): + for epoch in range(nb_epochs): for images, labels in train_ds: train_step(self.model, images, labels) + if scheduler is not None: + scheduler(epoch) + def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwargs) -> None: """ Fit the classifier using the generator that yields batches as specified. @@ -1007,8 +1013,9 @@ def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwarg :param generator: Batch generator providing `(x, y)` for each epoch. If the generator can be used for native training in TensorFlow, it will. :param nb_epochs: Number of epochs to use for training. - :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for - TensorFlow and providing it takes no effect. + :param kwargs: Dictionary of framework-specific arguments. This parameter currently only supports + "scheduler" which is an optional function that will be called at the end of every + epoch to adjust the learning rate. """ import tensorflow as tf from art.data_generators import TensorFlowV2DataGenerator @@ -1036,6 +1043,8 @@ def train_step(model, images, labels): else: train_step = self._train_step + scheduler = kwargs.get("scheduler") + # Train directly in TensorFlow from art.preprocessing.standardisation_mean_std.tensorflow import StandardisationMeanStdTensorFlow @@ -1050,11 +1059,14 @@ def train_step(model, images, labels): == (0, 1) ) ): - for _ in range(nb_epochs): + for epoch in range(nb_epochs): for i_batch, o_batch in generator.iterator: if self._reduce_labels: o_batch = tf.math.argmax(o_batch, axis=1) train_step(self._model, i_batch, o_batch) + + if scheduler is not None: + scheduler(epoch) else: # Fit a generic data generator through the API super().fit_generator(generator, nb_epochs=nb_epochs) diff --git a/art/metrics/metrics.py b/art/metrics/metrics.py index f3c0edc91f..473c4bfae1 100644 --- a/art/metrics/metrics.py +++ b/art/metrics/metrics.py @@ -46,11 +46,11 @@ SUPPORTED_METHODS: Dict[str, Dict[str, Any]] = { "auto": { "class": AutoAttack, - "params": {"eps_step": 0.1, "eps_max": 1.0}, + "params": {"eps_step": 0.1}, }, "fgsm": { "class": FastGradientMethod, - "params": {"eps_step": 0.1, "eps_max": 1.0, "clip_min": 0.0, "clip_max": 1.0}, + "params": {"eps_step": 0.1}, }, "hsj": { "class": HopSkipJump, diff --git a/art/utils.py b/art/utils.py index b8e13d5fae..b409e8d15a 100644 --- a/art/utils.py +++ b/art/utils.py @@ -31,7 +31,7 @@ import zipfile from functools import wraps from inspect import signature -from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Callable, List, Dict, Optional, Tuple, Union import numpy as np import six @@ -1062,6 +1062,115 @@ def compute_accuracy(preds: np.ndarray, labels: np.ndarray, abstain: bool = True return acc_rate, coverage_rate +def intersection_over_union(bbox_1: np.ndarray, bbox_2: np.ndarray) -> float: + """ + Compute the intersection over union (IoU) of two bounding boxes. + Both bounding boxes are expected to be in torchvision format [x1, y1, x2, y2]. + + param bbox_1: Bounding box 1 in torchvision format [x1, y1, x2, y2]. + param bbox_2: Bounding box 2 in torchvision format [x1, y2, x2, y2]. + return: The intersection over union (IoU) of the two bounding boxes. + """ + # Calculate area of the intersection + x_1 = max(bbox_1[0], bbox_2[0]) + y_1 = max(bbox_1[1], bbox_2[1]) + x_2 = min(bbox_1[2], bbox_2[2]) + y_2 = min(bbox_1[3], bbox_2[3]) + intersection = max(0, x_2 - x_1 + 1) * max(0, y_2 - y_1 + 1) + + # Calculate the area of the union + bbox_1_area = (bbox_1[2] - bbox_1[0] + 1) * (bbox_1[3] - bbox_1[1] + 1) + bbox_2_area = (bbox_2[2] - bbox_2[0] + 1) * (bbox_2[3] - bbox_2[1] + 1) + union = bbox_1_area + bbox_2_area - intersection + + return intersection / union + + +def intersection_over_area(bbox_1: np.ndarray, bbox_2: np.ndarray) -> float: + """ + Compute the intersection over area (IoA) of two bounding boxes. + Both bounding boxes are expected to be in torchvision format [x1, y1, x2, y2]. + + param bbox_1: Bounding box 1 in torchvision format [x1, y1, x2, y2]. + param bbox_2: Bounding box 2 in torchvision format [x1, y2, x2, y2]. + return: The intersection over area (IoA) of the two bounding boxes. + """ + # Calculate area of the intersection + x_1 = max(bbox_1[0], bbox_2[0]) + y_1 = max(bbox_1[1], bbox_2[1]) + x_2 = min(bbox_1[2], bbox_2[2]) + y_2 = min(bbox_1[3], bbox_2[3]) + intersection = max(0, x_2 - x_1 + 1) * max(0, y_2 - y_1 + 1) + + # Calculate the area of bbox_1 + bbox_1_area = (bbox_1[2] - bbox_1[0] + 1) * (bbox_1[3] - bbox_1[1] + 1) + + return intersection / bbox_1_area + + +def non_maximum_suppression( + preds: Dict[str, np.ndarray], iou_threshold: float, confidence_threshold: Optional[float] = None +) -> Dict[str, np.ndarray]: + """ + Perform non-maximum suppression on the predicted object detection labels of a single image. + + :param preds: Predicted labels of format `Dict[str, np.ndarray]` for a single image. The fields of the Dict are + as follows: + + - boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H. + - labels [N]: the labels for each image. + - scores [N]: the scores of each prediction. + :param iou_threshold: The IoU threshold to discard overlapping bounding boxes. + :param confidence_threshold: The confidence threshold to discard bounding boxes. + return: Filtered predicted labels of the single image in the same format as the input. + """ + boxes = preds["boxes"] + labels = preds["labels"] + scores = preds["scores"] + + # Filter out bounding boxes below confidence threshold + if confidence_threshold is not None: + mask = scores >= confidence_threshold + boxes = boxes[mask] + labels = labels[mask] + scores = scores[mask] + + # Candidate bounding boxes + keep_indices = [] + indices = np.argsort(scores)[::-1] + + while len(indices) > 0: + # Get first bounding box + current_idx = indices[0] + box_1 = boxes[current_idx] + label_1 = labels[current_idx] + + keep_indices.append(current_idx) + remove_indices = [0] + + # Find overlapping bounding boxes above IoU threshold + for i, idx in enumerate(indices[1:], start=1): + box_2 = boxes[idx] + label_2 = labels[idx] + + if label_1 != label_2: + continue + + iou = intersection_over_union(box_1, box_2) + if iou >= iou_threshold: + remove_indices.append(i) + + # Remove overlapping bounding boxes from candidates + indices = np.delete(indices, remove_indices) + + filtered_preds = { + "boxes": boxes[keep_indices], + "labels": labels[keep_indices], + "scores": scores[keep_indices], + } + return filtered_preds + + # -------------------------------------------------------------------------------------------------- DATASET OPERATIONS diff --git a/conftest.py b/conftest.py index 0f8e5cb331..8fee0169eb 100644 --- a/conftest.py +++ b/conftest.py @@ -49,6 +49,7 @@ get_image_classifier_pt, get_image_classifier_pt_functional, get_image_classifier_tf, + get_image_classifier_hf, get_image_gan_tf_v2, get_image_generator_tf_v2, get_tabular_classifier_kr, @@ -65,7 +66,7 @@ logger = logging.getLogger(__name__) deep_learning_frameworks = [ - "keras", "tensorflow1", "tensorflow2", "tensorflow2v1", "pytorch", "kerastf", "mxnet", "jax" + "keras", "tensorflow1", "tensorflow2", "tensorflow2v1", "pytorch", "kerastf", "mxnet", "jax", "huggingface", ] non_deep_learning_frameworks = ["scikitlearn"] @@ -236,7 +237,7 @@ def _get_image_iterator(): dataset = tf.data.Dataset.from_tensor_slices((x_train_mnist, y_train_mnist)).batch(default_batch_size) return dataset - if framework == "pytorch": + if framework in ["pytorch", "huggingface"]: import torch # Create tensors from data @@ -288,7 +289,7 @@ def _image_data_generator(**kwargs): batch_size=default_batch_size, ) - if framework == "pytorch": + if framework in ["pytorch", "huggingface"]: data_generator = PyTorchDataGenerator( iterator=image_it, size=x_train_mnist.shape[0], batch_size=default_batch_size ) @@ -596,6 +597,10 @@ def _image_dl_estimator(functional=False, **kwargs): if wildcard is False and functional is False: classifier = get_image_classifier_mx_instance(**kwargs) + if framework == "huggingface": + if not wildcard: + classifier = get_image_classifier_hf(**kwargs) + if classifier is None: raise ARTTestFixtureNotImplemented( "no test deep learning estimator available", image_dl_estimator.__name__, framework @@ -779,7 +784,7 @@ def default_dataset_subset_sizes(): @pytest.fixture() def mnist_shape(framework): - if framework == "pytorch" or framework == "mxnet": + if framework in ["pytorch", "mxnet", "huggingface"]: return (1, 28, 28) else: return (28, 28, 1) diff --git a/examples/adversarial_training_awp.py b/examples/adversarial_training_awp.py new file mode 100644 index 0000000000..dcb4765087 --- /dev/null +++ b/examples/adversarial_training_awp.py @@ -0,0 +1,240 @@ +""" +This is an example of how to use ART for adversarial training of a model with AWP protocol +""" + +from PIL import Image +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision.transforms as transforms +from torch.utils.data import Dataset, DataLoader +from torch.optim.lr_scheduler import MultiStepLR + +from art.estimators.classification import PyTorchClassifier +from art.data_generators import PyTorchDataGenerator +from art.defences.trainer import AdversarialTrainerAWPPyTorch +from art.utils import load_cifar10 +from art.attacks.evasion import ProjectedGradientDescent + +""" +For this example we choose the PreActResNet18 model as used in the paper +(https://proceedings.neurips.cc/paper/2020/file/1ef91c212e30e14bf125e9374262401f-Paper.pdf) +The code for the model architecture has been adopted from +https://github.com/csdongxian/AWP/blob/main/AT_AWP/preactresnet.py +""" + + +class PreActBlock(nn.Module): + """Pre-activation version of the BasicBlock.""" + + expansion = 1 + + def __init__(self, in_planes, planes, stride=1): + super(PreActBlock, self).__init__() + self.bn1 = nn.BatchNorm2d(in_planes) + self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) + + if stride != 1 or in_planes != self.expansion * planes: + self.shortcut = nn.Sequential( + nn.Conv2d(in_planes, self.expansion * planes, kernel_size=1, stride=stride, bias=False) + ) + + def forward(self, x): + out = F.relu(self.bn1(x)) + shortcut = self.shortcut(out) if hasattr(self, "shortcut") else x + out = self.conv1(out) + out = self.conv2(F.relu(self.bn2(out))) + out += shortcut + return out + + +class PreActBottleneck(nn.Module): + """Pre-activation version of the original Bottleneck module.""" + + expansion = 4 + + def __init__(self, in_planes, planes, stride=1): + super(PreActBottleneck, self).__init__() + self.bn1 = nn.BatchNorm2d(in_planes) + self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False) + self.bn2 = nn.BatchNorm2d(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) + self.bn3 = nn.BatchNorm2d(planes) + self.conv3 = nn.Conv2d(planes, self.expansion * planes, kernel_size=1, bias=False) + + if stride != 1 or in_planes != self.expansion * planes: + self.shortcut = nn.Sequential( + nn.Conv2d(in_planes, self.expansion * planes, kernel_size=1, stride=stride, bias=False) + ) + + def forward(self, x): + out = F.relu(self.bn1(x)) + shortcut = self.shortcut(out) if hasattr(self, "shortcut") else x + out = self.conv1(out) + out = self.conv2(F.relu(self.bn2(out))) + out = self.conv3(F.relu(self.bn3(out))) + out += shortcut + return out + + +class PreActResNet(nn.Module): + def __init__(self, block, num_blocks, num_classes=10): + super(PreActResNet, self).__init__() + self.in_planes = 64 + + self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) + self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) + self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) + self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) + self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) + self.bn = nn.BatchNorm2d(512 * block.expansion) + self.linear = nn.Linear(512 * block.expansion, num_classes) + + def _make_layer(self, block, planes, num_blocks, stride): + strides = [stride] + [1] * (num_blocks - 1) + layers = [] + for stride in strides: + layers.append(block(self.in_planes, planes, stride)) + self.in_planes = planes * block.expansion + return nn.Sequential(*layers) + + def forward(self, x): + out = self.conv1(x) + out = self.layer1(out) + out = self.layer2(out) + out = self.layer3(out) + out = self.layer4(out) + out = F.relu(self.bn(out)) + out = F.avg_pool2d(out, 4) + out = out.view(out.size(0), -1) + out = self.linear(out) + return out + + +def PreActResNet18(num_classes=10): + return PreActResNet(PreActBlock, [2, 2, 2, 2], num_classes=num_classes) + + +class CIFAR10_dataset(Dataset): + def __init__(self, data, targets, transform=None): + self.data = data + self.targets = torch.LongTensor(targets) + self.transform = transform + + def __getitem__(self, index): + x = Image.fromarray(((self.data[index] * 255).round()).astype(np.uint8).transpose(1, 2, 0)) + x = self.transform(x) + y = self.targets[index] + return x, y + + def __len__(self): + return len(self.data) + + +# Step 1: Load the CIFAR10 dataset +(x_train, y_train), (x_test, y_test), min_pixel_value, max_pixel_value = load_cifar10() + +cifar_mu = np.ones((3, 32, 32)) +cifar_mu[0, :, :] = 0.4914 +cifar_mu[1, :, :] = 0.4822 +cifar_mu[2, :, :] = 0.4465 + +cifar_std = np.ones((3, 32, 32)) +cifar_std[0, :, :] = 0.2471 +cifar_std[1, :, :] = 0.2435 +cifar_std[2, :, :] = 0.2616 + +x_train = x_train.transpose(0, 3, 1, 2).astype("float32") +x_test = x_test.transpose(0, 3, 1, 2).astype("float32") + +transform = transforms.Compose( + [transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor()] +) + +dataset = CIFAR10_dataset(x_train, y_train, transform=transform) +dataloader = DataLoader(dataset, batch_size=128, shuffle=True) + +# Step 2: create the PyTorch model +model = PreActResNet18() +opt = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4) +lr_scheduler = MultiStepLR(opt, milestones=[100, 150], gamma=0.1) + +proxy_model = PreActResNet18() +gamma = 0.01 +warmup = 0 +proxy_opt = torch.optim.SGD(proxy_model.parameters(), lr=gamma) + +criterion = nn.CrossEntropyLoss() + +# Step 3: Create the ART classifier + +classifier = PyTorchClassifier( + model=model, + clip_values=(min_pixel_value, max_pixel_value), + preprocessing=(cifar_mu, cifar_std), + loss=criterion, + optimizer=opt, + input_shape=(3, 32, 32), + nb_classes=10, +) + +proxy_classifier = PyTorchClassifier( + model=proxy_model, + clip_values=(min_pixel_value, max_pixel_value), + preprocessing=(cifar_mu, cifar_std), + loss=criterion, + optimizer=proxy_opt, + input_shape=(3, 32, 32), + nb_classes=10, +) + +attack = ProjectedGradientDescent( + classifier, + norm=np.inf, + eps=8.0 / 255.0, + eps_step=2.0 / 255.0, + max_iter=10, + targeted=False, + num_random_init=1, + batch_size=128, + verbose=False, +) + +# Step 4: Create the trainer object - AdversarialTrainerAWPPyTorch +trainer = AdversarialTrainerAWPPyTorch( + classifier, proxy_classifier, attack, mode="PGD", gamma=gamma, beta=6.0, warmup=warmup +) + + +# Build a PyTorch data generator in ART +art_datagen = PyTorchDataGenerator(iterator=dataloader, size=x_train.shape[0], batch_size=128) + +# Step 5: fit the trainer +trainer.fit_generator(art_datagen, validation_data=(x_test, y_test), nb_epochs=200, scheduler=lr_scheduler) + +x_test_pred = np.argmax(classifier.predict(x_test), axis=1) +print( + "Accuracy on benign test samples after adversarial training: %.2f%%" + % (np.sum(x_test_pred == np.argmax(y_test, axis=1)) / x_test.shape[0] * 100) +) + +attack_test = ProjectedGradientDescent( + classifier, + norm=np.inf, + eps=8.0 / 255.0, + eps_step=2.0 / 255.0, + max_iter=20, + targeted=False, + num_random_init=1, + batch_size=128, + verbose=False, +) +x_test_attack = attack_test.generate(x_test, y=y_test) +x_test_attack_pred = np.argmax(classifier.predict(x_test_attack), axis=1) +print( + "Accuracy on original PGD adversarial samples after adversarial training: %.2f%%" + % (np.sum(x_test_attack_pred == np.argmax(y_test, axis=1)) / x_test.shape[0] * 100) +) diff --git a/notebooks/README.md b/notebooks/README.md index 7ab184e397..95806cbf65 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -296,6 +296,9 @@ demonstrates using interval bound propagation for certification of neural networ

+[smoothed_vision_transformers.ipynb](smoothed_vision_transformers.ipynb) [[on nbviewer](https://nbviewer.jupyter.org/github/Trusted-AI/adversarial-robustness-toolbox/blob/main/notebooks/smoothed_vision_transformers.ipynb)] +Demonstrates training a neural network using smoothed vision transformers for certified performance against patch attacks. + ## MNIST [fabric_for_deep_learning_adversarial_samples_fashion_mnist.ipynb](fabric_for_deep_learning_adversarial_samples_fashion_mnist.ipynb) [[on nbviewer](https://nbviewer.jupyter.org/github/Trusted-AI/adversarial-robustness-toolbox/blob/main/notebooks/fabric_for_deep_learning_adversarial_samples_fashion_mnist.ipynb)] diff --git a/notebooks/attack_attribute_inference.ipynb b/notebooks/attack_attribute_inference.ipynb index b174bc636a..f099ab2e52 100644 --- a/notebooks/attack_attribute_inference.ipynb +++ b/notebooks/attack_attribute_inference.ipynb @@ -19,7 +19,6 @@ "metadata": {}, "source": [ "## Preliminaries\n", - "In order to mount a successful attribute inference attack, the attacked feature must be categorical, and with a relatively small number of possible values (preferably binary, but should at least be less then the number of label classes).\n", "\n", "In the case of the nursery dataset, the sensitive feature we want to infer is the 'social' feature. In the original dataset this is a categorical feature with 3 possible values. To make the attack more successful, we reduced this to two possible feature values by assigning the original value 'problematic' the new value 1, and the other original values were assigned the new value 0.\n", "\n", @@ -391,7 +390,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -405,7 +404,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.3" + "version": "3.9.6" } }, "nbformat": 4, diff --git a/notebooks/attack_attribute_inference_regressor.ipynb b/notebooks/attack_attribute_inference_regressor.ipynb index c50cae9ade..ee85c8b5b0 100644 --- a/notebooks/attack_attribute_inference_regressor.ipynb +++ b/notebooks/attack_attribute_inference_regressor.ipynb @@ -4,24 +4,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Running attribute inference attacks on Regression Models" + "# Running attribute inference attacks on regression models" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutorial we will show how to run black-box inference attacks on regression model. This will be demonstrated on the Nursery dataset (original dataset can be found here: https://archive.ics.uci.edu/ml/datasets/nursery). " + "In this tutorial we will show how to run a black-box attribute inference attack on a regression model. This will be demonstrated on the diabetes dataset from scikitlearn (https://scikit-learn.org/stable/datasets/toy_dataset.html#diabetes-dataset). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Preliminaries\n", - "In order to mount a successful attribute inference attack, the attacked feature must be categorical, and with a relatively small number of possible values (preferably binary).\n", - "\n", - "In the case of the diabetes dataset, the sensitive feature we want to infer is the 'sex' feature, which is a binary feature." + "## Attacking a categorical feature\n", + "We start by trying to infer the 'sex' feature, which is a binary feature." ] }, { @@ -33,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -41,6 +39,9 @@ "import sys\n", "sys.path.insert(0, os.path.abspath('..'))\n", "\n", + "import warnings\n", + "warnings.filterwarnings('ignore')\n", + "\n", "from art.utils import load_diabetes\n", "\n", "(x_train, y_train), (x_test, y_test), _, _ = load_diabetes(test_set=0.5)" @@ -55,14 +56,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Base model score: -0.04773984870966275\n" + "Base model score: -0.053305975661749994\n" ] } ], @@ -110,7 +111,7 @@ "# only attacked feature\n", "attack_x_test_feature = attack_x_test[:, attack_feature].copy().reshape(-1, 1)\n", "# training data without attacked feature\n", - "attack_x_test = np.delete(attack_x_test, attack_feature, 1)\n", + "x_test_for_attack = np.delete(attack_x_test, attack_feature, 1)\n", "\n", "bb_attack = AttributeInferenceBlackBox(art_regressor, attack_feature=attack_feature)\n", "\n", @@ -134,14 +135,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.5585585585585585\n" + "0.6126126126126126\n" ] } ], "source": [ "# get inferred values\n", "values = [-0.88085106, 1.]\n", - "inferred_train_bb = bb_attack.infer(attack_x_test, pred=attack_x_test_predictions, values=values)\n", + "inferred_train_bb = bb_attack.infer(x_test_for_attack, pred=attack_x_test_predictions, values=values)\n", "# check accuracy\n", "train_acc = np.sum(inferred_train_bb == np.around(attack_x_test_feature, decimals=8).reshape(1,-1)) / len(inferred_train_bb)\n", "print(train_acc)" @@ -151,20 +152,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This means that for 56% of the training set, the attacked feature is inferred correctly using this attack.\n", + "This means that for 74% of the training set, the attacked feature is inferred correctly using this attack.\n", "Now let's check the precision and recall:" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "(0.5483870967741935, 0.32075471698113206)\n" + "(0.5816326530612245, 0.9661016949152542)\n" ] } ], @@ -205,14 +206,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.5585585585585585\n" + "0.6666666666666666\n" ] } ], @@ -224,7 +225,7 @@ "# train attack model\n", "baseline_attack.fit(attack_x_train)\n", "# infer values\n", - "inferred_train_baseline = baseline_attack.infer(attack_x_test, values=values)\n", + "inferred_train_baseline = baseline_attack.infer(x_test_for_attack, values=values)\n", "# check accuracy\n", "baseline_train_acc = np.sum(inferred_train_baseline == np.around(attack_x_test_feature, decimals=8).reshape(1,-1)) / len(inferred_train_baseline)\n", "print(baseline_train_acc)" @@ -234,13 +235,85 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this case, the black-box attack does not do better than the baseline." + "In this case, the black-box attack does significantly better than the baseline." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attacking a numerical feature\n", + "Now we will try to infer the bmi level feature." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "54.80737471036833\n" + ] + } + ], + "source": [ + "attack_feature = 3 # bmi\n", + "\n", + "# only attacked feature\n", + "attack_x_test_feature = attack_x_test[:, attack_feature].copy().reshape(-1, 1)\n", + "# training data without attacked feature\n", + "x_test_for_attack = np.delete(attack_x_test, attack_feature, 1)\n", + "\n", + "bb_attack = AttributeInferenceBlackBox(art_regressor, attack_feature=attack_feature)\n", + "\n", + "# train attack model\n", + "bb_attack.fit(attack_x_train)\n", + "\n", + "inferred_train_bb = bb_attack.infer(x_test_for_attack, pred=attack_x_test_predictions)\n", + "# check MSE\n", + "train_acc = np.sum((attack_x_test_feature - inferred_train_bb) ** 2) / len(inferred_train_bb)\n", + "print(train_acc)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "67.66769489356126\n" + ] + } + ], + "source": [ + "baseline_attack = AttributeInferenceBaseline(attack_feature=attack_feature)\n", + "\n", + "# train attack model\n", + "baseline_attack.fit(attack_x_train)\n", + "# infer values\n", + "inferred_train_baseline = baseline_attack.infer(x_test_for_attack)\n", + "# check MSE\n", + "baseline_train_acc = np.sum((attack_x_test_feature - inferred_train_baseline) ** 2) / len(inferred_train_baseline)\n", + "print(baseline_train_acc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The attack succeeds better than the baseline (a lower MSE means higher accuracy)." ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -254,7 +327,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.3" + "version": "3.9.6" } }, "nbformat": 4, diff --git a/notebooks/attack_parallel_auto_attack.ipynb b/notebooks/attack_parallel_auto_attack.ipynb new file mode 100644 index 0000000000..e23485d463 --- /dev/null +++ b/notebooks/attack_parallel_auto_attack.ipynb @@ -0,0 +1,619 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Evaluating Robustness by executing AutoAttack in parallel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook demonstrates executing multiple attacks in parallel using AutoAttack. The __parallel__ mode of AutoAttack facilitates identifying attacks which are robust such that they successful fool an image classifier, and generate adversarial images which have fewer perturbations added, thus closer resembling the original image making it harder for it to be detected as malicious." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from art.attacks.evasion.auto_attack import AutoAttack\n", + "from art.estimators.classification.pytorch import PyTorchClassifier\n", + "import numpy as np\n", + "import os\n", + "from tests.utils import load_dataset\n", + "import torch\n", + "from art.attacks.evasion.projected_gradient_descent.projected_gradient_descent_pytorch import ProjectedGradientDescentPyTorch\n", + "from art.attacks.evasion.deepfool import DeepFool\n", + "from art.attacks.evasion.square_attack import SquareAttack\n", + "from tests.utils import get_cifar10_image_classifier_pt\n", + "import matplotlib.pyplot as plt\n", + "from art.utils import load_dataset\n", + "\n", + "import logging\n", + "logger = logging.getLogger()\n", + "logger.setLevel(logging.WARNING)\n", + "\n", + "(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset('cifar10')\n", + "i = 10\n", + "x_train = x_train[:i, :].transpose(0, 3, 1, 2).astype('float32')\n", + "x_test = x_test[:i, :].transpose(0, 3, 1, 2).astype('float32')\n", + "y_train = y_train[:i, :].astype('float32')\n", + "y_test = y_test[:i, :].astype('float32')\n", + "\n", + "labels = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example prediction using classifier" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbAAAAHrCAYAAACuB8geAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABIwElEQVR4nO3deXhU5fk+8Hv2yTrZNxJCWAUh2EalESooSFhqwa2IWIKCC4LK4hY3UNG4FZeq0NaWqBVRvAQtKirIUi2oIAioIIkBwpIFyDpJZn1/f/DLfB0TyDwhC4fcn+uaC+bMnTPvmXNmnpw5J8/RKaUUiIiINEbf0QMgIiJqCRYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwog7SrVs3TJkypcU/v3fvXowcORI2mw06nQ4rV65stbERaQELGHVKeXl50Ol0vpvVakXv3r0xc+ZMlJSUdPTwApKdnY2dO3fi8ccfxxtvvIHzzz+/o4dE1K6MHT0Aoo706KOPIi0tDfX19fjiiy+waNEifPTRR9i1axeCg4M7engnVVdXh02bNuGBBx7AzJkzO3o4RB2CBYw6tdGjR/v2XKZNm4bo6GgsXLgQ77//PiZOnNjkz9jtdoSEhLTnMBspKysDAERERDSbPRPGS9QW+BUi0S9ceumlAIDCwkIAwJQpUxAaGoqCggKMGTMGYWFhmDRpEgDA6/Xi+eefx7nnngur1Yr4+HjccsstKC8v95unUgoLFixAcnIygoODcckll+D7779v8vkLCgpQUFBwyjHOnz8fqampAIC7774bOp0O3bp18z2m0+nwww8/4LrrrkNkZCSGDBkCAHC73XjsscfQo0cPWCwWdOvWDffffz8cDoff/L1eL+bPn4+kpCTfeH/44YfTPmZH1Nq4B0b0Cw3FIzo62jfN7XYjKysLQ4YMwbPPPuv7avGWW25BXl4ebrjhBtxxxx0oLCzESy+9hG3btuHLL7+EyWQCADz88MNYsGABxowZgzFjxuDbb7/FyJEj4XQ6Gz3/8OHDAQD79u076RivvPJKREREYPbs2Zg4cSLGjBmD0NBQv8w111yDXr164YknnkDDFZOmTZuG1157DVdffTXmzp2Lr776Crm5ufjxxx+xYsUK38/m5OTg6aefxuWXX46srCx89913yMrKQn19fQteUaI2pIg6oSVLligAas2aNaqsrEwVFRWpZcuWqejoaBUUFKQOHjyolFIqOztbAVD33Xef38//97//VQDUm2++6Td99erVftNLS0uV2WxWY8eOVV6v15e7//77FQCVnZ3t9/OpqakqNTW12fEXFhYqAOqZZ57xmz5v3jwFQE2cONFv+vbt2xUANW3aNL/pd911lwKgPv/8c6WUUsXFxcpoNKrx48f75ebPn9/keIk6Er9CpE5txIgRiI2NRUpKCq699lqEhoZixYoV6NKli19u+vTpfveXL18Om82Gyy67DEePHvXdMjIyEBoainXr1gEA1qxZA6fTidtvvx06nc7387NmzWpyPPv27Tvl3legbr31Vr/7H330EQBgzpw5ftPnzp0LAPjwww8BAGvXroXb7cZtt93ml7v99ttPe0xErY1fIVKn9vLLL6N3794wGo2Ij49Hnz59oNf7/15nNBqRnJzsN23v3r2orKxEXFxck/MtLS0FAOzfvx8A0KtXL7/HY2NjERkZ2VqL0UhaWprf/f3790Ov16Nnz55+0xMSEhAREeEbZ8O/v85FRUW16XiJWoIFjDq1Cy+8sNm/n7JYLI2KmtfrRVxcHN58880mfyY2NrbVxtgSQUFBTU7/5V4gkdaxgBG1QI8ePbBmzRoMHjz4pMUCgO9swb1796J79+6+6WVlZY3OVmxLqamp8Hq92Lt3L/r27eubXlJSgoqKCt84G/7Nz8/324s7duxYu46XKBA8BkbUAn/605/g8Xjw2GOPNXrM7XajoqICwIljbCaTCX/96199ZwMCwPPPP9/kfAM5jb4lxowZ0+TzLly4EAAwduxYACfOgjQajVi0aJFf7qWXXmr1MRGdLu6BEbXA0KFDccsttyA3Nxfbt2/HyJEjYTKZsHfvXixfvhwvvPACrr76asTGxuKuu+5Cbm4u/vCHP2DMmDHYtm0bPv74Y8TExDSabyCn0bfEwIEDkZ2djb///e+oqKjA0KFD8fXXX+O1117D+PHjcckllwAA4uPjceedd+Ivf/kL/vjHP2LUqFH47rvvfOPlV5B0JmEBI2qhxYsXIyMjA3/7299w//33w2g0olu3brj++usxePBgX27BggWwWq1YvHgx1q1bh0GDBuHTTz/17fW0l1dffRXdu3dHXl4eVqxYgYSEBOTk5GDevHl+uaeeegrBwcH4xz/+gTVr1iAzMxOffvophgwZAqvV2q5jJjoVnfrl9xpERE2oqKhAZGQkFixYgAceeKCjh0MEgMfAiOhX6urqGk1rOHY2bNiw9h0M0SnwK0Qi8vP2228jLy/P16Lqiy++wFtvvYWRI0f6fTVK1NFYwIjIT3p6OoxGI55++mlUVVX5TuxYsGBBRw+NyA+PgRERkSbxGBgREWkSCxgREWkSCxhRK9LpdJg/f35HD+OUGi7S2dpqamowbdo0JCQkQKfTnbTjPlFrYQGjdldYWIiZM2eid+/eCA4ORnBwMPr164cZM2Zgx44dHT28NjVs2DDodLpmb6dbBGtrazF//nysX7++VcYdiCeeeAJ5eXmYPn063njjDfz5z39ut+emzolnIVK7WrVqFSZMmACj0YhJkyZh4MCB0Ov12L17N9577z0sWrQIhYWFvqayZ5sHHngA06ZN893/5ptv8OKLL+L+++/3a7Kbnp5+Ws9TW1uLRx55BED7/e3W559/jt/97neNOnsQtRUWMGo3BQUFuPbaa5Gamoq1a9ciMTHR7/GnnnoKr7zySqNLl/ya3W5HSEhIWw61zVx22WV+961WK1588UVcdtllpyw0Wljm0tJS9OvXr9lcfX09zGZzs+uZqDncgqjdPP3007Db7ViyZEmj4gWcuHDkHXfcgZSUFN+0huM1BQUFGDNmDMLCwjBp0iQAJz7U586di5SUFFgsFvTp0wfPPvusX9f3ffv2QafTIS8vr9Hz/fqruvnz50On0yE/Px9TpkxBREQEbDYbbrjhBtTW1vr9rMPhwOzZsxEbG4uwsDD88Y9/xMGDB0/zFfIfxw8//IDrrrsOkZGRGDJkCIATe1NNFbopU6agW7duvmVuuB7ZI488ctKvJQ8dOoTx48cjNDTU13TY4/H4ZY4cOYLdu3fD5XKddLzr16+HTqdDYWEhPvzwQ9/z7du3z/fYsmXL8OCDD6JLly4IDg5GVVUVgBNXts7IyEBQUBBiYmJw/fXX49ChQ42eY/ny5ejXrx+sViv69++PFStW+C0zdU7cA6N2s2rVKvTs2RODBg0S/Zzb7UZWVhaGDBmCZ599FsHBwVBK4Y9//CPWrVuHqVOn4rzzzsMnn3yCu+++G4cOHcJzzz3X4nH+6U9/QlpaGnJzc/Htt9/i1VdfRVxcHJ566ilfZtq0afj3v/+N6667DhdddBE+//zzVm/Oe80116BXr1544oknIPlzzdjYWCxatAjTp0/HFVdcgSuvvBKA/9eSHo8HWVlZGDRoEJ599lmsWbMGf/nLX9CjRw9Mnz7dl8vJycFrr72GwsLCkxaLvn374o033sDs2bORnJyMuXPn+sbR0FX/scceg9lsxl133QWHwwGz2Yy8vDzccMMNuOCCC5Cbm4uSkhK88MIL+PLLL7Ft2zZEREQAAD788ENMmDABAwYMQG5uLsrLyzF16lR06dJF8GrSWUkRtYPKykoFQI0fP77RY+Xl5aqsrMx3q62t9T2WnZ2tAKj77rvP72dWrlypAKgFCxb4Tb/66quVTqdT+fn5SimlCgsLFQC1ZMmSRs8LQM2bN893f968eQqAuvHGG/1yV1xxhYqOjvbd3759uwKgbrvtNr/cdddd12iezVm+fLkCoNatW9doHBMnTmyUHzp0qBo6dGij6dnZ2So1NdV3v6ys7KRjaXhNH330Ub/pv/nNb1RGRkaT2cLCwmaXJTU1VY0dO9Zv2rp16xQA1b17d7/16nQ6VVxcnOrfv7+qq6vzTV+1apUCoB5++GHftAEDBqjk5GRVXV3tm7Z+/XoFwG+ZqfPhV4jULhq+Mmrq9O1hw4YhNjbWd3v55ZcbZX65VwAAH330EQwGA+644w6/6XPnzoVSCh9//HGLx3rrrbf63f/973+PY8eO+Zbho48+AoBGz93ap43/ehytranl/Pnnn/2m5eXlQSl12l/VZWdn+125esuWLSgtLcVtt93md4mWsWPH4pxzzsGHH34IADh8+DB27tyJyZMn+207Q4cOxYABA05rTKR9LGDULsLCwgCc+FuhX/vb3/6Gzz77DP/+97+b/Fmj0Yjk5GS/afv370dSUpJvvg0azuTbv39/i8fatWtXv/uRkZEAgPLyct+89Xo9evTo4Zfr06dPi5+zKWlpaa06v1+yWq2+42QNIiMjfcvY2n69LA3rp6nX7JxzzvE93vBvz549G+WamkadC4+BUbuw2WxITEzErl27Gj3WcEzsZFchtlgsLT5j7WRXEP71yQq/ZDAYmpyu2rlt6C/3WBrodLomx3Gq5WnKyZaxrTS1LESni3tg1G7Gjh2L/Px8fP3116c9r9TUVBw+fBjV1dV+03fv3u17HPi/vaeKigq/3OnsoaWmpsLr9aKgoMBv+p49e1o8z0BFRkY2Whag8fKcrHCfKRrWT1Ov2Z49e3yPN/ybn5/fKNfUNOpcWMCo3dxzzz0IDg7GjTfeiJKSkkaPS/ZwxowZA4/Hg5deeslv+nPPPQedTofRo0cDAMLDwxETE4ONGzf65V555ZUWLMEJDfN+8cUX/aY3XPSxLfXo0QO7d+9GWVmZb9p3332HL7/80i8XHBwMoHHhlgrkNPqWOP/88xEXF4fFixfD4XD4pn/88cf48ccffWd0JiUloX///nj99df9vn7esGEDdu7c2apjIu3hV4jUbnr16oWlS5di4sSJ6NOnj68Th1IKhYWFWLp0KfR6faPjXU25/PLLcckll+CBBx7Avn37MHDgQHz66ad4//33MWvWLL/jU9OmTcOTTz6JadOm4fzzz8fGjRvx008/tXg5zjvvPEycOBGvvPIKKisrcdFFF2Ht2rXtskdw4403YuHChcjKysLUqVNRWlqKxYsX49xzz/WdZAKc+MquX79+ePvtt9G7d29ERUWhf//+6N+/v+j5AjmNviVMJhOeeuop3HDDDRg6dCgmTpzoO42+W7dumD17ti/7xBNPYNy4cRg8eDBuuOEGlJeX46WXXkL//v2bPKZKnQf3wKhdjRs3Djt37sR1112HTz/9FHfeeSdmz56N999/H2PHjsW3336La6+9ttn56PV6fPDBB5g1axZWrVqFWbNm4YcffsAzzzyDhQsX+mUffvhhTJ06Fe+++y7uueceeDye0zpLEQD+9a9/4Y477sDq1atxzz33wOVy+c6ca0t9+/bF66+/jsrKSsyZMwcffPAB3njjDfz2t79tlH311VfRpUsXzJ49GxMnTsS7777b5uOTmDJlCt5++204nU7ce++9+Nvf/oYrrrgCX3zxhe9vwIATv6y89dZbcDqduO+++/Dee+8hLy8Pffr08TuDkTofXtCSiDTpvPPOQ2xsLD777LOOHgp1EO6BEdEZzeVywe12+01bv349vvvuu3ZrVExnJu6BEdEZbd++fRgxYgSuv/56JCUlYffu3Vi8eDFsNht27dqF6Ojojh4idRCexEFEZ7TIyEhkZGTg1VdfRVlZGUJCQjB27Fg8+eSTLF6dHPfAiIhIk3gMjIiINOmM+wrR6/Xi8OHDCAsLO+O7CRARUetTSqG6uhpJSUmnbCN3xhWww4cP+13QkIiIOqeioqJTNjY44wpYQ3fxjAsHwWgMbHiVlbIO2ha9V5SPNMsOEyZHBovyMVGyfLRNdml5s94kyhssLWi8KmwOW15RKcq73LJ1EGGzifJ6j6xVksPpaD70C/X1srw1yCLKeyBr5ltXZxflw21hzYd+ScnG43TKXn+D8KNL2rw4NKTxZX+aExIsex8bTbI/wq53OEV5pRMeIdLLXlOnUzYetwr8G7V6hxMPvfhmo6tN/NoZV8AavjY0Go0BFzDpxmnQy76aNBpkH55mk2w8FpNsNVjNsoJkNsjyRossDwAwyJahTrgMer1sHViFy6CXfd5CB9kvQfDKnkC6jj3Cw9lej3Cbk24TwksN6iFbvwYIfykTfkYEteA9EGQ1i/ImkywvPaLS1gXMIByPpIA1aO4wUpudxPHyyy+jW7dusFqtGDRoUKt0ICciImrQJgXs7bffxpw5czBv3jx8++23GDhwILKyslBaWtoWT0dERJ1QmxSwhQsX4qabbsINN9yAfv36YfHixQgODsa//vWvRlmHw4Gqqiq/GxERUXNavYA5nU5s3boVI0aM+L8n0esxYsQIbNq0qVE+NzcXNpvNd+MZiEREFIhWL2BHjx6Fx+NBfHy83/T4+HgUFxc3yufk5KCystJ3Kyoqau0hERHRWajDz0K0WCywWGSnDBMREbX6HlhMTAwMBkOjS8aXlJQgISGhtZ+OiIg6qVYvYGazGRkZGVi7dq1vmtfrxdq1a5GZmdnaT0dERJ1Um3yFOGfOHGRnZ+P888/HhRdeiOeffx52ux033HBDWzwdERF1Qm1SwCZMmICysjI8/PDDKC4uxnnnnYfVq1c3OrHjVHbv/hG6UzRx/KWKo0dF44uSdXCBLlr2AzEeWdsdXVCcKG/3HhflazyyLgdKJ+sQAAC19bK2MrV1stZKLo+s88VRYZsAq1H2GrndsvEYhF0OpMeFa+tlraHcXtn60tXLrrullzW+gMsh2x6CjLL3ZI2wDdNxj7v50K8EB8tavOmELd50wo46CPDzs0Ftvaydl9slbP9lDHybdrgCe/3b7CSOmTNnYubMmW01eyIi6uR4PTAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItKkDr8e2MlYjTro9QH2sxNeTixV2NuwW7xNlI+LjRLlg6Q91HSyPn91jnpRvt4l60sHAEo4JnNQkOwJ3MJ+jl7ZMtiigkV5t0s2HrNJtrwejygOg1n2JnA4ZduEyy1bv8HC8RhDZK+PVTh/t07WK1KvZL0uAcAN2WskbNeJ0BDZNlpjrxXlXW5Zb8NAP54bVFdVBpx1ugJ7A3APjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINIkFjIiINOnM7YWo80CvC6wfWViYbDF6d4kU5aODDKK8ySvrM1dz3CnKe7yy3zvqat2ivN4sigMAwiNCRXmjsJddRWW1bP7CLTsqTNZnrrpK1lvPWS/L19XL+tIpYR++0BBZ/02Xs06U13tkK8BkkW0PHo/s9TEKGw86HLL5A4DZJHvj6L2y96WjplyUh0fWr9Mi+5iD2yvrF1lpD7w/qdMd2Ly5B0ZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJp0xvZCjLAYYNAHVl+DhH3UbCFBonxsuEmU93g9srwoDRiMwqZlAb6ODRxeeR84o7D5oFHJ+qh5HLJefMogW+bS0gpR3uOSrbXq2lpRvtYj648ZGhQuysMhG78BsvWl18n68BksVlG+zi7rNxpskr0+RiUbPwDU18vWWZ1L1gvRC9mYKmpkr1FFrex9XyPssVrvCvw96fawFyIREZ3FWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTztheiDE2K4wB9rMLM8l6A1qtsrzeIOtBFhQk67Xocsv60nmhE+WVkvVoc7rlfeA8TlkfNa+S5ZWwN6AymkX5aqddlPd4ZNtQbYC93RoE2guuQbVd9noeOi5bXpNeNp7wGtk26io+KsrXVcp6S3aN6SnKx8Uli/IAoAurFOUd5cdE+Zoa2TqrrJb1QjxaKes3uq9ItrweQ+DlxhtgL0rugRERkSaxgBERkSa1egGbP38+dDqd3+2cc85p7achIqJOrk2OgZ177rlYs2bN/z2J8FpRREREzWmTymI0GpGQkNAWsyYiIgLQRsfA9u7di6SkJHTv3h2TJk3CgQMHTpp1OByoqqryuxERETWn1QvYoEGDkJeXh9WrV2PRokUoLCzE73//e1RXVzeZz83Nhc1m891SUlJae0hERHQWavUCNnr0aFxzzTVIT09HVlYWPvroI1RUVOCdd95pMp+Tk4PKykrfraioqLWHREREZ6E2P7siIiICvXv3Rn5+fpOPWywWWCyWth4GERGdZdr878BqampQUFCAxMTEtn4qIiLqRFq9gN11113YsGED9u3bh//973+44oorYDAYMHHixNZ+KiIi6sRa/SvEgwcPYuLEiTh27BhiY2MxZMgQbN68GbGxsaL5JMQEw2wMrN9cuNktmndosKxPnk7Ytw+Q9RLUKVmfOUedrA+cXtg7MTrMJsoDQEiIVZSvqpT1vrOFh4vy1fWydbb/kGw8NQ5ZL0SzbBWjS7DsrWk0CfvYHasQ5R1Ktrwmnew9YAsPE+Uv6ne+KF91RNZvVNXK+4HaYkyivKNWto5ramT7GxaTbDwpCbJ1EBcXL8qXVAXem9Ht8eLAroPN5lq9gC1btqy1Z0lERNQIeyESEZEmsYAREZEmsYAREZEmsYAREZEmsYAREZEmsYAREZEmsYAREZEmsYAREZEmsYAREZEmsYAREZEmtfnlVFoqMjQIFlNg/deMzgrRvC0m2WIHW4JFeUedrA+fyyvr5RgRESnKKyXr6+b0yH+vcbkC73MGAMGhoaL84TKHKF+wv1KUL6uWrYNaWRypQbJeguN/f54on5woez3f3fqzKL8pv1iUd3udorxRL9tGqyvKRPnaGtn2ExYm6yMIAPDIeo5arbLnMFtl21CwTjZ/t0e2UXdNSRLlw443fVHjpjhdHmwMoBci98CIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTztheiLGRUbCaAxte3XFZHz69TrbYNbWy3oZ1TllPMaNO1uOs1uUR5aW/pdS5ZH3sACAiMlyUd3pkve9+PnhYlD9eJXuNlNEsyhsMslc13CobT5wx8L5xAGA9Luv11ys8QZQ/EiVb3pKKUlHeUSvb5rb99JMor3d7RXlXiGx7BgDY4mV5vexzyGaT9WQN88reY/VO2eecclaJ8t1iQwRjCewzlHtgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSSxgRESkSWdsM9+I6BgEWUwBZSNDg0Tz1usDm2+DiqpyUd5lrxHl9R5Zo1cvZI1JlUm2mkNDraI8ALgg+5kff5Y1Y7U77KK81WqR5QNsHN0gKETWWDXSIGvwvDW/RJR3O2Xjd9hkzXxjI2XrVwdZM1yXW9aQu9ZZJ8rba2WNbZ1u2foCAJ20CbZOFjfpZT+g9LIm4SajbBtyO2QNpJWggXegWe6BERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJrGAERGRJp2xvRChNwIB9izUmWS9DaUsVtn8gxEiyhuFv0fo9bK8S9g70RJkE+UB4GhxtShfe1TWX7J7lKwXn0PWWg9WYW/DPj26iPJ64YDcBtk2VyXs12k0VIryYWbZNh0d2UOU79GrqyhfeOAbUX73T4dEebNR1ucPAJSS9UB1u2Ufv3qjWZQ3mWXbkNcr+5zwCps56nSBf24FmuUeGBERaZK4gG3cuBGXX345kpKSoNPpsHLlSr/HlVJ4+OGHkZiYiKCgIIwYMQJ79+5trfESEREBaEEBs9vtGDhwIF5++eUmH3/66afx4osvYvHixfjqq68QEhKCrKws1NcLv9MhIiI6BfExsNGjR2P06NFNPqaUwvPPP48HH3wQ48aNAwC8/vrriI+Px8qVK3Httdee3miJiIj+v1Y9BlZYWIji4mKMGDHCN81ms2HQoEHYtGlTkz/jcDhQVVXldyMiImpOqxaw4uJiAEB8fLzf9Pj4eN9jv5abmwubzea7paSktOaQiIjoLNXhZyHm5OSgsrLSdysqKuroIRERkQa0agFLSEgAAJSUlPhNLykp8T32axaLBeHh4X43IiKi5rRqAUtLS0NCQgLWrl3rm1ZVVYWvvvoKmZmZrflURETUyYnPQqypqUF+fr7vfmFhIbZv346oqCh07doVs2bNwoIFC9CrVy+kpaXhoYceQlJSEsaPH9+a4yYiok5OXMC2bNmCSy65xHd/zpw5AIDs7Gzk5eXhnnvugd1ux80334yKigoMGTIEq1evhtUqawVERER0KuICNmzYMCilTvq4TqfDo48+ikcfffS0BlZf7wZUYL22dK464dzdorTdLju13+mSfTPr1suKe02trO9glTDfJUXeIlO5Zc+RGiPro9YjSdbXrbZeNv8uvQeK8mYl+8P88kqXKB8UES3K45hBFE9JSBTlK+x2Ub77Ob1E+fBIWS/K8Mi+onx5mWz7LK+U9YoEAJOwX6ReWUR5l9cjygtbG8Ljkn0u6mVvsVPWjZZmO/wsRCIiopZgASMiIk1iASMiIk1iASMiIk1iASMiIk1iASMiIk1iASMiIk1iASMiIk1iASMiIk1iASMiIk1iASMiIk2SN71rJx6dBx5dYPVVeWQ9vCQ9uQAgyBokyoeGyfq6HS6T9XIsPFgmyhtNsuU1lxwW5QGgvkQ2pl5xst6Gw4fJeusVHDouyod1iRXlY6Kbvr7dyZSWlTQf+oWICGFfPa/s9TTrZb0TS8sOifJGa4UoX1ZxRJQ/dKRGlDeZZO/JiHBhI0EAdXWy95kyyvYfdMLmg15h70S9TjZ/nV42fo/s5QkI98CIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTztheiDZbCIKs5oCybqOsF2JNTb0or1yynmKV1ZWi/P4Dsj55NTWyPnBBVtnvKUcKq0R5AIgPcF016NIlVZSPSEoT5U3Vwl52VlkvweSBF8pmXyzrJRjklvWW9EC2TdvtsnxisKxXpNMje/11IaGifHJIkigfFiHrXVl9rFiUB4DSkmOivEsn2+bqnQ5RHnpZ88EQi1WUd9YJ+1GaA19eDwLry8g9MCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0iQWMCIi0qQzthdiTeVxuOsD651ldFaL5m3SCeu2QRY3GmQ/UFsj650YGRYiykeEyHqc1ZXLeyHGJUWL8l3Sh4ryuw46Rfmf8mX5ixKjRPmKCtn843sMFOX1qBXlnQ5Z78QIJetVWFUq6/MX5HSJ8olRwtffYxHlTemRonxdxRFRHgC+/OgDUf5gkWydGQS9BE8IrJ9ggzpZ60S4hPs/elfg20S9K7D+ttwDIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTWIBIyIiTTpjeyHqdYAhwFZenroa0byVsEeYHoH15Wrg0cl6IZbL2sahqkrWtEw5ZH37Em2yXosAcMEll4jyyX1+J8q/t+RfonxCSKgob3DWifKHfi4Q5RO69xPlrdE9RfkQJesHWnu8VJQP8sp6CTrrZL0cj1bL8hGxaaJ8dEI3Ub6uJlyUBwC98Ec85npRXqeXfW65XLL3vc7tkeWVLO92B15uXJ7APuO4B0ZERJokLmAbN27E5ZdfjqSkJOh0OqxcudLv8SlTpkCn0/ndRo0a1VrjJSIiAtCCAma32zFw4EC8/PLLJ82MGjUKR44c8d3eeuut0xokERHRr4mPgY0ePRqjR48+ZcZisSAhIaHFgyIiImpOmxwDW79+PeLi4tCnTx9Mnz4dx46d/GJ4DocDVVVVfjciIqLmtHoBGzVqFF5//XWsXbsWTz31FDZs2IDRo0fD42n6jJXc3FzYbDbfLSUlpbWHREREZ6FWP43+2muv9f1/wIABSE9PR48ePbB+/XoMHz68UT4nJwdz5szx3a+qqmIRIyKiZrX5afTdu3dHTEwM8vPzm3zcYrEgPDzc70ZERNScNi9gBw8exLFjx5CYmNjWT0VERJ2I+CvEmpoav72pwsJCbN++HVFRUYiKisIjjzyCq666CgkJCSgoKMA999yDnj17Iisrq1UHTkREnZu4gG3ZsgWX/KJtUMPxq+zsbCxatAg7duzAa6+9hoqKCiQlJWHkyJF47LHHYLFYWm/URETU6YkL2LBhw6DUyftUffLJJ6c1oAY6deIWCI9L1kxQp5d9c2oUftGq6oTj8crmHxUdLMonBMt6Of72/N6iPAD0vUjW27C8VNa/0uKuFOW7JyeL8l7hSkiIixXl3fWydVBbIetj53TL5u+qk731PZD1liw4dFCU37lriyh/0e9kr090QrQoX1Ut6xUJACbZ2xIx3WQ9R73Czy2PU9irUNgztbKsQpR3VAf+AjlcgY2dvRCJiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWMCIiEiTWv2Clq3F6/bAawisvtY5ZH3szCGyvm5Go0mUN+hlPcV6JkSK8tYg2e8d3VJlFwgdOOSS5kO/ktgnXZTfvmmJKN81RfYaJZw7QJQ3x/YQ5Y3BNlG+tl7W+7GuqlqULzlcJMqXl8h6FXpctaJ8UJhVlI+Jkb3Hig5vE+XjE7uI8u5a2foCAFXnEOV19nJR3qPqRHkVaDPZ/y/IIlsH5gRZvsqiCzhb7wwsyz0wIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSJBYwIiLSpDO2F6LJYITJENjwyqtlfdo89YH35AKAoOAgUd6gl/Ugi4sOFuWLjlSI8j1+O0qUTx4gy58g61XoqraL8rYwWe/B2N7nifJ2Y5Qo//22b0R5R51seauqKkT5o4cOiPIGj6xfp9Uq+6jokibrPZjeu6co7zaEiPImQ4Qsb3aJ8gBgrK8X5Wv3HxLlvW6PKO8W7p7UGAyifHC0bB3EJ0UHnK2rD2xZuQdGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESaxAJGRESadMb2QnTWO6D3BtYPK9giWwydVdbzy6R3i/LKI8sHhcrG88cJfxTlLxo9XJQPj4kX5QGg5OcfRXmD8DWtqK4U5cv27RHlD1fL+sytX7lSlA8NMony9Y4aUT4hXtYrMjxM1seu8GCRKO8Urt+opG6ifO8BGaI8PBZR/HjFQdn8AdQKe6yW18leI52Sfc7V13lF+Rol6+GqamS9H/tGBJ6tD7BVJ/fAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk1jAiIhIk87YXohe5YRXBdjLK8CeiQ10blmPMLdyyeavk/UUs1rCRfnzMmR94CwmWR++H7ZvE+UBoPxwgSjvcMj6qFWXHxfli/J/EOVrVJAob/LIxh9qlPW7DLfKehXGRsp6IR4pKRbl3S7Ze6C2WtbLsajwgCgPfC9K19RUi/JWo+w9DABuS5wof8wte98HBVlF+eAw2TYdZJT1i6yurRLl3d7Aez+6A/xM5x4YERFpkqiA5ebm4oILLkBYWBji4uIwfvx47Nnj3/W7vr4eM2bMQHR0NEJDQ3HVVVehpKSkVQdNREQkKmAbNmzAjBkzsHnzZnz22WdwuVwYOXIk7Ha7LzN79mz85z//wfLly7FhwwYcPnwYV155ZasPnIiIOjfRMbDVq1f73c/Ly0NcXBy2bt2Kiy++GJWVlfjnP/+JpUuX4tJLLwUALFmyBH379sXmzZvxu9/9rvVGTkREndppHQOrrDxxkcGoqCgAwNatW+FyuTBixAhf5pxzzkHXrl2xadOmJufhcDhQVVXldyMiImpOiwuY1+vFrFmzMHjwYPTv3x8AUFxcDLPZjIiICL9sfHw8ioubPuspNzcXNpvNd0tJSWnpkIiIqBNpcQGbMWMGdu3ahWXLlp3WAHJyclBZWem7FRXJLl1ORESdU4v+DmzmzJlYtWoVNm7ciOTkZN/0hIQEOJ1OVFRU+O2FlZSUICEhocl5WSwWWCyyvz8gIiIS7YEppTBz5kysWLECn3/+OdLS0vwez8jIgMlkwtq1a33T9uzZgwMHDiAzM7N1RkxERAThHtiMGTOwdOlSvP/++wgLC/Md17LZbAgKCoLNZsPUqVMxZ84cREVFITw8HLfffjsyMzN5BiIREbUqUQFbtGgRAGDYsGF+05csWYIpU6YAAJ577jno9XpcddVVcDgcyMrKwiuvvNIqgyUiImqgU0rJm361oaqqKthsNuTeOARWc2D19fjBfaLnMAdFiPIet6wPnAuB9/wCgK49e8nmr5P1OIuKT2s+9AtxifIzQZ21laK8vbRQNv9jsl55XdO6ivIuk6z34E87d4nyddXlonxQsOy4sM4kOx/LXu8Q5RVkvRydSifK6yDr1xkaJOsj6HDXifIwyZYXADx62c8cqv5Z9gQhTlE82CI7xcHqleWDYBbl+6b3DjhbW+fChFs+QGVlJcLDT76u2QuRiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0iQWMiIg0qUXXA2sPXq8OXm9g/dTMRlkPMqvRKxuMXtbXTRlkffW8TlmvxaNHm7669cnUlMnyQa4qUR4AvMJeeVGR0aJ8RFKsKO/2yHr9HTose40UZC1E9XrZW83plvXTNOhkvQRDrMGivFv4ljFIf0Anez09TlnvTX2AnyUNqmplvSsBwGmR9VsMS5Jto/agClG+2ivrnVhvl+3PRId3F+Vj4gJ/z9vtgY2de2BERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJLGBERKRJZ2wvRL3OAr0usOFZLUGieSvI+syFBMn6xoWExYjyta56UT46zCzKG4XL66wsEeUBwKuXjanWJOuVFx+fJsp7nbI+cH3Sk0X5/61bK8o7Va0ob9LJevfV1cjmHx4WLsqbjbKPCoNOtn5r6mXvgcIjsl6FFRWy94BDZxflASC2t2x/oEuE7HPLqWTvsfKjsm3CXC/sp9lF1s+0rtYTeLYusCz3wIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJPO2F6IJqMOZmNg9bXW4RDN22ANEeW9BosoX+uqE+UNJiXKW8yyHmomk2x5zcE2UR4AbOGy5yguk/VbrO0i61UYl9JTlD9UelSUP/eCwaJ8TdlhUf7nn74X5e01FaK80SDbRm02We9EHWS9EI8ckr0+B/ZXivJ6i2z7DI+X9T8FgNgo4Wsk7P+oOy5bhshy2cd7l7goUT45QvaezP+hOOBsXb0roBz3wIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJPO2Ga+cdF6BFsDq6+uY8dE867zyBqN2u2iOJTeI8objbLVEB4eLcqbTSZRvs5eJcoDQJBJuCk5Zfkt//ufKN+9j6xZ8MGDgTcaBQC9XifKB1tk68AgbCAdFCRr9GqvkTXzrauT5d1upygfGiRb3ot+01uUt4bJGu26DW5RHgA8rlpRvq5I1sxXX20V5eOCw0T53/Q+Vzb/iHhRfuuRwoCz9c7AXn/ugRERkSaJClhubi4uuOAChIWFIS4uDuPHj8eePXv8MsOGDYNOp/O73Xrrra06aCIiIlEB27BhA2bMmIHNmzfjs88+g8vlwsiRI2H/1XdsN910E44cOeK7Pf300606aCIiItGBiNWrV/vdz8vLQ1xcHLZu3YqLL77YNz04OBgJCQkBzdPhcMDxiwtSVlXJj78QEVHnc1rHwCorT1wVNSrK/0qeb775JmJiYtC/f3/k5OSgtvbkBzdzc3Nhs9l8t5SUlNMZEhERdRItPgvR6/Vi1qxZGDx4MPr37++bft111yE1NRVJSUnYsWMH7r33XuzZswfvvfdek/PJycnBnDlzfPerqqpYxIiIqFktLmAzZszArl278MUXX/hNv/nmm33/HzBgABITEzF8+HAUFBSgR48ejeZjsVhgschOoSUiImrRV4gzZ87EqlWrsG7dOiQnJ58yO2jQIABAfn5+S56KiIioSaI9MKUUbr/9dqxYsQLr169HWlpasz+zfft2AEBiYmKLBkhERNQUUQGbMWMGli5divfffx9hYWEoLj7RvcBmsyEoKAgFBQVYunQpxowZg+joaOzYsQOzZ8/GxRdfjPT09DZZACIi6pxEBWzRokUATvyx8i8tWbIEU6ZMgdlsxpo1a/D888/DbrcjJSUFV111FR588MFWGzARERHQgq8QTyUlJQUbNmw4rQE1SE42IzQosP5xNp2sR1h+kaxnWUnZqZf715we2UkpoaGyc2nstZWivMdbI8obWnBo9HiZrB9ldY2s11y9S7bMBiXLh4VGivIlxcdF+YN2Wd87r5L1WoyPlfXH1Hldonx5RbkobwmRvQcibLK+fWaDbBt1OGX9SWGU9a4EALtDNiZnjew5Qryy+fdMCexvcRskJci2oaKDsn6jx8oC/9x1uAJbX+yFSEREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmtTiC1q2tfAIE0KDA+sVVifosQUAkXEG2WBCgkXxoyUOUb7e6RTljeZwUV44e3gD7EP2Sy6PbJkr62S99UKCZL316mtlvQfr6o+K8k7ha+QR5pWSbaM1VbL3QHh4kDBvE+Xr6mTjOXpMtj2EhoaI8jq97Hd1nVvW/xQAzEbZa2qRtXCF2SzbJrr17CbK19XKlnnjxh9E+R0/lQacdXu8AeW4B0ZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJrEAkZERJp0xvZCNFiNMFoDG5413Cyad1SorG4b62R9/kxBgfXxalBVLlwNHtn4g6xxstmbZOMHAI+jQpQ3B8uW2WSUrWODQda/0qFky+x0yRpMKqUT5XXCVnzKKev96JHFYTIG1pfUxyzrXVlRLuuFWOd0ifK2CFn/UKOwdyIA6IXbaC3conzJ0WpRvrxGNv9qe6Uov2b9blG+RNAe0+sN7A3APTAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItIkFjAiItKkM7YXor3GCJ03wP5rhlDRvENDZI3gTEGyxnQhFqsob7PJ+vDVVNUJ8yWyfK1HlAcAV73sZ8LM0aK81STrxed2yPpXGo2y3+XMwl/9TBaDKK/TyZ4gOFT2VtYL3/luj6yvnjlI9gThEbLelcePy/oCVgt7XYZHybZPAKh1y/pj7t13TJTfvbNIlI+PkvV/jE+WrQPoZa9pjC0s4KzH68X+8uY/p7kHRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmsQCRkREmnTG9kI8XAQEB9hS0FEh6z0YFivr62YNconyNllrRkRFyVZDjb1WlK+okOXLj5lF+RM/I8sbvLLegF4l60fp8Qj7OXpleelvfjq9TpQ3GGXbRJ1HNiIlewvA5JW9B9y1x0V5T51sG/UYZb0xK2pk83fK24HiuLBH6b582Zum4phdlHfaZQuRYEsQ5fumdhHlJS+Py+PFt/ua34a4B0ZERJokKmCLFi1Ceno6wsPDER4ejszMTHz88ce+x+vr6zFjxgxER0cjNDQUV111FUpKZJ3QiYiIAiEqYMnJyXjyySexdetWbNmyBZdeeinGjRuH77//HgAwe/Zs/Oc//8Hy5cuxYcMGHD58GFdeeWWbDJyIiDo30Rftl19+ud/9xx9/HIsWLcLmzZuRnJyMf/7zn1i6dCkuvfRSAMCSJUvQt29fbN68Gb/73e9ab9RERNTptfgYmMfjwbJly2C325GZmYmtW7fC5XJhxIgRvsw555yDrl27YtOmTSedj8PhQFVVld+NiIioOeICtnPnToSGhsJiseDWW2/FihUr0K9fPxQXF8NsNiMiIsIvHx8fj+Li4pPOLzc3FzabzXdLSUkRLwQREXU+4gLWp08fbN++HV999RWmT5+O7Oxs/PDDDy0eQE5ODiorK323oiLZZbOJiKhzEv8dmNlsRs+ePQEAGRkZ+Oabb/DCCy9gwoQJcDqdqKio8NsLKykpQULCyf++wGKxwGKxyEdORESd2mn/HZjX64XD4UBGRgZMJhPWrl3re2zPnj04cOAAMjMzT/dpiIiI/Ij2wHJycjB69Gh07doV1dXVWLp0KdavX49PPvkENpsNU6dOxZw5cxAVFYXw8HDcfvvtyMzM5BmIRETU6kQFrLS0FJMnT8aRI0dgs9mQnp6OTz75BJdddhkA4LnnnoNer8dVV10Fh8OBrKwsvPLKK20ycCIi6tx0SgmbzLWxqqoq2Gw2vPXk7Qi2BnZsrK6yVPQcIWEOUT7IclSUD4+W9b2LiJX1coRe1sjOXusV5SuOB4nyAFBxVNbbsM4uO/zqcQv7MyrZt+Net+w1qq+rF+XNZtn4DUbZ61ldLxt/XY1s/FblFOXD9LL5l9fK/nxmn0u2/Vh0so+5EJP8uHxtnew1Oni0WpQPCQkR5YdfPFCUT+/fU5Rf9/lmUf7g4ZqAsw6nG8++swaVlZUIDw8/aY69EImISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJPEl1Npaw2drWrrA2/LUifIAoDO5BLlvV5Z6yZ9rayVlNEuGw/0HlHcXidrM2SvE44HQK20lVG9rLWPcBVA+ruZuJWUQ/YaeZRsmzB4ZOu4ziF7PeudsvErJcsbhe3O6p2yvEO6PQhbSRmUrJUXADhcskE53bJ1bBLOX/IZCgA1dln7rzrhe8AhWMcNr2VznQ7PuF6IBw8e5FWZiYgIRUVFSE5OPunjZ1wB83q9OHz4MMLCwqDT/d9vrVVVVUhJSUFRUdEpmzueTTrbMnN5z25c3rNfay2zUgrV1dVISkqCXn/yb1POuK8Q9Xr9KStueHh4p9kYGnS2Zebynt24vGe/1lhmm83WbIYncRARkSaxgBERkSZppoBZLBbMmzcPFov8QnNa1dmWmct7duPynv3ae5nPuJM4iIiIAqGZPTAiIqJfYgEjIiJNYgEjIiJNYgEjIiJN0kwBe/nll9GtWzdYrVYMGjQIX3/9dUcPqU3Mnz8fOp3O73bOOed09LBa1caNG3H55ZcjKSkJOp0OK1eu9HtcKYWHH34YiYmJCAoKwogRI7B3796OGWwraG55p0yZ0midjxo1qmMGe5pyc3NxwQUXICwsDHFxcRg/fjz27Nnjl6mvr8eMGTMQHR2N0NBQXHXVVSgpKemgEZ++QJZ52LBhjdbxrbfe2kEjPj2LFi1Cenq674+VMzMz8fHHH/seb8/1q4kC9vbbb2POnDmYN28evv32WwwcOBBZWVkoLS3t6KG1iXPPPRdHjhzx3b744ouOHlKrstvtGDhwIF5++eUmH3/66afx4osvYvHixfjqq68QEhKCrKws1NfLmo2eKZpbXgAYNWqU3zp/66232nGErWfDhg2YMWMGNm/ejM8++wwulwsjR46E3W73ZWbPno3//Oc/WL58OTZs2IDDhw/jyiuv7MBRn55AlhkAbrrpJr91/PTTT3fQiE9PcnIynnzySWzduhVbtmzBpZdeinHjxuH7778H0M7rV2nAhRdeqGbMmOG77/F4VFJSksrNze3AUbWNefPmqYEDB3b0MNoNALVixQrffa/XqxISEtQzzzzjm1ZRUaEsFot66623OmCErevXy6uUUtnZ2WrcuHEdMp62VlpaqgCoDRs2KKVOrEuTyaSWL1/uy/z4448KgNq0aVNHDbNV/XqZlVJq6NCh6s477+y4QbWxyMhI9eqrr7b7+j3j98CcTie2bt2KESNG+Kbp9XqMGDECmzZt6sCRtZ29e/ciKSkJ3bt3x6RJk3DgwIGOHlK7KSwsRHFxsd/6ttlsGDRo0Fm7vgFg/fr1iIuLQ58+fTB9+nQcO3aso4fUKiorKwEAUVFRAICtW7fC5XL5rd9zzjkHXbt2PWvW76+XucGbb76JmJgY9O/fHzk5Oaitre2I4bUqj8eDZcuWwW63IzMzs93X7xnXzPfXjh49Co/Hg/j4eL/p8fHx2L17dweNqu0MGjQIeXl56NOnD44cOYJHHnkEv//977Fr1y6EhYV19PDaXHFxMQA0ub4bHjvbjBo1CldeeSXS0tJQUFCA+++/H6NHj8amTZtgMMivS3Wm8Hq9mDVrFgYPHoz+/fsDOLF+zWYzIiIi/LJny/ptapkB4LrrrkNqaiqSkpKwY8cO3HvvvdizZw/ee++9Dhxty+3cuROZmZmor69HaGgoVqxYgX79+mH79u3tun7P+ALW2YwePdr3//T0dAwaNAipqal45513MHXq1A4cGbWVa6+91vf/AQMGID09HT169MD69esxfPjwDhzZ6ZkxYwZ27dp11h3DPZWTLfPNN9/s+/+AAQOQmJiI4cOHo6CgAD169GjvYZ62Pn36YPv27aisrMS7776L7OxsbNiwod3HccZ/hRgTEwODwdDoLJaSkhIkJCR00KjaT0REBHr37o38/PyOHkq7aFinnXV9A0D37t0RExOj6XU+c+ZMrFq1CuvWrfO7PFJCQgKcTicqKir88mfD+j3ZMjdl0KBBAKDZdWw2m9GzZ09kZGQgNzcXAwcOxAsvvNDu6/eML2BmsxkZGRlYu3atb5rX68XatWuRmZnZgSNrHzU1NSgoKEBiYmJHD6VdpKWlISEhwW99V1VV4auvvuoU6xs4cVXyY8eOaXKdK6Uwc+ZMrFixAp9//jnS0tL8Hs/IyIDJZPJbv3v27MGBAwc0u36bW+ambN++HQA0uY6b4vV64XA42n/9tvppIW1g2bJlymKxqLy8PPXDDz+om2++WUVERKji4uKOHlqrmzt3rlq/fr0qLCxUX375pRoxYoSKiYlRpaWlHT20VlNdXa22bdumtm3bpgCohQsXqm3btqn9+/crpZR68sknVUREhHr//ffVjh071Lhx41RaWpqqq6vr4JG3zKmWt7q6Wt11111q06ZNqrCwUK1Zs0b99re/Vb169VL19fUdPXSx6dOnK5vNptavX6+OHDniu9XW1voyt956q+ratav6/PPP1ZYtW1RmZqbKzMzswFGfnuaWOT8/Xz366KNqy5YtqrCwUL3//vuqe/fu6uKLL+7gkbfMfffdpzZs2KAKCwvVjh071H333ad0Op369NNPlVLtu341UcCUUuqvf/2r6tq1qzKbzerCCy9Umzdv7ughtYkJEyaoxMREZTabVZcuXdSECRNUfn5+Rw+rVa1bt04BaHTLzs5WSp04lf6hhx5S8fHxymKxqOHDh6s9e/Z07KBPw6mWt7a2Vo0cOVLFxsYqk8mkUlNT1U033aTZX86aWk4AasmSJb5MXV2duu2221RkZKQKDg5WV1xxhTpy5EjHDfo0NbfMBw4cUBdffLGKiopSFotF9ezZU919992qsrKyYwfeQjfeeKNKTU1VZrNZxcbGquHDh/uKl1Ltu355ORUiItKkM/4YGBERUVNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIh+Yd++fdDpdL5edYHIy8trdPmIjhgHUWfDAkZnnaKiItx4441ISkqC2WxGamoq7rzzzoAuEpmSkoIjR474XcupORMmTMBPP/10OkNukWHDhmHWrFnt/rxEZwoWMDqr/Pzzzzj//POxd+9evPXWW8jPz8fixYt9Vy84fvz4SX/W6XTCYDAgISEBRmPgl8oLCgpCXFxcawyfiARYwOisMmPGDJjNZnz66acYOnQounbtitGjR2PNmjU4dOgQHnjgAV+2W7dueOyxxzB58mSEh4fj5ptvbvKruw8++AC9evWC1WrFJZdcgtdeew06nc53zaNff4U4f/58nHfeeXjjjTfQrVs32Gw2XHvttaiurvZlVq9ejSFDhiAiIgLR0dH4wx/+gIKCgtNa9m7dumHBggWYPHkyQkNDkZqaig8++ABlZWUYN24cQkNDkZ6eji1btvh+5tixY5g4cSK6dOmC4OBgDBgwAG+99ZbffKurqzFp0iSEhIQgMTERzz33XKO9P4fDgbvuugtdunRBSEgIBg0ahPXr15/W8hA1hwWMzhrHjx/HJ598gttuuw1BQUF+jyUkJGDSpEl4++238cv+1c8++ywGDhyIbdu24aGHHmo0z8LCQlx99dUYP348vvvuO9xyyy1+RfBkCgoKsHLlSqxatQqrVq3Chg0b8OSTT/oet9vtmDNnDrZs2YK1a9dCr9fjiiuugNfrPY1XAHjuuecwePBgbNu2DWPHjsWf//xnTJ48Gddffz2+/fZb9OjRA5MnT/a9BvX19cjIyMCHH36IXbt24eabb8af//xnfP311755zpkzB19++SU++OADfPbZZ/jvf/+Lb7/91u95Z86ciU2bNmHZsmXYsWMHrrnmGowaNQp79+49reUhOqU26XFP1AE2b96sAKgVK1Y0+fjChQsVAFVSUqKUUio1NVWNHz/eL1NYWKgAqG3btimllLr33ntV//79/TIPPPCAAqDKy8uVUkotWbJE2Ww23+Pz5s1TwcHBqqqqyjft7rvvVoMGDTrp2MvKyhQAtXPnzibH0ZShQ4eqO++803c/NTVVXX/99b77R44cUQDUQw895Ju2adMmBeCUl7cYO3asmjt3rlJKqaqqKmUymdTy5ct9j1dUVKjg4GDfc+/fv18ZDAZ16NAhv/kMHz5c5eTknPR5iE5X4F/0E2mEElwh6Pzzzz/l43v27MEFF1zgN+3CCy9sdr7dunVDWFiY735iYiJKS0t99/fu3YuHH34YX331FY4ePerb8zpw4IDoBJJfS09P9/0/Pj4eADBgwIBG00pLS5GQkACPx4MnnngC77zzDg4dOgSn0wmHw4Hg4GAAJ44pulwuv2W22Wzo06eP7/7OnTvh8XjQu3dvv7E4HA5ER0e3eFmImsMCRmeNnj17QqfT4ccff8QVV1zR6PEff/wRkZGRiI2N9U0LCQlpk7GYTCa/+zqdzu/rwcsvvxypqan4xz/+gaSkJHi9XvTv3x9Op7PVnlen0510WsNYnnnmGbzwwgt4/vnnMWDAAISEhGDWrFmicdTU1MBgMGDr1q0wGAx+j4WGhrZ4WYiaw2NgdNaIjo7GZZddhldeeQV1dXV+jxUXF+PNN9/EhAkTfB/igejTp4/fSQ8A8M0335zWOI8dO4Y9e/bgwQcfxPDhw9G3b1+Ul5ef1jxb6ssvv8S4ceNw/fXXY+DAgejevbvfnwR0794dJpPJb5krKyv9Mr/5zW/g8XhQWlqKnj17+t0SEhLadXmoc2EBo7PKSy+9BIfDgaysLGzcuBFFRUVYvXo1LrvsMnTp0gWPP/64aH633HILdu/ejXvvvRc//fQT3nnnHeTl5QGAqBD+UmRkJKKjo/H3v/8d+fn5+PzzzzFnzpwWzet09erVC5999hn+97//4ccff8Qtt9yCkpIS3+NhYWHIzs7G3XffjXXr1uH777/H1KlTodfrfcvfu3dvTJo0CZMnT8Z7772HwsJCfP3118jNzcWHH37YIctFnQMLGJ1VevXqhS1btqB79+7405/+hB49euDmm2/GJZdcgk2bNiEqKko0v7S0NLz77rt47733kJ6ejkWLFvnOQrRYLC0ao16vx7Jly7B161b0798fs2fPxjPPPNOieZ2uBx98EL/97W+RlZWFYcOGISEhAePHj/fLLFy4EJmZmfjDH/6AESNGYPDgwejbty+sVqsvs2TJEkyePBlz585Fnz59MH78eHzzzTfo2rVrOy8RdSY6JTniTUR4/PHHsXjxYhQVFXX0UDqE3W5Hly5d8Je//AVTp07t6OFQJ8aTOIia8corr+CCCy5AdHQ0vvzySzzzzDOYOXNmRw+r3Wzbtg27d+/GhRdeiMrKSjz66KMAgHHjxnXwyKizYwEjasbevXuxYMECHD9+HF27dsXcuXORk5PT0cNqV88++yz27NkDs9mMjIwM/Pe//0VMTExHD4s6OX6FSEREmsSTOIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJNYwIiISJP+H5smHraRV0fwAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "classifier = get_cifar10_image_classifier_pt()\n", + "preds = classifier.predict(x_train)\n", + "\n", + "f, ax = plt.subplots(1, 1, constrained_layout = False)\n", + "ax.set_title(f'Pred: {labels[np.argmax(y_train[0])]}\\nGround Truth: {labels[np.argmax(preds[[0]])]}')\n", + "ax.imshow(x_train[0].transpose(1,2,0))\n", + "ax.set_xlabel('Original Image')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a number of differently initialized attacks to test\n", + "- vary the max_iter values, all others fixed\n", + "- vary the eps_step values, all others fixed" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "max_iter values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\n", + "eps_step values: [0.001, 0.011, 0.021, 0.031, 0.041, 0.051, 0.061, 0.071, 0.081, 0.091, 0.101, 0.111, 0.121, 0.131, 0.141, 0.151, 0.161, 0.171, 0.181, 0.191]\n", + "Number of attacks: 40\n" + ] + } + ], + "source": [ + "n = 20\n", + "attacks = []\n", + "\n", + "# create multiple values for max_iter\n", + "max_iter = [i+1 for i in list(range(n))]\n", + "print('max_iter values:', max_iter)\n", + "\n", + "# create multiple values for eps\n", + "eps_steps = [round(float(i/100+0.001), 3) for i in list(range(n))]\n", + "print('eps_step values:', eps_steps)\n", + "\n", + "for i, eps_step in enumerate(eps_steps):\n", + " attacks.append(\n", + " ProjectedGradientDescentPyTorch(\n", + " estimator=classifier,\n", + " norm=np.inf,\n", + " eps=0.1,\n", + " eps_step=eps_step,\n", + " max_iter=10,\n", + " targeted=False,\n", + " batch_size=32,\n", + " verbose=False\n", + " )\n", + " )\n", + " attacks.append(\n", + " ProjectedGradientDescentPyTorch(\n", + " estimator=classifier,\n", + " norm=np.inf,\n", + " eps=0.1,\n", + " max_iter=max_iter[i],\n", + " targeted=False,\n", + " batch_size=32,\n", + " verbose=False\n", + " )\n", + " )\n", + " \n", + "print('Number of attacks:', len(attacks))\n", + "attack_parallel = AutoAttack(estimator=classifier, attacks=attacks, targeted=True, parallel=True)\n", + "attack_non_parallel = AutoAttack(estimator=classifier, attacks=attacks, targeted=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Run the attack\n", + "- using non-parallel AutoAttack\n", + "- using parallel AutoAttack" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/kieranfraser/git/personal/art/env/lib/python3.9/multiprocessing/resource_tracker.py:216: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown\n", + " warnings.warn('resource_tracker: There appear to be %d '\n", + "/Users/kieranfraser/git/personal/art/env/lib/python3.9/multiprocessing/resource_tracker.py:216: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown\n", + " warnings.warn('resource_tracker: There appear to be %d '\n", + "/Users/kieranfraser/git/personal/art/env/lib/python3.9/multiprocessing/resource_tracker.py:216: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown\n", + " warnings.warn('resource_tracker: There appear to be %d '\n", + "/Users/kieranfraser/git/personal/art/env/lib/python3.9/multiprocessing/resource_tracker.py:229: UserWarning: resource_tracker: '/loky-2595-nh_wgd4i': [Errno 2] No such file or directory\n", + " warnings.warn('resource_tracker: %r: %s' % (name, e))\n" + ] + } + ], + "source": [ + "# Run ART attack in non-parallel mode\n", + "nonparallel_adv = attack_non_parallel.generate(x=x_train, y=y_train)\n", + "\n", + "# Run ART attack in parallel mode\n", + "parallel_adv = attack_parallel.generate(x=x_train, y=y_train)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Is the attack robust?\n", + "If the attack is robust, predictions for each image will be incorrect and therefore the attack is fully robust." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Is AutoAttack non-parallel fully robust: True\n", + "Is AutoAttack parallel fully robust: True\n" + ] + } + ], + "source": [ + "predictions_parallel = np.sum(np.argmax(classifier.predict(parallel_adv), axis=1) == np.argmax(y_train, axis=1)) == 0\n", + "predictions_notparallel = np.sum(np.argmax(classifier.predict(nonparallel_adv), axis=1) == np.argmax(y_train, axis=1)) == 0\n", + "\n", + "print(f'Is AutoAttack non-parallel fully robust: {predictions_notparallel}')\n", + "print(f'Is AutoAttack parallel fully robust: {predictions_parallel}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Visualize the adversarial images and perturbations\n", + "- The first column images is the original image. The title shows the ground truth label and the classifier prediction on the original image. Note that as we are passing ground truth labels to the __generate__ method, attacks won't add perturbations to images that are already misclassified. Perturbations are added to images in which the classifier made correct predictions. \n", + "\n", + "- The second column images are adversarial images generated by AutoAttack in non-parallel mode - these should be identical to ART core (this is a sanity check to ensure changes have not changed core functionality).\n", + "\n", + "- The fourth column images are adversarial images generated by AutoAttack in parallel mode. Note how parallel model achieves lower L2 distance between the original and adversarial image - as all jobs are run in parallel mode, more attacks can be evaluated and successful attacks with lower perturbations added can be selected." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABxhUlEQVR4nO3deXhU5f3+8Xsmy2RPSAiEsIRVFlkUVMQNUBTRUrRWUVHBpSqCLaiVUqviilTrrtBWCxZ3rIraFhUE/IpgBaGKC5tBWcOafZ95fn/4y9QxZGaeQ5JJwvt1Xbk0M/ecec4suZlPJmdcxhgjAAAAAAAAAAAccEd6AQAAAAAAAACA5oshMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhM1CPXC6XZsyYEellBDVhwgQlJSXV+3aLi4t1zTXXKCsrSy6XS1OmTKn36wAAAAAAAEDTw5AZjS43N1eTJ0/WUUcdpYSEBCUkJKhPnz6aNGmSPv/880gvr0ENGzZMLpcr5NfhDqpLS0s1Y8YMLVu2rF7WHY77779f8+bN08SJEzV//nxdfvnljXbdAAAAAAAAiJzoSC8AR5Z33nlHY8eOVXR0tMaNG6cBAwbI7Xbrm2++0euvv67Zs2crNzdXOTk5kV5qg7jtttt0zTXX+L//9NNP9fjjj+v3v/+9evfu7T+9f//+h3U9paWluuuuuyT9MNhuDB988IFOPPFE3XnnnY1yfQAAAAAAAGgaGDKj0WzZskUXX3yxcnJytGTJErVr1y7g/FmzZunpp5+W2x38DfYlJSVKTExsyKU2mDPPPDPg+7i4OD3++OM688wzgw6Dm8M+79mzR3369AmZKy8vV2xsbMj7GQAAAAAAAM0DUx40mj/+8Y8qKSnR3Llzaw2YJSk6Olq//vWv1bFjR/9pNccP3rJli8455xwlJydr3Lhxkn4YvN58883q2LGjPB6PevbsqYceekjGGP/lt27dKpfLpXnz5tW6vp8elmLGjBlyuVzavHmzJkyYoLS0NKWmpurKK69UaWlpwGUrKio0depUZWZmKjk5WT//+c+1ffv2w7yFAtfx1Vdf6dJLL1WrVq10yimnSPrhXcmHGkZPmDBBnTt39u9zZmamJOmuu+6q8xAcO3bs0HnnnaekpCRlZmbqlltukdfrDcjs2rVL33zzjaqqqupc77Jly+RyuZSbm6t//vOf/uvbunWr/7yXX35Zf/jDH9S+fXslJCSosLBQkrRgwQINGjRI8fHxat26tS677DLt2LGj1nUsWLBAffr0UVxcnPr27as33ngjYJ8BAAAAAAAQObyTGY3mnXfeUffu3TV48GCry1VXV2vkyJE65ZRT9NBDDykhIUHGGP385z/X0qVLdfXVV+uYY47Ru+++q9/+9rfasWOHHnnkEcfrvOiii9SlSxfNnDlTn332mZ555hm1adNGs2bN8meuueYaPf/887r00kt10kkn6YMPPtC5557r+DoP5cILL1SPHj10//33BwzOQ8nMzNTs2bM1ceJEnX/++frFL34hKfAQHF6vVyNHjtTgwYP10EMPafHixfrTn/6kbt26aeLEif7c9OnT9dxzzyk3N7fOgW7v3r01f/58TZ06VR06dNDNN9/sX8fWrVslSffcc49iY2N1yy23qKKiQrGxsZo3b56uvPJKHX/88Zo5c6by8vL02GOPacWKFVq7dq3S0tIkSf/85z81duxY9evXTzNnztTBgwd19dVXq3379ha3JgAAAAAAABoKQ2Y0isLCQu3cuVPnnXderfPy8/NVXV3t/z4xMVHx8fH+7ysqKnThhRdq5syZ/tMWLlyoDz74QPfee69uu+02SdKkSZN04YUX6rHHHtPkyZPVrVs3R2s99thj9eyzz/q/379/v5599ln/kPm///2vnn/+ed1www166qmn/Nc9bty4ev3gwgEDBujFF1+0vlxiYqJ++ctfauLEierfv78uu+yyWpny8nKNHTtWt99+uyTp+uuv18CBA/Xss88GDJnD0bZtW1122WX+dyrXdX2rV6/2369VVVWaNm2a+vbtqw8//FBxcXGSpFNOOUU/+9nP9Mgjj/iPKT19+nS1b99eK1asUFJSkiTpjDPO0LBhw1rssbsBAAAAAACaEw6XgUZRc3iEmiHhjw0bNkyZmZn+r5rB7Y/9dPD5r3/9S1FRUfr1r38dcPrNN98sY4z+/e9/O17r9ddfH/D9qaeeqv379/v34V//+pck1bruKVOmOL7OcNZR3w61n99++23AafPmzZMx5rAPSzF+/PiAXxysXr1ae/bs0Q033OAfMEvSueeeq169eumf//ynJGnnzp364osvdMUVVwQ8doYOHap+/fod1poAAAAAAABQPxgyo1EkJydLkoqLi2ud9+c//1nvv/++nn/++UNeNjo6Wh06dAg47bvvvlN2drZ/uzV69+7tP9+pTp06BXzfqlUrSdLBgwf923a73bXeKd2zZ0/H13koXbp0qdft/VhcXJz/uM01WrVq5d/H+vbTfam5fw51m/Xq1ct/fs1/u3fvXit3qNMAAAAAAADQ+DhcBhpFamqq2rVrp/Xr19c6r+YYzTXH7/0pj8cjt9vZ70NcLtchT//pB9z9WFRU1CFPtzkucn348Tt/a7hcrkOuI9j+HEpd+9hQDrUvAAAAAAAAaBl4JzMazbnnnqvNmzfrP//5z2FvKycnRzt37lRRUVHA6d98843/fOl/70LOz88PyB3OO51zcnLk8/m0ZcuWgNM3bNjgeJvhatWqVa19kWrvT13D9aai5v451G22YcMG//k1/928eXOt3KFOAwAAAAAAQONjyIxGc+uttyohIUFXXXWV8vLyap1v807hc845R16vV08++WTA6Y888ohcLpdGjRolSUpJSVHr1q314YcfBuSefvppB3vwg5ptP/744wGnP/roo463Ga5u3brpm2++0d69e/2n/fe//9WKFSsCcgkJCZJqD9dt7dq1S998842qqqoOazs/ddxxx6lNmzaaM2eOKioq/Kf/+9//1tdff61zzz1XkpSdna2+ffvq73//e8ChVpYvX64vvviiXtcEAAAAAAAAZzhcBhpNjx499OKLL+qSSy5Rz549NW7cOA0YMEDGGOXm5urFF1+U2+2udfzlQxk9erSGDx+u2267TVu3btWAAQP03nvvaeHChZoyZUrA8ZKvueYaPfDAA7rmmmt03HHH6cMPP9TGjRsd78cxxxyjSy65RE8//bQKCgp00kknacmSJY3yztqrrrpKDz/8sEaOHKmrr75ae/bs0Zw5c3T00Uf7P5hQ+uHwFH369NErr7yio446Sunp6erbt6/69u1rdX3Tp0/Xc889p9zc3MP+8L8fi4mJ0axZs3TllVdq6NChuuSSS5SXl6fHHntMnTt31tSpU/3Z+++/X2PGjNHJJ5+sK6+8UgcPHtSTTz6pvn37HvIY3wAAAAAAAGhcvJMZjWrMmDH64osvdOmll+q9997Tb37zG02dOlULFy7Uueeeq88++0wXX3xxyO243W699dZbmjJlit555x1NmTJFX331lR588EE9/PDDAdk77rhDV199tV577TXdeuut8nq9+ve//31Y+/G3v/1Nv/71r7Vo0SLdeuutqqqq0j//+c/D2mY4evfurb///e8qKCjQTTfdpLfeekvz58/XwIEDa2WfeeYZtW/fXlOnTtUll1yi1157rcHXZ2PChAl65ZVXVFlZqWnTpunPf/6zzj//fH300UdKS0vz50aPHq2XXnpJlZWV+t3vfqfXX39d8+bNU8+ePRUXFxe5HQAAAAAAAIAkyWUa+9PMAKAeHHPMMcrMzNT7778f6aUAAAAAAAAc0XgnM4AmraqqStXV1QGnLVu2TP/97381bNiwyCwKAAAAAAAAfryTGUCTtnXrVo0YMUKXXXaZsrOz9c0332jOnDlKTU3V+vXrlZGREeklAgAAAAAAHNH44D8ATVqrVq00aNAgPfPMM9q7d68SExN17rnn6oEHHmDADAAAAAAA0ARwuAwATVpqaqpeeeUVbd++XRUVFTpw4IAWLFigbt26RXppAIBmrKKiQldddZU6deqklJQUnXjiiVq5cmWklwUAACzQ50DTwZAZAAAAR5zq6mp17txZH330kfLz8zVlyhSNHj1axcXFkV4aAAAIE30ONB0MmQEAANAi/PGPf1SvXr3k8/lCZhMTE3XHHXeoU6dOcrvduvjiixUbG6sNGzY0wkqPPHPmzFGnTp1UUVER6aUAAJo4+rzpos8RDENmIITOnTtrwoQJ/u+XLVsml8ulZcuW1dt1uFwuzZgxo962Vx8efPBBde3aVVFRUTrmmGMivRwAwBHk448/1owZM5Sfnx/2ZQoLCzVr1ixNmzZNbnfgP3HnzZunXr16aerUqXVeftOmTTpw4IC6d+/udNkBKioqNG3aNGVnZys+Pl6DBw/W+++/H9Zla/6tcaivVatW+XOffvqpJk+erKOPPlqJiYnq1KmTLrroIm3cuLHWNm2yEyZMqPP6XS6XduzYEZD/7LPP9POf/1zp6elKSEhQ37599fjjj9faZmVlpf785z+HdRsAAJo/+jyyfV4jnJ4ON0efIxg++A9N2rx583TllVf6v/d4POrUqZPOOuss3X777Wrbtm0EV2fnX//6l/7zn/80uWHyobz33nu69dZbddlll2nGjBlq3bp1pJcEADiC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P/10/+nPPPOMoqOjNWzYsFqX+eyzzw75rmgAQOTR5y2rz8Ptafoc9YUhM1q0iy66SE8//bTuueeeWsdWrq6uVnFxsdLS0jRixAjFxMToiSee0FlnneUfCD/66KMhr2PgwIHq0qWLHn30UU2YMCHguMw1JSwFFuxPj938Y1FRUTrrrLO0cOFCbd261f9b1Ly8PL344os65ZRTAn6LGq6CggLt2rVL7dq1U2pqqvXl62O9I0eO1FNPPaV169b5f+t74MCBOt9pDgBoPu6//3699957Gjp0qK699lr17t1bu3bt0oIFC/TRRx8pLS1NP/vZzzR//nylpqaqT58+WrlypRYvXlzrWL+DBg2SJN122226+OKLFRMTo9GjR/u79Ke6du2qvn37avHixbrqqqskSX/961+1bt06ST/8ZdPvf/97HXfccfrlL38Z9j45PYbj4MGDdeGFF2r69Onas2ePunfvrueee05bt27Vs88+G5D9z3/+o+HDh+vOO+/UjBkzJEljx45VfHy8TjrpJLVp00ZfffWV/vKXvyghIUEPPPCA/7I333yz3nrrLY0ePVoHDhzQ888/H7Dtyy67zFG2xiuvvKLq6uo6X0Qee+yxuuqqq/S3v/1N1dXVGjp0qJYtW6YFCxZo+vTptf50d82aNTpw4IDGjBkT+kYEAEQEff4/zb3Pw+1p+hz1JjKfNwiEZ+7cuUaS+fTTT4Pmxo8fbxITEw95Xs0noo4aNco88sgj5sknnzS/+c1vTHZ2tlmwYIE/N336dCPJnHPOOebJJ580V199tcnOzjatW7c248eP9+eWLl1qJJmlS5f6T1u0aJGJiYkxOTk5ZsaMGebPf/6zmTp1qjnrrLP8mVdffdVIMpdffrl5/vnnzUsvveQ/T5K58847/d+vX7/eJCYmmvbt25v77rvPzJo1y3Tt2tV4PB6zatWqkLfPodZYk507d27Q2zLY7SnJTJo0qdbp4a73+++/N2lpaaZ169bmrrvuMg899JDp1auXOeaYY4wks3Xr1pBrAwA0Xd9995254oorTGZmpvF4PKZr165m0qRJpqKiwhhjzMGDB82VV15pWrdubZKSkszIkSPNN998Y3JycgK61hhj7rnnHtO+fXvjdrvD+mT6hx9+2CQlJZnS0lJTVlZmxowZY/7973+bESNGmM6dO5sbbrgh5Ce016eysjJzyy23mKysLOPxeMzxxx9vFi1aVCtX09k//nfAY489Zk444QSTnp5uoqOjTbt27cxll11mNm3aFHDZoUOHGkl1fjnN1jjxxBNNmzZtgt5ulZWVZsaMGSYnJ8fExMSY7t27m0ceeeSQ2WnTpplOnToZn89X5/YAAJFHn/9Pc+/zcHuaPkd9YMiMJq0+hszGGPOXv/zFDBo0yMTHx5vk5GTTr18/c+utt5qdO3f6M16v19x1112mXbt2Jj4+3gwbNsysX7++VlEeaoBrjDEfffSROfPMM01ycrJJTEw0/fv3N0888YT//OrqanPjjTeazMxM43K5Agrgp2VkjDGfffaZGTlypElKSjIJCQlm+PDh5uOPPw7r9mnsIXO46zXGmLVr15pTTz3VeDwe06FDBzNz5kzz+OOPG0lm9+7dIdcGAMCh5Ofnm/T0dPPMM89Eeik4hPLycpOVlWUeffTRSC8FANCE0edNG32OYFzGNMAngwGAhSlTpujPf/6ziouL6/wQQwAAQpk1a5bmzp2rr776qtaHASOy5syZo/vvv1+bNm2Sx+OJ9HIAAE0Yfd500ecIhiEzgEZVVlYW8CGM+/fv11FHHaWBAweG/WEMAAAAAAAAaDr44D8AjWrIkCEaNmyYevfurby8PD377LMqLCzU7bffHumlAQAAAAAAwAGGzAAa1TnnnKPXXntNf/nLX+RyuTRw4EA9++yzOu200yK9NAAAAAAAADjA4TIAAAAAAAAAAI5xBHUAAAAAAAAAgGNN7nAZPp9PO3fuVHJyslwuV6SXAwBoZMYYFRUVKTs7m0+TbubodAA4ctHnLQd9DgBHtnA7vckNmXfu3KmOHTtGehkAgAjbtm2bOnToEOll4DDQ6QAA+rz5o88BAFLoTm9yQ+bk5GRJ0qATBis6OrzlFRQctLoOj9tnlW8Va3fY6g6tEqzyrdPt8hmpiVb5WHeMVT7KE2+V/+FCUVbxg/kFVvmqarv7IC011Srv9lZZ5SsqK6zy5eV2+bh4j1XeK69VvqysxCqfkppslZexW09lpd3tH2X5oyvK8vGZlJhklZekxAS753F0TJxVvryi0ipvXJbvGHLb3aaVlXbrqTbhv+ulvKJStz/+gr8P0HzR6aHR6aHR6cHR6cHR6aE1VKfT5y0HfR4afR4afR4cfR4cfR5aU3iN3uSGzDV/fhMdHR12gdk+OKPcdn/iEx1l98MzNsZuPZ4Yu7shLtaukGKj7PLRHru8JCnKbh/KLPfB7ba7D+Is98Ft9/NWLtn9I0g+uyuwvY+9lodX93ktH3O2jwljtx637O7fKFn+o8zyZ0S8g+dAfFysVT4mxi5v+5eJDV1gUZbrsSmwGvw5ZvNHp4dGp4dGpwdHpwdHp4fW0J1Onzd/9Hlo9Hlo9Hlw9Hlw9HloTeE1eoMdHOupp55S586dFRcXp8GDB+s///lPQ10VAABoIPQ5AAAtA50OAGhIDTJkfuWVV3TTTTfpzjvv1GeffaYBAwZo5MiR2rNnT0NcHQAAaAD0OQAALQOdDgBoaA0yZH744Yf1q1/9SldeeaX69OmjOXPmKCEhQX/7299qZSsqKlRYWBjwBQAAIs+mzyU6HQCAporX6ACAhlbvQ+bKykqtWbNGI0aM+N+VuN0aMWKEVq5cWSs/c+ZMpaam+r/41FoAACLPts8lOh0AgKaI1+gAgMZQ70Pmffv2yev1qm3btgGnt23bVrt3766Vnz59ugoKCvxf27Ztq+8lAQAAS7Z9LtHpAAA0RbxGBwA0BruPKmwAHo9HHo8n0ssAAACHiU4HAKD5o88BAE7U+zuZW7duraioKOXl5QWcnpeXp6ysrPq+OgAA0ADocwAAWgY6HQDQGOp9yBwbG6tBgwZpyZIl/tN8Pp+WLFmiIUOG1PfVAQCABkCfAwDQMtDpAIDG0CCHy7jppps0fvx4HXfccTrhhBP06KOPqqSkRFdeeWVDXB0AAGgA9DkAAC0DnQ4AaGgNMmQeO3as9u7dqzvuuEO7d+/WMccco0WLFtX6oIFgvvnma7nc4b3ROn/fPqv1pcdZxeXKsLtAa2+y3fbj21jlS3wHrPLFXmOVN65Yq7wklZZX2uXLKqzyVV6fVX5flMsqHxdtdxtVV9utJ8pt91SzPQZaaXmJVb7aZ3d/ucozrPLuKKu4qirsHg/x0XbPyeIKu/094K22yktSQkKiVd7ljrHLR9nlFebPzxql5VVW+eoqu3xUdPiP6Yoq+9sfDaM++lyi04Oh00Oj04Oj04Oj00NrqE6nz5sWXqPT5yHz9HlQ9Hlw9HkYmmmfS+F3eoN98N/kyZM1efLkhto8AABoBPQ5AAAtA50OAGhI9X5MZgAAAAAAAADAkYMhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAsehIL6AucdEuud2u8MIeu23nZMRZ5Tu3TbXKt8lMt8rHJyRa5V2uMG+X/6+sotwqX15VYZWXJGO5ptj4eLsrqDZWceOz24fU9ASrfHWV3XpiY+z21+u1iisq1u5JUFFp95ioqra7fxMs1xOdaHf7xFluv9pVYpV3G59VXpKqZXcbRdnFlZRo9xgtLim1yldVV1nlw/3xXKOosCDsbGWV5RMATR6dXjc6PTQ6PTg6PTg6PbSG6nT6vOWhz+tGn4dGnwdHnwdHn4fWFF6j805mAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOBYd6QXUJc7lldvlCyubnGy3G0e1b2WVz4iPssrH+Mqt8sUHKq3yXp/d7wbKSqut8u5Yq7gkKSUtySofHeuxyucXFNlt3/KRnZ6cYJUvKiyxyleW2+XLyqus8kYuq3xSYqJVvqqyzCrv9trdATEeu8eD12t3+0RH2d0+FRV225ek2Bi7J47bZ/e8rCg+aJWX11jFPXY/5lTtC+/nc42Ckoqws5XVdttG00en141OD2P7dHpQdHpwdHpoDdXp9HnLQ5/XjT4PY/v0eVD0eXD0eWhN4TU672QGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAY9GRXkBd0jxRinKHNwOP93istp2aGG+Vz0yJscp7fV67vFVaioqOsrtAmLdjjQpfld32JUVH2z2Uoo3PKu+tKLPKmyi7fd6zJ98q762yu9eKSkut8qXeSqt8UnyKVV4VduuPkt395XYZu+174qzyZSXlVvmEGLvbJ9rYrV+Sysvt7rOyqmqrvE92a8ovtruN8kvtnvfFpXbrL68K/zlZ7bV7vKHpo9PrRqeHRqeHQKcHRaeH1lCdTp+3PPR53ejz0OjzEOjzoOjz0JrCa3TeyQwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHoiO9gLq0To1TdFR4M/DkmCirbcfF2eXdUcYqHx8fb5WvqvZa5X1yWeWNqbTKV1bb7a8keSurrPI+Y5c3Xrt9MNGxVvmiyhKrvNdr9xgq9fqs8tWW+aISu9tzxwG7/Y1x260npdjuMVq1e59Vvqyg1CrfqXV3q3ybNh2s8pLkSi6wylcc3G+VLy62u88Kisqt8vsKyqzyW7fZ7a83Kvy68Rn7n0Fo2uj0utHpYeTp9KDo9ODo9NAaqtPp85aHPq8bfR5Gnj4Pij4Pjj4PrSm8RuedzAAAAAAAAAAAxxgyAwAAAAAAAAAcq/ch84wZM+RyuQK+evXqVd9XAwAAGhB9DgBAy0CnAwAaQ4Mck/noo4/W4sWL/3cl0U320M8AAKAO9DkAAC0DnQ4AaGgN0izR0dHKyspqiE0DAIBGQp8DANAy0OkAgIbWIMdk3rRpk7Kzs9W1a1eNGzdO33//fZ3ZiooKFRYWBnwBAIDIs+lziU4HAKCp4jU6AKCh1fuQefDgwZo3b54WLVqk2bNnKzc3V6eeeqqKiooOmZ85c6ZSU1P9Xx07dqzvJQEAAEu2fS7R6QAANEW8RgcANIZ6HzKPGjVKF154ofr376+RI0fqX//6l/Lz8/Xqq68eMj99+nQVFBT4v7Zt21bfSwIAAJZs+1yi0wEAaIp4jQ4AaAwNfrT/tLQ0HXXUUdq8efMhz/d4PPJ4PA29DAAAcBhC9blEpwMA0BzwGh0A0BAa5JjMP1ZcXKwtW7aoXbt2DX1VAACggdDnAAC0DHQ6AKAh1PuQ+ZZbbtHy5cu1detWffzxxzr//PMVFRWlSy65pL6vCgAANBD6HACAloFOBwA0hno/XMb27dt1ySWXaP/+/crMzNQpp5yiVatWKTMz02o7Wa0TFBsdFVY2JbbaattJCbFWeZepsspLxnL7Pqt8RVmpVd4tl1U+IznVKi9JiYlxVvnCgn1W+dSUFKt8UbndffbdDrv1FFeE99isEWt3F6t9gt1TMzqmzCq/dX++Vb7C2O1vjMvuOZCakmyVP6nPcVb5wl1eq7wptVu/JKW2jrHKV5Ta3cfFxXa/E/TE2K2nY5bdfdCmTVurfF5hedjZaq9P36/fbrV91L/66nOJTg+GTg+NTg+OTg+OTg+toTqdPm86eI0u0eeh0efB0efB0eehNdc+l8Lv9HofMr/88sv1vUkAANDI6HMAAFoGOh0A0Bga/JjMAAAAAAAAAICWiyEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMCx6EgvoC6tkuLliYkKKxtdmW+1bU+M3W4neBKs8hVlVVb5Kl+1VT4trZVV3hhjla/02v/uoaqq3CqfkJRkld+5t8Iqv+W7Aqv83iK7+6DULq6c+PAeyzXOO/UYq3yHdna352trvrXKr9y82ypf7au0yke77R6jRfl7rfKlxXaPn+TkGKu8JMnrsorHxdldR2yc3WMowWW3/Wqv3YO6U8dsq3zygaKws5VVXn24frvV9tG00el1o9NDo9ODo9ODo9NDa6hOp89bHvq8bvR5aPR5cPR5cPR5aE3hNTrvZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBj0ZFeQF0yW6UrLja85ZUdKLfatttlt9vFpVVW+bLKaqt8tCvKKl9a5bXK2/4moayq0vISUlqrFKt8pddY5b/dvtMqf6DQ7jYy0bFW+agou1s1Jc5uPW2ii6zycQcqrPI9UrKs8rvS7fY3L3+PVb6i1O4xt3bjRqu8u9pnla9KtHs8S5JS29rl3XY/h1JTE6zyyT6751h5pd3POVNZaJXvnJlosRa7n6Fo+uj0utHpodHpwdHpwdHpoTVUp9PnLQ99Xjf6PDT6PDj6PDj6PLSm8BqddzIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHoiO9gLqkZbRWvCcmrGyrpHirbbvd4W23Rn7hQat8VUmxVd7t9VrlffJZ5U2M3d2clBRnlZekKtld5utvN1rlSypKrPJxcR67fKzdbRSfmGCVbxVVbZVfsznPKl9dabf+itQsq3xmK7v716UUq3xVdblVvrSyzCpfUmqs8pXVdveXJLmqKi0vYBePcdtdwLij7LYfbfcYqq6osMobb/j3gU0WzQOdXjc6PTQ6PTg6PTg6PbSG6nT6vOWhz+tGn4dGnwdHnwdHn4fWFF6j805mAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOBYd6QXUyR0tuWPCirpiwss55Ymz236CEq3y0ZazfrfbLl8ln1XeE59qlZekfbuLrPKl+w5a5bumx1nlK8qt4opLTLDK9+zW3irvtlxQdZTdY66w0O72jI4qsMonx9o9pjNadbPKd+vRySqf+/2nVvlvNu6wysdGV1jlJcmYYqt8dbXdj193dKxVPibW7jHk89n9nPDJZZV3ucL/uWWTRTNBp9eJTg+NTg+OTg+OTg8j30CdTp+3QPR5nejz0Ojz4Ojz4OjzMPJN4DU6zQ8AAAAAAAAAcMx6yPzhhx9q9OjRys7Olsvl0ptvvhlwvjFGd9xxh9q1a6f4+HiNGDFCmzZtqq/1AgCAekCfAwDQMtDpAICmwHrIXFJSogEDBuipp5465Pl//OMf9fjjj2vOnDn65JNPlJiYqJEjR6q83PJvIwAAQIOhzwEAaBnodABAU2B9TOZRo0Zp1KhRhzzPGKNHH31Uf/jDHzRmzBhJ0t///ne1bdtWb775pi6++OLDWy0AAKgX9DkAAC0DnQ4AaArq9ZjMubm52r17t0aMGOE/LTU1VYMHD9bKlSsPeZmKigoVFhYGfAEAgMhx0ucSnQ4AQFPDa3QAQGOp1yHz7t27JUlt27YNOL1t27b+835q5syZSk1N9X917NixPpcEAAAsOelziU4HAKCp4TU6AKCx1OuQ2Ynp06eroKDA/7Vt27ZILwkAADhApwMA0PzR5wAAJ+p1yJyVlSVJysvLCzg9Ly/Pf95PeTwepaSkBHwBAIDIcdLnEp0OAEBTw2t0AEBjqdchc5cuXZSVlaUlS5b4TyssLNQnn3yiIUOG1OdVAQCABkKfAwDQMtDpAIDGEm17geLiYm3evNn/fW5urtatW6f09HR16tRJU6ZM0b333qsePXqoS5cuuv3225Wdna3zzjuvPtcNAAAOA30OAEDLQKcDAJoC6yHz6tWrNXz4cP/3N910kyRp/Pjxmjdvnm699VaVlJTo2muvVX5+vk455RQtWrRIcXFx9bdqAABwWOhzAABaBjodANAUWA+Zhw0bJmNMnee7XC7dfffduvvuuw9rYeXl1ZJxhZV1VZVZbr3aKl1SUmiVr6yyOwpJtduu3ItLi6zyhZb59h2tHxYy1XbXkdM6vPu2RrfsGKt8abnd9tsfNcAqH2vKrfIHC6qs8vFpGVZ57Y+yinfMameVzy8pscp37dXDKp/SKsEy39sqf3Cv3ePzYEGBVV6SYmITrfJu47HKV/m8Vnmfzyoub5Xdz0W33VMsaG8cThbONVafS3R6MHR6aHR6cHR6iDydHlJDdTp93nh4jR4afR4afR4cfR4qT5+H0lz73CZbr8dkBgAAAAAAAAAcWRgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHAsOtILqIvX5ZXXFd4M3HirrbZtjLHKx8fFW+WTkhOs8jv3llnlc7fvtcpHx9jtb2zeTqu8JJXn2a2pR5sYq/wZw3pY5bfsOGCVT26faZVvnZFlld+zN88qn5aWaJV3++xuz1h3lFV+z94dVvnouHyr/N78XVb5HbuKrfIxMXbPybQUn1VeksrK7J5nJtrud3wut8sq7/N5rfJul932XW679Xvtbh60MHR63ej00Oj04Oj04Oj00Oh0hIs+rxt9Hhp9Hhx9Hhx9HlpT6HPeyQwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHoiO9gLqkpiYqPi42rGx1dLXVtouLy63ypsprlS8oKrDKf/d9nlW+uLjYKh8fZ/e7hF25hVZ5SWob5n1Vo337HKt8WnYXq3xMkc8qr7gYq3iHASfYbX73Dqt8fPVeq7xXdo/pkhK7fLuETKt8pdfu9nclJlnlOyRmW+WT07Ks8kX7d1vlJWlP3n6rfJXL7jFXXllhlZfbWMUTPXFW+coyu59DMbHh769XLqtto+mj0+tGp4dGpwdHpwdHp4fWUJ1On7c89Hnd6PPQ6PPg6PPg6PPQmsJrdN7JDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMeiI72AuhQXHFB1eUxY2ejKIqttx7gsZ+tRdvHoKLsLlBYXWOVbJSda5dMS46zyZQcLrfKS1CY7wyrfvv9Qq/z67ZVW+Y2b7fIntUu3yufn222/bbcBVnm3Sq3ylRV7rfJpxmeVL9yz3yofX1lllW+Xbnn7ez1W+Zj+razyZfm7rPKStOJfb1nlt2+zu8+iYsP7efg/Lqt0mbHbepXl7yjdVeE/Jsqrqu0WgyaPTq8bnR4anR4cnR4cnR5aQ3U6fd7y0Od1o89Do8+Do8+Do89Dawqv0XknMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAAByLjvQC6uJ2SVGu8LLesmKrbRuFueGatajaKu91RVnlD1ZZxVVYaKzypqLSKt8uNdEqL0nHDx9ule/Q80Sr/Otz/2aVz0pMsspHVZZZ5Xd8u8Uqn9W1j1U+LqO7VT7RFFnlSw/sscrH+1pZ5SvLSq3y+4rs8mmZXazyGVmdrfJlxSlWeUlyW17EG1tulXe57X5uVVXZPe9d1V67vLHLV1eHXzdVXrufcWj66PS60emh0enB0enB0elh5Buo0+nzloc+rxt9Hhp9Hhx9Hhx9Hka+CbxG553MAAAAAAAAAADHrIfMH374oUaPHq3s7Gy5XC69+eabAedPmDBBLpcr4Ovss8+ur/UCAIB6QJ8DANAy0OkAgKbAeshcUlKiAQMG6Kmnnqozc/bZZ2vXrl3+r5deeumwFgkAAOoXfQ4AQMtApwMAmgLrYzKPGjVKo0aNCprxeDzKyspyvCgAANCw6HMAAFoGOh0A0BQ0yDGZly1bpjZt2qhnz56aOHGi9u/fX2e2oqJChYWFAV8AACDybPpcotMBAGiqeI0OAGho9T5kPvvss/X3v/9dS5Ys0axZs7R8+XKNGjVKXu+hP+Vw5syZSk1N9X917NixvpcEAAAs2fa5RKcDANAU8RodANAYrA+XEcrFF1/s//9+/fqpf//+6tatm5YtW6YzzjijVn769Om66aab/N8XFhZSYgAARJhtn0t0OgAATRGv0QEAjaFBDpfxY127dlXr1q21efPmQ57v8XiUkpIS8AUAAJqWUH0u0ekAADQHvEYHADSEBh8yb9++Xfv371e7du0a+qoAAEADoc8BAGgZ6HQAQEOwPlxGcXFxwG88c3NztW7dOqWnpys9PV133XWXLrjgAmVlZWnLli269dZb1b17d40cObJeFw4AAJyjzwEAaBnodABAU2A9ZF69erWGDx/u/77mWE3jx4/X7Nmz9fnnn+u5555Tfn6+srOzddZZZ+mee+6Rx+Opv1UDAIDDQp8DANAy0OkAgKbAesg8bNgwGWPqPP/dd989rAXVcJkfvsLhraqy27bb7igh0ZYHFTFlluvx2W0/PSPBKp+VUG2VH3jcUVZ5Sep90olW+YN7iq3ynuoCq3zXDh2s8j7LOyGrTaZVvrrc7j4oza+0yldW222/qszuqe9VklV+y47tVvkv1q+2yp90ot3tk5GVYZUvLNpjlZekGLunpVp3TrTK+yx/bnkrD/1p4XWprrC7TQv25lvlK4rCv4EqquzWDmcaq88lOj0YOj00Oj04Oj04Oj20hup0+rzx8Bo9NPo8NPo8OPo8OPo8tOba51L4nd7gx2QGAAAAAAAAALRcDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOBYd6QXUxVftlS8qvBl4WYXPatuxiUlW+ejoGKt8lLvSKt89q5VVPi7e7ncDnXM6WuUHnDLcKi9J7Xr2t8qvWznXKt+po91tlHV0P6t8bGY3q3x0QqpVvrS82CpfVlhklc/buc0qfzBvu1XeW1VqlY9PjrPKt25t9xzbtnOtVb5tu/ZW+epSu/tLkkxZhVXeVXLQKu81ZVZ54zJW+XiP3X0Qm2WXL/S4ws6WV4afRfNAp9eNTg+NTg+OTg+OTg+toTqdPm956PO60eeh0efB0efB0eehNYXX6LyTGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI5FR3oBdYmJilZMVHjLO1hUarVtb7nLKh+fEG+Vj3Ibq3ybjASr/LZd+Vb5bgPPtsp36GeX/0Erq3RVUYlVPjU51SqfedQxVvmS6HSr/JdrP7XKV5TZ7W9hYb5Vft+O763yUd5Kq3xcnN2PivZd2lvl+x/V3SpfHZVolY+JSrPLx1ZZ5SUpurzcKl/63Q6rvK/aa5WvtvwVYnFUlFU+IcPuPmibnRF2tqzcbl/R9NHpdaPTQ6PTg6PTQ+Tp9JAaqtPp85aHPq8bfR4afR4cfR4iT5+H1BReo/NOZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADgWHekF1KWyvEJunzesbILHbjdccVFW+Rh3tVXeeO3y8Ul26/n52J9b5U8adYZVPqV1W6u8JOV9+7VVPsryNs0vKrDK7926wSq/syi8x1qNZW++aZVPio+xypdXFFvls9qmWuVTkhOt8rnbt1nlKy3v3/Tszlb5o/oNssrL67GKH8jfbrd9SaXlLqv8wTK728hl7H7OlZf5rPLFxljlTXG5Vb53WvjZ8kqrTaMZoNPrRqeHRqcHR6cHR6eH1lCdTp+3PPR53ejz0Ojz4Ojz4Ojz0JrCa3TeyQwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHoiO9gLr4TKV8xhdm2Gu1bVd1mNv9/6pNld32XcYqH+dJscofM2iQVd4TE2OV/2rdWqu8JB3cucUqX1FRbpUvOnjAKr9t81dW+WITb5WP8dqtPyk6yiqfEpdolc9slWqV35W32ypfXWX3HCgtKrbKb8v93iovfWmVLi4ussrHRds9hyWp2tPGKr+/2u55Hx8fZ5VPSLZ7TMdHe6zyRaWFVvlqX7VF1u5nOpo+Or1udHpodHpwdHpwdHpoDdXp9HnLQ5/XjT4PjT4Pjj4Pjj4PrSm8RuedzAAAAAAAAAAAx6yGzDNnztTxxx+v5ORktWnTRuedd542bNgQkCkvL9ekSZOUkZGhpKQkXXDBBcrLy6vXRQMAgMNDpwMA0PzR5wCApsJqyLx8+XJNmjRJq1at0vvvv6+qqiqdddZZKikp8WemTp2qt99+WwsWLNDy5cu1c+dO/eIXv6j3hQMAAOfodAAAmj/6HADQVFgdk3nRokUB38+bN09t2rTRmjVrdNppp6mgoEDPPvusXnzxRZ1++umSpLlz56p3795atWqVTjzxxPpbOQAAcIxOBwCg+aPPAQBNxWEdk7mgoECSlJ6eLklas2aNqqqqNGLECH+mV69e6tSpk1auXHnIbVRUVKiwsDDgCwAANC46HQCA5o8+BwBEiuMhs8/n05QpU3TyySerb9++kqTdu3crNjZWaWlpAdm2bdtq9+5Df1LmzJkzlZqa6v/q2LGj0yUBAAAH6HQAAJo/+hwAEEmOh8yTJk3S+vXr9fLLLx/WAqZPn66CggL/17Zt2w5rewAAwA6dDgBA80efAwAiyeqYzDUmT56sd955Rx9++KE6dOjgPz0rK0uVlZXKz88P+E1pXl6esrKyDrktj8cjj8fjZBkAAOAw0ekAADR/9DkAINKs3slsjNHkyZP1xhtv6IMPPlCXLl0Czh80aJBiYmK0ZMkS/2kbNmzQ999/ryFDhtTPigEAwGGj0wEAaP7ocwBAU2H1TuZJkybpxRdf1MKFC5WcnOw/hlNqaqri4+OVmpqqq6++WjfddJPS09OVkpKiG2+8UUOGDOFTawEAaELodAAAmj/6HADQVFgNmWfPni1JGjZsWMDpc+fO1YQJEyRJjzzyiNxuty644AJVVFRo5MiRevrpp+tlsQAAoH7Q6QAANH/0OQCgqXAZY0ykF/FjhYWFSk1N1cyrTlFcbHgz8APbt1pdR2x8mlXeW11lla9StVW+U/cedtt3xVvl09t2CR36kTbt7D89uLK0wCpfsifXbvv7v7fKd+rSySpfFZNold/4xXqrfFnRQat8fILdMdBcMXaf4VlSXmGVN4qyylcal1XepRirfFJ8ilW+orrMKq8Yu/2VJK/b7jI7ir61u4LESqt4gsfukPtxPrt8vGKt8r37HxV2trSsSmOve0sFBQVKSbG7r9G00OlhbJ9OD4lOD45OD4FOD6mhOp0+bzno8zC2T5+HRJ8HR5+HQJ+H1BReo9s96gEAAAAAAAAA+BGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAci470Auri87nk87nCysZGR1ltOy7aZ7cYd3jrqGGiEq3yvsoqq/y+fbut8sV77fLxVYVWeUnyye4+SG+VYZVPy860yld7K6zyO3ba3UZGxirvdts91Sqrq63yUa4Yq3xiXIJVvtryKRNlewGX3e3prSywyrvD/FlSo7D0oFVekio9ZVb55Gy7x2hJfL5VvshXaZUvL7H7nWNGSlerfOs24T/nS0rs1o6mj06vG50eGp0eHJ0eHJ0eWkN1On3e8tDndaPPQ6PPg6PPg6PPQ2sKr9F5JzMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAci470AuridnnkdoW3vDhPvNW2jaqt8onxCXb55NZW+dKqcqt8RnKsVT7acn8rC/Ks8pLkc9utqTTGZ5Vv27aLVd5XWWmV79m/g1X+46VLrPKVptQqH+NyWeXLiu22n5KcYpWPjbb7URHlsrt/i8vtngO5uw5a5fPz7Z4DFa4Sq7wkZR5l9zu79ml2P7cqjd1z7OA+u8dEbHmMVT6xfYZVvqzUG362LPwsmgc6vW50emh0enB0enB0emgN1en0ectDn9eNPg+NPg+OPg+OPg+tKbxG553MAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcCw60guoS0y0S7HR4c3ASysqrLYdFZdolfdFeazypVVlVvmoGGOV98TGW+VjYuz2NzYh1SovSakpdtexe2+eVb60fQerfJuO3a3yO/bss8offfzJVvnivTut8t9u/NIqX1Kcb5WPjrJ7jKampljlXfJZ5XftsLt9vv+uwCrv9tg9PlPaJljlJSkz3fI2Ki+3yx+w24dWB+1+vLdvk26V75Bm95zc/NXusLNl5VVW20bTR6fXjU4PjU4Pjk4Pjk4PraE6nT5veejzutHnodHnwdHnwdHnoTWF1+i8kxkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADgWHekF1KVNhlsJceHNwKv277fadpnXZ5UvKbGKy7i9VvnoaLu7ISUlwyofGxNjlS8rKbTKS1J8jOVDqdIuv/rjj63yXXvmWeW3b99tlXe7XVb5BI/dfRAV5bHKx8cnWuVLisus8mVldvnq6kqrfFK83f6edOxRVvm45BSrfHVUtVVekrxVpVb5sm3lVnl3UZxVvk1CslX+2KOOttt+Wlur/JpduWFnyyvtb380bXR63ej00Oj04Oj04Oj0MLbfQJ1On7c89Hnd6PPQ6PPg6PPg6PMwtt8EXqPzTmYAAAAAAAAAgGNWQ+aZM2fq+OOPV3Jystq0aaPzzjtPGzZsCMgMGzZMLpcr4Ov666+v10UDAIDDQ6cDAND80ecAgKbCasi8fPlyTZo0SatWrdL777+vqqoqnXXWWSr5yd+q/OpXv9KuXbv8X3/84x/rddEAAODw0OkAADR/9DkAoKmwOujOokWLAr6fN2+e2rRpozVr1ui0007zn56QkKCsrKywtllRUaGKigr/94WF9scaAgAAduh0AACaP/ocANBUHNYxmQsKCiRJ6enpAae/8MILat26tfr27avp06ertLTug23PnDlTqamp/q+OHTsezpIAAIADdDoAAM0ffQ4AiBTLjxv9H5/PpylTpujkk09W3759/adfeumlysnJUXZ2tj7//HNNmzZNGzZs0Ouvv37I7UyfPl033XST//vCwkJKDACARkSnAwDQ/NHnAIBIcjxknjRpktavX6+PPvoo4PRrr73W///9+vVTu3btdMYZZ2jLli3q1q1bre14PB55PB6nywAAAIeJTgcAoPmjzwEAkeTocBmTJ0/WO++8o6VLl6pDhw5Bs4MHD5Ykbd682clVAQCABkSnAwDQ/NHnAIBIs3onszFGN954o9544w0tW7ZMXbp0CXmZdevWSZLatWvnaIEAAKD+0ekAADR/9DkAoKmwGjJPmjRJL774ohYuXKjk5GTt3r1bkpSamqr4+Hht2bJFL774os455xxlZGTo888/19SpU3Xaaaepf//+DbIDAADAHp0OAEDzR58DAJoKqyHz7NmzJUnDhg0LOH3u3LmaMGGCYmNjtXjxYj366KMqKSlRx44ddcEFF+gPf/hDvS0YAAAcPjodAIDmjz4HADQV1ofLCKZjx45avnz5YS2oRocOsUqKjwkrm+qKs9r25m2lVvm8vcH3+6cqvXYfkpCUZPf5iyWlBVZ5r6/YKh/l4FDdB/but8oXFVdb5cur7PY5ytjlk5NaWeXzdh+wym8vKbfK+4zLKt82M8Mq7/JVWeUP5h+0ynsS7Z4DaanJVvnYKLvHaEWl1yqv6PB+9vxYSYXdmiqL7a4j0We3/e4ds6zy2Vl2j6Ft2/Os8vv3hv9zt6LK8v6CI3R6eOj00Oj04Oj04Oj00Jprp9PnjYM+Dw99Hhp9Hhx9Hhx9Hlpz7XMp/E539MF/AAAAAAAAAABIDJkBAAAAAAAAAIeBITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwLHoSC+gLilpMUpKiAkrW7a31GrbrdpE2S0mMcEqvi+vwipfXllplY+OTbHKW25eviqv3QUkVXnt9rmg7KBVPjHeY5UvLy23ypeV77PKV1reRl7LvDF2j9HiQrvnQEpKvGU+1SpfVma3nn377R4PSUmJVnmX2+73aa5qY5WXpNhou9vUE2e5/Vi7x0Tn7p2t8mWldvv84YdfWeU/37gn7Gy112e1bTR9dHrd6PTQ6PTg6PQQeTo9pIbqdPq85aHP60afh0afB0efh8jT5yE1hdfovJMZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjkVHegF1iYqLVnRceMuLS4m12nZ6kt1sPbqswiofE++zyhcetLwbvHbrj49rY7f5GLv1S5K3It8qH5tgt88x0Xb3cVRUglW+wtjtc2VVpVXeGJdV3mWs4jKV5VZ5r11cMdExdheI9VjF8w8etMqXVVZZ5VPTUqzy0W7737+5LR+jpaq2yuftK7LKHyy2235RSYFVfvGyb6zyeaXhZ30+yycAmjw6PQg6PSQ6PTg6PTg6PbSG6nT6vOWhz4Ogz0Oiz4Ojz4Ojz0NrCq/ReSczAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHIuO9ALqUlIcLZcvJrxwVJLVtpMSy63yMfHGKp/oibPKp6b6rPLFhWWW+Ty7fKnXKi9JVeV2l0mOzbDKx8WE+Vj4/6orKqzy0dF2v2+Jtfz1TIwnyirvctldQUKS3VPZbfnMr/ZWW+Vj4+2uICUtwSp/4ECRVb7I2D3HUtLtHp+SVFpdaZXftHW/Vf6bL7ZZ5dump9jlO9jdB3Lb3aatU5PDznp9Pn130O7nNJo2Or1udHpodHpwdHpwdHoYGqjT6fOWhz6vG30eGn0eHH0eHH0ehibwGp13MgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMCx6EgvoC47t0kJceFlK/LDDP5/yZnVVvm4+CqrfGqSVVzp6XZ3Q3FJqVU+P98uf3B/rFX+h8vY5aN8UVZ5nzFWea/Xa5WXzy5v+9sZl9tllY+KtntMlHntVmTsngKK8dk9B6pLD1jlvWV2j1FvdIxVPr/YbvuVlg8fSTpQWGaV37rZ7kmTv7/EKl9ZYrcTWalZVvneOe2t8jY3T5XXp8+22j2G0LTR6XWj00Oj04Oj04Oj00NrqE6nz1se+rxu9Hlo9Hlw9Hlw9HloTeE1Ou9kBgAAAAAAAAA4ZjVknj17tvr376+UlBSlpKRoyJAh+ve//+0/v7y8XJMmTVJGRoaSkpJ0wQUXKC8vr94XDQAADg+dDgBA80efAwCaCqshc4cOHfTAAw9ozZo1Wr16tU4//XSNGTNGX375pSRp6tSpevvtt7VgwQItX75cO3fu1C9+8YsGWTgAAHCOTgcAoPmjzwEATYXVQWVGjx4d8P19992n2bNna9WqVerQoYOeffZZvfjiizr99NMlSXPnzlXv3r21atUqnXjiifW3agAAcFjodAAAmj/6HADQVDg+JrPX69XLL7+skpISDRkyRGvWrFFVVZVGjBjhz/Tq1UudOnXSypUr69xORUWFCgsLA74AAEDjodMBAGj+6HMAQCRZD5m/+OILJSUlyePx6Prrr9cbb7yhPn36aPfu3YqNjVVaWlpAvm3bttq9e3ed25s5c6ZSU1P9Xx07drTeCQAAYI9OBwCg+aPPAQBNgfWQuWfPnlq3bp0++eQTTZw4UePHj9dXX33leAHTp09XQUGB/2vbtm2OtwUAAMJHpwMA0PzR5wCApsDqmMySFBsbq+7du0uSBg0apE8//VSPPfaYxo4dq8rKSuXn5wf8pjQvL09ZWVl1bs/j8cjj8divHAAAHBY6HQCA5o8+BwA0BY6PyVzD5/OpoqJCgwYNUkxMjJYsWeI/b8OGDfr+++81ZMiQw70aAADQwOh0AACaP/ocABAJVu9knj59ukaNGqVOnTqpqKhIL774opYtW6Z3331Xqampuvrqq3XTTTcpPT1dKSkpuvHGGzVkyBA+tRYAgCaGTgcAoPmjzwEATYXVkHnPnj264oortGvXLqWmpqp///569913deaZZ0qSHnnkEbndbl1wwQWqqKjQyJEj9fTTTzfIwgEAgHN0OgAAzR99DgBoKlzGGBPpRfxYYWGhUlNT9dIDNyohLrzjQJUV7LG6jsTkCqt8vGefVT4lw2WVT8uMs8rLXW0VLyn1WeXzD8Rb5SUpf1+UVb6sxO5w4N7qWKu8jN2RYHzVdrdReVm5VT421m79UdF2t2dRud36y4rt1h9nKq3yyW677R8sLbTKb62ye/x4XHY/5hJj7I9BV1pmdxtt31dklU9MTLTKn3HaAKt8/77drfJLP1hlld++szjsbEVltR56dbEKCgqUkpJidT1oWuj0MNDpodHpQdHpwdHpoTVUp9PnLQd9Hgb6PDT6PCj6PDj6PLSm8Br9sI/JDAAAAAAAAAA4cjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4Fh3pBfyUMUaSVFpeGfZlyiyykuSKqbLK+3zVVnl3qcsqH11itx65vVbxkjKfZd5yPZJKy+2uo6zcWOUt7wLZ/v7EV223/vIKu9vIa+weE1Feu/u4rMLu9iyvtFu/MXb5aLfdHVZeaZevsH08uOxunygTZXkFUkWV3aIqq+3u4xjL7dv8DJWk4pJyq3yZ5XOgwuI+rrkta/oAzRedHs4V0Omh0enB0OnB0emhNVSn0+ctB30ezhXQ56HR58HQ58HR56E1hdfoLtPEWn/79u3q2LFjpJcBAIiwbdu2qUOHDpFeBg4DnQ4AoM+bP/ocACCF7vQmN2T2+XzauXOnkpOT5XL97zdLhYWF6tixo7Zt26aUlJQIrrDxHGn7zP62bOxvy1df+2yMUVFRkbKzs+V2c1Sn5oxO/wH72/IdafvM/rZs9Dl+ij7/nyNtn9nflo39bfkau9Ob3OEy3G530Kl4SkrKEfNgqHGk7TP727Kxvy1ffexzampqPa0GkUSnB2J/W74jbZ/Z35aNPkcN+ry2I22f2d+Wjf1t+Rqr0/mVMgAAAAAAAADAMYbMAAAAAAAAAADHms2Q2ePx6M4775TH44n0UhrNkbbP7G/Lxv62fEfiPsOZI+2xwv62fEfaPrO/LduRtr9w7kh8rBxp+8z+tmzsb8vX2Pvc5D74DwAAAAAAAADQfDSbdzIDAAAAAAAAAJoehswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwrNkMmZ966il17txZcXFxGjx4sP7zn/9EekkNYsaMGXK5XAFfvXr1ivSy6tWHH36o0aNHKzs7Wy6XS2+++WbA+cYY3XHHHWrXrp3i4+M1YsQIbdq0KTKLrQeh9nfChAm17vOzzz47Mos9TDNnztTxxx+v5ORktWnTRuedd542bNgQkCkvL9ekSZOUkZGhpKQkXXDBBcrLy4vQig9fOPs8bNiwWvfx9ddfH6EVH57Zs2erf//+SklJUUpKioYMGaJ///vf/vNb2v2L+nek9LnU8judPn8z4PyW1OfSkdfp9Dl9DntHSqe39D6X6HQ6vWX93KfTI9fpzWLI/Morr+imm27SnXfeqc8++0wDBgzQyJEjtWfPnkgvrUEcffTR2rVrl//ro48+ivSS6lVJSYkGDBigp5566pDn//GPf9Tjjz+uOXPm6JNPPlFiYqJGjhyp8vLyRl5p/Qi1v5J09tlnB9znL730UiOusP4sX75ckyZN0qpVq/T++++rqqpKZ511lkpKSvyZqVOn6u2339aCBQu0fPly7dy5U7/4xS8iuOrDE84+S9KvfvWrgPv4j3/8Y4RWfHg6dOigBx54QGvWrNHq1at1+umna8yYMfryyy8ltbz7F/XrSOtzqWV3On1eW0vpc+nI63T6nD6HnSOt01tyn0t0+qHQ6c335z6dHsFON83ACSecYCZNmuT/3uv1muzsbDNz5swIrqph3HnnnWbAgAGRXkajkWTeeOMN//c+n89kZWWZBx980H9afn6+8Xg85qWXXorACuvXT/fXGGPGjx9vxowZE5H1NLQ9e/YYSWb58uXGmB/uy5iYGLNgwQJ/5uuvvzaSzMqVKyO1zHr10302xpihQ4ea3/zmN5FbVANr1aqVeeaZZ46I+xeH50jqc2OOrE6nz1t2nxtz5HU6fd5y71vUjyOp04+kPjeGTjeGTjemZf3cp9Mb775t8u9krqys1Jo1azRixAj/aW63WyNGjNDKlSsjuLKGs2nTJmVnZ6tr164aN26cvv/++0gvqdHk5uZq9+7dAfd3amqqBg8e3GLvb0latmyZ2rRpo549e2rixInav39/pJdULwoKCiRJ6enpkqQ1a9aoqqoq4P7t1auXOnXq1GLu35/uc40XXnhBrVu3Vt++fTV9+nSVlpZGYnn1yuv16uWXX1ZJSYmGDBlyRNy/cO5I7HPpyO10+rxl9bl05HU6fd5y71scviOx04/UPpfodDq9+d/HdHrj3bfR9b7FerZv3z55vV61bds24PS2bdvqm2++idCqGs7gwYM1b9489ezZU7t27dJdd92lU089VevXr1dycnKkl9fgdu/eLUmHvL9rzmtpzj77bP3iF79Qly5dtGXLFv3+97/XqFGjtHLlSkVFRUV6eY75fD5NmTJFJ598svr27Svph/s3NjZWaWlpAdmWcv8eap8l6dJLL1VOTo6ys7P1+eefa9q0adqwYYNef/31CK7WuS+++EJDhgxReXm5kpKS9MYbb6hPnz5at25di75/cXiOtD6XjuxOp89bTp9LR16n0+f0OYI70jr9SO5ziU6n05v3fUynN26nN/kh85Fm1KhR/v/v37+/Bg8erJycHL366qu6+uqrI7gyNJSLL77Y///9+vVT//791a1bNy1btkxnnHFGBFd2eCZNmqT169e3uOOVBVPXPl977bX+/+/Xr5/atWunM844Q1u2bFG3bt0ae5mHrWfPnlq3bp0KCgr02muvafz48Vq+fHmklwU0OXT6kaWl9rl05HU6fQ7gx+jzIw+d3nLQ6Y2ryR8uo3Xr1oqKiqr1yYd5eXnKysqK0KoaT1pamo466iht3rw50ktpFDX36ZF6f0tS165d1bp162Z9n0+ePFnvvPOOli5dqg4dOvhPz8rKUmVlpfLz8wPyLeH+rWufD2Xw4MGS1Gzv49jYWHXv3l2DBg3SzJkzNWDAAD322GMt+v7F4TvS+1w6sjqdPm8ZfS4deZ1On9PnCO1I7/Qjqc8lOl2i05srOr3xO73JD5ljY2M1aNAgLVmyxH+az+fTkiVLNGTIkAiurHEUFxdry5YtateuXaSX0ii6dOmirKysgPu7sLBQn3zyyRFxf0vS9u3btX///mZ5nxtjNHnyZL3xxhv64IMP1KVLl4DzBw0apJiYmID7d8OGDfr++++b7f0bap8PZd26dZLULO/jQ/H5fKqoqGiR9y/qz5He59KR1en0efPuc+nI63T6nD5H+I70Tj+S+lyi0yU6vbmh0yPY6fX+UYIN4OWXXzYej8fMmzfPfPXVV+baa681aWlpZvfu3ZFeWr27+eabzbJly0xubq5ZsWKFGTFihGndurXZs2dPpJdWb4qKiszatWvN2rVrjSTz8MMPm7Vr15rvvvvOGGPMAw88YNLS0szChQvN559/bsaMGWO6dOliysrKIrxyZ4Ltb1FRkbnlllvMypUrTW5urlm8eLEZOHCg6dGjhykvL4/00q1NnDjRpKammmXLlpldu3b5v0pLS/2Z66+/3nTq1Ml88MEHZvXq1WbIkCFmyJAhEVz14Qm1z5s3bzZ33323Wb16tcnNzTULFy40Xbt2NaeddlqEV+7M7373O7N8+XKTm5trPv/8c/O73/3OuFwu89577xljWt79i/p1JPW5MS2/0+nzltvnxhx5nU6f0+ewcyR1ekvvc2PodDq9Zf3cp9Mj1+nNYshsjDFPPPGE6dSpk4mNjTUnnHCCWbVqVaSX1CDGjh1r2rVrZ2JjY0379u3N2LFjzebNmyO9rHq1dOlSI6nW1/jx440xxvh8PnP77bebtm3bGo/HY8444wyzYcOGyC76MATb39LSUnPWWWeZzMxMExMTY3JycsyvfvWrZvuPs0PtpyQzd+5cf6asrMzccMMNplWrViYhIcGcf/75ZteuXZFb9GEKtc/ff/+9Oe2000x6errxeDyme/fu5re//a0pKCiI7MIduuqqq0xOTo6JjY01mZmZ5owzzvCXlzEt7/5F/TtS+tyYlt/p9HnL7XNjjrxOp8/pc9g7Ujq9pfe5MXQ6nd6yfu7T6ZHrdJcxxjh/HzQAAAAAAAAA4EjW5I/JDAAAAAAAAABouhgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzEAj2bp1q1wul9atWxf2ZebNm6e0tLSIrwMAADQtP/03wowZM3TMMceEfXn+PQAAwOFzuVx68803JTnr1mHDhmnKlCkNsjagsTFkBixt27ZNV111lbKzsxUbG6ucnBz95je/0f79+4NermPHjtq1a5f69u0b9nWNHTtWGzduPNwlAwDQIk2YMEEul0sPPPBAwOlvvvmmXC5XhFYFAAAaW82/CVwul2JjY9W9e3fdfffdqq6ujvTSgCMGQ2bAwrfffqvjjjtOmzZt0ksvvaTNmzdrzpw5WrJkiYYMGaIDBw4c8nKVlZWKiopSVlaWoqOjw76++Ph4tWnTpr6WDwBAixMXF6dZs2bp4MGDkV5KvaisrIz0EgAAaJbOPvts7dq1S5s2bdLNN9+sGTNm6MEHH7Tejtfrlc/na4AVAi0bQ2bAwqRJkxQbG6v33ntPQ4cOVadOnTRq1CgtXrxYO3bs0G233SZJ6ty5s+655x5dccUVSklJ0bXXXnvIP51566231KNHD8XFxWn48OF67rnn5HK5lJ+fL6nuP4WdP3++OnfurNTUVF188cUqKiryZxYtWqRTTjlFaWlpysjI0M9+9jNt2bKlMW4eAAAa3YgRI5SVlaWZM2fWmfnHP/6ho48+Wh6PR507d9af/vSngPM7d+6s+++/X1dddZWSk5PVqVMn/eUvfwl6vcuWLZPL5dI///lP9e/fX3FxcTrxxBO1fv16f2b//v265JJL1L59eyUkJKhfv3566aWXArYzbNgwTZ48WVOmTFHr1q01cuRISdLDDz+sfv36KTExUR07dtQNN9yg4uJiq9vmmWeeUe/evRUXF6devXrp6aeftro8AADNicfjUVZWlnJycjRx4kSNGDFCb731VshOrXnd/dZbb6lPnz7yeDz6/vvv9emnn+rMM89U69atlZqaqqFDh+qzzz6zWtP69es1atQoJSUlqW3btrr88su1b9+++t51oElgyAyE6cCBA3r33Xd1ww03KD4+PuC8rKwsjRs3Tq+88oqMMZKkhx56SAMGDNDatWt1++2319pebm6ufvnLX+q8887Tf//7X1133XX+IXUwW7Zs0Ztvvql33nlH77zzjpYvXx7wZ8IlJSW66aabtHr1ai1ZskRut1vnn38+v4kFALRIUVFRuv/++/XEE09o+/bttc5fs2aNLrroIl188cX64osvNGPGDN1+++2aN29eQO5Pf/qTjjvuOK1du1Y33HCDJk6cqA0bNoS8/t/+9rf605/+pE8//VSZmZkaPXq0qqqqJEnl5eUaNGiQ/vnPf2r9+vW69tprdfnll+s///lPwDaee+45xcbGasWKFZozZ44kye126/HHH9eXX36p5557Th988IFuvfXWsG+XF154QXfccYfuu+8+ff3117r//vt1++2367nnngt7GwAANGfx8fGqrKwMq1NLS0s1a9YsPfPMM/ryyy/Vpk0bFRUVafz48froo4+0atUq9ejRQ+ecc07Am7yCyc/P1+mnn65jjz1Wq1ev1qJFi5SXl6eLLrqoIXYXiDwDICyrVq0ykswbb7xxyPMffvhhI8nk5eWZnJwcc9555wWcn5ubaySZtWvXGmOMmTZtmunbt29A5rbbbjOSzMGDB40xxsydO9ekpqb6z7/zzjtNQkKCKSws9J/229/+1gwePLjOde/du9dIMl988cUh1wEAQHM1fvx4M2bMGGOMMSeeeKK56qqrjDHGvPHGG6bmn7mXXnqpOfPMMwMu99vf/tb06dPH/31OTo657LLL/N/7fD7Tpk0bM3v27Dqve+nSpUaSefnll/2n7d+/38THx5tXXnmlzsude+655uabb/Z/P3ToUHPssceG3NcFCxaYjIwM//eH+jfCgAED/N9369bNvPjiiwHbuOeee8yQIUOMMfx7AADQsvz43wQ+n8+8//77xuPxmFtuuaVW9lCdKsmsW7cu6HV4vV6TnJxs3n77bf9pP54R/LRb77nnHnPWWWcFbGPbtm1GktmwYYMx5od/B/zmN7+x3FugaeKdzIAl8//fqRzKcccdF/T8DRs26Pjjjw847YQTTgi53c6dOys5Odn/fbt27bRnzx7/95s2bdIll1yirl27KiUlRZ07d5Ykff/992GtGwCA5mjWrFl67rnn9PXXXwec/vXXX+vkk08OOO3kk0/Wpk2b5PV6/af179/f//8ul0tZWVn+fq35M9ekpCQdffTRAdsaMmSI///T09PVs2dP/xq8Xq/uuece9evXT+np6UpKStK7775bq5MHDRpUa38WL16sM844Q+3bt1dycrIuv/xy7d+/X6WlpSFvi5KSEm3ZskVXX321f91JSUm69957OYQWAKDFeuedd5SUlKS4uDiNGjVKY8eO1YwZM8Lq1NjY2IB/C0hSXl6efvWrX6lHjx5KTU1VSkqKiouLw35t/d///ldLly4N6OJevXpJEn2MFin8TyADjnDdu3eXy+XS119/rfPPP7/W+V9//bVatWqlzMxMSVJiYmKDrCMmJibge5fLFXAojNGjRysnJ0d//etflZ2dLZ/Pp759+/JBQgCAFu20007TyJEjNX36dE2YMMH68sH69ZlnnlFZWdkhc8E8+OCDeuyxx/Too4/6jwU5ZcqUWp38038zbN26VT/72c80ceJE3XfffUpPT9dHH32kq6++WpWVlUpISAh6vTXHmfzrX/+qwYMHB5wXFRUV9voBAGhOhg8frtmzZys2NlbZ2dmKjo4Ou1Pj4+PlcrkCtjd+/Hjt379fjz32mHJycuTxeDRkyJCwX1sXFxdr9OjRmjVrVq3z2rVrd/g7DDQxDJmBMGVkZOjMM8/U008/ralTpwYcl3n37t164YUXdMUVV9Qqprr07NlT//rXvwJO+/TTTw9rjfv379eGDRv017/+Vaeeeqok6aOPPjqsbQIA0Fw88MADOuaYY9SzZ0//ab1799aKFSsCcitWrNBRRx0V9sC1ffv2dZ63atUqderUSZJ08OBBbdy4Ub179/Zfz5gxY3TZZZdJknw+nzZu3Kg+ffoEvb41a9bI5/PpT3/6k9zuH/7w8NVXXw1rrZLUtm1bZWdn69tvv9W4cePCvhwAAM1ZYmKiunfvHnDa4XTqihUr9PTTT+ucc86RJG3bts3qQ/sGDhyof/zjH+rcubOioxm/oeXjcBmAhSeffFIVFRUaOXKkPvzwQ23btk2LFi3SmWeeqfbt2+u+++4Le1vXXXedvvnmG02bNk0bN27Uq6++6v8QonAH1T/VqlUrZWRk6C9/+Ys2b96sDz74QDfddJOjbQEA0Nz069dP48aN0+OPP+4/7eabb9aSJUt0zz33aOPGjXruuef05JNP6pZbbqmX67z77ru1ZMkSrV+/XhMmTFDr1q113nnnSZJ69Oih999/Xx9//LG+/vprXXfddcrLywu5ze7du6uqqkpPPPGEvv32W82fP9//gYDhuuuuuzRz5kw9/vjj2rhxo7744gvNnTtXDz/8sJPdBACgWTqcTu3Ro4fmz5+vr7/+Wp988onGjRsX8GazUCZNmqQDBw7okksu0aeffqotW7bo3Xff1ZVXXhlwyC6gpWDIDFjo0aOHVq9era5du+qiiy5St27ddO2112r48OFauXKl0tPTw95Wly5d9Nprr+n1119X//79NXv2bN12222SJI/H42h9brdbL7/8stasWaO+fftq6tSpevDBBx1tCwCA5ujuu+8OOIzUwIED9eqrr+rll19W3759dccdd+juu+92dEiNQ3nggQf0m9/8RoMGDdLu3bv19ttvKzY2VpL0hz/8QQMHDtTIkSM1bNgwZWVl+QfQwQwYMEAPP/ywZs2apb59++qFF17QzJkzrdZ1zTXX6JlnntHcuXPVr18/DR06VPPmzVOXLl2c7CYAAM3S4XTqs88+q4MHD2rgwIG6/PLL9etf/1pt2rQJ+7qzs7O1YsUKeb1enXXWWerXr5+mTJmitLQ0/7uqgZbEZcL9FDMADe6+++7TnDlztG3btkgvBQAABLFs2TINHz5cBw8eVFpaWqSXAwAAAEQUB4UBIujpp5/W8ccfr4yMDK1YsUIPPvigJk+eHOllAQAAAAAAAGFjyAxE0KZNm3TvvffqwIED6tSpk26++WZNnz490ssCAAAAAAAAwsbhMgAAAAAAAAAAjnGkcQAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4Nj/AzhoMuDMrhWJAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB3C0lEQVR4nO3dd3hUZf7+8XvSJr2RhCSU0HtRUREbqEhRWbCjqGAX0RX7shYQC/aCBWwL9rorKKtIEXBBUEARUbpRUCDUJKSXeX5/+GO+xiQz8xwSUni/rivXRWbunPOcmUlu5pPJGZcxxggAAAAAAAAAAAeC6noBAAAAAAAAAICGiyEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEz4JDL5dKECRPqehk+jRo1StHR0XW9jBr3yy+/yOVy6fHHH6/rpQAAAAAAABz2GDKjVmVmZuqGG25Qhw4dFBkZqcjISHXp0kVjxozR6tWr63p5tapfv35yuVx+Pw52UF1QUKAJEyZo4cKFNbLu+rY/AAAAAAAA1G8hdb0ANF6zZs3ShRdeqJCQEI0YMUI9e/ZUUFCQ1q1bp//85z+aMmWKMjMzlZGRUddLrRV33XWXrrrqKu/ny5cv1+TJk/XPf/5TnTt39l7eo0ePg9pPQUGB7rvvPkl/DLZr26HeHwAAAAAAAOo3hsyoFZs3b9bw4cOVkZGh+fPnKy0trcL1jzzyiF544QUFBfl+MX1+fr6ioqJqc6m15vTTT6/weXh4uCZPnqzTTz/d53C2IR9zVRrb8QAAAAAAAKAiTpeBWvHoo48qPz9f06ZNqzRglqSQkBD9/e9/V4sWLbyXHTh/8ObNm3XGGWcoJiZGI0aMkPTHoPLWW29VixYt5Ha71bFjRz3++OMyxni//sB5eqdPn15pf389LcWECRPkcrm0adMmjRo1SvHx8YqLi9Pll1+ugoKCCl9bXFysm2++WcnJyYqJidHf/vY3/fbbbwd5C1Vcx08//aSLL75YCQkJOvHEEyX98SrhqobRo0aNUqtWrbzHnJycLEm67777qj0Fx++//65hw4YpOjpaycnJuu2221ReXl4hs337dq1bt06lpaXVrtff/nzdh61atdKoUaMqbbOq4ywqKtKECRPUoUMHhYeHKy0tTeecc442b95c7dqMMbrmmmsUFham//znP9XmAAAAAAAAULN4JTNqxaxZs9SuXTv17t3b6uvKyso0cOBAnXjiiXr88ccVGRkpY4z+9re/acGCBbryyit1xBFH6PPPP9ftt9+u33//XU899ZTjdV5wwQVq3bq1Jk2apG+//VavvPKKUlJS9Mgjj3gzV111ld58801dfPHFOv744/XFF1/ozDPPdLzPqpx//vlq3769HnrooQqDc3+Sk5M1ZcoUjR49WmeffbbOOeccSRVPwVFeXq6BAweqd+/eevzxxzVv3jw98cQTatu2rUaPHu3NjRs3Tq+99poyMzO9Q2wn+6vqPrRRXl6us846S/Pnz9fw4cN10003af/+/Zo7d67WrFmjtm3bVvk1V1xxhd577z199NFHNX7/AAAAAAAAoHoMmVHjcnNztW3bNg0bNqzSddnZ2SorK/N+HhUVpYiICO/nxcXFOv/88zVp0iTvZTNnztQXX3yhBx54QHfddZckacyYMTr//PP1zDPP6IYbbqhy8BiII488Uq+++qr38z179ujVV1/1Dpm///57vfnmm7r++uv1/PPPe/c9YsSIGn3jwp49e+rtt9+2/rqoqCidd955Gj16tHr06KFLLrmkUqaoqEgXXnih7rnnHknSddddp6OOOkqvvvpqhSFzTe2vqvvQxuuvv6758+frySef1M033+y9/B//+EeVA/iysjJdcskl+vjjj/Xxxx9rwIABjvYLAAAAAAAAZzhdBmpcbm6uJCk6OrrSdf369VNycrL348Dg9s/+Ovj89NNPFRwcrL///e8VLr/11ltljNFnn33meK3XXXddhc9POukk7dmzx3sMn376qSRV2vfYsWMd7zOQddS0qo7z559/rnDZ9OnTZYyp9lXMNmyH13/273//W0lJSbrxxhsrXedyuSp8XlJSovPPP1+zZs3Sp59+yoAZAAAAAACgDvBKZtS4mJgYSVJeXl6l61588UXt379fWVlZVb4KNiQkRM2bN69w2a+//qr09HTvdg/o3Lmz93qnWrZsWeHzhIQESdK+ffsUGxurX3/9VUFBQZVeKd2xY0fH+6xK69ata3R7fxYeHu49j/IBCQkJ2rdvX63sr6r70MbmzZvVsWNHhYT4//E0adIk5eXl6bPPPvP5ZooAAAAAAACoPbySGTUuLi5OaWlpWrNmTaXrevfurf79++uEE06o8mvdbreCgpw9LP/6KtcD/voGd38WHBxc5eU250WuCX8+ZcgBTo6nKtUdY22p7j6sqeP5s4EDByoqKkqPPvqoioqKHG8HAAAAAAAAzjFkRq0488wztWnTJn3zzTcHva2MjAxt27ZN+/fvr3D5unXrvNdL//cq5Ozs7Aq5g3mlc0ZGhjwejzZv3lzh8vXr1zveZqASEhIqHYtU+XiqG97WFqf7C/R42rZtq/Xr16u0tNTvNo877jjNmDFDX331lc4///wK5/sGAAAAAADAocGQGbXijjvuUGRkpK644gplZWVVut7mlcJnnHGGysvL9dxzz1W4/KmnnpLL5dLgwYMlSbGxsUpKStKXX35ZIffCCy84OII/HNj25MmTK1z+9NNPO95moNq2bat169Zp165d3su+//57LVmypEIuMjJSUuXhuq3t27dr3bp1foe7TvfXtm1bLVu2TCUlJd7LZs2apa1bt1bInXvuudq9e3el+1uq+nHTv39/vfvuu5o9e7YuvfRSeTweq3UBAAAAAADg4HBOZtSK9u3b6+2339ZFF12kjh07asSIEerZs6eMMcrMzNTbb7+toKCggM7dO2TIEJ1yyim666679Msvv6hnz56aM2eOZs6cqbFjx1Y4X/JVV12lhx9+WFdddZWOPvpoffnll9qwYYPj4zjiiCN00UUX6YUXXlBOTo6OP/54zZ8/X5s2bXK8zUBdccUVevLJJzVw4EBdeeWV2rlzp6ZOnaquXbt635hQ+uNUG126dNF7772nDh06KDExUd26dVO3bt2s9jdu3Di99tpryszM9Pnmf073d9VVV+nDDz/UoEGDdMEFF2jz5s168803K53v+rLLLtPrr7+uW265Rd98841OOukk5efna968ebr++us1dOjQStseNmyYpk2bpssuu0yxsbF68cUXrY4dAAAAAAAAzvFKZtSaoUOH6ocfftDFF1+sOXPm6KabbtLNN9+smTNn6swzz9S3336r4cOH+91OUFCQPv74Y40dO1azZs3S2LFj9dNPP+mxxx7Tk08+WSF777336sorr9SHH36oO+64Q+Xl5frss88O6jj+9a9/6e9//7tmz56tO+64Q6Wlpfrvf/97UNsMROfOnfX6668rJydHt9xyiz7++GO98cYbOuqooyplX3nlFTVr1kw333yzLrroIn344Ye1ujYn+xs4cKCeeOIJbdiwQWPHjtXSpUs1a9asSr9oCA4O1qeffqq77rpLX3/9tcaOHasnn3xSsbGx6t69e7Xbv+SSS/T888/rpZde0u23337QxwgAAAAAAIDAuMyhfoczAAAAAAAAAECjwSuZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAABzWiouLdcUVV6hly5aKjY3Vcccdp6VLl9b1sgAAgB90OFB/MGQGAADAYa2srEytWrXS4sWLlZ2drbFjx2rIkCHKy8ur66UBAAAf6HCg/mDIDAAAgBo1YcIEuVyuOtv/o48+qk6dOsnj8QSUj4qK0r333quWLVsqKChIw4cPV1hYmNavX1/LKz38TJ06VS1btlRxcXFdLwUAUE/Z9DgdfmjR4/CFITPwJ61atdKoUaO8ny9cuFAul0sLFy6ssX24XC5NmDChxrbXGLRq1UpnnXVWXS8DANAI5Obm6pFHHtGdd96poKDK/9WdPn26OnXqpJtvvrnabWzcuFF79+5Vu3btDmotB/4fUdXHsmXL/H79jz/+qPPPP19t2rRRZGSkkpKSdPLJJ+uTTz6pMl9cXKw777xT6enpioiIUO/evTV37twqsytXrtSgQYMUGxurmJgYDRgwQKtWrXKUW758uW644QZ17dpVUVFRatmypS644AJt2LCh0vZGjRqlkpISvfjii36PHwDQsH311VeaMGGCsrOzA/4aXz1+KDtcsu/hqgTSo0728+233+pvf/ubEhMTFRkZqW7dumny5MkVMnl5eRo/frwGDRqkxMREuVwuTZ8+/aCOkx6HLwyZUW9Mnz69wpOv8PBwdejQQTfccIOysrLqenlWPv300wYzSH7hhReqLBoAABqif/3rXyorK9NFF11U6bpNmzbpuuuu04UXXqiPP/64yq8vLCzUJZdconHjxikuLq5G1vT3v/9db7zxRoWPQJ78/vrrr9q/f79GjhypZ555Rvfcc48k6W9/+5teeumlSvlRo0bpySef1IgRI/TMM88oODhYZ5xxhhYvXlwh9+233+rEE0/Uzz//rPHjx+vee+/Vxo0b1bdv3wqv/Ao098gjj+jf//63TjvtND3zzDO65ppr9OWXX+qoo47SmjVrKuw7PDxcI0eO1JNPPiljjNXtCABoWL766ivdd999VkPm6nq8Ljrctof/KtAetd3PnDlz1KdPH+3cuVP33HOPnnnmGZ111ln67bffKuR2796tiRMnau3aterZs2eNHCc9Dp8MUE9MmzbNSDITJ040b7zxhnn55ZfNyJEjTVBQkGndurXJz8+v9TVkZGSYkSNHej8vLy83hYWFpry83Go7Y8aMMdV9exUWFprS0tKDWWaN6tq1q+nbt2+driEjI8OceeaZdboGAEDNGT9+fLU9WNt69OhhLrnkkiqvu+uuu8xZZ51lnn32WdO7d+9K15eUlJgzzzzTXHzxxcbj8Rz0WhYsWGAkmQ8++OCgt3VAWVmZ6dmzp+nYsWOFy7/++msjyTz22GPeywoLC03btm1Nnz59KmTPOOMMk5CQYHbv3u29bNu2bSY6Otqcc8451rklS5aY4uLiCvvYsGGDcbvdZsSIEZWOYcWKFUaSmT9/vuXRAwAakscee8xIMpmZmQF/TXU9fqg7vDrV9XBVAu1Rm/3k5OSYpk2bmrPPPtvvnKKoqMhs377dGGPM8uXLjSQzbdo0v+v2tX9j6HFUj1cyo94ZPHiwLrnkEl111VWaPn26xo4dq8zMTM2cObPar8nPz6+VtQQFBSk8PLzKP7d1Kjw8XCEhITW2vUOptm5nAEDDtXjxYh1zzDEKDw9X27Ztq/3zye+++06DBw9WbGysoqOjddppp1V5yoiFCxfq6KOPrrC9QM/xnJmZqdWrV6t///5VXv/JJ5/ob3/7m5YvX64jjzyywnUej0eXXnqpXC6XXnvttRo/p/T+/ftVVlZ20NsJDg5WixYtKr0q7MMPP1RwcLCuueYa72Xh4eG68sortXTpUm3dutV7+f/+9z/1799fTZo08V6Wlpamvn37atasWd43Swo0d/zxxyssLKzCetq3b6+uXbtq7dq1lY6hV69eSkxM9Pl/OwBA3fn999915ZVXKj09XW63W61bt9bo0aNVUlIi6Y9Xvl5//fXq2LGjIiIi1KRJE51//vn65ZdfvNuYMGGCbr/9dklS69atvX+x/OfMX/nq8brs8D+rroerEmiP2uzn7bffVlZWlh588EEFBQUpPz+/2nNXu91upaamBnRcge5fosdRPYbMqPdOPfVUSX8UjvTHn4JGR0dr8+bNOuOMMxQTE6MRI0ZI+qNcnn76aXXt2lXh4eFq2rSprr32Wu3bt6/CNo0xeuCBB9S8eXNFRkbqlFNO0Y8//lhp39Wdk/nrr7/WGWecoYSEBEVFRalHjx565plnvOt7/vnnJanC6T8OqOqczIE88T5wOpElS5bolltuUXJysqKionT22Wdr165dFbI5OTlat26dcnJyfN62rVq10o8//qhFixZ519mvX78K+1u0aJGuv/56paSkqHnz5t5jbNWqVaXtVTcEePPNN3XssccqMjJSCQkJOvnkkzVnzhyfa3vttdcUEhLi/Y8JAKD++eGHHzRgwADt3LlTEyZM0OWXX67x48fro48+qpD78ccfddJJJ+n777/XHXfcoXvuuUeZmZnq16+fvv76a2/uu+++06BBg7Rnzx7dd999uvLKKzVx4kTNmDEjoPV89dVXkqSjjjqq0nW7d+/WDz/8oL59+2revHk67bTTKlx/7bXXavv27frggw+q/GVwaWmpdu/eHdDHX5/sXX755YqNjVV4eLhOOeUUrVixIqDjOSA/P1+7d+/W5s2b9dRTT+mzzz6rtP7vvvtOHTp0UGxsbIXLjz32WEmqcP7H4uJiRUREVNpPZGSkSkpKvKe4CDRXFWOMsrKylJSUVOX1Rx11lJYsWVLt1wMA6sa2bdt07LHH6t1339WFF16oyZMn69JLL9WiRYtUUFAg6Y9z8X/11VcaPny4Jk+erOuuu07z589Xv379vJlzzjnHe8qLp556ynu6qOTk5Gr3XV2P12WHS4H1cFVsezSQ/cybN0+xsbH6/fff1bFjR0VHRys2NlajR49WUVGR3zX5YnOc9DiqVNcvpQYOOHC6jOXLl1e4/JlnnjGSzNSpU40xxowcOdK43W7Ttm1bM3LkSDN16lTz+uuvG2OMueqqq0xISIi5+uqrzdSpU82dd95poqKizDHHHGNKSkq827z77ruNJHPGGWeY5557zlxxxRUmPT3dJCUlVThdxoE/c12wYIH3sjlz5piwsDCTkZFhxo8fb6ZMmWL+/ve/m/79+xtjjPnqq6/M6aefbiSZN954w/txgCQzfvx47+dr1qwxUVFRJi0tzdx///3m4YcfNq1btzZut9ssW7as0u1z5JFHmlNPPdU8++yz5tZbbzXBwcHmggsuqPK29PenMB999JFp3ry56dSpk3edc+bMqbCNLl26mL59+5pnn33WPPzww977ICMjo9L2qvrz6AkTJhhJ5vjjjzePPfaYeeaZZ8zFF19s7rzzTm/mr6fLePHFF43L5TJ33XWXz/UDAOrWsGHDTHh4uPn111+9l/30008mODi4Qh8MGzbMhIWFmc2bN3sv27Ztm4mJiTEnn3yy97IhQ4aYyMhI8/vvv3sv27hxowkJCQno9BsH+n3//v2Vrvv4449NQkKCWbBggYmLizMFBQXe63755RcjyYSHh5uoqCjvx5dffunNHPg/QSAfB/4seMmSJebcc881r776qpk5c6aZNGmSadKkiQkPDzfffvut3+M54Nprr/VuOygoyJx33nlm7969FTJdu3Y1p556aqWv/fHHHyv8P8oYY7p37246dOhgysrKvJcVFxebli1bGknmww8/tMpV5Y033jCSzKuvvlrl9ddcc42JiIgI7AYAABwyl112mQkKCqr0vNwY4z0NxZ879IClS5caSd7n5sbYny6juh6viw7/s0B6uCq2PRrIfnr06GEiIyNNZGSkufHGG82///1vc+ONNxpJZvjw4dWuJZDTZdgcJz2OqjTMv9lHo5aTk6Pdu3erqKhIS5Ys0cSJExUREaGzzjrLmykuLtb555+vSZMmeS9bvHixXnnlFb311lu6+OKLvZefcsopGjRokD744ANdfPHF2rVrlx599FGdeeaZ+uSTT7yvvL3rrrv00EMP+VxbeXm5rr32WqWlpWnVqlWKj4/3Xmf+/0nv+/Tpow4dOmju3Lm65JJL/B7v3XffrdLSUi1evFht2rSRJF122WXq2LGj7rjjDi1atKhCvkmTJpozZ4533R6PR5MnT1ZOTo71mxsMGzZMd999t5KSkqpda2JioubPn6/g4GCrbUt/vDnDxIkTdfbZZ+vDDz+scNoRU82bBEyePFljx47VxIkTdffdd1vvEwBwaJSXl+vzzz/XsGHD1LJlS+/lnTt31sCBA/Xpp596c3PmzNGwYcO8PSf98eeiF198sV5++WXl5uYqKipK8+bN09lnn6309HRvrl27dho8eHBA7+S+Z88ehYSEKDo6utJ1y5cvV/fu3TV16lSNGDGiwiuLMjIy/L55Tc+ePTV37ly/a5Dk/dPU448/Xscff7z38r/97W8677zz1KNHD40bN06zZ88OaHtjx47Veeedp23btun9999XeXm590+WDygsLJTb7a70teHh4d7rD7j++us1evRoXXnllbrjjjvk8Xj0wAMPaPv27RWygeb+at26dRozZoz69OmjkSNHVplJSEhQYWGhCgoKFBkZGdDtAACoXR6PRzNmzNCQIUN09NFHV7r+wHPQP3doaWmpcnNz1a5dO8XHx+vbb7/VpZde6mj/1fV4XXT4nwXSw1Wx7dFA9pOXl6eCggJdd911mjx5sqQ/XjVeUlKiF198URMnTlT79u0DOtaDOU56HFVhyIx656/nX8rIyNBbb72lZs2aVbh89OjRFT7/4IMPFBcXp9NPP127d+/2Xt6rVy9FR0drwYIFuvjiizVv3jyVlJToxhtvrHBqh7Fjx/odMn/33XfKzMzUU089VWHALMnReZ8CfeL95z99veaaayrs66STTtJTTz2lX3/9VT169JD0x+ksRo0aZb2eqlx99dWOBsySNGPGDHk8Ht17772Vzmtd1e316KOP6s4779Sjjz7KaTIAoJ7btWuXCgsLq3wi07FjR++QedeuXSooKFDHjh0r5Tp37iyPx6OtW7cqMTFRhYWFateuXaVcVZfZWr9+vYKDgzVz5kz99NNP1l+fkJBQ7bmebbRr105Dhw7Vf/7zH5WXlwfUsZ06dVKnTp0k/fGL6AEDBmjIkCH6+uuvKzzhLy4urvS1B/509s9PyK+77jpt3bpVjz32mF577TVJ0tFHH6077rhDDz74oPfJfaC5P9uxY4fOPPNMxcXFec8TXZUDA4HaPG8mAMDOrl27lJubq27duvnMFRYWatKkSZo2bZp+//33CkNef6dsdKKuOzyQHq6KbY8G2veSvKciOeDiiy/Wiy++qKVLlzoeMtscJz2OqnBOZtQ7zz//vObOnasFCxbop59+0s8//6yBAwdWyISEhHjPD3zAxo0blZOTo5SUFCUnJ1f4yMvL086dOyX98SYFkir94E1OTlZCQoLPtW3evFmS/JZuoAJ94v1nf361mCTvmv963uma0rp1a8dfu3nzZgUFBalLly5+s4sWLdKdd96pO++8kwEzAMCRJk2aqKysTPv376903e7du/Xll19q+PDhjrqtpKREO3bsCOijvLzc57ZatGihkpISx2+oe95552n58uXasGGD97K0tDTvK6P+7MBlf351uCQ9+OCDysrK0v/+9z+tXr1ay5cv956HskOHDtY56Y/BwuDBg5Wdna3Zs2dX2uef7du3T5GRkVWeqxIAUL/deOONevDBB3XBBRfo/fff15w5czR37lw1adKk2jehC0R1PV6fOlyquoerY9OjgeznQLc2bdq0QjYlJUVSzc4FfB0nPY6q8Epm1DvHHntslX+a82dut7vSK2M9Ho9SUlL01ltvVfk1vt5goCHx94qgmlZVaVT328pACrk6Xbt2VXZ2tt544w1de+21BzXcBgDUvuTkZEVERGjjxo2Vrlu/fn2FXGRkZIXLDli3bp2CgoLUokULRUVFKTw8XJs2baqUq+qyqhx49U1mZqb3r3sOCAoKktvt1gMPPBDQtv7qq6++0imnnBJQNjMzs8o3yD3g559/Vnh4eJWvBA7EgT+v/fOrxY444ggtWLCg0l9AHXhjxSOOOKLSdhISEnTiiSd6P583b56aN2/uvR1tckVFRRoyZIg2bNigefPm+f0Fc2Zmpjp37hzA0QIADpXk5GTFxsb6fGNXSfrwww81cuRIPfHEE97LioqKlJ2dXSFn+yrX6nq8PnW4VHUP+xJo3wayn169emnu3LneN/47YNu2bZJqdu7h6zjpcVSFITMajbZt22revHk64YQTfP42LSMjQ9Ifr3z+8ykqdu3a5fe3fm3btpUkrVmzxuef2wRapoE+8a5NTv68JSEhodJ/IKT/e5X4AW3btpXH49FPP/1U5ZPbP0tKStKHH36oE088UaeddpoWL17s8xVQAIC6FRwcrIEDB2rGjBnasmWL9y9t1q5dq88//7xCbsCAAZo5c6Z++eUX7xO3rKwsvf322zrxxBO9Q9H+/ftrxowZ2rZtm7cDNm3apM8++yygNfXp00eStGLFigpPTo0x2rdvny677LJKp98KlJPzOe7atavSk73vv/9eH3/8sQYPHlzhF+YFBQXasmWLkpKSlJSUJEnauXOn95VJB5SWlur1119XREREhUHueeedp8cff1wvvfSSbrvtNkl/vIfFtGnT1Lt3b7//n3jvvfe0fPlyPf7445V+ke8vV15ergsvvFBLly7VzJkzvfeDL99++61GjBjhNwcAOHSCgoI0bNgwvfnmm1qxYkWlF38ZY+RyuRQcHFzpRU7PPvtspRcdRUVFSVKVzx2rUlWP11WHS3Y9XFWPV6WqHrXZzwUXXKCHH35Yr776qk499VTv5a+88opCQkLUr1+/gI7zz2z2fwA9jqowZEajccEFF+iFF17Q/fffX+ncymVlZcrLy1N8fLz69++v0NBQPfvssxowYIB3yPr000/73cdRRx2l1q1b6+mnn9aoUaMqvfHfgW39uUz/eu7mP7N54m0jJydH27dvV1pamt83A4yKigq49A9o27atcnJytHr1am/5b9++XR999FGF3LBhw3TnnXdq4sSJVb7x318H3M2bN9e8efN00kkn6fTTT9eXX36pJk2aWK0NAHDo3HfffZo9e7ZOOukkXX/99SorK9Ozzz6rrl27avXq1d7cAw88oLlz5+rEE0/U9ddfr5CQEL344osqLi7Wo48+6s1NmDBBc+bM0QknnKDRo0ervLxczz33nLp166ZVq1b5XU+bNm3UrVs3zZs3T1dccYX38pdfftn79R6PR//85z919NFH67zzzgv4WJ2cz/HCCy9URESEjj/+eKWkpOinn37SSy+9pMjISD388MMVst98841OOeUUjR8/XhMmTJAkXXvttcrNzdXJJ5+sZs2aaceOHXrrrbe0bt06PfHEExVeCd27d2+df/75GjdunHbu3Kl27drptdde0y+//KJXX321wr6+/PJLTZw4UQMGDFCTJk20bNkyTZs2TYMGDdJNN91knbv11lv18ccfa8iQIdq7d6/efPPNCvv765sLr1y5Unv37tXQoUOtbk8AQO176KGHNGfOHPXt21fXXHONOnfurO3bt+uDDz7Q4sWLFR8fr7POOktvvPGG4uLi1KVLFy1dulTz5s2r9NytV69ekqS77rpLw4cPV2hoqIYMGeJ9vvxXVfV4XXW4ZNfDVfV4oD1qs58jjzxSV1xxhf71r3+prKxMffv21cKFC/XBBx9o3LhxlV6o9dxzzyk7O9v7SudPPvlEv/32m6Q/TnsSFxdntX+JHocPBqgnpk2bZiSZ5cuX+8yNHDnSREVFVXndtddeaySZwYMHm6eeeso899xz5qabbjLp6enmgw8+8ObGjRtnJJkzzjjDPPfcc+bKK6806enpJikpyYwcOdKbW7BggZFkFixY4L1s9uzZJjQ01GRkZJgJEyaYF1980dx8881mwIAB3sz7779vJJlLL73UvPnmm+add97xXifJjB8/3vv5mjVrTFRUlGnWrJl58MEHzSOPPGLatGlj3G63WbZsmd/bp6o1HshOmzbN521pjDHXX3+9cblc5v777zfvvPOOmT9/vs/9GWPM7t27TVRUlGnTpo15+umnzUMPPWRatGhhjjrqKPPXHyv33HOPkWSOP/548/jjj5tnn33WXHbZZeYf//iHN5ORkWHOPPNM7+erV682iYmJplevXiYnJ8fvMQAA6s6iRYtMr169TFhYmGnTpo2ZOnWqGT9+fKU++Pbbb83AgQNNdHS0iYyMNKeccor56quvKm1v/vz55sgjjzRhYWGmbdu25pVXXjG33nqrCQ8PD2g9Tz75pImOjjYFBQXGGGMKCwvN0KFDzWeffWb69+9vWrVqZa6//npTVlZ28AfvxzPPPGOOPfZYk5iYaEJCQkxaWpq55JJLzMaNGytlD/T5n/+P8M4775j+/fubpk2bmpCQEJOQkGD69+9vZs6cWeX+CgsLzW233WZSU1ON2+02xxxzjJk9e3al3KZNm8yAAQNMUlKScbvdplOnTmbSpEmmuLjYUa5v375GUrUff3XnnXeali1bGo/HE8jNCAA4xH799Vdz2WWXmeTkZON2u02bNm3MmDFjvD//9+3bZy6//HKTlJRkoqOjzcCBA826detMRkZGhefTxhhz//33m2bNmpmgoCAjyWRmZvrc9597vC473Bi7Hq6qxwPtUdu+LykpMRMmTDAZGRkmNDTUtGvXzjz11FNVZjMyMqrt5wP3he3+6XFUhyEz6o2aGDIbY8xLL71kevXqZSIiIkxMTIzp3r27ueOOO8y2bdu8mfLycnPfffeZtLQ0ExERYfr162fWrFlTqRSrGuAaY8zixYvN6aefbmJiYkxUVJTp0aOHefbZZ73Xl5WVmRtvvNEkJycbl8tV4QnWX4vHmMCeeNfWkHnHjh3mzDPPNDExMUaS6du3r8/9HTBnzhzTrVs3ExYWZjp27GjefPPNKocKxhjzr3/9yxx55JHG7XabhIQE07dvXzN37lzv9X8dMhtjzNdff21iYmLMySef7B0UAAAOT0OHDjXt2rULKJudnW0SExPNK6+8UsurghNFRUUmNTXVPP3003W9FABAPUSP12/0OHxxGVNL7xYGAAAAWCosLKzw3gobN25U165dNXLkSL388ssBbeORRx7RtGnT9NNPP/k8vzAOvalTp+qhhx7Sxo0b5Xa763o5AIB6iB6vv+hx+MKQGQAAAPVGWlqaRo0apTZt2ujXX3/VlClTVFxcrO+++07t27ev6+UBAAAAqAJv/AcAAIB6Y9CgQXrnnXe0Y8cOud1u9enTRw899BADZgAAAKAe45XMAAAAAAAAAADHOLkNAAAAAAAAAMAxhswAAAAAAAAAAMfq3TmZPR6Ptm3bppiYGLlcrrpeDgDgEDPGaP/+/UpPT+fdpBs4Oh0ADl/0eeNBnwPA4S3QTq93Q+Zt27apRYsWdb0MAEAd27p1q5o3b17Xy8BBoNMBAPR5w0efAwAk/51e74bMMTExkqRHXn1b4ZGRAX3Ntg2rrPax+9f1VvnycrubKaV5B6t889YdrfLxTe3+kxYeYbf+TWu/tspL0paf11jly/LyrfLBlvdBTHysVT7EHdhj7YBexx1vlW/Tzu4xUZS7zyq/9qfVVnmPp8QqX1pWZJVft/Ynq/z+nD1W+eKSYqt8WWmwVX7f3kKrvCTlFdjdRmXldvdBUlKCVT4+Icoq7zF5VvmyMqu4igoDf4/Z0tIyzf38S28foOGi0/2j0/2j032j032j0/2rrU6nzxsP+tw/+tw/+tw3+tw3+ty/+vAcvd4NmQ/8+U14ZKQiIgO7A9zh4Vb7CAsLs8rbFpjteiICLOoDIqOirfK2BRYeEWGVlyS3222VDyoptcrbFpjtekLC7fKRUXY/HKIt/3Md4rG7fSIj7e4zj8fuB3pJqd2fxbnddt9jxWGhVnkjj1XeJbvjDQmxu/3/+BrLH6eucqt4aKjd9sMsb9NyY7d927+ULC8LvMD+bx/8OWZDR6f7R6f7R6f7Rqf7Rqf7V9udTp83fPS5f/S5f/S5b/S5b/S5f/XhOXqtnRzr+eefV6tWrRQeHq7evXvrm2++qa1dAQCAWkKfAwDQONDpAIDaVCtD5vfee0+33HKLxo8fr2+//VY9e/bUwIEDtXPnztrYHQAAqAX0OQAAjQOdDgCobbUyZH7yySd19dVX6/LLL1eXLl00depURUZG6l//+lelbHFxsXJzcyt8AACAumfT5xKdDgBAfcVzdABAbavxIXNJSYlWrlyp/v37/99OgoLUv39/LV26tFJ+0qRJiouL837wrrUAANQ92z6X6HQAAOojnqMDAA6FGh8y7969W+Xl5WratGmFy5s2baodO3ZUyo8bN045OTnej61bt9b0kgAAgCXbPpfodAAA6iOeowMADgXLt1qseW632/pdRgEAQP1DpwMA0PDR5wAAJ2r8lcxJSUkKDg5WVlZWhcuzsrKUmppa07sDAAC1gD4HAKBxoNMBAIdCjQ+Zw8LC1KtXL82fP997mcfj0fz589WnT5+a3h0AAKgF9DkAAI0DnQ4AOBRq5XQZt9xyi0aOHKmjjz5axx57rJ5++mnl5+fr8ssvr43dAQCAWkCfAwDQONDpAIDaVitD5gsvvFC7du3Svffeqx07duiII47Q7NmzK73RgC/7s/eptLg4oGyT+ESr9ZnkwNchSSYk1iqf1rKNVb7cU2qVD/IUWOU9BWVW+aJ9e6zykmQKi6zyzZJSrPItW7Szyrdol2GVT2/W3CqfkmL3GAoNtTunWVl8pFW+RXO7P3MrKyuxyhcVFVrls/flWeV3795rlQ8JC7fKyxVsFU9oYn8OuvAou9soJ3efVd4dbvfj2mPsvu9DQ+yOOTcn2ypfUmwCzpaV2q0dtacm+lyi032h0/2j0/1sn073iU73r7Y6nT6vX3iOTp/7Q5/7Rp/7Rp/711D7XAq802vtjf9uuOEG3XDDDbW1eQAAcAjQ5wAANA50OgCgNtX4OZkBAAAAAAAAAIcPhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMdC6noB1SotlUJKA4qWFAeWO6CgoMQq36pDM6t8Xn6+Vb6ktMgqn5gUZ5UPCbX7XUL79h2s8pJ0/HFHW+WbNW1ulY+LS7bKl4aUW+Ujw91W+RBjFZerrMwqX5ifZ5UvLrX7HoiMiLTKJ8SnWOXbtulilV+7dr1VXi674y0uLrDKx8UmWOUlKTTMLp+Tm2WVN7L7ueXx2D1I9+2z+7lVWFBslTcWyykrt/t+QQNAp1eLTvePTveNTveNTvevtjqdPm+E6PNq0ef+0ee+0ee+0ef+1Yfn6LySGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI6F1PUCqlNWVKQylyugrKus3Grb7rAIq3zO7t1W+Sapza3yLbu2s8qntEi3yoeGhlnlVVZql5dUWlZklV+3fY9VvuDnXVb50qASq/z6H763yh/TuYtV/uRjj7HKG2Os8rm5OVb5Lb9us8qHhYbb5cNirfJJyc2s8lu2brTKh4VHWuXzCvOt8pKUm2v3cyIkNLCfbwfExtodQ2FhgVW+vMwqrrIyj1Xe7bb4OWT38EcDQKdXj073j073jU73jU73r9Y6nT5vdOjz6tHn/tHnvtHnvtHn/tWH5+i8khkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOhdT1AqpTXFggl/EElI2OCLfadmxislX+qJ5HWOVbtGlvld9fVmaVX//zVqt8bkGBVT4vO9sqL0l7svdY5bfv2GeVj42zu88UVGwVn/Xev63yoRfY/X6mb58T7bYfWmqVT01Nt8rL7LaKZ+/bb5X/9rvVVvmQULdVPiom1ipfVm6s8iV52VZ5SQq2/JVdcnKiVb68vMQqv2ev3X0cpEirfEiIXX3Ex8cFnC0ttXv8o/6j06tHpweATveNTveJTvevtjqdPm986PPq0ecBoM99o899os/9qw/P0XklMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABwLqesFVMftDpHbHRpQtjQ4xmrbhRHRVvnM3EKr/KrF31jl9+7Js8r/vi3LKh8a7LLLB3ms8pJUXFZilS8qssunJds9VHfu+NUqH+sOs8rvz861ym/IzLTKp6UlWeVDQ+1un7QWqVb5dMv8lh1brfLrf7DLp6QlW+V/2bLbKq9S++8BT4nd15SHlFvlw8PcVnl3SGA/Pw8oLLJbT2xsrFU+JCTw9RsPv/9sbOj06tHp/tHpfrZPp/tGp/tVW51Onzc+9Hn16HP/6HM/26fPfaPP/aoPz9FpfgAAAAAAAACAYwyZAQAAAAAAAACO1fiQecKECXK5XBU+OnXqVNO7AQAAtYg+BwCgcaDTAQCHQq2ck7lr166aN2/e/+0kpN6e+hkAAFSDPgcAoHGg0wEAta1WmiUkJESpqXYnIQcAAPULfQ4AQONApwMAalutnJN548aNSk9PV5s2bTRixAht2bKl2mxxcbFyc3MrfAAAgLpn0+cSnQ4AQH3Fc3QAQG2r8SFz7969NX36dM2ePVtTpkxRZmamTjrpJO3fv7/K/KRJkxQXF+f9aNGiRU0vCQAAWLLtc4lOBwCgPuI5OgDgUKjxIfPgwYN1/vnnq0ePHho4cKA+/fRTZWdn6/33368yP27cOOXk5Hg/tm7dWtNLAgAAlmz7XKLTAQCoj3iODgA4FGr9bP/x8fHq0KGDNm3aVOX1brdbbre7tpcBAAAOgr8+l+h0AAAaAp6jAwBqQ62ck/nP8vLytHnzZqWlpdX2rgAAQC2hzwEAaBzodABAbajxIfNtt92mRYsW6ZdfftFXX32ls88+W8HBwbroootqelcAAKCW0OcAADQOdDoA4FCo8dNl/Pbbb7rooou0Z88eJScn68QTT9SyZcuUnJxstZ2IiBRFREQGlN2ZXWa17U2W55T66cc1VvmgULubtby41CpfuD/fKh8c5LHbfrH9uwdn77f7mv35eVb5X35ba5WPioixynds29Eqr7ISq/iS/y20yme0bm2V79Cxg1W+SZM4q7w73O4xHRdr9+d1QWU5Vvn8YrvfjxUWFNvls6t/Y7PqlJcXWeXDI0Kt8nm5dmuKjYm1yrvDg63yJSV2P7cKCgoCzpaW2v1MR+2oqT6X6HRf6HT/6HTf6HQ/eTrdr9rqdPq8/uA5On0eCPrcN/rcN/rcv4ba51LgnV7jQ+Z33323pjcJAAAOMfocAIDGgU4HABwKtX5OZgAAAAAAAABA48WQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgWEhdL6A68QlNFBEZFVB209YNVtve/kumVT4ytNgqn5O/zyqfl7vTKu/yeKzy2fvz7PKFRVZ5SQpxh1rlk5qmWOUjYuKs8s1a9bTKtwgPtspnfr/UKh/sKrHKl5aXW+V37d5jle/evbNVvl37Nlb5FmnJVvno4460yq9et8UqX1wUbpcPtfsekySPYu3ypswqv2PHNqt8mNttlY9LsPuelPKt0oWFhQFnS0vtbhvUf3R69eh0/+h03+h0P3k6PQC10+n0eeNDn1ePPvePPveNPveTp88DUPfP0XklMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAAAAAAAAcIwhMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABwLqesFVCczc6Xc4eEBZddt3mS17W3bN1vly/fnW+Vj4qKs8h3bt7LKd+vczSq/fVehVf7XXXbHK0nJqU2t8hltW1vlY5qkWOWz9tkdg9mdaZXf8usWq/yu7D1W+c5drOI6vUNnq3x+nt1jwlNuFZcpKbHK/7hsqVW+fccjrPJNm8Vb5Zd986VVXpJ2ZOVa5UtLy6zyRYV2t+m+ffut8hHR8VZ5j/FY5fMLAv+eLCuzfMCh3qPTq0en+0en+0an+0an+1dbnU6fNz70efXoc//oc9/oc9/oc//qw3N0XskMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcC6nrBVRn+ZIFCgkNbHkhTTtabbtt5+5W+YgSj1W+c5f2VvmOHZpb5cuLgq3yJqjQKp+v3VZ5SQoJDbfKBwfHW+VLy9xW+fz9e63ycSVlVvmycmOV37Jzn1U+PPp3q3xcbIJVvk3bVlZ5Y/n7qMLsAqv8uq9XWeVNod33ZLeBg6zy3Xu0scpLUuGKXKv85k2/WOUjI6Ot8nHxTazyUrlVOjfX7jFdXBz4Y6KszG4tqP/o9OrR6f7R6b7R6b7R6f7VVqfT540PfV49+tw/+tw3+tw3+ty/+vAcnVcyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwLGQul5AdXb9vkfBwcEBZY/seabVtt3uZKt8YmDL8EpLj7XK783eb5XfummvVb7E47bKB7nKrfKSFBziscqXm2K7HZTZPVTLiwut8qbcbv3RcUlW+T15+Vb5oLAoq7zHGKu8ZJm3u3kUHW73PdAqvYVVPjzYbv1ByrPKd+/W2iovSfHx8Vb5jwvnWOV3bN9nlW+Wkm6VL3cVWeVDQ+2+J3NzcwPOlpaWSdpgtX3Ub3R69ej0ALZPp/tBp/tCp/tXW51Onzc+9Hn16PMAtk+f+0Gf+0Kf+1cfnqPzSmYAAAAAAAAAgGPWQ+Yvv/xSQ4YMUXp6ulwul2bMmFHhemOM7r33XqWlpSkiIkL9+/fXxo0ba2q9AACgBtDnAAA0DnQ6AKA+sB4y5+fnq2fPnnr++eervP7RRx/V5MmTNXXqVH399deKiorSwIEDVVRk9zJvAABQe+hzAAAaBzodAFAfWJ+TefDgwRo8eHCV1xlj9PTTT+vuu+/W0KFDJUmvv/66mjZtqhkzZmj48OEHt1oAAFAj6HMAABoHOh0AUB/U6DmZMzMztWPHDvXv3997WVxcnHr37q2lS5dW+TXFxcXKzc2t8AEAAOqOkz6X6HQAAOobnqMDAA6VGh0y79ixQ5LUtGnTCpc3bdrUe91fTZo0SXFxcd6PFi3s3sESAADULCd9LtHpAADUNzxHBwAcKjU6ZHZi3LhxysnJ8X5s3bq1rpcEAAAcoNMBAGj46HMAgBM1OmROTU2VJGVlZVW4PCsry3vdX7ndbsXGxlb4AAAAdcdJn0t0OgAA9Q3P0QEAh0qNDplbt26t1NRUzZ8/33tZbm6uvv76a/Xp06cmdwUAAGoJfQ4AQONApwMADpUQ2y/Iy8vTpk2bvJ9nZmZq1apVSkxMVMuWLTV27Fg98MADat++vVq3bq177rlH6enpGjZsWE2uGwAAHAT6HACAxoFOBwDUB9ZD5hUrVuiUU07xfn7LLbdIkkaOHKnp06frjjvuUH5+vq655hplZ2frxBNP1OzZsxUeHl5zqwYAAAeFPgcAoHGg0wEA9YH1kLlfv34yxlR7vcvl0sSJEzVx4sSDWlhEVIJCQgJbXmj1y6lSdvZOq7w7Md4qX1DmscoXFVnFFZEQY5V3e1x2Oygqt8tLMpaPpKLSAqt8eITdDoJcJVZ5T5Dd9qObpFvlw8xeq3xwRIJV3oQFW+U9Lrvb31UeZZUPCra7PUOjwqzyEdF2+bLi/Vb5Pb9n+Q/9RZOoZKv80DMGWuVXfP+LVT6v0O57oKh4l1W+uLDQKh8fEx9wtqSk1GrbcOZQ9blEp/tCp/tHp/tGp/tGp/tXW51Onx86PEf3jz4PYBf0uU/0uW/0uX8Ntc+lwDu9Rs/JDAAAAAAAAAA4vDBkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOBYSF0voDqpLTIUGhoWUNYVZDcrLyrKtcpn5drdTGHxSVb50rLAjvMAV2ioVb4wL88qX2rsf/cQEuK2ypcF2+UjY2Ot8ilNsq3yZm+hVb6ktMwq7/LY3aYRERFW+aBgq7g8xm795eXlVvmgULsFmWC72ycvf79V3uXxWOXdlj9TJCl3V5ZVPiIy0Sp/cp8eVvn1m3+1yq/5aYdVPi833yofFhoecLbU8vsL9R+dXj063T863Tc63Tc63b/a6nT6vPGhz6tHn/tHn/tGn/tGn/tXH56j80pmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOBZS1wuojnEFy7iCA8qWlpZZbbtg/36rvDsiwiq/P3evVb6kqNgqX5Brt/5Ql1VcMVFuuy+QlJyQaJWPTYyy23683X1QHhJnlS902z2G9makW+WLy7db5VVaYBUvLyuxyns8dg+K8iCPVd4VGtj37gHxiQlWeU+55e1j+TMiLs7u8SZJYS5jlc/en22VN6V5VvkjOqda5eNj7L7vZ82aY5XflbU74GxZWbnVtlH/0enVo9P9o9N9o9N9o9P9q61Op88bH/q8evS5f/S5b/S5b/S5f/XhOTqvZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjIXW9gGqVlUiuwKIhnhKrTceF2y2lRVyAC/n/OrWJt8pHh0dY5YNddr8byM/NtsoXFeRY5SUpIqrUKt+xfaJVvkVGc6t8UGiGVT4vO9sq3yItzSrfMXOnVT420e5BmpgQa5UPCQmzynuMVVwm2C4fHhVplS8rKrPKB1muPzTI/vdvRSq2yjdJirbK5xUUWOXzs3dY5ZslJ1vlhw0ZYJWf8d95AWdLS+3uXzQAdHq16HT/6HTf6HTf6HT/aqvT6fNGiD6vFn3uH33uG33uG33uX314js4rmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOBYSF0voDonHHuEIsIjAsq26dLTatvbfv/dKt8sPdEq36F9W6t8anKKVT7YuKzy+/dnW+WLSwus8pLkCrJbU3RUlF0+OtwqHxwW2GPngFBPiVW+MH+XVf6obhlW+VYdWlnlSz2lVnlj+fulMk+Z3faD7R4PwaF2P4pKi4xV3lNqt/6gEPvfv7nC7Y5ZlvsoLrW7j0OCQ63y5SXZVvnkpGir/IknHRNwtrCoWB99vMBq+6jf6PTq0en+0em+0em+0en+1Van0+eND31ePfrcP/rcN/rcN/rcv/rwHJ1XMgMAAAAAAAAAHLMeMn/55ZcaMmSI0tPT5XK5NGPGjArXjxo1Si6Xq8LHoEGDamq9AACgBtDnAAA0DnQ6AKA+sB4y5+fnq2fPnnr++eerzQwaNEjbt2/3frzzzjsHtUgAAFCz6HMAABoHOh0AUB9Yn5N58ODBGjx4sM+M2+1Wamqq40UBAIDaRZ8DANA40OkAgPqgVs7JvHDhQqWkpKhjx44aPXq09uzZU222uLhYubm5FT4AAEDds+lziU4HAKC+4jk6AKC21fiQedCgQXr99dc1f/58PfLII1q0aJEGDx6s8vLyKvOTJk1SXFyc96NFixY1vSQAAGDJts8lOh0AgPqI5+gAgEPB+nQZ/gwfPtz77+7du6tHjx5q27atFi5cqNNOO61Sfty4cbrlllu8n+fm5lJiAADUMds+l+h0AADqI56jAwAOhVo5XcaftWnTRklJSdq0aVOV17vdbsXGxlb4AAAA9Yu/PpfodAAAGgKeowMAakOtD5l/++037dmzR2lpabW9KwAAUEvocwAAGgc6HQBQG6xPl5GXl1fhN56ZmZlatWqVEhMTlZiYqPvuu0/nnnuuUlNTtXnzZt1xxx1q166dBg4cWKMLBwAAztHnAAA0DnQ6AKA+sB4yr1ixQqeccor38wPnaho5cqSmTJmi1atX67XXXlN2drbS09M1YMAA3X///XK73TW3agAAcFDocwAAGgc6HQBQH1gPmfv16ydjTLXXf/755we1oAOO7NpBUVFRAWW7HtnTatuF3dpa5aPi7M5B5bFKS8blssoHBYda5ROjUq3yxsFJVGy/xOOxu5XKSsvsdlBaahUvLi60yrdt19IqHxEW2GP5gML8HKu8CbL8VnbZ5Y2r+u/5qnh8/IyoSrnl94DHY7f9kkK7+7fcY3d/SVJQiOX3seV3zf49BVb5XzO3WuVPOPFIq3xB6X6rfGR44LePy9jdlnDmUPW5RKf7QqcHgE73jU73iU73r7Y6nT4/dHiO7h997h997ht97mf79LlfDbXPpcA7vdbPyQwAAAAAAAAAaLwYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwLKSuF1Cd8KgoRURFBZSNDndbbTsq0vKwQ4Kt4h5jt3mXy2WVD7LMe4zHLl9ql/9jH3YH7Qqy+/1GmezWFGR3E8m47NYTHZ9olS8rt1t/ucfuMSeP3QEblVvlg2xv0HK7fHlIqFXeyPKbrKzEKu7y2N0+kuS2vM9Cy+0ec1FFdts3WYVW+V0/Z1nlm3dsbpXfHZQXeDjI8v5FvUenV49O949O941O941O96/WOp0+b3To8+rR5/7R577R577R5/7Vh+fovJIZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjoXU9QKqEx2boJjo6ICyJjjUatsFxSVWeVNcbJUvttx+fl6+Vb6k1G77xcWlVvmyMo9VXpJKS+32UWp5DAUFBXb5/P1W+TKP3THHJMbZ5ePirfLxMUlW+fCwMKt8ucfu9perzCoeJLt8TEy4VX7PTrv1FxXmWeU9ngSrvCS5ZHcfeMrtfq7Exrit8hktm1rlCwvsfg4Zj919HBcTFXA2NDjYatuo/+j06tHpAeTpdJ/odN/odP9qq9Pp88aHPq8efR5Anj73iT73jT73rz48R+eVzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHAspK4XUJ3/fjpX4eHhAWXLQ/9nte19+7Ks8nk5u63yQcYqruLiEqt8Vpbd+ss9dgtKTE6xyktSQlITq7w72O6hl7832yq/YeNaq3xuXp5VvkXrDKt8cGioVT42xu72bN26pVW+eYtUu+23aWaVT3S7rPIx4Xa3jycu1iqv4GCreGl5md32JQWH2P3OLtjyNmraKskqHx7rtsqXmnKrfHCYVVyJiYHfZ2633eMB9R+dXj063T863Tc63Tc63b/a6nT6vPGhz6tHn/tHn/tGn/tGn/tXH56j80pmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOBZS1wuozoL/fa2QkNCAsvHNO1pt25TnWeW/+2qBVT6jeXOrfFKTJlb533/bYZUv85Rb5SMT463yklQS5LHKZ/221Sp/2rF9rPJH9OhqlS8oLrLKB4XafetkbvnVKr9h42ar/A9rvrPKx8dFW+XPPe9sq/wJXTtY5cOM3e+7mqe1sMqXBAdb5V1BLqu8JHmMscqXyu77MijELu+OD7fKRwTZ3Qee4BKrfGA/zf8QUm+bCU7R6dWj0/2j032j032j0/2rrU6nzxsf+rx69Ll/9Llv9Llv9Ll/9eE5Oq9kBgAAAAAAAAA4ZjVknjRpko455hjFxMQoJSVFw4YN0/r16ytkioqKNGbMGDVp0kTR0dE699xzlZWVVaOLBgAAB4dOBwCg4aPPAQD1hdWQedGiRRozZoyWLVumuXPnqrS0VAMGDFB+fr43c/PNN+uTTz7RBx98oEWLFmnbtm0655xzanzhAADAOTodAICGjz4HANQXVietmT17doXPp0+frpSUFK1cuVInn3yycnJy9Oqrr+rtt9/WqaeeKkmaNm2aOnfurGXLlum4446ruZUDAADH6HQAABo++hwAUF8c1DmZc3JyJEmJiYmSpJUrV6q0tFT9+/f3Zjp16qSWLVtq6dKlVW6juLhYubm5FT4AAMChRacDANDw0ecAgLrieMjs8Xg0duxYnXDCCerWrZskaceOHQoLC1N8fHyFbNOmTbVjR9Xvtjpp0iTFxcV5P1q0sHtHSgAAcHDodAAAGj76HABQlxwPmceMGaM1a9bo3XffPagFjBs3Tjk5Od6PrVu3HtT2AACAHTodAICGjz4HANQlq3MyH3DDDTdo1qxZ+vLLL9W8eXPv5ampqSopKVF2dnaF35RmZWUpNTW1ym253W653W4nywAAAAeJTgcAoOGjzwEAdc3qlczGGN1www366KOP9MUXX6h169YVru/Vq5dCQ0M1f/5872Xr16/Xli1b1KdPn5pZMQAAOGh0OgAADR99DgCoL6xeyTxmzBi9/fbbmjlzpmJiYrzncIqLi1NERITi4uJ05ZVX6pZbblFiYqJiY2N14403qk+fPrxrLQAA9QidDgBAw0efAwDqC6sh85QpUyRJ/fr1q3D5tGnTNGrUKEnSU089paCgIJ177rkqLi7WwIED9cILL9TIYgEAQM2g0wEAaPjocwBAfWE1ZDbG+M2Eh4fr+eef1/PPP+94UZI07LyLFBERGVDWndLeatsF+6t+F93qbPzhe6t8Wqrdu+8GBdm9/2JEeKxVvsRTaJXv0M3u9pSkhLQUq3xBUoJV/qzB/a3ykTERVvn84iKrvMdlFVeZ8Vjli8rs1rNz516r/K+Z26zykZF2j7kdv+2xyv/y40arfFCR3e3z846dVvljBxxtlZekjFbpVvnS8jKrfFB4mFVeoeVWcZfHbj1y2W0/zBX490BYqP+uwcGj0wNDp/tHp/tGp/tGpweggXY6fX5o0OeBoc/9o899o899o88D0ED7XAq80+1+cgIAAAAAAAAA8CcMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4FlLXC6iOOzRI7rDAZuAb1q2x2nZuzg6rvDHGKl9aUmKVz8vLt8q7XC6rfLg71CpfWrDfKi9JObvsbqOsLVut8p99/plVft9+u2PIycuxysfExlrl4xISrfJRsW6r/G+/bbPKpyQ1s8qHx6ZY5f/3X7v7a+/G1Vb58pJSq/ymHVlW+d/y7b8H2ndub5WPi420yyfEWeUjIsPtth9l93MiNDzYKh8ZGfhjuqTMY7Vt1H90evXodP/odN/odN/odP9qq9Pp88aHPq8efe4ffe4bfe4bfe5ffXiOziuZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4FhIXS+gOvv3ZqmsMCKg7Bcz/2u17a07frPKB5UWWuVXr861ysvlsoqXlZVZbt9jFZ876wu77UsKC3Vb5Y848iirfElYjFU+t7jAKv/zlp1W+T171lrlS4rs7oNtO36xymf+Yreeo4/sZZX/+5hbrPLfLFtqlS/L2WOVzy0utsoXyljlf16x1SovSf9bud0qHxVSapUPDQu2yge77b4nY6JCrfLNM1pZ5YeeOzzgbEGB3f2F+o9Orx6d7h+d7hud7hud7l9tdTp93vjQ59Wjz/2jz32jz32jz/2rD8/ReSUzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHAup6wVUJzWlqSIjowLKtm/V2mrbRh6rfEiQXT7Y5bLKBwXbzfqNx1jlw8IDux29QsPt8pLS05tZ5fsNHGiVj4mMtMrHhSdY5X9a871VfsOmzVb51GatrPJFxu4xERxhd/us2bDOKv/Thg1W+chWna3y27bZ3V8J8Xb5lLAwq3xkdIRVXpL27vjVKr/n901W+V27s6zyReV2PydKPXY/t7Zn29XH8acFvv3CQru1oP6j06tHp/tHp/tGp/tGp/tXW51Onzc+9Hn16HP/6HPf6HPf6HP/6sNzdF7JDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHAup6wVUZ9/ufSqKKA4oe1zv4622fXzfvlZ5tzvYKh8SbDe7Dwqyy3uMxyofLLv1l5aUW+UlqbCkwCq/57dMq/zeolK7/O69VvmfN222ym/bucMqH52SbpWXO9wq7gqLtMqXlAX2vXXA3EWLrfIZbbtb5VskNrPKhwfZ/eiKDHVb5YuL9lvlJenn3B+t8tExsVb5clNmld+xL88qn5TUyipfUGr3c+iLRd8EnC0tLbHaNuo/Or16dHoAeTrdJzrdNzrdv9rqdPq88aHPq0efB5Cnz32iz32jz/2rD8/ReSUzAAAAAAAAAMAxqyHzpEmTdMwxxygmJkYpKSkaNmyY1q9fXyHTr18/uVyuCh/XXXddjS4aAAAcHDodAICGjz4HANQXVkPmRYsWacyYMVq2bJnmzp2r0tJSDRgwQPn5+RVyV199tbZv3+79ePTRR2t00QAA4ODQ6QAANHz0OQCgvrA6acrs2bMrfD59+nSlpKRo5cqVOvnkk72XR0ZGKjU1NaBtFhcXq7j4/849k5uba7MkAADgAJ0OAEDDR58DAOqLgzonc05OjiQpMTGxwuVvvfWWkpKS1K1bN40bN04FBdWfcH7SpEmKi4vzfrRo0eJglgQAAByg0wEAaPjocwBAXbF7+8c/8Xg8Gjt2rE444QR169bNe/nFF1+sjIwMpaena/Xq1brzzju1fv16/ec//6lyO+PGjdMtt9zi/Tw3N5cSAwDgEKLTAQBo+OhzAEBdcjxkHjNmjNasWaPFixdXuPyaa67x/rt79+5KS0vTaaedps2bN6tt27aVtuN2u+V2u50uAwAAHCQ6HQCAho8+BwDUJUeny7jhhhs0a9YsLViwQM2bN/eZ7d27tyRp06ZNTnYFAABqEZ0OAEDDR58DAOqa1SuZjTG68cYb9dFHH2nhwoVq3bq1369ZtWqVJCktLc3RAgEAQM2j0wEAaPjocwBAfWE1ZB4zZozefvttzZw5UzExMdqxY4ckKS4uThEREdq8ebPefvttnXHGGWrSpIlWr16tm2++WSeffLJ69OhRKwcAAADs0ekAADR89DkAoL6wGjJPmTJFktSvX78Kl0+bNk2jRo1SWFiY5s2bp6efflr5+flq0aKFzj33XN199901tmAAAHDw6HQAABo++hwAUF9Yny7DlxYtWmjRokUHtaADIiPdiowI7M0G9uQWWW37u9UrrfIpKQlW+aYpSVb50tJSq/y+fdlWeRXZ3T4hHrv1SFKz1ulW+RYJMVb53zdst8rn5xVb5VOaplrlI5vEW+WDw2Ot8gWFdvdZWlpLq/yObb9Z5XfvybHKp6XnW+Vdfn62/FVeseVjNMTujUtKPeV225fkjoiyy7tcVvmSPbus8goKtYo3bdbKKl9SXGKVt7mLLR8OcIhODwyd7h+d7hud7hudHoAG2un0+aFBnweGPvePPveNPveNPg9AA+1zm6yjN/4DAAAAAAAAAEBiyAwAAAAAAAAAOAgMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjoXU9QKq4w7xyB3qCShbXJRtte2vvppvlTelRVb52MgIq3xpaZlVvqiw0CofYvm7hIxWLazyktTtuC5W+bYt063y2Vt/s8rv2LfbKh8W4bbKt22SapXftSvPKt+9YzerfNfuHa3y7775ulU+RGFW+dJ8u++ZkhK7vCkrt8or3O57LNht93iQpFat21jld25db7eDoGCreESU3TF07tzBKl9UYPeYbpGWEnC2uNju8YD6j06vHp3uH53uG53uG53uX211On3e+NDn1aPP/aPPfaPPfaPP/asPz9F5JTMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcC6nrBVSnoKhQcgUYDrKblQ8cfJZV3lOSb5UPLi2z2365xypvgoOt8sEhYVb58KhIq7wk7cgutMrvz95gld9baHebusLDrfLrV/1sld+zdJdVvk3rjlb5Y9q1t8qXFBZZ5SPC3FZ5U1pqlS+wXE9QsN2PIk+gPxv+v0KP3fdYSLnd402SMpq3scoX5e2xyneJjbLKf7PyO6v8tl/XW+UL8+1+LpqCfQFnS0pLrLaN+o9Orx6d7h+d7hud7hud7l9tdTp93vjQ59Wjz/2jz32jz32jz/2rD8/ReSUzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHAup6wVUJyoqVJGRYQFl44zdtmOSO1jli4uLrfLhlrP7MFdgx3mAiYiwyrsDvB0P8BTlWeUlaf/+XKt8cGSsVT6lbbxVvm3kbqv8xszNVnm5gq3ioZFuq/zv27dY5ZskJdRqvqQw3ypfXJxjlc/PL7LbfoHdY7S0uMAqHxIeaZWXpKbpyVb5X7dnWeWzttg9Rovy7O6DzT+usso3aWJ3vCYhMfBsqcdq26j/6PTq0en+0ek1m6fT/aPTfQu00+nzxoc+rx597h99XrN5+tw/+ty32niOziuZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4FhIXS+gOgV5m6Ty8MDCHrtZeagr2iqflZVjld/40y9W+fCQCKt8WFy8VT4pJcEqn54UZ5WXpJAgu/ugSVwTq3y5xyquosJ9VvmUlFirfLP0RKv89h07rPIbNqy1yrcqaW2VLy4utsrv32/3PVBQkGWVz83JtcoXF+RZ5ctLCq3ywe4oq7wk/bgmySpfUlxilU9JaWqVb9ajm932k+22n5ScapUPt7hNi4qLrLaN+o9Orx6d7h+d7hud7hud7l9tdTp93vjQ59Wjz/2jz32jz32jz/2rD8/ReSUzAAAAAAAAAMAxqyHzlClT1KNHD8XGxio2NlZ9+vTRZ5995r2+qKhIY8aMUZMmTRQdHa1zzz1XWVl2vy0BAAC1j04HAKDho88BAPWF1ZC5efPmevjhh7Vy5UqtWLFCp556qoYOHaoff/xRknTzzTfrk08+0QcffKBFixZp27ZtOuecc2pl4QAAwDk6HQCAho8+BwDUF1bnZB4yZEiFzx988EFNmTJFy5YtU/PmzfXqq6/q7bff1qmnnipJmjZtmjp37qxly5bpuOOOq7lVAwCAg0KnAwDQ8NHnAID6wvE5mcvLy/Xuu+8qPz9fffr00cqVK1VaWqr+/ft7M506dVLLli21dOnSardTXFys3NzcCh8AAODQodMBAGj46HMAQF2yHjL/8MMPio6Oltvt1nXXXaePPvpIXbp00Y4dOxQWFqb4+PgK+aZNm2qHj3ftnDRpkuLi4rwfLVq0sD4IAABgj04HAKDho88BAPWB9ZC5Y8eOWrVqlb7++muNHj1aI0eO1E8//eR4AePGjVNOTo73Y+vWrY63BQAAAkenAwDQ8NHnAID6wOqczJIUFhamdu3aSZJ69eql5cuX65lnntGFF16okpISZWdnV/hNaVZWllJTU6vdntvtltvttl85AAA4KHQ6AAANH30OAKgPHJ+T+QCPx6Pi4mL16tVLoaGhmj9/vve69evXa8uWLerTp8/B7gYAANQyOh0AgIaPPgcA1AWrVzKPGzdOgwcPVsuWLbV//369/fbbWrhwoT7//HPFxcXpyiuv1C233KLExETFxsbqxhtvVJ8+fXjXWgAA6hk6HQCAho8+BwDUF1ZD5p07d+qyyy7T9u3bFRcXpx49eujzzz/X6aefLkl66qmnFBQUpHPPPVfFxcUaOHCgXnjhhVpZOAAAcI5OBwCg4aPPAQD1hcsYY+p6EX+Wm5uruLg4vfTYzYqICOw8UKWWZ/0IDY+yyu/dVWSVf+nFN6zyO7J2W+VdoXbnxzr22F5W+RP7HG2Vl6ScnByrfGGh3W2aX2SX37DF7s0pfv7lF6t8YUGBVd4Yl1U+PDbZKh8SHGaV37/P7jGXn7vPKm93tFJIsN1XxMVEWuXTmyZZ5ROapFnlJSklvY1VPr2p3X2cGGv3cyssONgqH2yZl8sybwL/OV1UVKRx9/9DOTk5io2NtdsP6hU63T863T863Tc63Tc6PQC11On0eeNBn/tHn/tHn/tGn/tGnwegHjxHP+hzMgMAAAAAAAAADl8MmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjoXU9QL+yhgjSSosKg74a0otZ+VlJtgqX2SxFkkq93is8p7/f8yBchm77ZeWlVnli4rtjleSiotL7PIldvmSklKrfJnlMXss7zNjmzcuq7zHU26Xl13efv12j1Fbtpu3vb/Ky+1uH9vHjySVllp+D1h+nxUV2/3c8gTZ5YOD7fJyWeZN4D+ni4qL/viSWn7cofbR6f7R6f7R6b7R6b7R6QGopU6nzxsP+tw/+tw/+tw3+tw3+jwA9eA5usvUs9b/7bff1KJFi7peBgCgjm3dulXNmzev62XgINDpAAD6vOGjzwEAkv9Or3dDZo/Ho23btikmJkYu1//9Zik3N1ctWrTQ1q1bFRsbW4crPHQOt2PmeBs3jrfxq6ljNsZo//79Sk9PV1AQZ3VqyOj0P3C8jd/hdswcb+NGn+Ov6PP/c7gdM8fbuHG8jd+h7vR6d7qMoKAgn1Px2NjYw+bBcMDhdswcb+PG8TZ+NXHMcXFxNbQa1CU6vSKOt/E73I6Z423c6HMcQJ9XdrgdM8fbuHG8jd+h6nR+pQwAAAAAAAAAcIwhMwAAAAAAAADAsQYzZHa73Ro/frzcbnddL+WQOdyOmeNt3Djexu9wPGY4c7g9Vjjexu9wO2aOt3E73I4Xzh2Oj5XD7Zg53saN4238DvUx17s3/gMAAAAAAAAANBwN5pXMAAAAAAAAAID6hyEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGswQ+bnn39erVq1Unh4uHr37q1vvvmmrpdUKyZMmCCXy1Xho1OnTnW9rBr15ZdfasiQIUpPT5fL5dKMGTMqXG+M0b333qu0tDRFRESof//+2rhxY90stgb4O95Ro0ZVus8HDRpUN4s9SJMmTdIxxxyjmJgYpaSkaNiwYVq/fn2FTFFRkcaMGaMmTZooOjpa5557rrKysupoxQcvkGPu169fpfv4uuuuq6MVH5wpU6aoR48eio2NVWxsrPr06aPPPvvMe31ju39R8w6XPpcaf6fT5zMqXN+Y+lw6/DqdPqfPYe9w6fTG3ucSnU6nN66f+3R63XV6gxgyv/fee7rllls0fvx4ffvtt+rZs6cGDhyonTt31vXSakXXrl21fft278fixYvrekk1Kj8/Xz179tTzzz9f5fWPPvqoJk+erKlTp+rrr79WVFSUBg4cqKKiokO80prh73gladCgQRXu83feeecQrrDmLFq0SGPGjNGyZcs0d+5clZaWasCAAcrPz/dmbr75Zn3yySf64IMPtGjRIm3btk3nnHNOHa764ARyzJJ09dVXV7iPH3300Tpa8cFp3ry5Hn74Ya1cuVIrVqzQqaeeqqFDh+rHH3+U1PjuX9Ssw63Ppcbd6fR5ZY2lz6XDr9Ppc/ocdg63Tm/MfS7R6VWh0xvuz306vQ473TQAxx57rBkzZoz38/LycpOenm4mTZpUh6uqHePHjzc9e/as62UcMpLMRx995P3c4/GY1NRU89hjj3kvy87ONm6327zzzjt1sMKa9dfjNcaYkSNHmqFDh9bJemrbzp07jSSzaNEiY8wf92VoaKj54IMPvJm1a9caSWbp0qV1tcwa9ddjNsaYvn37mptuuqnuFlXLEhISzCuvvHJY3L84OIdTnxtzeHU6fd64+9yYw6/T6fPGe9+iZhxOnX449bkxdLoxdLoxjevnPp1+6O7bev9K5pKSEq1cuVL9+/f3XhYUFKT+/ftr6dKldbiy2rNx40alp6erTZs2GjFihLZs2VLXSzpkMjMztWPHjgr3d1xcnHr37t1o729JWrhwoVJSUtSxY0eNHj1ae/bsqesl1YicnBxJUmJioiRp5cqVKi0trXD/durUSS1btmw09+9fj/mAt956S0lJSerWrZvGjRungoKCulhejSovL9e7776r/Px89enT57C4f+Hc4djn0uHb6fR54+pz6fDrdPq88d63OHiHY6cfrn0u0el0esO/j+n0Q3ffhtT4FmvY7t27VV5erqZNm1a4vGnTplq3bl0drar29O7dW9OnT1fHjh21fft23XfffTrppJO0Zs0axcTE1PXyat2OHTskqcr7+8B1jc2gQYN0zjnnqHXr1tq8ebP++c9/avDgwVq6dKmCg4PrenmOeTwejR07VieccIK6desm6Y/7NywsTPHx8RWyjeX+reqYJeniiy9WRkaG0tPTtXr1at15551av369/vOf/9Thap374Ycf1KdP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L730ks/9Lly4UC6XS//973/Vo0cPhYeH67jjjtOaNWu8mT179uiiiy5Ss2bNFBkZqe7du+udd96psJ1+/frphhtu0NixY5WUlKSBAwdKkp588kl1795dUVFRatGiha6//nrl5eVZ3TavvPKKOnfurPDwcHXq1EkvvPCC1dcDANCQuN1upaamKiMjQ6NHj1b//v318ccf++3UA8+7P/74Y3Xp0kVut1tbtmzR8uXLdfrppyspKUlxcXHq27evvv32W6s1rVmzRoMHD1Z0dLSaNm2qSy+9VLt3767pQwfqBYbMQID27t2rzz//XNdff70iIiIqXJeamqoRI0bovffekzFGkvT444+rZ8+e+u6773TPPfdU2l5mZqbOO+88DRs2TN9//72uvfZa75Dal82bN2vGjBmaNWuWZs2apUWLFlX4M+H8/HzdcsstWrFihebPn6+goCCdffbZ/CYWANAoBQcH66GHHtKzzz6r3377rdL1K1eu1AUXXKDhw4frhx9+0IQJE3TPPfdo+vTpFXJPPPGEjj76aH333Xe6/vrrNXr0aK1fv97v/m+//XY98cQTWr58uZKTkzVkyBCVlpZKkoqKitSrVy/997//1Zo1a3TNNdfo0ksv1TfffFNhG6+99prCwsK0ZMkSTZ06VZIUFBSkyZMn68cff9Rrr72mL774QnfccUfAt8tbb72le++9Vw8++KDWrl2rhx56SPfcc49ee+21gLcBAEBDFhERoZKSkoA6taCgQI888oheeeUV/fjjj0pJSdH+/fs1cuRILV68WMuWLVP79u11xhlnVHiRly/Z2dk69dRTdeSRR2rFihWaPXu2srKydMEFF9TG4QJ1zwAIyLJly4wk89FHH1V5/ZNPPmkkmaysLJORkWGGDRtW4frMzEwjyXz33XfGGGPuvPNO061btwqZu+66y0gy+/btM8YYM23aNBMXF+e9fvz48SYyMtLk5uZ6L7v99ttN7969q133rl27jCTzww8/VLkOAAAaqpEjR5qhQ4caY4w57rjjzBVXXGGMMeajjz4yB/6be/HFF5vTTz+9wtfdfvvtpkuXLt7PMzIyzCWXXOL93OPxmJSUFDNlypRq971gwQIjybz77rvey/bs2WMiIiLMe++9V+3XnXnmmebWW2/1ft63b19z5JFH+j3WDz74wDRp0sT7eVX/R+jZs6f387Zt25q33367wjbuv/9+06dPH2MM/x8AADQuf/4/gcfjMXPnzjVut9vcdtttlbJVdaoks2rVKp/7KC8vNzExMeaTTz7xXvbnGcFfu/X+++83AwYMqLCNrVu3Gklm/fr1xpg//h9w0003WR4tUD/xSmbAkvn/r1T25+ijj/Z5/fr163XMMcdUuOzYY4/1u91WrVopJibG+3laWpp27tzp/Xzjxo266KKL1KZNG8XGxqpVq1aSpC1btgS0bgAAGqJHHnlEr732mtauXVvh8rVr1+qEE06ocNkJJ5ygjRs3qry83HtZjx49vP92uVxKTU319uuBP3ONjo5W165dK2yrT58+3n8nJiaqY8eO3jWUl5fr/vvvV/fu3ZWYmKjo6Gh9/vnnlTq5V69elY5n3rx5Ou2009SsWTPFxMTo0ksv1Z49e1RQUOD3tsjPz9fmzZt15ZVXetcdHR2tBx54gFNoAQAarVmzZik6Olrh4eEaPHiwLrzwQk2YMCGgTg0LC6vwfwFJysrK0tVXX6327dsrLi5OsbGxysvLC/i59ffff68FCxZU6OJOnTpJEn2MRinwdyADDnPt2rWTy+XS2rVrdfbZZ1e6fu3atUpISFBycrIkKSoqqlbWERoaWuFzl8tV4VQYQ4YMUUZGhl5++WWlp6fL4/GoW7duvJEQAKBRO/nkkzVw4ECNGzdOo0aNsv56X/36yiuvqLCwsMqcL4899pieeeYZPf30095zQY4dO7ZSJ//1/wy//PKLzjrrLI0ePVoPPvigEhMTtXjxYl155ZUqKSlRZGSkz/0eOM/kyy+/rN69e1e4Ljg4OOD1AwDQkJxyyimaMmWKwsLClJ6erpCQkIA7NSIiQi6Xq8L2Ro4cqT179uiZZ55RRkaG3G63+vTpE/Bz67y8PA0ZMkSPPPJIpevS0tIO/oCBeoYhMxCgJk2a6PTTT9cLL7ygm2++ucJ5mXfs2KG33npLl112WaViqk7Hjh316aefVrhs+fLlB7XGPXv2aP369Xr55Zd10kknSZIWL158UNsEAKChePjhh3XEEUeoY8eO3ss6d+6sJUuWVMgtWbJEHTp0CHjg2qxZs2qvW7ZsmVq2bClJ2rdvnzZs2KDOnTt79zN06FBdcsklkiSPx6MNGzaoS5cuPve3cuVKeTwePfHEEwoK+uMPD99///2A1ipJTZs2VXp6un7++WeNGDEi4K8DAKAhi4qKUrt27SpcdjCdumTJEr3wwgs644wzJElbt261etO+o446Sv/+97/VqlUrhYQwfkPjx+kyAAvPPfeciouLNXDgQH355ZfaunWrZs+erdNPP13NmjXTgw8+GPC2rr32Wq1bt0533nmnNmzYoPfff9/7JkSBDqr/KiEhQU2aNNFLL72kTZs26YsvvtAtt9ziaFsAADQ03bt314gRIzR58mTvZbfeeqvmz5+v+++/Xxs2bNBrr72m5557TrfddluN7HPixImaP3++1qxZo1GjRikpKUnDhg2TJLVv315z587VV199pbVr1+raa69VVlaW3222a9dOpaWlevbZZ/Xzzz/rjTfe8L4hYKDuu+8+TZo0SZMnT9aGDRv0ww8/aNq0aXryySedHCYAAA3SwXRq+/bt9cYbb2jt2rX6+uuvNWLEiAovNvNnzJgx2rt3ry666CItX75cmzdv1ueff67LL7+8wim7gMaCITNgoX379lqxYoXatGmjCy64QG3bttU111yjU045RUuXLlViYmLA22rdurU+/PBD/ec//1GPHj00ZcoU3XXXXZIkt9vtaH1BQUF69913tXLlSnXr1k0333yzHnvsMUfbAgCgIZo4cWKF00gdddRRev/99/Xuu++qW7duuvfeezVx4kRHp9SoysMPP6ybbrpJvXr10o4dO/TJJ58oLCxMknT33XfrqKOO0sCBA9WvXz+lpqZ6B9C+9OzZU08++aQeeeQRdevWTW+99ZYmTZpkta6rrrpKr7zyiqZNm6bu3burb9++mj59ulq3bu3kMAEAaJAOplNfffVV7du3T0cddZQuvfRS/f3vf1dKSkrA+05PT9eSJUtUXl6uAQMGqHv37ho7dqzi4+O9r6oGGhOXCfRdzADUugcffFBTp07V1q1b63opAADAh4ULF+qUU07Rvn37FB8fX9fLAQAAAOoUJ4UB6tALL7ygY445Rk2aNNGSJUv02GOP6YYbbqjrZQEAAAAAAAABY8gM1KGNGzfqgQce0N69e9WyZUvdeuutGjduXF0vCwAAAAAAAAgYp8sAAAAAAAAAADjGmcYBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBj/w9IPVP0S8v2fAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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pggAAAAAAICqw5HMqHTJyclq1qyZ1qxZU+a67t27q2/fvjrppJPKva3H45Hb7exh+dejXEv99Qvu/iwiIqLc5TbnRa4Mfz5lSCkn21OeiraxqlS0Dytre/6sf//+io+P14MPPqiCggLH4wAAAAAAAMA5msyoEmeccYY2bNigr7/++qDHysjI0NatW7V///6A5WvXrvVfL/3fUchZWVkBdQdzpHNGRoZ8Pp82btwYsHzdunWOxwxXSkpKmW2Rym5PRc3bquJ0feFuT9u2bbVu3ToVFxeHHPNvf/ub3n//fX311Ve64IILAs73DQAAAAAAgEODJjOqxPjx4xUXF6errrpKO3bsKHO9zZHCgwYNktfr1dNPPx2w/LHHHpPL5dLAgQMlSUlJSWrUqJG++OKLgLrp06c72II/lI795JNPBix//PHHHY8ZrrZt22rt2rXatWuXf9m3336rxYsXB9TFxcVJKttct7Vt2zatXbs2ZHPX6fratm2rpUuXqqioyL9s9uzZ2rJlS0Ddeeedp927d5fZ31L5j5u+ffvqzTff1Jw5c3T55ZfL5/NZzQsAAAAAAAAHh3Myo0q0b99er7/+ui655BJ16NBBl156qbp27SpjjDIzM/X666/L7XaHde7es846S3369NEdd9yhTZs2qWvXrvrss8/0wQcfaOzYsQHnS7766qt1//336+qrr9Zxxx2nL774Qj///LPj7Tj66KN1ySWXaPr06crOztaJJ56o+fPna8OGDY7HDNdVV12lRx99VP3799eIESO0c+dOPfvsszrqqKP8X0wo/XGqjSOPPFJvvfWWDj/8cKWmpqpTp07q1KmT1fomTJigl19+WZmZmUG//M/p+q6++mq9++67GjBggC688EJt3LhRr776apnzXV9xxRX617/+pXHjxunrr79Wz549lZubq3nz5mnUqFEaPHhwmbGHDBmimTNn6oorrlBSUpKee+45q20HAAAAAACAcxzJjCozePBgff/99xo6dKg+++wz3XTTTbr55pv1wQcf6IwzztA333yjiy++OOQ4brdbH374ocaOHavZs2dr7Nix+vHHH/XQQw/p0UcfDai9++67NWLECL377rsaP368vF6vPvnkk4Pajn/+85+68cYbNWfOHI0fP17FxcX673//e1BjhuOII47Qv/71L2VnZ2vcuHH68MMP9corr+jYY48tU/viiy/qsMMO080336xLLrlE7777bpXOzcn6+vfvr0ceeUQ///yzxo4dqyVLlmj27NllPmiIiIjQxx9/rDvuuEPLli3T2LFj9eijjyopKUmdO3eucPzLLrtMzzzzjJ5//nnddtttB72NAAAAAAAACI/LHOpvOAMAAAAAAAAA1BkcyQwAAAAAAAAAcIwmMwAAAAAAAADAMZrMAAAAAAAAAADHaDIDAAAAkgoLC3XVVVepZcuWSkpK0t/+9jctWbKkuqcFAAAskOdA9aDJDAAAAEgqKSlRq1at9OWXXyorK0tjx47VWWedpQMHDlT31AAAQJjIc6B60GQGAABApZs0aZJcLpd2794dsnbWrFlyuVzatGlTpc7hwQcfVMeOHeXz+cKqj4+P1913362WLVvK7Xbr4osvVnR0tNatW1ep84L07LPPqmXLliosLKzuqQAAJC1fvlwnnnii4uPj5XK5tHr16uqekh95XnOR5/gzmszAn7Rq1UrDhw/3X164cKFcLpcWLlxYaetwuVyaNGlSpY1XF7Rq1UpnnnlmdU8DAFCH5OTk6IEHHtDtt98ut7vsr7yzZs1Sx44ddfPNN1c4xvr167V37161a9fuoOdT+jtFeT9Lly4NefvCwkLdfvvtSk9PV2xsrLp37665c+ce9HrCHVeSvvnmG5199tlKTU1VXFycOnXqpCeffDLovKdMmSKXy6VOnToFLB8+fLiKior03HPPhdx2AEDVKi4u1gUXXKC9e/fqscce0yuvvKKMjIzqnpakmpfnP/zwgy644AK1adNGcXFxatSokU455RR99NFHYd1+5cqVGjBggJKSkpSYmKh+/fpV2NC3qV2/fr0uvvhiNW/eXHFxcerYsaMmT56svLy8MrXh5Hm420me488iq3sCQKlZs2bpyiuv9F/2eDxq2bKl+vXrp7vuuktNmzatxtnZ+fjjj/X111/Ximby9OnTFRcXF9BcBwDgULr88st18cUXy+PxVNqY//znP1VSUqJLLrmkzHUbNmzQ9ddfr9tvv12vvvqqHnvssTI1+fn5uuyyyzRhwgQlJydX2rxuvPFGHX/88QHLwnnTO3z4cL377rsaO3as2rdvr1mzZmnQoEFasGCBTj75ZMfrCXfczz77TGeddZaOOeYY3XXXXUpISNDGjRv122+/VTjn3377TVOnTlV8fHyZ62Ji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0yY9/vjjGjNmjN566y1/zXvvvacrr7xSM2fODHqu5ccff1w33HCDEhIS/L9I/PXTz1GjRqlx48a6++67HR3JfM8992jSpEk68cQTNXnyZEVHR2vZsmX6/PPP1a9fv3Jv8/zzz+v666/XP/7xD913333W6wQAVL3//ve/uuaaa/TII4/4l40fP75M3THHHKOXXnrJf3nPnj166aWX9MADD4RcR+vWrf1HxYwePVpJSUn+hnaXLl0qvN3atWv9t/+rJUuWqEGDBtq6datyc3N1xhln+K/bvHmzXnzxRcXExKhRo0b+5Z988ol69uwpSVq8eLH69OkTcu7SHx+Ql34oGx0drfPOO0+DBg1So0aN9OOPP+rhhx9Wz5499dVXX5U5AuvPtm3bpmbNmpVZXrps69at/mU26wl33PXr16ukpESDBw/WiBEjNG3aNC1cuFBPPfWUsrKy9MYbbwTc/tlnn9XmzZs1b968kPdRmzZtwvp9CQBQNXr06KHCwkJNnTpVPXv21Pnnny9J/oZh165d9frrrwfc5s4771RxcbG+/PJLtWnTRpJ0xRVXqEOHDho/frwWLVokSZo4caIiIiK0ePFipaenS/rjA+QjjjgirLnVxDwvdcstt/i/7M7tduvcc8/V008/HXScDh06aOnSpfJ6vYqIiJAkFRUV+Rvzv//+u6PaAQMG6N5779XUqVP14Ycf+pffcccdAe+nbfPcZjvJc0g0mVEDZWdna/fu3SooKNDixYs1efJkxcbG6swzz/TXFBYW6oILLtC0adP8y7788ku9+OKLeu211zR06FD/8j59+mjAgAF65513NHToUO3atUsPPvigzjjjDH300Uf+I2/vuOMOTZ06NejcvF6vrrvuOjVr1kyrV69WgwYN/NcZYyT9EdKHH3645s6dq8suuyzk9oYb0KUaNmyozz77zD9vn8+nJ598UtnZ2dZfZDBkyBDdeeedatSoUYVzTU1N1fz58/3BZmPDhg2aPHmyzjnnHL377rsBpx0pvb/+6sknn9TYsWM1efJk3XnnndbrBAAcGg0aNNCyZcu0detW/xvH8lx//fUBl3v27Kn33ntPOTk5ZY6g/avRo0cHXL7hhhs0ffp0ffzxx0GbzHv27FFkZKQSEhLKXLd8+XJ17txZzz77rC699NKAo4QyMjIqzKdSXbt21dy5c4PWlEpLS/P/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ZIyRJD388MPq2rWrVq1apbvuuqvMeJmZmTr//PM1ZMgQffvtt7ruuuv8TepgNm7cqPfff1+zZ8/W7NmztWjRooA/E87NzdW4ceO0YsUKzZ8/X263W+eccw6fxAIA6qSIiAhNnTpVTz31lH777bcy169cuVIXXnihLr74Yn3//feaNGmS7rrrLs2aNSug7pFHHtFxxx2nVatWadSoURo5cqTWrVsXcv233XabHnnkES1fvlyNGzfWWWedpeLiYklSQUGBunXrpv/+979as2aNrr32Wl1++eX6+uuvA8Z4+eWXFR0drcWLF+vZZ5+VJLndbj355JP64Ycf9PLLL+vzzz/X+PHjw75fXnvtNd19992aMmWKfvrpJ02dOlV33XWXXn755bDHAACgNouNjVVRUVFYmZqXl6cHHnhAL774on744Qc1adJE+/fv17Bhw/Tll19q6dKlat++vQYNGhRwkFcwWVlZOvXUU3XMMcdoxYoVmjNnjnbs2KELL7ywKjYXqH4GQFiWLl1qJJn33nuv3OsfffRRI8ns2LHDZGRkmCFDhgRcn5mZaSSZVatWGWOMuf32202nTp0Cau644w4jyezbt88YY8zMmTNNcnKy//qJEyeauLg4k5OT41922223me7du1c47127dhlJ5vvvvy93HgAA1FbDhg0zgwcPNsYY87e//c1cddVVxhhj3nvvPVP6a+7QoUPN6aefHnC72267zRx55JH+yxkZGeayyy7zX/b5fKZJkyZmxowZFa57wYIFRpJ58803/cv27NljYmNjzVtvvVXh7c444wxzyy23+C/36tXLHHPMMSG39Z133jENGzb0Xy7vd4SuXbv6L7dt29a8/vrrAWPce++9pkePHsYYfh8AANQtf/6dwOfzmblz5xqPx2NuvfXWMrXlZaoks3r16qDr8Hq9JjEx0Xz00Uf+ZX/uEfw1W++9917Tr1+/gDG2bNliJJl169YZY/74PeCmm26y3FqgZuJIZsCS+f9HKody3HHHBb1+3bp1Ov744wOWnXDCCSHHbdWqlRITE/2XmzVrpp07d/ovr1+/XpdcconatGmjpKQktWrVSpL066+/hjVvAABqowceeEAvv/yyfvrpp4DlP/30k0466aSAZSeddJLWr18vr9frX9alSxf//10ul9LS0vz5WvpnrgkJCTrqqKMCxurRo4f//6mpqerQoYN/Dl6vV/fee686d+6s1NRUJSQk6NNPPy2Tyd26dSuzPfPmzdNpp52mww47TImJibr88su1Z88e5eXlhbwvcnNztXHjRo0YMcI/74SEBN13332cQgsAUGfNnj1bCQkJiomJ0cCBA3XRRRdp0qRJYWVqdHR0wO8CkrRjxw5dc801at++vZKTk5WUlKQDBw6E/d7622+/1YIFCwKyuGPHjpJEHqNOCv8byIB6rl27dnK5XPrpp590zjnnlLn+p59+UkpKiho3bixJio+Pr5J5REVFBVx2uVwBp8I466yzlJGRoRdeeEHp6eny+Xzq1KkTXyQEAKjTTjnlFPXv318TJkzQ8OHDrW8fLF9ffPFF5efnl1sXzEMPPaQnnnhCjz/+uP9ckGPHji2TyX/9nWHTpk0688wzNXLkSE2ZMkWpqan68ssvNWLECBUVFSkuLi7oekvPM/nCCy+oe/fuAddFRESEPX8AAGqTPn36aMaMGYqOjlZ6eroiIyPDztTY2Fi5XK6A8YYNG6Y9e/boiSeeUEZGhjwej3r06BH2e+sDBw7orLPO0gMPPFDmumbNmh38BgM1DE1mIEwNGzbU6aefrunTp+vmm28OOC/z9u3b9dprr+mKK64oE0wV6dChgz7++OOAZcuXLz+oOe7Zs0fr1q3TCy+8oJ49e0qSvvzyy4MaEwCA2uL+++/X0UcfrQ4dOviXHXHEEVq8eHFA3eLFi3X44YeH3XA97LDDKrxu6dKlatmypSRp3759+vnnn3XEEUf41zN48GBddtllkiSfz6eff/5ZRx55ZND1rVy5Uj6fT4888ojc7j/+8PDtt98Oa66S1LRpU6Wnp+uXX37RpZdeGvbtAACozeLj49WuXbuAZQeTqYsXL9b06dM1aNAgSdKWLVusvrTv2GOP1b///W+1atVKkZG031D3cboMwMLTTz+twsJC9e/fX1988YW2bNmiOXPm6PTTT9dhhx2mKVOmhD3Wddddp7Vr1+r222/Xzz//rLffftv/JUThNqr/KiUlRQ0bNtTzzz+vDRs26PPPP9e4ceMcjQUAQG3TuXNnXXrppXryySf9y2655RbNnz9f9957r37++We9/PLLevrpp3XrrbdWyjonT56s+fPna82aNRo+fLgaNWqkIUOGSJLat2+vuXPn6quvvtJPP/2k6667Tjt27Ag5Zrt27VRcXKynnnpKv/zyi1555RX/FwKG65577tG0adP05JNP6ueff9b333+vmTNn6tFHH3WymQAA1EoHk6nt27fXK6+8op9++knLli3TpZdeGnCwWSijR4/W3r17dckll2j58uXauHGjPv30U1155ZUBp+wC6gqazICF9u3ba8WKFWrTpo0uvPBCtW3bVtdee6369OmjJUuWKDU1NeyxWrdurXfffVf/+c9/1KVLF82YMUN33HGHJMnj8Tian9vt1ptvvqmVK1eqU6dOuvnmm/XQQw85GgsAgNpo8uTJAaeROvbYY/X222/rzTffVKdOnXT33Xdr8uTJjk6pUZ77779fN910k7p166bt27fro48+UnR0tCTpzjvv1LHHHqv+/furd+/eSktL8zegg+nataseffRRPfDAA+rUqZNee+01TZs2zWpeV199tV588UXNnDlTnTt3Vq9evTRr1iy1bt3ayWYCAFArHUymvvTSS9q3b5+OPfZYXX755brxxhvVpEmTsNednp6uxYsXy+v1ql+/furcubPGjh2rBg0a+I+qBuoSlwn3W8wAVLkpU6bo2Wef1ZYtW6p7KgAAIIiFCxeqT58+2rdvnxo0aFDd0wEAAACqFSeFAarR9OnTdfzxx6thw4ZavHixHnroIY0ZM6a6pwUAAAAAAACEjSYzUI3Wr1+v++67T3v37lXLli11yy23aMKECdU9LQAAAAAAACBsnC4DAAAAAAAAAOAYZxoHAAAAAAAAADhGkxkAAAAAAAAA4BhNZgAAAAAAAACAYzSZAQAAAAAAAACO0WQGAAAAAAAAADhGkxkAAAAAAAAA4BhNZgAAAAAAAACAYzSZAQAAAAAAAACO/T+hwLvAVmCfiQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABtjklEQVR4nO3deXiU5f3+/XOWzGRPCARCBMIqgiwqVpq6gaKI1qq1rmhBrStYcS+1Ki4Vca9W0YoFa8WtrWvrBgL+pGAFsYoCAg2KsgeyJ5PJzPX84cN8jSEzc91kmCzv13HkOMjMOfdc92xn5pPhjssYYwQAAAAAAAAAgAPuZC8AAAAAAAAAANB2MWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQG9pLL5dK0adOSvYyoJk6cqMzMzH1yXRs2bJDL5dKcOXP2yfUBAAAAAAAguRgyY58oKSnR5MmTtf/++ys9PV3p6ekaPHiwJk2apE8//TTZy0uoUaNGyeVyxfza20F1TU2Npk2bpoULF7bIugEAAAAAAIB4eJO9ALR/b7zxhs466yx5vV6NHz9ew4cPl9vt1urVq/WPf/xDM2fOVElJiYqKipK91IS46aab9Ktf/Sry/UcffaSHH35Yv/3tbzVo0KDI6cOGDdur66mpqdFtt90m6bvBNgAAAAAAALAvMGRGQq1fv15nn322ioqKNH/+fHXv3r3R+TNmzNBjjz0mtzv6h+qrq6uVkZGRyKUmzHHHHdfo+9TUVD388MM67rjjog6D2/I+J0NDQ4PC4bB8Pl+ylwIAAAAAANChcLgMJNQ999yj6upqzZ49u8mAWZK8Xq9+/etfq2fPnpHTdh8/eP369TrxxBOVlZWl8ePHS/pu8HrttdeqZ8+e8vv9GjhwoO677z4ZYyKXj3ZM4B8elmLatGlyuVxat26dJk6cqNzcXOXk5OiCCy5QTU1No8sGAgFdffXVys/PV1ZWln72s5/pm2++2ctbqPE6vvjiC5177rnq1KmTjjjiCEnffSp5T8PoiRMnqnfv3pF9zs/PlyTddtttzR6C49tvv9Wpp56qzMxM5efn67rrrlMoFGqU2bx5s1avXq1gMBhz3WVlZZo4caJycnKUm5urCRMmqKysbI/Z1atX6xe/+IXy8vKUmpqqQw89VK+99toetzllypTIfdy/f3/NmDFD4XA4ktl9H99333166KGH1K9fP/n9fn3xxRcx1wwAAAAAAICWxSeZkVBvvPGG+vfvr5EjR1pdrqGhQWPHjtURRxyh++67T+np6TLG6Gc/+5kWLFigiy66SAcddJDefvttXX/99fr222/14IMPOl7nmWeeqT59+mj69On6+OOPNWvWLHXt2lUzZsyIZH71q1/pr3/9q84991z95Cc/0XvvvaeTTjrJ8XXuyRlnnKEBAwborrvuajQ4jyU/P18zZ87U5ZdfrtNOO00///nPJTU+BEcoFNLYsWM1cuRI3XfffZo3b57uv/9+9evXT5dffnkkN3XqVD399NMqKSmJDLH3xBijU045RR988IEuu+wyDRo0SC+//LImTJjQJPv555/r8MMP13777aff/OY3ysjI0IsvvqhTTz1Vf//733XaaadJ+u6QH0cffbS+/fZbXXrpperVq5f+/e9/a+rUqdq8ebMeeuihRtudPXu26urqdMkll8jv9ysvLy/u2wwAAAAAAAAtgyEzEqaiokKbNm3Sqaee2uS8srIyNTQ0RL7PyMhQWlpa5PtAIKAzzjhD06dPj5z26quv6r333tOdd96pm266SZI0adIknXHGGfrDH/6gyZMnq1+/fo7WevDBB+upp56KfF9aWqqnnnoqMmT+73//q7/+9a+64oor9Oijj0aue/z48S36hwuHDx+uuXPnWl8uIyNDv/jFL3T55Zdr2LBhOu+885pk6urqdNZZZ+nmm2+WJF122WU65JBD9NRTTzUaMsfrtdde0/vvv6977rlH119/vSTp8ssv1+jRo5tkr7rqKvXq1UsfffSR/H6/JOmKK67QEUccoRtvvDEyZH7ggQe0fv16rVixQgMGDJAkXXrppSosLNS9994b+RT7bt98843WrVsX+RQ3AAAAAAAA9j0Ol4GEqaiokCRlZmY2OW/UqFHKz8+PfO0e3H7fDwef//rXv+TxePTrX/+60enXXnutjDF68803Ha/1sssua/T9kUceqdLS0sg+/Otf/5KkJtc9ZcoUx9cZzzpa2p7283//+1+j0+bMmSNjTNRPMUvf3SZer7fR/eTxeHTllVc2yu3cuVPvvfeezjzzTFVWVmrHjh3asWOH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L9Vvf/tbSdKvf/1rPfPMM7rpppsi559xxhmqqanRXXfdpYsvvliS9Pnnn+vII4/Uf//7X91www26+eabVVJSolGjRunDDz+MXHbFihU64YQTVFpaqttuu00XXXSRbr/9dr3yyitxre3f//63JOmQQw5pct6OHTv02Wef6eijj9a8efN07LHHNtmvzZs366WXXtrjL3+DwaB27NgR19f3/zjRihUrtP/++ys7O7vR9g477DBJivrHlWwum4hsoq5f+u4+Wrx48Z52GwCwj9DpdDqdjpbSej46CVhYv369JKlz586R0xoaGjR27FgdccQRuu+++5Seni7pu3KZM2eOLrjgAv36179WSUmJ/vjHP2rFihVavHixUlJSJEm33HKL7rzzTp144ok68cQT9fHHH+v4449XfX19zPW8++67+ulPf6ru3bvrqquuUkFBgVatWqU33nhDV111lS699FJt2rRJ7777rp555pmY29td2tnZ2brhhhuUkpKiJ554QqNGjdKiRYs0cuTIRvkrr7xSnTp10q233qoNGzbooYce0uTJk/XCCy9EMi+//LIuuOACzZ49u9EfMvyhZ555Rn/605/0n//8R7NmzZIk/eQnP4mc/9577+nFF1/U5MmT1aVLF/Xu3Tvu9X777bcaPXq0XC6Xpk6dqoyMDM2aNUt+vz/mbQIAaD2Ki4sVCAR011136cgjj9QvfvELSYq8uRg+fLjmzp3b6DK/+93vFAwG9cEHH6hv376SpF/+8pcaOHCgbrjhBi1atEiSdOutt8rj8Wjx4sUqLCyUJJ155pkaNGhQXGtbvXq1JKlPnz5NzluyZIlyc3O1adMmVVdX66STToqc99VXX2nWrFlKTU1Vly5dIqe/+eabOvLIIyP7N3r06LjWUVJSot69e0uSNm/erO7duzfJ7D5t06ZNzW7H5rKJyCbq+iWpb9++cf1cBABIHDo9Njo9elai0/EdhsxoE8rLy7Vjxw7V1dVp8eLFuv3225WWlqaf/vSnkUwgENAZZ5yh6dOnR0774IMPNGvWLD377LM699xzI6ePHj1aJ5xwgl566SWde+652r59u+655x6ddNJJev311yOfMr7pppt01113RV1bKBTSpZdequ7du+uTTz5Rbm5u5DxjjKTvinv//ffXu+++q/POOy/m/sZb2rt17txZ77zzTmTd4XBYDz/8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AMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHPMmewHNSZFRijFxZd2hBqtt+zx2u+2SyyrvdtvN7sPhsFXel5JilW9osLt9wmG7vCSlWu5zTlamVd5tdxco1ZdulQ/Xh6zy6Zl22w8G6q3ydbU1VvlAg+X6fXbPgRSf3ypfXWO3/tSsbKt8bb3dY7TW8vZPMXbPMUnyuD1Webcn1SofsvyVYE2t3etKWdkuq7zt64rP54s763LF99qPtoNObx6dHhudHh2dHh2dHluiOp0+b3/o8+bR57HR59HR59HR57G1hvfofJIZAAAAAAAAAOCY1ZB55syZGjZsmLKzs5Wdna3i4mK9+eabkfPr6uo0adIkde7cWZmZmTr99NO1devWFl80AADYO3Q6AABtH30OAGgtrIbMPXr00N13363ly5dr2bJlOuaYY3TKKafo888/lyRdffXVev311/XSSy9p0aJF2rRpk37+858nZOEAAMA5Oh0AgLaPPgcAtBZWB305+eSTG33/+9//XjNnztTSpUvVo0cPPfXUU5o7d66OOeYYSdLs2bM1aNAgLV26VD/+8Y9bbtUAAGCv0OkAALR99DkAoLVwfEzmUCik559/XtXV1SouLtby5csVDAY1ZsyYSOaAAw5Qr169tGTJkma3EwgEVFFR0egLAADsO3Q6AABtH30OAEgm6yHzZ599pszMTPn9fl122WV6+eWXNXjwYG3ZskU+n0+5ubmN8t26ddOWLVua3d706dOVk5MT+erZs6f1TgAAAHt0OgAAbR99DgBoDayHzAMHDtQnn3yiDz/8UJdffrkmTJigL774wvECpk6dqvLy8sjXxo0bHW8LAADEj04HAKDto88BAK2B1TGZJcnn86l///6SpBEjRuijjz7SH/7wB5111lmqr69XWVlZo9+Ubt26VQUFBc1uz+/3y+/3268cAADsFTodAIC2jz4HALQGjo/JvFs4HFYgENCIESOUkpKi+fPnR85bs2aNvv76axUXF+/t1QAAgASj0wEAaPvocwBAMlh9knnq1KkaN26cevXqpcrKSs2dO1cLFy7U22+/rZycHF100UW65pprlJeXp+zsbF155ZUqLi7mr9YCANDK0OkAALR99DkAoLWwGjJv27ZNv/zlL7V582bl5ORo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2rUr2UtpEfX19cleAgAAbdIJJ5ygzZs3a+3atbr22ms1bdo03XvvvdbbCYVCCofDCVgh0L4xZAYsTJo0ST6fT++8846OPvpo9erVS+PGjdO8efP07bff6qabbpIk9e7dW3fccYd++ctfKjs7W5dccske/+vMa6+9pgEDBig1NVWjR4/W008/LZfLpbKyMknN/1fYZ555Rr1791ZOTo7OPvtsVVZWRjJvvfWWjjjiCOXm5qpz58766U9/qvXr1++LmwcAgH1uzJgxKigo0PTp05vN/P3vf9eBBx4ov9+v3r176/777290fu/evXXXXXfpwgsvVFZWlnr16qU//elPUa934cKFcrlc+uc//6lhw4YpNTVVP/7xj7Vy5cpIprS0VOecc472228/paena+jQoXruuecabWfUqFGaPHmypkyZoi5dumjs2LGSpAceeEBDhw5VRkaGevbsqSuuuEJVVVVWt82sWbM0aNAgpaam6oADDtBjjz1mdXkAANoSv9+vgoICFRUV6fLLL9eYMWP02muvxezU3e+7X3vtNQ0ePFh+v19ff/21PvroIx133HHq0qWLcnJydPTRR+vjjz+2WtPKlSs1btw4ZWZmqlu3bjr//PO1Y8eOlt51oFVgyAzEaefOnXr77bd1xRVXKC0trdF5BQUFGj9+vF544QUZYyRJ9913n4YPH64VK1bo5ptvbrK9kpIS/eIXv9Cpp56q//73v7r00ksjQ+po1q9fr1deeUVvvPGG3njjDS1atKjRfxOurq7WNddco2XLlmn+/Plyu9067bTT+E0sAKBd8ng8uuuuu/TII4/om2++aXL+8uXLdeaZZ+rss8/WZ599pmnTpunmm2/WnDlzGuXuv/9+HXrooVqxYoWuuOIKXX755VqzZk3M67/++ut1//3366OPPlJ+fr5OPvlkBYNBSVJdXZ1GjBihf/7zn1q5cqUuueQSnX/++frPf/7TaBtPP/20fD6fFi9erMcff1yS5Ha79fDDD+vzzz/X008/rffee0833HBD3LfLs88+q1tuuUW///3vtWrVKt111126+eab9fTTT8e9DQAA2rK0tDTV19fH1ak1NTWaMWOGZs2apc8//1xdu3ZVZWWlJkyYoA8++EBLly7VgAEDdOKJJzb6kFc0ZWVlOuaYY3TwwQdr2bJleuutt7R161adeeaZidhdIPkMgLgsXbrUSDIvv/zyHs9/4IEHjCSzdetWU1RUZE499dRG55eUlBhJZsWKFcYYY2688UYzZMiQRpmbbrrJSDK7du0yxhgze/Zsk5OTEzn/1ltvNenp6aaioiJy2vXXX29GjhzZ7Lq3b99uJJnPPvtsj+sAAKCtmjBhgjnllFOMMcb8+Mc/NhdeeKExxpiXX37Z7P4x99xzzzXHHXdco8tdf/31ZvDgwZHvi4qKzHnnnRf5PhwOm65du5qZM2c2e90LFiwwkszzzz8fOa20tNSkpaWZF154odnLnXTSSebaa6+NfH/00Uebgw8+OOa+vvTSS6Zz586R7/f0M8Lw4cMj3/fr18/MnTu30TbuuOMOU1xcbIzh5wEAQPvy/Z8JwuGweffdd43f7zfXXXddk+yeOlWS+eSTT6JeRygUMllZWeb111+PnPb9GcEPu/WOO+4wxx9/fKNtbNy40Ugya9asMcZ893PAVVddZbm3QOvEJ5kBS+b//6RyLIceemjU89esWaMf/ehHjU477LDDYm63d+/eysrKinzfvXt3bdu2LfL92rVrdc4556hv377Kzs5W7969JUlff/11XOsGAKAtmjFjhp5++mmtWrWq0emrVq3S4Ycf3ui0ww8/XGvXrlUoFIqcNmzYsMi/XS6XCgoKIv26+7+5ZmZm6sADD2y0reLi4si/8/LyNHDgwMgaQqGQ7rjjDg0dOlR5eXnKzMzU22+/3aSTR4wY0WR/5s2bp2OPPVb77befsrKydP7556u0tFQ1NTUxb4vq6mqtX79eF110UWTdmZmZuvPOOzmEFgCg3XrjjTeUmZmp1NRUjRs3TmeddZamTZsWV6f6fL5GPwtI0tatW3XxxRdrwIABysnJUXZ2tqqqquJ+b/3f//5XCxYsaNTFBxxwgCTRx2iX4v8LZEAH179/f7lcLq1atUqnnXZak/NXrVqlTp06KT8/X5KUkZGRkHWkpKQ0+t7lcjU6FMbJJ5+soqIiPfnkkyosLFQ4HNaQIUP4Q0IAgHbtqKOO0tixYzV16lRNnDjR+vLR+nXWrFmqra3dYy6ae++9V3/4wx/00EMPRY4FOWXKlCad/MOfGTZs2KCf/vSnuvzyy/X73/9eeXl5+uCDD3TRRRepvr5e6enpUa9393Emn3zySY0cObLReR6PJ+71AwDQlowePVozZ86Uz+dTYWGhvF5v3J2alpYml8vVaHsTJkxQaWmp/vCHP6ioqEh+v1/FxcVxv7euqqrSySefrBkzZjQ5r3v37nu/w0Arw5AZiFPnzp113HHH6bHHHtPVV1/d6LjMW7Zs0bPPPqtf/vKXTYqpOQMHDtS//vWvRqd99NFHe7XG0tJSrVmzRk8++aSOPPJISdIHH3ywV9sEAKCtuPvuu3XQQQdp4MCBkdMGDRqkxYsXN8otXrxY+++/f9wD1/3226/Z85YuXapevXpJknbt2qUvv/xSgwYNilzPKaecovPOO0+SFA6H9eWXX2rw4MFRr2/58uUKh8O6//775XZ/9x8PX3zxxbjWKkndunVTYWGh/ve//2n8+PFxXw4AgLYsIyND/fv3b3Ta3nTq4sWL9dhjj+nEE0+UJG3cuNHqj/Ydcsgh+vvf/67evXvL62X8hvaPw2UAFv74xz8qEAho7Nixev/997Vx40a99dZbOu6447Tffvvp97//fdzbuvTSS7V69WrdeOON+vLLL/Xiiy9G/ghRvIPqH+rUqZM6d+6sP/3pT1q3bp3ee+89XXPNNY62BQBAWzN06FCNHz9eDz/8cOS0a6+9VvPnz9cdd9yhL7/8Uk8//bT++Mc/6rrrrmuR67z99ts1f/58rVy5UhMnTlSXLl106qmnSpIGDBigd999V//+97+1atUqXXrppdq6dWvMbfbv31/BYFCPPPKI/ve//+mZZ56J/EHAeN12222aPn26Hn74YX355Zf67LPPNHv2bD3wwANOdhMAgDZpbzp1wIABeuaZZ7Rq1Sp9+OGHGj9+fKMPm8UyadIk7dy5U+ecc44++ugjrV+/Xm+//bYuuOCCRofsAtoLhsyAhQEDBmjZsmXq27evzjzzTPXr10+XXHKJRo8erSVLligvLy/ubfXp00d/+9vf9I9//EPDhg3TzJkzddNNN0mS/H6/o/W53W49//zzWr58uYYMGaKrr75a9957r6NtAQDQFt1+++2NDiN1yCGH6MUXX9Tzzz+vIUOG6JZbbtHtt9/u6JAae3L33Xfrqquu0ogRI7Rlyxa9/vrr8vl8kqTf/e53OuSQQzR27FiNGjVKBQUFkQF0NMOHD9cDDzygGTNmaMiQIXr22Wc1ffp0q3X96le/0qxZszR79mwNHTpURx99tObMmaM+ffo42U0AANqkvenUp556Srt27dIhhxyi888/X7/+9a/VtWvXuK+7sLBQixcvVigU0vHHH6+hQ4dqypQpys3NjXyqGmhPXCbev2IGIOF+//vf6/HHH9fGjRuTvRQAABDFwoULNXr0aO3atUu5ubnJXg4AAACQVBwUBkiixx57TD/60Y/UuXNnLV68WPfee68mT56c7GUBAAAAAAAAcWPIDCTR2rVrdeedd2rnzp3q1auXrr32Wk2dOjXZywIAAAAAAADixuEyAAAAAAAAAACOcaRxAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADg2P8HyRYNNj/iqhgAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABwYklEQVR4nO3deXhTdf7+/zvd0r1lKbRlKauIsiio2HEDRTZlQB1RQQfcF3AE3IZxRnFF1MEdHMUBxnEbnVEUFRcU/ImggKCigIBFUHak+568f3/wbT7E0iTvQ0O35+O6el02uXPyPjlJb/JqPHUZY4wAAAAAAAAAAHAgoq4XAAAAAAAAAABouBgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyo9FxuVyaOnVqXS8joHHjxikxMbGul9GoTJ06VS6XS3v37g2a7dChg8aNG+f7fvHixXK5XFq8eHH4FggAAAAAANBIMWRuonJycjRhwgQdddRRio+PV3x8vI455hiNHz9e33zzTV0vL6z69+8vl8sV9OtwB9XFxcWaOnVqgx5cfv7555o6dapyc3PreikAAAAAAACop6LqegE48hYsWKCLLrpIUVFRGjNmjHr37q2IiAitX79e//vf/zRr1izl5OQoKyurrpcaFnfccYeuuuoq3/crVqzQE088ob/85S/q3r277/JevXod1v0UFxfr7rvvlnRgsN0Qff7557r77rs1btw4paam1vVyas2GDRsUEcHv2AAAAAAAAGoDQ+YmZvPmzbr44ouVlZWlRYsWKSMjw+/66dOna+bMmUEHcEVFRUpISAjnUsPm7LPP9vs+NjZWTzzxhM4+++yAw+CGvM/w53a763oJAAAAAAAAjQYf5WtiHnroIRUVFWnOnDnVBsySFBUVpT/96U9q166d77Kq8wdv3rxZw4YNU1JSksaMGSPpwOD15ptvVrt27eR2u9WtWzc98sgjMsb4br9lyxa5XC7NnTu32v399rQUVefV3bRpk+/TsykpKbr88stVXFzsd9uysjJNmjRJaWlpSkpK0u9//3v9/PPPh/kI+a/j+++/1+jRo9WsWTOdeuqpkg58KvlQw+hx48apQ4cOvn1OS0uTJN199901noLjl19+0ciRI5WYmKi0tDTdcsst8ng8fpkdO3Zo/fr1qqioCLruRx55RL/73e/UokULxcXFqW/fvnr99df9MqEej6lTp+rWW2+VJHXs2NG3D1u2bJEkVVZW6t5771Xnzp3ldrvVoUMH/eUvf1FZWZnfNjt06KBzzz1Xixcv1gknnKC4uDj17NnTdxqR//3vf+rZs6diY2PVt29frV69utq6Pv74Y5122mlKSEhQamqqRowYoXXr1h3yMdi7d69GjRql5ORktWjRQjfddJNKS0urrengczLX5IsvvtCQIUOUkpKi+Ph4nXHGGVq6dGnQ2wEAAAAAADQlDJmbmAULFqhLly7q16+f1e0qKys1ePBgtWrVSo888oguuOACGWP0+9//Xo8++qiGDBmiGTNmqFu3brr11ls1efLkw1rnqFGjVFBQoGnTpmnUqFGaO3eu79QTVa666io99thjGjRokB588EFFR0frnHPOOaz7/a0LL7xQxcXFeuCBB3T11VeHfLu0tDTNmjVLknTeeefphRde0AsvvKDzzz/fl/F4PBo8eLBatGihRx55RGeccYb+/ve/69lnn/Xb1pQpU9S9e3f98ssvQe/38ccf1/HHH6977rlHDzzwgKKionThhRfqnXfeCXntVc4//3xdcsklkqRHH33Utw9Vw/OrrrpKd955p/r06aNHH31UZ5xxhqZNm6aLL7642rY2bdqk0aNHa/jw4Zo2bZr279+v4cOH68UXX9SkSZN06aWX6u6779bmzZs1atQoeb1e320/+ugjDR48WLt379bUqVM1efJkff755zrllFN8A++DjRo1SqWlpZo2bZqGDRumJ554Qtdcc431/n/88cc6/fTTlZ+fr7vuuksPPPCAcnNzdeaZZ+rLL7+03h4AAAAAAEBjxekympD8/Hxt375dI0eOrHZdbm6uKisrfd8nJCQoLi7O931ZWZkuvPBCTZs2zXfZ/Pnz9fHHH+u+++7THXfcIUkaP368LrzwQj3++OOaMGGCOnfu7Gitxx9/vJ5//nnf9/v27dPzzz+v6dOnS5K+/vpr/fvf/9YNN9ygp59+2nffY8aMqdU/XNi7d2+99NJL1rdLSEjQH/7wB11//fXq1auXLr300mqZ0tJSXXTRRfrb3/4mSbruuuvUp08fPf/887r++usdrfeHH37wO24TJkxQnz59NGPGDOsBfK9evdSnTx+9/PLLGjlypO9T2tKBx3/evHm66qqr9Nxzz0mSbrjhBt8vIT755BMNGDDAl9+wYYM+//xzZWdnS5KOOeYYDR48WFdffbXWr1+v9u3bS5KaNWuma6+9Vp9++qnv0+K33nqrmjdvrmXLlql58+aSpJEjR+r444/XXXfdpXnz5vmtu2PHjpo/f76kA8+J5ORkzZw5U7fcckvI59k2xui6667TgAED9N5778nlckmSrr32Wh177LH661//qg8++MDq8QQAAAAAAGis+CRzE5Kfny9JSkxMrHZd//79lZaW5vuqGtwe7LeDz3fffVeRkZH605/+5Hf5zTffLGOM3nvvPcdrve666/y+P+2007Rv3z7fPrz77ruSVO2+J06c6Pg+Q1lHbTvUfv74449+l82dO1fGGL8hb00OHjDv379feXl5Ou200/TVV1/VynqrVD3+v/3E+s033yxJ1T45fcwxx/gGzJJ8n6Q/88wzfQPmgy+vegx27NihNWvWaNy4cb4Bs3RgAH722Wf71nGw8ePH+31/4403+q05FGvWrNHGjRs1evRo7du3T3v37tXevXtVVFSks846S59++qnfp60BAAAAAACaMj7J3IQkJSVJkgoLC6td949//EMFBQXatWvXIT91GxUVpbZt2/pd9tNPPykzM9O33Srdu3f3Xe/UwYNH6cAnXKUDg9Pk5GT99NNPioiIqPZJ6W7dujm+z0Pp2LFjrW7vYLGxsb5TT1Rp1qyZ9u/f73ibCxYs0H333ac1a9b4nRu56pO4taXq8e/SpYvf5enp6UpNTa127H97PFNSUiTJ79zfB19e9RhUbedQx7V79+56//33q/1Bxq5du/rlOnfurIiIiEOeWqMmGzdulCSNHTu2xkxeXp7veQkAAAAAANCUMWRuQlJSUpSRkaG1a9dWu67qE6Q1DeLcbrciIpx98L2mAedv/8DdwSIjIw95+cF/UPBIOPiTwVVcLtch1xFofw6lpn106v/7//4//f73v9fpp5+umTNnKiMjQ9HR0ZozZ47fKT+cHI+ahDq8rmlfj8RxdjJgr/qU8sMPP6zjjjvukJlD/R8BAAAAAAAATRFD5ibmnHPO0ezZs/Xll1/qpJNOOqxtZWVl6aOPPlJBQYHfp5nXr1/vu176v08h5+bm+t3+cD7pnJWVJa/Xq82bN/t9ynXDhg2OtxmqZs2aVTulhVR9f2r708PB/Pe//1VsbKzef/99ud1u3+Vz5szxy9kcj5r2oerx37hxo++T65K0a9cu5ebm+o794arazqGO6/r169WyZUu/TzFLBz6FfPAn0Ddt2iSv1xvS6UaqVH1CPjk5WQMHDnSwcgAAAAAAgKaDczI3Mbfddpvi4+N1xRVXaNeuXdWut/kE6bBhw+TxePTUU0/5Xf7oo4/K5XJp6NChkg4M6lq2bKlPP/3ULzdz5kwHe3BA1bafeOIJv8sfe+wxx9sMVefOnbV+/Xrt2bPHd9nXX3+tpUuX+uXi4+MlVR/m2tqxY4fWr1+vioqKgLnIyEi5XC6/TyRv2bJFb775pl/O5nhUDXB/uw/Dhg2TVP3xnjFjhiRZ/5HBmmRkZOi4447TvHnz/Nawdu1affDBB751HOy35xN/8sknJf3fcyYUffv2VefOnfXII48c8vQyBx97AAAAAACApo5PMjcxXbt21UsvvaRLLrlE3bp105gxY9S7d28ZY5STk6OXXnpJERER1c6/fCjDhw/XgAEDdMcdd2jLli3q3bu3PvjgA82fP18TJ070O1/yVVddpQcffFBXXXWVTjjhBH366af64YcfHO/Hcccdp0suuUQzZ85UXl6efve732nRokXatGmT422G6oorrtCMGTM0ePBgXXnlldq9e7eeeeYZHXvssb4/TCgdONXGMccco1dffVVHHXWUmjdvrh49eqhHjx5W9zdlyhTNmzdPOTk5AT+Ne84552jGjBkaMmSIRo8erd27d+vpp59Wly5d9M033/hlQz0effv2lSTdcccduvjiixUdHa3hw4erd+/eGjt2rJ599lnl5ubqjDPO0Jdffql58+Zp5MiRGjBggNU+BvLwww9r6NChys7O1pVXXqmSkhI9+eSTSklJ0dSpU6vlc3Jy9Pvf/15DhgzRsmXL9O9//1ujR49W7969Q77PiIgIzZ49W0OHDtWxxx6ryy+/XG3atNEvv/yiTz75RMnJyXr77bdrbR8BAAAAAAAaMj7J3ASNGDFC3377rUaPHq0PPvhAN910kyZNmqT58+frnHPO0VdffaWLL7446HYiIiL01ltvaeLEiVqwYIEmTpyo77//Xg8//LDvE61V7rzzTl155ZV6/fXXddttt8nj8ei99947rP345z//qT/96U9auHChbrvtNlVUVOidd945rG2Gonv37vrXv/6lvLw8TZ48WW+99ZZeeOEF9enTp1p29uzZatOmjSZNmqRLLrlEr7/+etjWdeaZZ+r555/Xzp07NXHiRL388suaPn26zjvvvGrZUI/HiSeeqHvvvVdff/21xo0bp0suucT3Kd7Zs2fr7rvv1ooVKzRx4kR9/PHHmjJlil555ZVa3a+BAwdq4cKFatGihe6880498sgjOvnkk7V06dJD/mHGV199VW63W3/+85/1zjvvaMKECXr++eet77d///5atmyZTjjhBD311FO68cYbNXfuXKWnp2vSpEm1sWsAAAAAAACNgssc6b+kBgAAAAAAAABoNPgkMwAAAAAAAADAMYbMAAAAAAAAAADHGDIDAAAAAAAAABxjyAwAAABIKisr0xVXXKH27dsrOTlZJ598spYtW1bXywIAABboc6BuMGQGAAAAJFVWVqpDhw767LPPlJubq4kTJ2r48OEqLCys66UBAIAQ0edA3WDIDAAAgFo3depUuVwu7d27N2h27ty5crlc2rJlS62u4aGHHtLRRx8tr9cbUj4hIUF33nmn2rdvr4iICF188cWKiYnRhg0banVdkJ555hm1b99eZWVldb0UAEAA9DkCoc9xMIbMQAAdOnTQuHHjfN8vXrxYLpdLixcvrrX7cLlcmjp1aq1tL9y2bNkil8ulRx55JGi26h8kAAAcafn5+Zo+fbpuv/12RURU/yfv3LlzdfTRR2vSpEk1bmPjxo369ddf1aVLl1pZU1lZmW6//XZlZmYqLi5O/fr104cffljrtw1Htra3OW7cOJWXl+sf//hHSPsPAGia6HP6HA0HQ2bUW1W/Ba36io2N1VFHHaUJEyZo165ddb08K++++26DGiQDAHAkXXbZZSopKVFWVlatbfOf//ynKisrdckll1S7btOmTbruuut00UUX6a233jrk7UtKSnTppZdqypQpSklJqZU1jRs3TjNmzNCYMWP0+OOPKzIyUsOGDdNnn31Wq7cNR7a2txkbG6uxY8dqxowZMsaE8vABAOo5+pw+RxNngHpqzpw5RpK55557zAsvvGCee+45M3bsWBMREWE6duxoioqKwr6GrKwsM3bsWN/3Ho/HlJSUGI/HY7Wd8ePHm5pebiUlJaaiouJwlnlE5eTkGEnm4YcfDpqtqKgwJSUlR2BVAID65q677jKSzJ49e+rk/nv16mUuvfTSQ153xx13mHPPPdc8+eSTpl+/ftWuLy8vN+ecc44ZPXq08Xq9tbKeL774olp/lpSUmM6dO5vs7Oxau204suG6/5UrVxpJZtGiRQH3HwBQd+hzf/Q5fY6a8Ulm1HtDhw7VpZdeqquuukpz587VxIkTlZOTo/nz59d4m6KiorCsJSIiQrGxsYf833Scio2NVVRUVK1trz6JiopSbGxsXS8DABAGBQUFmjhxojp06CC3261WrVrp7LPP1ldffeWXy83N1bhx45SamqqUlBRdfvnlKi4u9sv89hyOVadbWr9+vUaNGqXk5GS1aNFCN910k0pLS4OuLScnR998840GDhx4yOvffvtt/f73v9eKFSt0/PHH+13n9Xp12WWXyeVyad68ebV22qfXX39dkZGRuuaaa3yXxcbG6sorr9SyZcu0bdu2WrltOLLhuv++ffuqefPmAf9NBwAIL/rcDn1On6NmDJnR4Jx55pmSDhSOdOB/4UhMTNTmzZs1bNgwJSUlacyYMZIOFMtjjz2mY489VrGxsWrdurWuvfZa7d+/32+bxhjdd999atu2reLj4zVgwAB999131e67pnMyf/HFFxo2bJiaNWumhIQE9erVS48//rhvfU8//bQk+Z3+o8qhzsm8evVqDR06VMnJyUpMTNRZZ52l5cuX+2WqCnzp0qWaPHmy0tLSlJCQoPPOO0979uzxy+bl5Wn9+vXKy8sL+viuXLlSgwcPVsuWLRUXF6eOHTvqiiuuOGT22WefVefOneV2u3XiiSdqxYoVftcf6pzMLpdLEyZM0Isvvqhu3bopNjZWffv21aeffhp0bQCA+uO6667TrFmzdMEFF2jmzJm65ZZbFBcXp3Xr1vnlRo0apYKCAk2bNk2jRo3S3Llzdffdd4d0H6NGjVJpaammTZumYcOG6YknnvB7s1OTzz//XJLUp0+fatft3btX3377rc444wx99NFHOuuss/yuv/baa7Vjxw699tprh/wlcEVFhfbu3RvS18F/oGj16tU66qijlJyc7Le9k046SZK0Zs2aGvfH5rbhyIbr/qUDx2jp0qWH2m0AwBFAn9Pn9DlqS+P8+CQatc2bN0uSWrRo4bussrJSgwcP1qmnnqpHHnlE8fHxkg4Uy9y5c3X55ZfrT3/6k3JycvTUU09p9erVWrp0qaKjoyVJd955p+677z4NGzZMw4YN01dffaVBgwapvLw86Ho+/PBDnXvuucrIyNBNN92k9PR0rVu3TgsWLNBNN92ka6+9Vtu3b9eHH36oF154Iej2vvvuO5122mlKTk7WbbfdpujoaP3jH/9Q//79tWTJEvXr188vf+ONN6pZs2a66667tGXLFj322GOaMGGCXn31VV/mjTfe0OWXX645c+b4/SHD39q9e7cGDRqktLQ0/fnPf1Zqaqq2bNmi//3vf9WyL730kgoKCnTttdfK5XLpoYce0vnnn68ff/zR97jWZMmSJXr11Vf1pz/9SW63WzNnztSQIUP05ZdfqkePHkEfIwBA3XvnnXd09dVX6+9//7vvsttuu61a7vjjj9fzzz/v+37fvn16/vnnNX369KD30bFjR9+nYsaPH6/k5GTfG+BevXrVeLv169f7bv9by5YtU2pqqrZv366ioiKdc845vut++uknzZ49W7GxsWrZsqXv8vfee0+nnXaaJGnp0qUaMGBA0LVLB34h3qFDB0nSjh07lJGRUS1Tddn27dtr3I7NbcORDdf9S1KnTp1C+vcRACA86PPg6PPAWYk+xwEMmVHv5eXlae/evSotLdXSpUt1zz33KC4uTueee64vU1ZWpgsvvFDTpk3zXfbZZ59p9uzZevHFFzV69Gjf5QMGDNCQIUP02muvafTo0dqzZ48eeughnXPOOXr77bd9n7y944479MADDwRcm8fj0bXXXquMjAytWbNGqampvuvM/zvpfXZ2to466ih9+OGHuvTSS4Pu71//+ldVVFTos88+U6dOnSRJf/zjH9WtWzfddtttWrJkiV++RYsW+uCDD3zr9nq9euKJJ5SXl2f9hw0+//xz7d+/Xx988IFOOOEE3+X33XdftezWrVu1ceNGNWvWTJLUrVs3jRgxQu+//77fsTmUtWvXauXKlerbt68k6eKLL1a3bt105513HnKgDQCof1JTU/XFF19o+/btyszMrDF33XXX+X1/2mmn6Y033lB+fn61T8f81vjx4/2+v/HGGzVz5ky9++67Ad+U7tu3T1FRUUpMTKx23YoVK9SzZ08988wzGjNmjOLi4nzXZWVlBf2jNb179w75L8inp6f7/rukpERut7tapuq0UiUlJTVux+a24ciG6/4lqVmzZiopKVFxcbHvQwIAgCOHPg+OPg+clehzHMCQGfXeb8+/lJWVpRdffFFt2rTxu/z666/3+/61115TSkqKzj77bO3du9d3ed++fZWYmKhPPvlEo0eP1kcffaTy8nLdeOONfqd2mDhxYtAh8+rVq5WTk6NHH33Ub8AsydE5nzwejz744AONHDnSN2CWDvy2cPTo0Xruueeqlfg111zjd1+nnXaaHn30Uf3000++wh43blzATzBXqdqHBQsWqHfv3gE/kXzRRRf5BsxV9ytJP/74Y9D7yc7O9g2YJal9+/YaMWKE3n77bXk8HkVGRgbdBgCgbj300EMaO3as2rVrp759+2rYsGH64x//6Ndf0oGf8Qer6o79+/cHfVPatWtXv+87d+6siIgI37kendiwYYMiIyM1f/58ff/999a3b9asWY3nhgwkLi5OZWVl1S6vOiflwW+OD+e24ciG6/6l//ulfG2dKxMAYIc+t0Of0+eoGedkRr339NNP68MPP9Qnn3yi77//Xj/++KMGDx7sl4mKilLbtm39Ltu4caPy8vLUqlUrpaWl+X0VFhZq9+7dkg78rzRS9eJLS0vzG6IeStWpO2rrFA979uxRcXGxunXrVu267t27y+v1VvtDAoHK3tYZZ5yhCy64QHfffbdatmypESNGaM6cOYcsl8O5398+1pJ01FFHqbi4uNr5pAEA9dOoUaP0448/6sknn1RmZqYefvhhHXvssXrvvff8cjX94jDYJ4wOJdQ3Li1atFBlZaUKCgqqXbd37159+umnuvjiiw/5v98GU15erp07d4b05fF4fLfLyMjQjh07qm2v6rJAnx6zuW04suG6f+nAvxvi4+MDvikHAIQPfU6f0+eoLQyZUe+ddNJJGjhwoPr376/u3bsrIqL609btdle73Ov1qlWrVvrwww8P+XXPPfccqV0Iq9ou+9dff13Lli3ThAkT9Msvv+iKK65Q3759VVhYGLb7BQA0TBkZGbrhhhv05ptvKicnRy1atND9999fa9vfuHGj3/ebNm2S1+v1nRexJkcffbSk//sjwQeLiIiQ2+0+5KmgQvH5558rIyMjpK+DfzF83HHH6YcfflB+fr7f9r744gvf9TWxuW04suG6f+nAMerevfuhdhsAcITQ5/T54dy/RJ/jAIbMaLQ6d+6sffv26ZRTTtHAgQOrffXu3VvSgdNvSNWLb8+ePUE/ldu5c2dJB84xHEio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rTRy5Eht2LDBL1NaWqrx48erRYsWSkxM1AUXXKBdu3bV0YoPXyj73L9//2rH+LrrrqujFR+eWbNmqVevXkpOTlZycrKys7P13nvv+a5vbMcXta+p9LnU+DudPn/T7/rG1OdS0+t0+pw+h72m0umNvc8lOp1Ob1w/9+n0uuv0BjFkfvXVVzV58mTddddd+uqrr9S7d28NHjxYu3fvruulhcWxxx6rHTt2+L4+++yzul5SrSoqKlLv3r319NNPH/L6hx56SE888YSeeeYZffHFF0pISNDgwYNVWlp6hFdaO4LtryQNGTLE75i//PLLR3CFtWfJkiUaP368li9frg8//FAVFRUaNGiQioqKfJlJkybp7bff1muvvaYlS5Zo+/btOv/88+tw1YcnlH2WpKuvvtrvGD/00EN1tOLD07ZtWz344INatWqVVq5cqTPPPFMjRozQd999J6nxHV/UrqbW51Lj7nT6vLrG0udS0+t0+pw+h52m1umNuc8lOv1Q6PSG+3OfTq/DTjcNwEknnWTGjx/v+97j8ZjMzEwzbdq0OlxVeNx1112md+/edb2MI0aSeeONN3zfe71ek56ebh5++GHfZbm5ucbtdpuXX365DlZYu367v8YYM3bsWDNixIg6WU+47d6920gyS5YsMcYcOJbR0dHmtdde82XWrVtnJJlly5bV1TJr1W/32RhjzjjjDHPTTTfV3aLCrFmzZmb27NlN4vji8DSlPjemaXU6fd64+9yYptfp9HnjPbaoHU2p05tSnxtDpxtDpxvTuH7u0+lH7tjW+08yl5eXa9WqVRo4cKDvsoiICA0cOFDLli2rw5WFz8aNG5WZmalOnTppzJgx2rp1a10v6YjJycnRzp07/Y53SkqK+vXr12iPtyQtXrxYrVq1Urdu3XT99ddr3759db2kWpGXlydJat68uSRp1apVqqio8Du+Rx99tNq3b99oju9v97nKiy++qJYtW6pHjx6aMmWKiouL62J5tcrj8eiVV15RUVGRsrOzm8TxhXNNsc+lptvp9Hnj6nOp6XU6fd54jy0OX1Ps9Kba5xKdTqc3/GNMpx+5YxtV61usZXv37pXH41Hr1q39Lm/durXWr19fR6sKn379+mnu3Lnq1q2bduzYobvvvlunnXaa1q5dq6SkpLpeXtjt3LlTkg55vKuua2yGDBmi888/Xx07dtTmzZv1l7/8RUOHDtWyZcsUGRlZ18tzzOv1auLEiTrllFPUo0cPSQeOb0xMjFJTU/2yjeX4HmqfJWn06NHKyspSZmamvvnmG91+++3asGGD/ve//9Xhap379ttvlZ2drdLSUiUmJuqNN97QMcccozVr1jTq44vD09T6XGranU6fN54+l5pep9Pn9DkCa2qd3pT7XKLT6fSGfYzp9CPb6fV+yNzUDB061PffvXr1Ur9+/ZSVlaX//Oc/uvLKK+twZQiXiy++2PffPXv2VK9evdS5c2ctXrxYZ511Vh2u7PCMHz9ea9eubXTnKwukpn2+5pprfP/ds2dPZWRk6KyzztLmzZvVuXPnI73Mw9atWzetWbNGeXl5ev311zV27FgtWbKkrpcF1Dt0etPSWPtcanqdTp8DOBh93vTQ6Y0HnX5k1fvTZbRs2VKRkZHV/vLhrl27lJ6eXkerOnJSU1N11FFHadOmTXW9lCOi6pg21eMtSZ06dVLLli0b9DGfMGGCFixYoE8++URt27b1XZ6enq7y8nLl5ub65RvD8a1pnw+lX79+ktRgj3FMTIy6dOmivn37atq0aerdu7cef/zxRn18cfiaep9LTavT6fPG0edS0+t0+pw+R3BNvdObUp9LdLpEpzdUdPqR7/R6P2SOiYlR3759tWjRIt9lXq9XixYtUnZ2dh2u7MgoLCzU5s2blZGRUddLOSI6duyo9PR0v+Odn5+vL774okkcb0n6+eeftW/fvgZ5zI0xmjBhgt544w19/PHH6tixo9/1ffv2VXR0tN/x3bBhg7Zu3dpgj2+wfT6UNWvWSFKDPMaH4vV6VVZW1iiPL2pPU+9zqWl1On3esPtcanqdTp/T5whdU+/0ptTnEp0u0ekNDZ1eh51e639KMAxeeeUV43a7zdy5c833339vrrnmGpOammp27txZ10urdTfffLNZvHixycnJMUuXLjUDBw40LVu2NLt3767rpdWagoICs3r1arN69WojycyYMcOsXr3a/PTTT8YYYx588EGTmppq5s+fb7755hszYsQI07FjR1NSUlLHK3cm0P4WFBSYW265xSxbtszk5OSYjz76yPTp08d07drVlJaW1vXSrV1//fUmJSXFLF682OzYscP3VVxc7Mtcd911pn379ubjjz82K1euNNnZ2SY7O7sOV314gu3zpk2bzD333GNWrlxpcnJyzPz5802nTp3M6aefXscrd+bPf/6zWbJkicnJyTHffPON+fOf/2xcLpf54IMPjDGN7/iidjWlPjem8Xc6fd54+9yYptfp9Dl9DjtNqdMbe58bQ6fT6Y3r5z6dXned3iCGzMYY8+STT5r27dubmJgYc9JJJ5nly5fX9ZLC4qKLLjIZGRkmJibGtGnTxlx00UVm06ZNdb2sWvXJJ58YSdW+xo4da4wxxuv1mr/97W+mdevWxu12m7POOsts2LChbhd9GALtb3FxsRk0aJBJS0sz0dHRJisry1x99dUN9h9nh9pPSWbOnDm+TElJibnhhhtMs2bNTHx8vDnvvPPMjh076m7RhynYPm/dutWcfvrppnnz5sbtdpsuXbqYW2+91eTl5dXtwh264oorTFZWlomJiTFpaWnmrLPO8pWXMY3v+KL2NZU+N6bxdzp93nj73Jim1+n0OX0Oe02l0xt7nxtDp9PpjevnPp1ed53uMsYY55+DBgAAAAAAAAA0ZfX+nMwAAAAAAAAAgPqLITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMHCFbtmyRy+XSmjVrQr7N3LlzlZqaWufrAAAA9ctv/40wdepUHXfccSHfnn8PAABw+Fwul958801Jzrq1f//+mjhxYljWBhxpDJkBS9u2bdMVV1yhzMxMxcTEKCsrSzfddJP27dsX8Hbt2rXTjh071KNHj5Dv66KLLtIPP/xwuEsGAKBRGjdunFwulx588EG/y9988025XK46WhUAADjSqv5N4HK5FBMToy5duuiee+5RZWVlXS8NaDIYMgMWfvzxR51wwgnauHGjXn75ZW3atEnPPPOMFi1apOzsbP3666+HvF15ebkiIyOVnp6uqKiokO8vLi5OrVq1qq3lAwDQ6MTGxmr69Onav39/XS+lVpSXl9f1EgAAaJCGDBmiHTt2aOPGjbr55ps1depUPfzww9bb8Xg88nq9YVgh0LgxZAYsjB8/XjExMfrggw90xhlnqH379ho6dKg++ugj/fLLL7rjjjskSR06dNC9996rP/7xj0pOTtY111xzyP915q233lLXrl0VGxurAQMGaN68eXK5XMrNzZVU8/8K+8ILL6hDhw5KSUnRxRdfrIKCAl9m4cKFOvXUU5WamqoWLVro3HPP1ebNm4/EwwMAwBE3cOBApaena9q0aTVm/vvf/+rYY4+V2+1Whw4d9Pe//93v+g4dOuiBBx7QFVdcoaSkJLVv317PPvtswPtdvHixXC6X3nnnHfXq1UuxsbE6+eSTtXbtWl9m3759uuSSS9SmTRvFx8erZ8+eevnll/22079/f02YMEETJ05Uy5YtNXjwYEnSjBkz1LNnTyUkJKhdu3a64YYbVFhYaPXYzJ49W927d1dsbKyOPvpozZw50+r2AAA0JG63W+np6crKytL111+vgQMH6q233graqVXvu9966y0dc8wxcrvd2rp1q1asWKGzzz5bLVu2VEpKis444wx99dVXVmtau3athg4dqsTERLVu3VqXXXaZ9u7dW9u7DtQLDJmBEP366696//33dcMNNyguLs7vuvT0dI0ZM0avvvqqjDGSpEceeUS9e/fW6tWr9be//a3a9nJycvSHP/xBI0eO1Ndff61rr73WN6QOZPPmzXrzzTe1YMECLViwQEuWLPH734SLioo0efJkrVy5UosWLVJERITOO+88fhMLAGiUIiMj9cADD+jJJ5/Uzz//XO36VatWadSoUbr44ov17bffaurUqfrb3/6muXPn+uX+/ve/64QTTtDq1at1ww036Prrr9eGDRuC3v+tt96qv//971qxYoXS0tI0fPhwVVRUSJJKS0vVt29fvfPOO1q7dq2uueYaXXbZZfryyy/9tjFv3jzFxMRo6dKleuaZZyRJEREReuKJJ/Tdd99p3rx5+vjjj3XbbbeF/Li8+OKLuvPOO3X//fdr3bp1euCBB/S3v/1N8+bNC3kbAAA0ZHFxcSovLw+pU4uLizV9+nTNnj1b3333nVq1aqWCggKNHTtWn332mZYvX66uXbtq2LBhfh/yCiQ3N1dnnnmmjj/+eK1cuVILFy7Url27NGrUqHDsLlD3DICQLF++3Egyb7zxxiGvnzFjhpFkdu3aZbKysszIkSP9rs/JyTGSzOrVq40xxtx+++2mR48efpk77rjDSDL79+83xhgzZ84ck5KS4rv+rrvuMvHx8SY/P9932a233mr69etX47r37NljJJlvv/32kOsAAKChGjt2rBkxYoQxxpiTTz7ZXHHFFcYYY9544w1T9c/c0aNHm7PPPtvvdrfeeqs55phjfN9nZWWZSy+91Pe91+s1rVq1MrNmzarxvj/55BMjybzyyiu+y/bt22fi4uLMq6++WuPtzjnnHHPzzTf7vj/jjDPM8ccfH3RfX3vtNdOiRQvf94f6N0Lv3r1933fu3Nm89NJLftu49957TXZ2tjGGfw8AABqXg/9N4PV6zYcffmjcbre55ZZbqmUP1amSzJo1awLeh8fjMUlJSebtt9/2XXbwjOC33XrvvfeaQYMG+W1j27ZtRpLZsGGDMebAvwNuuukmy70F6ic+yQxYMv/vk8rBnHDCCQGv37Bhg0488US/y0466aSg2+3QoYOSkpJ832dkZGj37t2+7zdu3KhLLrlEnTp1UnJysjp06CBJ2rp1a0jrBgCgIZo+fbrmzZundevW+V2+bt06nXLKKX6XnXLKKdq4caM8Ho/vsl69evn+2+VyKT093devVf+ba2Jioo499li/bWVnZ/v+u3nz5urWrZtvDR6PR/fee6969uyp5s2bKzExUe+//361Tu7bt2+1/fnoo4901llnqU2bNkpKStJll12mffv2qbi4OOhjUVRUpM2bN+vKK6/0rTsxMVH33Xcfp9ACADRaCxYsUGJiomJjYzV06FBddNFFmjp1akidGhMT4/dvAUnatWuXrr76anXt2lUpKSlKTk5WYWFhyO+tv/76a33yySd+XXz00UdLEn2MRin0v0AGNHFdunSRy+XSunXrdN5551W7ft26dWrWrJnS0tIkSQkJCWFZR3R0tN/3LpfL71QYw4cPV1ZWlp577jllZmbK6/WqR48e/CEhAECjdvrpp2vw4MGaMmWKxo0bZ337QP06e/ZslZSUHDIXyMMPP6zHH39cjz32mO9ckBMnTqzWyb/9N8OWLVt07rnn6vrrr9f999+v5s2b67PPPtOVV16p8vJyxcfHB7zfqvNMPvfcc+rXr5/fdZGRkSGvHwCAhmTAgAGaNWuWYmJilJmZqaioqJA7NS4uTi6Xy297Y8eO1b59+/T4448rKytLbrdb2dnZIb+3Liws1PDhwzV9+vRq12VkZBz+DgP1DENmIEQtWrTQ2WefrZkzZ2rSpEl+52XeuXOnXnzxRf3xj3+sVkw16datm959912/y1asWHFYa9y3b582bNig5557Tqeddpok6bPPPjusbQIA0FA8+OCDOu6449StWzffZd27d9fSpUv9ckuXLtVRRx0V8sC1TZs2NV63fPlytW/fXpK0f/9+/fDDD+revbvvfkaMGKFLL71UkuT1evXDDz/omGOOCXh/q1atktfr1d///ndFRBz4Hw//85//hLRWSWrdurUyMzP1448/asyYMSHfDgCAhiwhIUFdunTxu+xwOnXp0qWaOXOmhg0bJknatm2b1R/t69Onj/773/+qQ4cOiopi/IbGj9NlABaeeuoplZWVafDgwfr000+1bds2LVy4UGeffbbatGmj+++/P+RtXXvttVq/fr1uv/12/fDDD/rPf/7j+yNEoQ6qf6tZs2Zq0aKFnn32WW3atEkff/yxJk+e7GhbAAA0ND179tSYMWP0xBNP+C67+eabtWjRIt1777364YcfNG/ePD311FO65ZZbauU+77nnHi1atEhr167VuHHj1LJlS40cOVKS1LVrV3344Yf6/PPPtW7dOl177bXatWtX0G126dJFFRUVevLJJ/Xjjz/qhRde8P1BwFDdfffdmjZtmp544gn98MMP+vbbbzVnzhzNmDHDyW4CANAgHU6ndu3aVS+88ILWrVunL774QmPGjPH7sFkw48eP16+//qpLLrlEK1as0ObNm/X+++/r8ssv9ztlF9BYMGQGLHTt2lUrV65Up06dNGrUKHXu3FnXXHONBgwYoGXLlql58+Yhb6tjx456/fXX9b///U+9evXSrFmzdMcdd0iS3G63o/VFRETolVde0apVq9SjRw9NmjRJDz/8sKNtAQDQEN1zzz1+p5Hq06eP/vOf/+iVV15Rjx49dOedd+qee+5xdEqNQ3nwwQd10003qW/fvtq5c6fefvttxcTESJL++te/qk+fPho8eLD69++v9PR03wA6kN69e2vGjBmaPn26evTooRdffFHTpk2zWtdVV12l2bNna86cOerZs6fOOOMMzZ07Vx07dnSymwAANEiH06nPP/+89u/frz59+uiyyy7Tn/70J7Vq1Srk+87MzNTSpUvl8Xg0aNAg9ezZUxMnTlRqaqrvU9VAY+Iyof4VMwBhd//99+uZZ57Rtm3b6nopAAAggMWLF2vAgAHav3+/UlNT63o5AAAAQJ3ipDBAHZo5c6ZOPPFEtWjRQkuXLtXDDz+sCRMm1PWyAAAAAAAAgJAxZAbq0MaNG3Xffffp119/Vfv27XXzzTdrypQpdb0sAAAAAAAAIGScLgMAAAAAAAAA4BhnGgcAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI79/wfPmLdfWaIeAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABxv0lEQVR4nO3dd3hUdd7+8XsmZdJDIEASSmiCdAUVsVAERVBW1LUAKtgLuAK2ZW2IrtgWFbuLC6xrW33WvlYU/ImAgqKigIBBEUioSSA9M9/fHz7Mw5hkZr6HhEl5v64r10Vm7jnzPXMmuTOfDCcuY4wRAAAAAAAAAAAOuCO9AAAAAAAAAABAw8WQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGQAAAAAAAADgGENmAAAAAAAAAIBjDJkBAAAAAAAAAI4xZAYAAAAAAAAAOMaQGY2Oy+XSjBkzIr2MoCZOnKikpKRIL6NRmTFjhlwul3bu3Bky26FDB02cONH/+aJFi+RyubRo0aK6WyAAAAAAAEAjxZC5icrJydHkyZPVtWtXJSQkKCEhQT169NCkSZP07bffRnp5dWrIkCFyuVwhPw52UF1cXKwZM2Y06MHl559/rhkzZig/Pz/SSwEAAAAAAEA9FR3pBeDQe/vtt3XeeecpOjpa48ePV9++feV2u7V27Vr95z//0ZNPPqmcnBxlZ2dHeql14pZbbtFll13m//zLL7/UnDlz9Je//EXdu3f3X96nT5+Dup/i4mLdeeedkn4bbDdEn3/+ue68805NnDhRzZo1i/Ryas26devkdvM7NgAAAAAAgNrAkLmJ2bhxo84//3xlZ2dr4cKFyszMDLj+vvvu0xNPPBFyAFdUVKTExMS6XGqdOfnkkwM+j4uL05w5c3TyyScHHQY35H1GII/HE+klAAAAAAAANBq8la+Juf/++1VUVKR58+ZVGTBLUnR0tP70pz+pXbt2/sv2nz9448aNGjVqlJKTkzV+/HhJvw1er7/+erVr104ej0fdunXTgw8+KGOM//abNm2Sy+XS/Pnzq9zf709Lsf+8uhs2bPC/ezY1NVUXX3yxiouLA25bVlamqVOnqmXLlkpOTtYf/vAH/frrrwf5CAWu44cfftC4ceOUlpamE044QdJv70qubhg9ceJEdejQwb/PLVu2lCTdeeedNZ6CY8uWLRozZoySkpLUsmVL3XDDDfJ6vQGZbdu2ae3ataqoqAi57gcffFDHHXecWrRoofj4ePXv31+vvvpqQCbc4zFjxgzdeOONkqSOHTv692HTpk2SpMrKSt11113q3LmzPB6POnTooL/85S8qKysL2GaHDh10+umna9GiRTrqqKMUHx+v3r17+08j8p///Ee9e/dWXFyc+vfvr6+//rrKuj7++GOdeOKJSkxMVLNmzXTGGWdozZo11T4GO3fu1LnnnquUlBS1aNFC1113nUpLS6us6cBzMtdk+fLlOvXUU5WamqqEhAQNHjxYS5YsCXk7AAAAAACApoQhcxPz9ttvq0uXLhowYIDV7SorKzVixAi1atVKDz74oM4++2wZY/SHP/xBDz30kE499VTNnj1b3bp104033qhp06Yd1DrPPfdc7d27V7NmzdK5556r+fPn+089sd9ll12mhx9+WKeccoruvfdexcTE6LTTTjuo+/29c845R8XFxbrnnnt0+eWXh327li1b6sknn5QknXnmmXruuef03HPP6ayzzvJnvF6vRowYoRYtWujBBx/U4MGD9be//U3PPPNMwLamT5+u7t27a8uWLSHv95FHHtGRRx6pmTNn6p577lF0dLTOOeccvfPOO2Gvfb+zzjpLY8eOlSQ99NBD/n3YPzy/7LLLdPvtt6tfv3566KGHNHjwYM2aNUvnn39+lW1t2LBB48aN0+jRozVr1izt2bNHo0eP1vPPP6+pU6fqggsu0J133qmNGzfq3HPPlc/n89/2o48+0ogRI7R9+3bNmDFD06ZN0+eff67jjz/eP/A+0LnnnqvS0lLNmjVLo0aN0pw5c3TFFVdY7//HH3+sQYMGqbCwUHfccYfuuece5efn66STTtIXX3xhvT0AAAAAAIDGitNlNCGFhYXaunWrxowZU+W6/Px8VVZW+j9PTExUfHy8//OysjKdc845mjVrlv+yN954Qx9//LHuvvtu3XLLLZKkSZMm6ZxzztEjjzyiyZMnq3Pnzo7WeuSRR+rZZ5/1f75r1y49++yzuu+++yRJ33zzjf71r3/pmmuu0eOPP+6/7/Hjx9fqHy7s27evXnjhBevbJSYm6o9//KOuvvpq9enTRxdccEGVTGlpqc477zzddtttkqSrrrpK/fr107PPPqurr77a0Xp//PHHgOM2efJk9evXT7Nnz7YewPfp00f9+vXTiy++qDFjxvjfpS399vgvWLBAl112mf7+979Lkq655hr/LyE++eQTDR061J9ft26dPv/8cw0cOFCS1KNHD40YMUKXX3651q5dq/bt20uS0tLSdOWVV+rTTz/1v1v8xhtvVPPmzbV06VI1b95ckjRmzBgdeeSRuuOOO7RgwYKAdXfs2FFvvPGGpN+eEykpKXriiSd0ww03hH2ebWOMrrrqKg0dOlTvvvuuXC6XJOnKK69Uz549deutt+qDDz6wejwBAAAAAAAaK97J3IQUFhZKkpKSkqpcN2TIELVs2dL/sX9we6DfDz7/+9//KioqSn/6058CLr/++utljNG7777reK1XXXVVwOcnnniidu3a5d+H//73v5JU5b6nTJni+D7DWUdtq24/f/rpp4DL5s+fL2NMwJC3JgcOmPfs2aOCggKdeOKJ+uqrr2plvfvtf/x//47166+/XpKqvHO6R48e/gGzJP876U866ST/gPnAy/c/Btu2bdOqVas0ceJE/4BZ+m0AfvLJJ/vXcaBJkyYFfH7ttdcGrDkcq1at0vr16zVu3Djt2rVLO3fu1M6dO1VUVKRhw4bp008/DXi3NQAAAAAAQFPGO5mbkOTkZEnSvn37qlz39NNPa+/evcrLy6v2XbfR0dFq27ZtwGU///yzsrKy/Nvdr3v37v7rnTpw8Cj99g5X6bfBaUpKin7++We53e4q75Tu1q2b4/usTseOHWt1eweKi4vzn3piv7S0NO3Zs8fxNt9++23dfffdWrVqVcC5kfe/E7e27H/8u3TpEnB5RkaGmjVrVuXY//54pqamSlLAub8PvHz/Y7B/O9Ud1+7du+v999+v8gcZDzvssIBc586d5Xa7qz21Rk3Wr18vSZowYUKNmYKCAv/zEgAAAAAAoCljyNyEpKamKjMzU6tXr65y3f53kNY0iPN4PHK7nb3xvaYB5+//wN2BoqKiqr38wD8oeCgc+M7g/VwuV7XrCLY/1alpH536f//v/+kPf/iDBg0apCeeeEKZmZmKiYnRvHnzAk754eR41CTc4XVN+3oojrOTAfv+dyk/8MADOuKII6rNVPc/AgAAAAAAAJoihsxNzGmnnaa5c+fqiy++0DHHHHNQ28rOztZHH32kvXv3Brybee3atf7rpf97F3J+fn7A7Q/mnc7Z2dny+XzauHFjwLtc161b53ib4UpLS6tySgup6v7U9ruHQ/mf//kfxcXF6f3335fH4/FfPm/evICczfGoaR/2P/7r16/3v3NdkvLy8pSfn+8/9gdr/3aqO65r165Venp6wLuYpd/ehXzgO9A3bNggn88X1ulG9tv/DvmUlBQNHz7cwcoBAAAAAACaDs7J3MTcdNNNSkhI0CWXXKK8vLwq19u8g3TUqFHyer167LHHAi5/6KGH5HK5NHLkSEm/DerS09P16aefBuSeeOIJB3vwm/3bnjNnTsDlDz/8sONthqtz585au3atduzY4b/sm2++0ZIlSwJyCQkJkqoOc21t27ZNa9euVUVFRdBcVFSUXC5XwDuSN23apNdffz0gZ3M89g9wf78Po0aNklT18Z49e7YkWf+RwZpkZmbqiCOO0IIFCwLWsHr1an3wwQf+dRzo9+cTf/TRRyX933MmHP3791fnzp314IMPVnt6mQOPPQAAAAAAQFPHO5mbmMMOO0wvvPCCxo4dq27dumn8+PHq27evjDHKycnRCy+8ILfbXeX8y9UZPXq0hg4dqltuuUWbNm1S37599cEHH+iNN97QlClTAs6XfNlll+nee+/VZZddpqOOOkqffvqpfvzxR8f7ccQRR2js2LF64oknVFBQoOOOO04LFy7Uhg0bHG8zXJdccolmz56tESNG6NJLL9X27dv11FNPqWfPnv4/TCj9dqqNHj166OWXX1bXrl3VvHlz9erVS7169bK6v+nTp2vBggXKyckJ+m7c0047TbNnz9app56qcePGafv27Xr88cfVpUsXffvttwHZcI9H//79JUm33HKLzj//fMXExGj06NHq27evJkyYoGeeeUb5+fkaPHiwvvjiCy1YsEBjxozR0KFDrfYxmAceeEAjR47UwIEDdemll6qkpESPPvqoUlNTNWPGjCr5nJwc/eEPf9Cpp56qpUuX6l//+pfGjRunvn37hn2fbrdbc+fO1ciRI9WzZ09dfPHFatOmjbZs2aJPPvlEKSkpeuutt2ptHwEAAAAAABoy3sncBJ1xxhn67rvvNG7cOH3wwQe67rrrNHXqVL3xxhs67bTT9NVXX+n8888PuR23260333xTU6ZM0dtvv60pU6bohx9+0AMPPOB/R+t+t99+uy699FK9+uqruummm+T1evXuu+8e1H784x//0J/+9Ce9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RKfT6XW9zQPR6U1Pw3w7JZqUjRs3SpJatGjhv6yyslIjRozQCSecoAcffFAJCQmSfiuO+fPn6+KLL9af/vQn5eTk6LHHHtPXX3+tJUuWKCYmRpJ0++236+6779aoUaM0atQoffXVVzrllFNC/nZVkj788EOdfvrpyszM1HXXXaeMjAytWbNGb7/9tq677jpdeeWV2rp1qz788EM999xzIbf3/fff68QTT1RKSopuuukmxcTE6Omnn9aQIUO0ePFiDRgwICB/7bXXKi0tTXfccYc2bdqkhx9+WJMnT9bLL7/sz7z22mu6+OKLNW/evIA/ZPh7Dz/8sK699lolJSXplltukaQqhX7NNdeoZcuWuv322x29k/nOO+/UjBkzdNxxx2nmzJmKjY3V8uXL9fHHH+uUU06p9jbPPPOMrrrqKv3lL3/R3XffbX2fAIDa06dPH/Xr108vvviixowZU+0vGfv27asXXnjB0fanT5+u3NxcLV++XEcddZT/8pkzZ9Z4Hr+1a9dq2LBhatOmjd5//32lpaXVuP21a9dK+u0dW7+3dOlSNWvWTFu3blVRUZFOO+00/3U///yz5s6dq7i4OKWnp/svf/fdd3XiiSdKkpYsWaKhQ4eGtZ85OTn+x27btm3KzMysktl/2datW4NuK9zb29xPJLd5oE6dOoX18xMAwB6dTqfX9TYPRKc3PQyZUe8UFBRo586dKi0t1ZIlSzRz5kzFx8fr9NNP92fKysp0zjnnaNasWf7LPvvsM82dO1fPP/+8xo0b57986NChOvXUU/XKK69o3Lhx2rFjh+6//36ddtppeuutt/zvvL3lllt0zz33BF2b1+vVlVdeqczMTK1atUrNmjXzX7e/NAcOHKiuXbvqww8/1AUXXBByf2+99VZVVFTos88+U6dOnSRJF110kbp166abbrpJixcvDsi3aNFCH3zwgX/dPp9Pc+bMUUFBgfUfLhgzZoxuvfVWpaen17jW5s2ba+HChYqKirLatvTbH16YOXOmzjzzTL366qsBpx2p6YeMOXPmaMqUKZo5c6ZuvfVW6/sEABx6V111laPb+Xw+vf766xo9enTAi9H9qvvfMatXr9Z5552nLl266N13363yjprf27Vrl6Kjo5WUlFTlui+//FK9e/fWU089pfHjxys+Pt5/XXZ2dsg/VtO3b9+w/3p8RkaG/98lJSXyeDxVMnFxcf7rgwn39jb3E8ltHigtLU0lJSUqLi72v4kAAHDo0Omh0enBs/vR6U0PQ2bUO78/v1J2draef/55tWnTJuDyq6++OuDzV155RampqTr55JO1c+dO/+X9+/dXUlKSPvnkE40bN04fffSRysvLde211wYU3ZQpU0IOmb/++mvl5OTooYceChgwS9WXZiher1cffPCBxowZ4x8wS7/9NnDcuHH6+9//rsLCwoCyveKKKwLu68QTT9RDDz2kn3/+WX369JH02+ksgr2D2cbll1/uaMAsSa+//rp8Pp9uv/32Kue1ru7xuv/++3XzzTfr/vvv5zQZANCAVPeOonDs2LFDhYWF6tWrV9i3GT16tFq3bq3333+/2heZNtatW6eoqCi98cYb+uGHH6xvn5aWVuN5IYOJj49XWVlZlctLS0v919fG7W3uJ5LbPND+IUBtnSsTAGCHTrdDp9Pp+D8MmVHvPP744+ratauio6PVunVrdevWrcqAMjo62n9+4P3Wr1+vgoICtWrVqtrtbt++XdJv/1VGkg477LCA61u2bBn0v+ZI/3fqDpviDGbHjh0qLi5Wt27dqlzXvXt3+Xw+bd68WT179vRf3r59+4Dc/jX//rzTtcXpDxnSb4+X2+1Wjx49QmYXL16sd955RzfffDMDZgBoYKp7YVHTCwqv13tQ93X22WdrwYIFev7553XllVeGzLdo0UKVlZXau3evkpOTA67buXOnPv30U1144YWO+q68vFy7d+8OK9uyZUv/L20zMzO1ZcuWKplt27ZJkrKysoJuK9zb29xPJLd5oD179ighISHki3IAQN2g00Oj04Nn96PTmx6GzKh3jjnmmGr/e82BPB5PlcGzz+dTq1at9Pzzz1d7mwP/mEFDVtO7ikP99x+nDtUPGT179lR+fr6ee+45XXnllQc13AYA1C4n70BJS0tTfn5+lcv3/7JX+q2bU1JStHr16rC3+8ADDyg6OlrXXHONkpOTA06RVZ3DDz9c0m/nT9z/P372c7vd8ng8js////nnnzs6f+MRRxyhTz75pMr/Vlq+fLn/+mDCvb3N/URymwfKyclR9+7dg+4/AMA5Or16dDqdjoPnDh0BGobOnTtr165dOv744zV8+PAqH3379pX02+k3pN/e+XygHTt2hHw3cOfOnSUpZHGGW9wtW7ZUQkKC1q1b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N2/WJZdcoqysLMXGxio7O1vXXXeddu3aFfR27dq107Zt29SrV6+w7+u8887Tjz/+eLBLBgCgUZo4caJcLpfuvffegMtff/11uVyuCK0KAAAcavt/JnC5XIqNjVWXLl00c+ZMVVZWRnppQJPBkBmw8NNPP+moo47S+vXr9eKLL2rDhg166qmntHDhQg0cOFC7d++u9nbl5eWKiopSRkaGoqOjw76/+Ph4tWrVqraWDwBAoxMXF6f77rtPe/bsifRSakV5eXmklwAAQIN06qmnatu2bVq/fr2uv/56zZgxQw888ID1drxer3w+Xx2sEGjcGDIDFiZNmqTY2Fh98MEHGjx4sNq3b6+RI0fqo48+0pYtW3TLLbdIkjp06KC77rpLF110kVJSUnTFFVdU+19n3nzzTR122GGKi4vT0KFDtWDBArlcLuXn50uq+b/CPvfcc+rQoYNSU1N1/vnna+/evf7Me++9pxNOOEHNmjVTixYtdPrpp2vjxo2H4uEBAOCQGz58uDIyMjRr1qwaM//zP/+jnj17yuPxqEOHDvrb3/4WcH2HDh10zz336JJLLlFycrLat2+vZ555Juj9Llq0SC6XS++884769OmjuLg4HXvssVq9erU/s2vXLo0dO1Zt2rRRQkKCevfurRdffDFgO0OGDNHkyZM1ZcoUpaena8SIEZKk2bNnq3fv3kpMTFS7du10zTXXaN++fVaPzdy5c9W9e3fFxcXp8MMP1xNPPGF1ewAAGhKPx6OMjAxlZ2fr6quv1vDhw/Xmm2+G7NT9r7vffPNN9ejRQx6PR7/88ou+/PJLnXzyyUpPT1dqaqoGDx6sr776ympNq1ev1siRI5WUlKTWrVvrwgsv1M6dO2t714F6gSEzEKbdu3fr/fff1zXXXKP4+PiA6zIyMjR+/Hi9/PLLMsZIkh588EH17dtXX3/9tW677bYq28vJydEf//hHjRkzRt98842uvPJK/5A6mI0bN+r111/X22+/rbfffluLFy8O+G/CRUVFmjZtmlasWKGFCxfK7XbrzDPP5DexAIBGKSoqSvfcc48effRR/frrr1WuX7lypc4991ydf/75+u677zRjxgzddtttmj9/fkDub3/7m4466ih9/fXXuuaaa3T11Vdr3bp1Ie//xhtv1N/+9jd9+eWXatmypUaPHq2KigpJUmlpqfr376933nlHq1ev1hVXXKELL7xQX3zxRcA2FixYoNjYWC1ZskRPPfWUJMntdmvOnDn6/vvvtWDBAn388ce66aabwn5cnn/+ed1+++3661//qjVr1uiee+7RbbfdpgULFoS9DQAAGrL4+HiVl5eH1anFxcW67777NHfuXH3//fdq1aqV9u7dqwkTJuizzz7TsmXLdNhhh2nUqFEBb/IKJj8/XyeddJKOPPJIrVixQu+9957y8vJ07rnn1sXuApFnAIRl2bJlRpJ57bXXqr1+9uzZRpLJy8sz2dnZZsyYMQHX5+TkGEnm66+/NsYYc/PNN5tevXoFZG655RYjyezZs8cYY8y8efNMamqq//o77rjDJCQkmMLCQv9lN954oxkwYECN696xY4eRZL777rtq1wEAQEM1YcIEc8YZZxhjjDn22GPNJZdcYowx5rXXXjP7f8wdN26cOfnkkwNud+ONN5oePXr4P8/OzjYXXHCB/3Ofz2datWplnnzyyRrv+5NPPjGSzEsvveS/bNeuXSY+Pt68/PLLNd7utNNOM9dff73/88GDB5sjjzwy5L6+8sorpkWLFv7Pq/sZoW/fvv7PO3fubF544YWAbdx1111m4MCBxhh+HgAANC4H/kzg8/nMhx9+aDwej7nhhhuqZKvrVElm1apVQe/D6/Wa5ORk89Zbb/kvO3BG8Ptuveuuu8wpp5wSsI3NmzcbSWbdunXGmN9+Drjuuuss9xaon3gnM2DJ/O87lUM56qijgl6/bt06HX300QGXHXPMMSG326FDByUnJ/s/z8zM1Pbt2/2fr1+/XmPHjlWnTp2UkpKiDh06SJJ++eWXsNYNAEBDdN9992nBggVas2ZNwOVr1qzR8ccfH3DZ8ccfr/Xr18vr9fov69Onj//fLpdLGRkZ/n7d/99ck5KS1LNnz4BtDRw40P/v5s2bq1u3bv41eL1e3XXXXerdu7eaN2+upKQkvf/++1U6uX///lX256OPPtKwYcPUpk0bJScn68ILL9SuXbtUXFwc8rEoKirSxo0bdemll/rXnZSUpLvvvptTaAEAGq23335bSUlJiouL08iRI3XeeedpxowZYXVqbGxswM8CkpSXl6fLL79chx12mFJTU5WSkqJ9+/aF/dr6m2++0SeffBLQxYcffrgk0cdolML/C2RAE9elSxe5XC6tWbNGZ555ZpXr16xZo7S0NLVs2VKSlJiYWCfriImJCfjc5XIFnApj9OjRys7O1t///ndlZWXJ5/OpV69e/CEhAECjNmjQII0YMULTp0/XxIkTrW8frF/nzp2rkpKSanPBPPDAA3rkkUf08MMP+88FOWXKlCqd/PufGTZt2qTTTz9dV199tf7617+qefPm+uyzz3TppZeqvLxcCQkJQe93/3km//73v2vAgAEB10VFRYW9fgAAGpKhQ4fqySefVGxsrLKyshQdHR12p8bHx8vlcgVsb8KECdq1a5ceeeQRZWdny+PxaODAgWG/tt63b59Gjx6t++67r8p1mZmZB7/DQD3DkBkIU4sWLXTyySfriSee0NSpUwPOy5ybm6vnn39eF110UZViqkm3bt303//+N+CyL7/88qDWuGvXLq1bt05///vfdeKJJ0qSPvvss4PaJgAADcW9996rI444Qt26dfNf1r17dy1ZsiQgt2TJEnXt2jXsgWubNm1qvG7ZsmVq3769JGnPnj368ccf1b17d//9nHHGGbrgggskST6fTz/++KN69OgR9P5Wrlwpn8+nv/3tb3K7f/uPh//+97/DWqsktW7dWllZWfrpp580fvz4sG8HAEBDlpiYqC5dugRcdjCdumTJEj3xxBMaNWqUJGnz5s1Wf7SvX79++p//+R916NBB0dGM39D4cboMwMJjjz2msrIyjRgxQp9++qk2b96s9957TyeffLLatGmjv/71r2Fv68orr9TatWt1880368cff9S///1v/x8hCndQ/XtpaWlq0aKFnnnmGW3YsEEff/yxpk2b5mhbAAA0NL1799b48eM1Z84c/2XXX3+9Fi5cqLvuuks//vijFixYoMcee0w33HBDrdznzJkztXDhQq1evVoTJ05Uenq6xowZI0k67LDD9OGHH+rzzz/XmjVrdOWVVyovLy/kNrt06aKKigo9+uij+umnn/Tcc8/5/yBguO68807NmjVLc+bM0Y8//qjvvvtO8+bN0+zZs53sJgAADdLBdOphhx2m5557TmvWrNHy5cs1fvz4gDebhTJp0iTt3r1bY8eO1ZdffqmNGzfq/fff18UXXxxwyi6gsWDIDFg47LDDtGLFCnXq1EnnnnuuOnfurCuuuEJDhw7V0qVL1bx587C31bFjR7366qv6z3/+oz59+ujJJ5/ULbfcIknyeDyO1ud2u/XSSy9p5cqV6tWrl6ZOnaoHHnjA0bYAAGiIZs6cGXAaqX79+unf//63XnrpJfXq1Uu33367Zs6c6eiUGtW59957dd1116l///7Kzc3VW2+9pdjYWEnSrbfeqn79+mnEiBEaMmSIMjIy/APoYPr27avZs2frvvvuU69evfT8889r1qxZVuu67LLLNHfuXM2bN0+9e/fW4MGDNX/+fHXs2NHJbgIA0CAdTKc+++yz2rNnj/r166cLL7xQf/rTn9SqVauw7zsrK0tLliyR1+vVKaecot69e2vKlClq1qyZ/13VQGPiMuH+FTMAde6vf/2rnnrqKW3evDnSSwEAAEEsWrRIQ4cO1Z49e9SsWbNILwcAAACIKE4KA0TQE088oaOPPlotWrTQkiVL9MADD2jy5MmRXhYAAAAAAAAQNobMQAStX79ed999t3bv3q327dvr+uuv1/Tp0yO9LAAAAAAAACBsnC4DAAAAAAAAAOAYZxoHAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACOMWQGAAAAAAAAADjGkBkAAAAAAAAA4BhDZgAAAAAAAACAYwyZAQAAAAAAAACO/X+o6pg90a5Z0gAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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bFxcnHr27NnotC+//FL5+fmh7e4xZMiQ0Ple9e7du9H3WVlZkqTdu3eHth0TE7PXO6UHDx7s+Tr3pW/fvs26vW9KTEwMHbd5j6ysrNA+erWvdxgPGjRIX3zxRcTL7mt/v/zyy31us7lvawAAAAAAAHjH4TJwQGRkZKh79+57fQicpNAxmpsaRPr9fsXEePt7yLc/YG6Pb3/A3TfFxsbu83TLcZGbw76OO+zz+fa5jnD7sy9N7WNr4jjLAAAAAAAA7RPvZMYBc+qpp2r9+vX6z3/+s9/bKigo0JYtW1ReXt7o9DVr1oTOl/7vXcglJSWNcvvzTueCggIFg0Ft2LCh0elr1671vM1oZWVl7bUv0t7709RwvaWtW7dur9M+++yzqA+38W0FBQX73OaBuK0BAAAAAAAQHYbMOGCuu+46JScn68ILL1RRUdFe51veKTxx4kQFAgHdf//9jU6/++675fP5NGHCBElSenq6unbtqrfffrtR7sEHH/SwB1/bs+377ruv0en33HOP521Gq3///lqzZo127NgROu2///2vlixZ0iiXnJwsae/hutXWrVu1Zs0a1dfXR5V//vnntXnz5tD3//nPf7Rs2bLQbWY1ceJELV26tNEfJnbs2KEnnnjC0/YAAAAAAADQ/DhcBg6YgQMH6sknn9Q555yjwYMHa/LkyRoxYoSccyosLNSTTz6pmJiYvY6/vC+nnXaaxo4dqxtuuEFffPGFRowYoX//+9964YUXNGPGjEbHS7744ot122236eKLL9bhhx+ut99+W5999pnn/Tj00EN1zjnn6MEHH1Rpaam++93vauHChVq/fr3nbUbrwgsv1Ny5czV+/HhddNFF2r59ux566CEdcsghoQ8mlL4+9MTBBx+sp59+WoMGDVJ2draGDh2qoUOHmq5v5syZeuyxx1RYWBjVu5EHDBigY445RldccYVqa2t1zz33qEuXLrruuuusuyrp6z9MPP744zrllFN01VVXKSUlRY888ogKCgr08ccfe9omAAAAAAAAmhdDZhxQp59+ulauXKm77rpL//73v/WnP/1JPp9PBQUFOvXUU3X55ZdrxIgREbcTExOjf/7zn7rxxhv19NNPa/78+erTp4/uuOMOXXvttY2yN954o3bs2KG//e1veuaZZzRhwgS98sor6tatm+f9+NOf/qScnBw98cQTev75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eG3evFmXXXaZevfurXfffVczZ87U1q1bG/0CEe39IElr167VOeeco8suu0yXXHKJBg8eHPE2AwAceDfeeKOcc/p//+//qXfv3pKkM844Q8OGDWuUu+GGGxQIBLRy5cpQd19++eU655xzNGvWLF122WVKSkqKOvdNI0aM0JNPPhlxrWvWrJEk9e3bt9Hp7733njIzM7VlyxZVVlbq1FNPDZ335Zdf6tFHH1ViYqK6du0aOv2VV17RscceK0lasmSJxo4dG9XtVVhYqD59+kiStm7dqu7du++V2XPali1bwm4r2stbrqclsl72s1+/flH9vgQAaB70OX3enNe/B33euTFkRptVWlqqnTt3qqamRkuWLNHNN9+spKQkfe973wtlamtrdeaZZ2r27Nmh09555x09+uijeuKJJ3TuueeGTh87dqxOOeUUPfvsszr33HO1Y8cO3X777Tr11FP14osvht5lfMMNN+jWW28Nu7ZAIKDLLrtM3bt310cffaTMzMzQee5/D3I/evRoDRo0SK+//rrOO++8iPv7q1/9SvX19XrnnXfUr18/SdJPfvITDR48WNddd50WL17cKN+lSxf9+9//Dq07GAzqvvvuU2lpqfnDDCy3xdy5c7VhwwZ9+OGHGjhwoKSvh8n5+fm64447dO2116pXr15R3w97rF+/Xq+++qrGjx9vWjsA4MAJBAJ67bXXNGnSpNALUkkaMmSIxo8fr5dfflnS113497//XT/+8Y/lnNPOnTtD2fHjx+upp57SBx98oO9+97tR5Y4++uhG67j88sujWm9xcbHi4uKUmpra6PT3339fw4YN00MPPaTJkyc3etFbUFAQ8QNrRowYEfUnyOfl5YX+XV1dLb/fv1cmMTExdH440V7ecj0tkfWyn1lZWaqurlZVVVXoTQMAgJZBn3+NPm++69+DPu/cGDKjzfr28ZYKCgr0xBNPqEePHo1Ov+KKKxp9/+yzzyojI0MnnXRSo3IbOXKkUlNT9dZbb+ncc8/VG2+8obq6Ol155ZWNDmMxY8aMiEPmDz/8UIWFhbr77rsbDZgleTrOUyAQ0L///W9NmjQpNGCWvv4L4bnnnqs//OEPKisra/TfVC699NJG13Xsscfq7rvv1pdffqnhw4dL+vqQHZHewSzJdFs8++yzOvbYY5WVldXo9h03bpxuu+02vf3225o8eXLU98Meffv2ZcAMAG3cjh07VF1dHfoj4zcNHjw49KJ0x44dKikp0SOPPKJHHnlkn9vavn171Llv+/Y7mazWrl2r2NhYvfDCC/r000/Nl8/KytrncSEjSUpKUm1t7V6n19TUhM5vjstbrqclsl72c88goLmOlwkAaBp9/jX6vPmufw/6vHNjyIw264EHHtCgQYMUFxen3NxcDR48eK8P3ouLi1PPnj0bnbZu3TqVlpaqW7du+9zunnL78ssvJWmvYs3JyVFWVlbYte05dMfQoUOj36EwduzYoaqqqn0eImLIkCEKBoPatGmTDjnkkNDp3/yLs6TQmr99vONoWG6LdevW6eOPP1ZOTs4+t7Xn9o32fthjf3/BAAC0HXuOW3jeeedpypQp+8wMHz486ty3RXrxtkeXLl3U0NCg8vJypaWlhU7fuXOn3n77bZ1//vme+qeurk67du2KKpuTk6PY2FhJX//xePPmzXtltm7dKknKz88Pu61oL2+5npbIetnP3bt3Kzk5Oer7FgDQ8ujz/0Ofh9/mHvR558aQGW3WkUceqcMPPzxsxu/37zV4DgaD6tatm5544ol9Xqap4Wh7s6fgvi3SfwnaX8FgUCeddJKuu+66fZ4/aNCgUM5yP1BCAND25eTkKCkpSevWrdvrvLVr1zbKpaWlKRAIhH2HUCAQiCrn1UEHHSTp6+MofvPFbUxMjPx+v2655RZP23333Xc9HcPx0EMP1VtvvbXX/05atmxZ6Pxwor285XpaIutlPwsLCzVkyJCw+w8AaB70+dfoc/oczSsmcgRoX/r376/i4mIdffTRGjdu3F5fI0aMkPT14Tck7VWsO3bsiPhu4P79+0uSVq1aFTYX7X8RycnJUXJycqNC32PNmjWKiYlRr169otqWF5bbon///qqoqNjnbTtu3LjQO6yjvR8AAO1HbGysxo8fr+eff14bN24Mnb569Wq99tprjXJnnHGG/v73v++zK3fs2GHKeTV69GhJ0vLly0OnOee0e/du/eQnP9nrEFzR2nMMx2i+vnkMxx/96EcKBAKN/itxbW2t5s+fr1GjRjXq+qqqKq1Zs6bRIaeivbzleloia9nmHnuO6QkAaHn0+dfoc/oczcwBbcz8+fOdJPf++++HzU2ZMsWlpKTsdfqiRYucJDdz5sy9zquvr3e7d+92zjm3fft2Fx8f70499VQXDAZDmf/5n/9xktyUKVNCp7311ltOknvrrbecc84FAgHXt29fV1BQENreHt/c1vXXX+8k7ZVxzjlJ7qabbgp9P2nSJOf3+11hYWHotG3btrn09HR33HHHhU5r6vb59hqdc66kpMStXr3alZSU7HX932S5LWbNmuUkuVdffXWv7ezevdvV19c756K/H5xzrqCgwJ166qlh1wgAaBv++9//usTERNe7d2932223uVtuucXl5ua64cOHu2/+arlt2zZXUFDgkpOT3VVXXeUefvhhN3v2bHfmmWe6rKwsc84552666SYnye3YsSPq9Q4dOtSdc845oe8ffvhhFxsb60aOHOkCgYC7/vrr3bPPPrsft4jNmWee6eLi4twvfvEL9/DDD7vvfve7Li4uzi1evLhRbk+vf/N3Bcvlo821VNayzeXLlztJ7o033oj2ZgQA7Cf6fP/Q5/Q59saQGW3O/g6ZnXPusssuc5LchAkT3N133+3uv/9+d9VVV7n8/PxGxTNz5kwnyU2cONHdf//97qKLLnL5+fmua9euYYfMzjn36quvuvj4eFdQUOBmzZrlHn74YXf11Ve7k08+OZR55plnnCR3/vnnu7/85S/ur3/9a+i8bxfNqlWrXEpKiuvRo4f73e9+5+bMmeP69evn/H6/W7p0acTbZ19r3JOdP39+2NvScltUVla6ww47zMXFxbmLL77YzZs3z915552h++ObvyhEez8wZAaA9mXx4sVu5MiRLiEhwfXr18899NBDoReM31RUVOSmTZvmevXq5eLj411eXp478cQT3SOPPOIp5+VF6dy5c11qaqqrqqpy1dXV7vTTT3evvPKKGzdunOvTp4/76U9/6hoaGrzfGEbV1dXu5z//ucvLy3N+v98dccQR+/zDbVMvSqO9fLS5lspatnn99de73r17N/pDNwCg5dHn3tHne6PPwZAZbU5zDJmdc+6RRx5xI0eOdElJSS4tLc0NGzbMXXfddW7Lli2hTCAQcL/5zW9c9+7dXVJSkhszZoxbtWqVKygoiDhkds65d955x5100kkuLS3NpaSkuOHDh7vf//73ofMbGhrclVde6XJycpzP52tU1vsqmg8++MCNHz/epaamuuTkZDd27Fj37rvvRnX77O+QOdrbwjnnysvL3cyZM92AAQNcQkKC69q1q/vud7/r7rzzTldXV9coG839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IfPTTz+ta665RjfddJM++OADjRgxQuPHj9f27dtbe2kt4pBDDtHWrVtDX++8805rL6lZVVZWasSIEXrggQf2ef7tt9+u++67Tw899JCWLVumlJQUjR8/XjU1NQd4pc0j0v5K0imnnNLoPv/rX/96AFfYfBYvXqxp06Zp6dKlev3111VfX6+TTz5ZlZWVoczVV1+tF198Uc8++6wWL16sLVu26Ic//GErrnr/RLPPknTJJZc0uo9vv/32Vlrx/unZs6duu+02rVixQsuXL9cJJ5yg008/XZ988omkjnf/onl1tj6XOnan0+d76yh9LnW+TqfP6XPYdLZO78h9LtHp+0Knt9/nfTq9FTvdtQNHHnmkmzZtWuj7QCDg8vPz3ezZs1txVS3jpptuciNGjGjtZRwwktxzzz0X+j4YDLq8vDx3xx13hE4rKSlxfr/f/fWvf22FFTavb++vc85NmTLFnX766a2ynpa2fft2J8ktXrzYOff1fRkfH++effbZUGb16tVOknvvvfdaa5nN6tv77Jxzxx9/vLvqqqtab1EtLCsryz366KOd4v7F/ulMfe5c5+p0+rxj97lzna/T6fOOe9+ieXSmTu9Mfe4cne4cne5cx3rep9MP3H3b5t/JXFdXpxUrVmjcuHGh02JiYjRu3Di99957rbiylrNu3Trl5+erX79+mjx5sjZu3NjaSzpgCgsLtW3btkb3d0ZGhkaNGtVh729JWrRokbp166bBgwfriiuuUHFxcWsvqVmUlpZKkrKzsyVJK1asUH19faP796CDDlLv3r07zP377X3e44knnlDXrl01dOhQzZw5U1VVVa2xvGYVCAT01FNPqbKyUqNHj+4U9y+864x9LnXeTqfPO1afS52v0+nzjnvfYv91xk7vrH0u0el0evu/j+n0A3ffxjX7FpvZzp07FQgElJub2+j03NxcrVmzppVW1XJGjRqlBQsWaPDgwdq6dat+85vf6Nhjj9WqVauUlpbW2strcdu2bZOkfd7fe87raE455RT98Ic/VN++fbVhwwb9z//8jyZMmKD33ntPsbGxrb08z4LBoGbMmKGjjz5aQ4cOlfT1/ZuQkKDMzMxG2Y5y/+5rnyXp3HPPVUFBgfLz8/Xxxx/r+uuv19q1a/WPf/yjFVfr3cqVKzV69GjV1NQoNTVVzz33nA4++GB99NFHHfr+xf7p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/UvDhw9XYmKijjrqKK1atSqUKS4u1jnnnKMePXooOTlZw4YN01//+tdG2xkzZoymT5+uGTNmqGvXrho/frwkae7cuRo2bJhSUlLUq1cv/fSnP1VFRYXptnn00Uc1ZMgQJSYm6qCDDtKDDz5oujwAAO2J3+9XXl6eCgoKdMUVV2jcuHH65z//GbFT97zu/uc//6mDDz5Yfr9fGzdu1Pvvv6+TTjpJXbt2VUZGho4//nh98MEHpjWtWrVKEyZMUGpqqnJzc3X++edr586dzb3rQJvAkBmI0q5du/Taa6/ppz/9qZKSkhqdl5eXp8mTJ+vpp5+Wc06SdOedd2rEiBH68MMP9etf/3qv7RUWFupHP/qRJk2apP/+97+67LLLQkPqcDZs2KDnn39eL730kl566SUtXry40X8Trqys1DXXXKPly5dr4cKFiomJ0Q9+8AP+EgsA6JBiY2N166236ve//72++uqrvc5fsWKFfvzjH+vss8/WypUrNWvWLP3617/WggULGuXuuusuHX744frwww/105/+VFdccYXWrl0b8fp/8Ytf6K677tL777+vnJwcnXbaaaqvr5ck1dTUaOTIkfrXv/6lVatW6dJLL9X555+v//znP4228dhjjykhIUFLlizRQw89JEmKiYnRfffdp08++USPPfaY3nzzTV133XVR3y5PPPGEbrzxRv3ud7/T6tWrdeutt+rXv/61Hnvssai3AQBAe5aUlKS6urqoOrWqqkpz5szRo48+qk8++UTdunVTeXm5pkyZonfeeUdLly7VwIEDNXHixEZv8gqnpKREJ5xwgr7zne9o+fLlevXVV1VUVKQf//jHLbG7QOtzAKKydOlSJ8k999xz+zx/7ty5TpIrKipyBQUFbtKkSY3OLywsdJLchx9+6Jxz7vrrr3dDhw5tlLnhhhucJLd7927nnHPz5893GRkZofNvuukml5yc7MrKykKn/eIXv3CjRo1qct07duxwktzKlSv3uQ4AANqrKVOmuNNPP90559xRRx3lLrzwQuecc88995zb82vuueee60466aRGl/vFL37hDj744ND3BQUF7rzzzgt9HwwGXbdu3dy8efOavO633nrLSXJPPfVU6LTi4mKXlJTknn766SYvd+qpp7prr7029P3xxx/vvvOd70Tc12effdZ16dIl9P2+fkcYMWJE6Pv+/fu7J598stE2fvvb37rRo0c75/h9AADQsXzzd4JgMOhef/115/f73c9//vO9svvqVEnuo48+CnsdgUDApaWluRdffDF02jdnBN/u1t/+9rfu5JNPbrSNTZs2OUlu7dq1zrmvfw+46qqrjHsLtE28kxkwcv/7TuVIDj/88LDnr127VkcccUSj04488siI2+3Tp4/S0tJC33fv3l3bt28Pfb9u3Tqdc8456tevn9LT09WnTx9J0saNG6NaNwAA7dGcOXP02GOPafXq1Y1OX716tY4++uhGpx199NFat26dAoFA6LThw4eH/u3z+ZSXlxfq1z3/zTU1NVWHHHJIo22NHj069O/s7GwNHjw4tIZAIKDf/va3GjZsmLKzs5WamqrXXnttr04eOXLkXvvzxhtv6MQTT1SPHj2Ulpam888/X8XFxaqqqop4W1RWVmrDhg266KKLQutOTU3VLbfcwiG0AAAd1ksvvaTU1FQlJiZqwoQJOuusszRr1qyoOjUhIaHR7wKSVFRUpEsuuUQDBw5URkaG0tPTVVFREfVr6//+97966623GnXxQQcdJEn0MTqk6D+BDOjkBgwYIJ/Pp9WrV+sHP/jBXuevXr1aWVlZysnJkSSlpKS0yDri4+Mbfe/z+RodCuO0005TQUGB/vCHPyg/P1/BYFBDhw7lg4QAAB3acccdp/Hjx2vmzJmaOnWq+fLh+vXRRx9VdXX1PnPh3HHHHbr33nt1zz33hI4FOWPGjL06+du/M3zxxRf63ve+pyuuuEK/+93vlJ2drXfeeUcXXXSR6urqlJycHPZ69xxn8g9/+INGjRrV6LzY2Nio1w8AQHsyduxYzZs3TwkJCcrPz1dcXFzUnZqUlCSfz9doe1OmTFFxcbHuvfdeFRQUyO/3a/To0VG/tq6oqNBpp52mOXPm7HVe9+7d93+HgTaGITMQpS5duuikk07Sgw8+qKuvvrrRcZm3bdumJ554Qj/5yU/2KqamDB48WC+//HKj095///39WmNxcbHWrl2rP/zhDzr22GMlSe+8885+bRMAgPbitttu06GHHqrBgweHThsyZIiWLFnSKLdkyRINGjQo6oFrjx49mjxv6dKl6t27tyRp9+7d+uyzzzRkyJDQ9Zx++uk677zzJEnBYFCfffaZDj744LDXt2LFCgWDQd11112Kifn6Px4+88wzUa1VknJzc5Wfn6/PP/9ckydPjvpyAAC0ZykpKRowYECj0/anU5csWaIHH3xQEydOlCRt2rTJ9KF9hx12mP7+97+rT58+iotj/IaOj8NlAAb333+/amtrNX78eL399tvatGmTXn31VZ100knq0aOHfve730W9rcsuu0xr1qzR9ddfr88++0zPPPNM6EOIoh1Uf1tWVpa6dOmiRx55ROvXr9ebb76pa665xtO2AABob4YNG6bJkyfrvvvuC5127bXXauHChfrtb3+rzz77TI899pjuv/9+/fznP2+W67z55pu1cOFCrVq1SlOnTlXXrl01adIkSdLAgQP1+uuv691339Xq1at12WWXqaioKOI2BwwYoPr6ev3+97/X559/rscffzz0gYDR+s1vfqPZs2frvvvu02effaaVK1dq/vz5mjt3rpfdBACgXdqfTh04cKAef/xxrV69WsuWLdPkyZMbvdkskmnTpmnXrl0655xz9P7772vDhg167bXXdMEFFzQ6ZBfQUTBkBgwGDhyo5cuXq1+/fvrxj3+s/v3769JLL9XYsWP13nvvKTs7O+pt9e3bV3/729/0j3/8Q8OHD9e8efN0ww03SJL8fr+n9cXExOipp57SihUrNHToUF199dW64447PG0LAID26Oabb250GKnDDjtMzzzzjJ566ikNHTpUN954o26++WZPh9TYl9tuu01XXXWVRo4cqW3btunFF19UQkKCJOlXv/qVDjvsMI0fP15jxoxRXl5eaAAdzogRIzR37lzNmTNHQ4cO1RNPPKHZs2eb1nXxxRfr0Ucf1fz58zVs2DAdf/zxWrBggfr27etlNwEAaJf2p1P/+Mc/avfu3TrssMN0/vnn62c/+5m6desW9XXn5+dryZIlCgQCOvnkkzVs2DDNmDFDmZmZoXdVAx2Jz0X7KWYAWtzvfvc7PfTQQ9q0aVNrLwUAAISxaNEijR07Vrt371ZmZmZrLwcAAABoVRwUBmhFDz74oI444gh16dJFS5Ys0R133KHp06e39rIAAAAAAACAqDFkBlrRunXrdMstt2jXrl3q3bu3rr32Ws2cObO1lwUAAAAAAABEjcNlAAAAAAAAAAA840jjAAAAAAAAAADPGDIDAAAAAAAAADxjyAwAAAAAAAAA8IwhMwAAAAAAAADAM4bMAAAAAAAAAADPGDIDAAAAAAAAADxjyAwAAAAAAAAA8IwhMwAAAAAAAADAs/8P1X0elu0i8QMAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB21klEQVR4nO3deXxTZf7+/yvpku4tZWvLUlZBZFFRkFERFUFQBnfFDRRFEXRwG4dxEFwRHfcF3AYYR9xm3EdRQcCvCi4Io6AiYBWUfeneJm1y//7w13yMbZPch5YuvJ7z6GNscvXkPjlpr+bdcOIyxhgBAAAAAAAAAOCAu6EXAAAAAAAAAABouhgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyAwAAAAAAAAAcY8gMAAAAAAAAAHCMITMAAAAAAAAAwDGGzAAAAAAAAAAAxxgyA5ZcLpdmzJjR0MsIa9y4cUpJSanTbf74449yuVz6+9//XqfbBQAAAAAAQNPGkBn1Ii8vT5MnT9ZBBx2kpKQkJSUlqVevXpo0aZK++uqrhl5evRoyZIhcLlfEj30dVJeWlmrGjBlaunRpnawbAAAAAAAAcCK2oReA5uett97Sueeeq9jYWF1wwQXq16+f3G63vvvuO73yyiuaPXu28vLylJub29BLrRc333yzLrvssuDnn3/+uR5++GH99a9/1cEHHxy8vG/fvvt0O6Wlpbr11lsl/TrYBgAAAAAAABoCQ2bUqY0bN+q8885Tbm6uFi9erOzs7JDrZ82apccff1xud/gX0ZeUlCg5Obk+l1pvTjrppJDPExIS9PDDD+ukk04KOwxuyvtcl7gfAAAAAAAAmhZOl4E6dc8996ikpERz586tNmCWpNjYWF1zzTXq0KFD8LKq8wdv3LhRI0eOVGpqqi644AJJvw4cr7/+enXo0EEej0c9evTQ3//+dxljgl9fda7gefPmVbu935+WYsaMGXK5XNqwYYPGjRunjIwMpaen65JLLlFpaWnI13q9Xl177bVq3bq1UlNT9cc//lE///zzPt5Doev45ptvdP7556tFixY65phjJP36quSahtHjxo1Tp06dgvvcunVrSdKtt95a6yk4fvnlF5122mlKSUlR69atdcMNN8jv94dktm7dqu+++04VFRVRr//JJ59U165d5fF4dOSRR+rzzz+vlvnggw907LHHKjk5WRkZGRo9erS+/fbbqO+Hbdu26ZJLLlH79u3l8XiUnZ2t0aNH68cffwzZxjvvvBO8ndTUVJ1yyilau3Zt1PsCAAAAAACAfcMrmVGn3nrrLXXr1k0DBw60+rrKykoNHz5cxxxzjP7+978rKSlJxhj98Y9/1JIlSzR+/Hgdeuihevfdd3XjjTfql19+0QMPPOB4neecc446d+6smTNn6ssvv9TTTz+tNm3aaNasWcHMZZddpn/96186//zz9Yc//EEffPCBTjnlFMe3WZOzzz5b3bt311133RUyOI+kdevWmj17tiZOnKjTTz9dZ5xxhqTQU3D4/X4NHz5cAwcO1N///nctWrRI9913n7p27aqJEycGc1OnTtX8+fOVl5cXHGKHs2DBAhUVFemKK66Qy+XSPffcozPOOEM//PCD4uLiJEmLFi3SiBEj1KVLF82YMUNlZWV65JFHdPTRR+vLL7+sdjs13Q9nnnmm1q5dq6uvvlqdOnXSjh079P7772vTpk3Br3/22Wc1duxYDR8+XLNmzVJpaalmz56tY445RqtWrYpqfwAAAAAAALBvGDKjzhQWFmrLli067bTTql2Xn5+vysrK4OfJyclKTEwMfu71enX22Wdr5syZwctef/11ffDBB7rjjjt08803S5ImTZqks88+Ww899JAmT56srl27OlrrYYcdpmeeeSb4+e7du/XMM88Eh8z/+9//9K9//UtXXXWVHnvsseBtX3DBBXX6xoX9+vXTggULrL8uOTlZZ511liZOnKi+ffvqwgsvrJYpLy/Xueeeq2nTpkmSrrzySh1++OF65plnQobMtjZt2qT169erRYsWkqQePXpo9OjRevfdd3XqqadKkm688UZlZmZq+fLlyszMlCSddtppOuywwzR9+nTNnz8/ZJu/vx/y8/P1ySef6N5779UNN9wQvHzq1KnB/y4uLtY111yjyy67TE8++WTw8rFjx6pHjx666667Qi4HAAAAAABA/eB0GagzhYWFkqSUlJRq1w0ZMkStW7cOflQNbn/r94PPt99+WzExMbrmmmtCLr/++utljNE777zjeK1XXnllyOfHHnusdu/eHdyHt99+W5Kq3faUKVMc32Y066hrNe3nDz/8EHLZvHnzZIyJ+lW/5557bnDAXLVNScHtbt26VatXr9a4ceOCA2bp11dZn3TSScH7Ntw6ExMTFR8fr6VLl2rv3r01ruP9999Xfn6+xowZo127dgU/YmJiNHDgQC1ZsiSq/QEAAAAAAMC+4ZXMqDOpqamSfn2F6e898cQTKioq0vbt22t81W1sbKzat28fctlPP/2knJyc4HarHHzwwcHrnerYsWPI51VD07179yotLU0//fST3G53tVdK9+jRw/Ft1qRz5851ur3fSkhICJ63uUqLFi1qHdpGK9x9J/3fcanpvjr44IP17rvvVntzv9/fDx6PR7NmzdL111+vtm3b6qijjtKpp56qiy++WFlZWZKk9evXS5JOOOGEGteZlpbmZPcAAAAAAABgiSEz6kx6erqys7O1Zs2aatdVnaP592/aVsXj8cjtdvbCepfLVePlv3+Du9+KiYmp8XKb8yLXhd+eMqSKy+WqcR3h9qcmte3jvqqP+66m+2HKlCkaNWqUXnvtNb377ruaNm2aZs6cqQ8++ECHHXaYAoGApF/Py1w1eP6t2Fh+vAEAAAAAAOwPnC4DdeqUU07Rhg0b9Nlnn+3ztnJzc7VlyxYVFRWFXP7dd98Fr5f+75W0+fn5Ibl9eaVzbm6uAoGANm7cGHL5unXrHG8zWi1atKi2L1L1/altuN7Qqo5LTffVd999p1atWoW8ijmcrl276vrrr9d7772nNWvWyOfz6b777gteJ0lt2rTR0KFDq30MGTKkbnYIAAAAAAAAYTFkRp3685//rKSkJF166aXavn17tettXu06cuRI+f1+PfrooyGXP/DAA3K5XBoxYoSkX0+L0KpVK3344Ychuccff9zBHvyqatsPP/xwyOUPPvig421Gq2vXrvruu++0c+fO4GX/+9//9PHHH4fkkpKSJFUfrtvaunWrvvvuO1VUVOzTdqpkZ2fr0EMP1fz580PWtmbNGr333nsaOXJkxG2UlpaqvLw85LKuXbsqNTVVXq9XkjR8+HClpaXprrvuqnHtv73/AAAAAAAAUH/49+SoU927d9eCBQs0ZswY9ejRQxdccIH69esnY4zy8vK0YMECud3uaudfrsmoUaN0/PHH6+abb9aPP/6ofv366b333tPrr7+uKVOmhJwv+bLLLtPdd9+tyy67TEcccYQ+/PBDff/9947349BDD9WYMWP0+OOPq6CgQH/4wx+0ePFibdiwwfE2o3XppZfq/vvv1/DhwzV+/Hjt2LFDc+bM0SGHHBJ8Y0Lp11NM9OrVSy+++KIOOuggZWZmqnfv3urdu7fV7U2dOlXz589XXl5e1G/+F8m9996rESNGaNCgQRo/frzKysr0yCOPKD09XTNmzIj49d9//71OPPFEnXPOOerVq5diY2P16quvavv27TrvvPMk/frHhdmzZ+uiiy7S4YcfrvPOO0+tW7fWpk2b9N///ldHH310tT9QAAAAAAAAoO4xZEadGz16tL7++mvdd999eu+99/SPf/xDLpdLubm5OuWUU3TllVeqX79+Ebfjdrv1xhtv6JZbbtGLL76ouXPnqlOnTrr33nt1/fXXh2RvueUW7dy5U//+97/10ksvacSIEXrnnXfUpk0bx/vxj3/8Q61bt9Zzzz2n1157TSeccIL++9//qkOHDo63GY2DDz5Y//znP3XLLbfouuuuU69evfTss89qwYIFWrp0aUj26aef1tVXX61rr71WPp9P06dPtx4y14ehQ4dq4cKFmj59um655RbFxcXpuOOO06xZs6J6s8MOHTpozJgxWrx4sZ599lnFxsaqZ8+eeumll3TmmWcGc+eff75ycnJ09913695775XX61W7du107LHH6pJLLqnPXQQAAAAAAMD/z2X29zudAQAAAAAAAACaDc7JDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAOCA5vV6demll6pjx45KS0vTUUcdpeXLlzf0sgAAwD6g34H9iyEzAAAADmiVlZXq1KmTPvroI+Xn52vKlCkaNWqUiouLG3ppAADAIfod2L8YMgMAAKBOzZgxQy6Xq8Fu/5577lHPnj0VCASiyicnJ+uWW25Rx44d5Xa7dd555yk+Pl7r1q2r55UeeObMmaOOHTvK6/U29FIAALVo6B4Px6bj6ff9h36HxJAZkCR16tRJ48aNC36+dOlSuVwuLV26tM5uw+VyacaMGXW2vX3VqVMnnXrqqQ29DAAA6lRhYaFmzZqlm266SW539V91582bp549e+raa6+tdRvr16/Xnj171K1bt31aS9XvEzV9rFixIuLXr127Vmeffba6dOmipKQktWrVSoMHD9abb765T7cV7XaLi4s1ffp0nXzyycrMzJTL5dK8efNqXa/X69VNN92knJwcJSYmauDAgXr//fdDMuPGjZPP59MTTzwRcf8BAPitcB2/P/tdsuvo2tZy3nnnqX379kpKSlLPnj112223qbS0NCRn+7tENF1c5csvv9Qf//hHZWZmKikpSb1799bDDz/sKEu/Q5JiG3oBwLx583TJJZcEP/d4POrYsaOGDRumadOmqW3btg24Ojtvv/22Pvvss0Y1TAYA4EDyj3/8Q5WVlRozZky16zZs2KArr7xSN910k/71r3/pgQceqJYpKyvThRdeqKlTpyo9Pb1O1nTNNdfoyCOPDLksmie4P/30k4qKijR27Fjl5OSotLRU//nPf/THP/5RTzzxhCZMmODotqLd7q5du3TbbbepY8eO6tevX8Q/vo8bN07//ve/NWXKFHXv3l3z5s3TyJEjtWTJEh1zzDGSpISEBI0dO1b333+/rr766kb7SjkAQONTW8c3RL876egqmzdv1oABA5Senq7JkycrMzNTy5cv1/Tp07Vy5Uq9/vrr1b4m2t8louliSXrvvfc0atQoHXbYYZo2bZpSUlK0ceNG/fzzz9W2GU2WfockyQANbO7cuUaSue2228yzzz5rnnrqKTN27FjjdrtN586dTUlJSb2vITc314wdOzb4ud/vN2VlZcbv91ttZ9KkSaa2b6uysjJTUVGxL8usU7m5ueaUU05p6GUAAJqh6dOn19qH9a1v377mwgsvrPG6m2++2Zx66qnmkUceMQMHDqx2vc/nM6eccoo5//zzTSAQ2Oe1LFmyxEgyL7/88j5vq0plZaXp16+f6dGjR53eVk3bLS8vN1u3bjXGGPP5558bSWbu3Lk1fv2nn35qJJl77703eFlZWZnp2rWrGTRoUEj2iy++MJLM4sWLHa0VAFC/GqrHi4uLw15fW8fv736vTW0d/Xt33nmnkWTWrFkTcvnFF19sJJk9e/YEL7Pp92i7uKCgwLRt29acfvrpEWceNln6HZwuA43GiBEjdOGFF+qyyy7TvHnzNGXKFOXl5dX4V7wqJSUl9bIWt9uthISEGv+ZrVMJCQmKjT2w/vFAeXl51OfDBAA0TR999JGOPPJIJSQkqGvXrrX+M8lVq1ZpxIgRSktLU0pKik488cQa/5nn0qVLdcQRR4RsL9pzQ+bl5emrr77S0KFDa7z+zTff1B//+Ed9/vnnOuyww0KuCwQCuuiii+RyuTR//vw6fwVOUVGRKisr93k7MTEx6tChg/Lz8+v0tmrarsfjUVZWVlRf/+9//1sxMTEhr9xKSEjQ+PHjtXz5cm3evDl4ef/+/ZWZmRn2dzwAwP4RbY//8ssvuvTSS9W2bVt5PB4dcsgh+sc//uE4V9Xt33zzjc4//3y1aNEi5JW2vxeu4xuy338rmo6Wfj3th6Rq/2o7Oztbbrdb8fHxNX5dpH6PtosXLFig7du3684775Tb7VZJSUmtz9ttsvQ7GDKj0TrhhBMk/Vom0q//7KPqn2WMHDlSqampuuCCCyT9WhwPPvigDjnkECUkJKht27a64oortHfv3pBtGmN0xx13BM97dPzxx2vt2rXVbru2czJ/+umnGjlypFq0aKHk5GT17dtXDz30UHB9jz32mCSFnCepSk3nZI7mCfe8efPkcrn08ccf67rrrlPr1q2VnJys008/XTt37gzJFhQU6LvvvlNBQUE0d7GkX3+pGDBggBISEtSlSxf985//rJb54YcfdPbZZwfPv3TUUUfpv//9b4332QsvvKC//e1vateunZKSklRYWKiKigrdeuut6t69uxISEtSyZUsdc8wx1c4N9d133+mss85SZmamEhISdMQRR+iNN96Iel8AAPvX119/rWHDhmnHjh2aMWOGLrnkEk2fPl2vvvpqSG7t2rU69thj9b///U9//vOfNW3aNOXl5WnIkCH69NNPg7lVq1bp5JNP1u7du3Xrrbdq/Pjxuu222/Taa69FtZ5PPvlEknT44YdXu27Xrl36+uuvddxxx2nRokU68cQTQ66/4oortHXrVr388ss1/lG4oqJCu3btiurj90++LrnkEqWlpSkhIUHHH3+8vvjii6j2p0pJSYl27dqljRs36oEHHtA777xTbf1Obstmu5GsWrVKBx10kNLS0kIuHzBggCRp9erVIZcffvjh+vjjjx3dFgCgbkTb49u3b9dRRx2lRYsWafLkyXrooYfUrVs3jR8/Xg8++KB17rfOPvtslZaW6q677tLll19e61pr6/iG7HfJWZcOGTJEkjR+/HitXr1amzdv1osvvqjZs2frmmuuUXJycrWviabfo+3iRYsWKS0tTb/88ot69OihlJQUpaWlaeLEiSovLw/5WpusRL8f8Br6pdRA1ekyPv/885DLH3roISPJzJkzxxhjzNixY43H4zFdu3Y1Y8eONXPmzDH//Oc/jTHGXHbZZSY2NtZcfvnlZs6cOeamm24yycnJ5sgjjzQ+ny+4zb/97W9Gkhk5cqR59NFHzaWXXmpycnJMq1atQk6XUfVPUpYsWRK87L333jPx8fEmNzfXTJ8+3cyePdtcc801ZujQocYYYz755BNz0kknGUnm2WefDX5UkWSmT58e/HzNmjUmOTnZZGdnm9tvv93cfffdpnPnzsbj8ZgVK1ZUu38OO+wwc8IJJ5hHHnnEXH/99SYmJsacc845Nd6Xtf1T1t/Kzc01PXr0MG3btjV//etfzaOPPmoOP/xw43K5Qv7ZzrZt20zbtm1Namqqufnmm839999v+vXrZ9xut3nllVeq3We9evUyhx56qLn//vvNzJkzTUlJifnrX/9qXC6Xufzyy81TTz1l7rvvPjNmzBhz9913h9wf6enpplevXmbWrFnm0UcfNYMHDzYulyvkdgAAjcdpp51mEhISzE8//RS87JtvvjExMTEh/8z2tNNOM/Hx8Wbjxo3By7Zs2WJSU1PN4MGDg5eNGjXKJCUlmV9++SV42fr1601sbGxU/2y3queLioqqXffGG2+YFi1amCVLlpj09HRTWloavO7HH380kkxCQoJJTk4Ofnz44YfBTFXPRfORl5dnjDHm448/NmeeeaZ55plnzOuvv25mzpxpWrZsaRISEsyXX34ZcX+qXHHFFcFtu91uc9ZZZ4X8U1qntxXNdqtEOl3GIYccYk444YRql69duzbk97kqEyZMMImJiVHsPQCgvkTb4+PHjzfZ2dlm165dIV9/3nnnhXRqtDlj/u+UHGPGjIlqrbV1fEP0+2/ZdOlv3X777SYxMTFk+zfffHO1nE2/R9vFffv2NUlJSSYpKclcffXV5j//+Y+5+uqrjSRz3nnnhXytTdYY+v1Ax5AZDa5qMLpo0SKzc+dOs3nzZvPCCy+Yli1bmsTERPPzzz8bY34dMksyf/nLX0K+/v/9v/9nJJnnnnsu5PKFCxeGXL5jxw4THx9vTjnllJDzMP31r381ksIOmSsrK03nzp1Nbm6u2bt3b8jt/HZb4c7J/Pshc7RPuKvun6FDh4bc1rXXXmtiYmJMfn5+tWy0Q2ZJIQW7Y8cO4/F4zPXXXx+8bMqUKUaS+X//7/8FLysqKjKdO3c2nTp1Cp6Xqeo+69KlS0ixG2NMv379Ip7/+cQTTzR9+vQx5eXlwcsCgYD5wx/+YLp37x5xfwAA+1dlZaVJTEys8QnGyJEjg31YWVlpkpKSqv1h1Jhfn5i53W5TUFAQ3N75559fLTdq1KiohswTJ040sbGxNV43bdo0M3jwYHPuueeaq666KuK2fm/Pnj3m/fffj+qjrKys1u2sX7/eJCYmmuHDh0d9299++615//33zfz5880pp5xiTj/9dLNt27aIXxfptmy2G2nI3KVLFzNixIhql2/cuNFIMg888EDI5TfddJORtF/eewMAUF20PR4IBExGRoaZMGGC2blzZ8hH1fPPjz76KOpclaoh87Jly6Jab20d39D97rSjn332WTN8+HDz5JNPmv/85z/m0ksvNS6XyzzyyCMRv7a2fo+2i7t06WIkmSuvvDIkVzUw//7770O2GW3WGPr9QHdgnSAWjdrvz62Um5ur5557Tu3atQu5fOLEiSGfv/zyy0pPT9dJJ52kXbt2BS/v37+/UlJStGTJEp1//vlatGiRfD5ftXc6nTJliu66666wa1u1apXy8vL0wAMPKCMjI+Q6J+d08vv9eu+993TaaaepS5cuwcuzs7N1/vnn66mnnlJhYWHIP3OZMGFCyG0de+yxeuCBB/TTTz+pb9++kn49Zce4ceOiXkevXr107LHHBj9v3bq1evTooR9++CF42dtvv60BAwaEnB8rJSVFEyZM0NSpU/XNN9+od+/ewevGjh2rxMTEkNvJyMjQ2rVrtX79enXv3r3aOvbs2aMPPvhAt912m4qKilRUVBS8bvjw4Zo+fbp++eWXao8FAEDD2blzp8rKymr8ud6jRw+9/fbbwVxpaal69OhRLXfwwQcrEAho8+bNyszMVFlZWY3vlF7TZbbWrVunmJgYvf766/rmm2+sv75Fixa1nuvZRrdu3TR69Gi98sor8vv9iomJifg1PXv2VM+ePSVJF198sYYNG6ZRo0bp008/Dft7SKTbcrrdmiQmJsrr9Va7vOqf0v7+dwNjjCRnv0cBAPadTY/n5+frySef1JNPPlnjtnbs2BF17vc6d+68D3vR8P3upEtfeOEFTZgwQd9//73at28vSTrjjDMUCAR00003acyYMWrZsmWtt1lbv0fbxVX/P2bMmJDc+eefryeeeELLly8PPi5sshL9fqBjyIxG47HHHtNBBx2k2NhYtW3bVj169Kj2xnuxsbHBH8JV1q9fr4KCArVp06bG7VYV2U8//SRJ1Uq0devWatGiRdi1bdy4UZJChqn7Iton3Iccckjw8o4dO4bkqtb8+/NO2/j9Nqu2+9tt/vTTTxo4cGCN66y6/rf3S02/JNx2220aPXq0DjroIPXu3Vsnn3yyLrroouBwfMOGDTLGaNq0aZo2bVqNa92xYwdDZgBAWC1btlRlZaWKioqUmpoact2uXbv04Ycf6qKLLnL0hNbn82nPnj1RZVu3bh12eNyhQwf5fD6VlJRUO29iNM466yxdccUV+v7772v8XcLpbdls9/eys7P1yy+/VLt869atkqScnJyQy/fu3aukpKRqw2cAQONSdR7iCy+8UGPHjq0x07dv36hzvxdtD9TW8Y2p36XouvTxxx/XYYcdVm228cc//lHz5s3TqlWrIg6+a+r3aLs4JydHa9eurfbGg1Uzld/OA2yyVZ/T7wcuhsxoNAYMGKAjjjgibMbj8VQbPAcCAbVp00bPPfdcjV/TunXrOltjQ6qtzKr+UthYtllTmQwePFgbN27U66+/rvfee09PP/20HnjgAc2ZM0eXXXZZ8BeSG264QcOHD69xu3XxKjYAQN1p3bq1EhMTtX79+mrXrVu3LiSXlJQUclmV7777Tm63Wx06dFBycrISEhK0YcOGarmaLqtJ1SuJ8vLyqj2Rdbvd8ng8uuOOO6La1u998sknOv7446PK5uXlqVOnTrVe/8MPPyghIUEpKSmO1lJWViZJUb3Rr81t2Wz39w499FAtWbKk2r/Eqnpjx0MPPTQkn5eXF/yDNQBg/7Pp8dTUVPn9/rCDT7/fH1XOqdo6vjH1uxRdl27fvr3GF7pVVFRIkiorKyOupaZ+j7aL+/fvr/fffz/4Zn5VtmzZIil0hmKTlej3Ax1DZjR5Xbt21aJFi3T00UeH/WtZbm6upF9f+fzbU1Ts3Lkz4quBu3btKklas2ZN2MKM9p+ERPuEuzHIzc2tdZ1V10cjMzNTl1xyiS655BIVFxdr8ODBmjFjhi677LLg8YiLi6uXX0gAAHUvJiZGw4cP12uvvaZNmzYF/3XMt99+q3fffTckN2zYML3++uv68ccfg0/Otm/frgULFuiYY44JPhEaOnSoXnvtNW3ZsiX4apsNGzbonXfeiWpNgwYNkiR98cUXIU9AjTHau3evLr74Ysf/KqZfv356//33o8pmZWVJ+vV3jN8/+frf//6nN954QyNGjAj5w3lpaak2bdqkVq1aqVWrVpJ+/Vc8v/+XWhUVFfrnP/+pxMRE9erVK3i5zW3ZbDdaZ511lv7+97/rySef1A033CBJ8nq9mjt3rgYOHFjt95ovv/xSF1xwgfXtAADqhk2Pn3nmmVqwYIHWrFlT7V/3VvVPtDmnaur4hup3ya5Lf9/xBx10kN577z19//33Ouigg4K5559/Xm63O+R3GJt+j7aLzznnHN1999165plndMIJJwS//umnn1ZsbKyGDBkSvMwmK9HvBzqGzGjyzjnnHD3++OO6/fbbq51bubKyUsXFxcrIyNDQoUMVFxenRx55RMOGDQsOhB988MGIt3H44Yerc+fOevDBBzVu3LiQ8zIbY4LbSk5OliTl5+dXO3fzb9k84bZRUFCgrVu3Kjs7W+np6dZfX5ORI0fqwQcf1PLly4PFXlJSoieffFKdOnWK6ono7t27Q84plZKSom7dumnz5s2Sfv2nNkOGDNETTzyhq6++WtnZ2SFfv6+/kAAA6sett96qhQsX6thjj9VVV12lyspKPfLIIzrkkEP01VdfBXN33HGH3n//fR1zzDG66qqrFBsbqyeeeEJer1f33HNPMDdjxgy99957OvroozVx4kT5/X49+uij6t27t1avXh1xPV26dFHv3r21aNEiXXrppcHLn3rqqeDXBwIB/fWvf9URRxyhs846K+p9dXLOxnPPPVeJiYn6wx/+oDZt2uibb77Rk08+qaSkJN19990h2c8++0zHH3+8pk+frhkzZkiSrrjiChUWFmrw4MFq166dtm3bpueee07fffed7rvvvpBXL9ncls12H330UeXn5wdfsfTmm2/q559/liRdffXVwd83Bg4cqLPPPltTp07Vjh071K1bN82fP18//vijnnnmmZDbX7lypfbs2aPRo0db3Z8AgLoVbY/ffffdWrJkiQYOHKjLL79cvXr10p49e/Tll19q0aJFwdNNRJtzoqaOb6h+l+y69Pcdf+ONN+qdd97Rscceq8mTJ6tly5Z666239M477+iyyy4LOcWUTb9H28WHHXaYLr30Uv3jH/9QZWWljjvuOC1dulQvv/yypk6dGnL7Nln6HZHfphuoZ1XvNPv555+HzY0dO9YkJyfXeF3VO5uOGDHCPPDAA+bRRx81f/rTn0xOTo55+eWXg7mpU6caSWbkyJHm0UcfNePH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1aklS5YYSdU+xo4da4wxJhAImGnTppm2bdsaj8djTjzxRLNu3bqGXfQ+CLe/paWlZtiwYaZ169YmLi7O5Obmmssvv7zJ/nJW035KMnPnzg1mysrKzFVXXWVatGhhkpKSzOmnn262bt3acIveR5H2edOmTWbw4MEmMzPTeDwe061bN3PjjTeagoKChl24Q5deeqnJzc018fHxpnXr1ubEE08Mlpcxze/4ou4dKH1uTPPvdPq8+fa5MQdep9Pn9DnsHSid3tz73Bg6nU5vXj/36fSG63SXMcY4fx00AAAAAAAAAOBA1ujPyQwAAAAAAAAAaLwYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhswAAAAAAAAAAMcYMgMAAAAAAAAAHGPIDAAAAAAAAABwjCEzAAAAAAAAAMAxhszAfvLjjz/K5XJp9erVUX/NvHnzlJGR0eDrAAAAjcvvf0eYMWOGDj300Ki/nt8HAADYdy6XS6+99pokZ906ZMgQTZkypV7WBuxvDJkBS5s3b9all16qnJwcxcfHKzc3V3/605+0e/fusF/XoUMHbd26Vb179476ts4991x9//33+7pkAACapXHjxsnlcunuu+8Oufy1116Ty+VqoFUBAID9rep3ApfLpfj4eHXr1k233XabKisrG3ppwAGDITNg4YcfftARRxyh9evX6/nnn9eGDRs0Z84cLV68WIMGDdKePXtq/Dqfz6eYmBhlZWUpNjY26ttLTExUmzZt6mr5AAA0OwkJCZo1a5b27t3b0EupEz6fr6GXAABAk3TyySdr69atWr9+va6//nrNmDFD9957r/V2/H6/AoFAPawQaN4YMgMWJk2apPj4eL333ns67rjj1LFjR40YMUKLFi3SL7/8optvvlmS1KlTJ91+++26+OKLlZaWpgkTJtT4T2feeOMNde/eXQkJCTr++OM1f/58uVwu5efnS6r9n8I+++yz6tSpk9LT03XeeeepqKgomFm4cKGOOeYYZWRkqGXLljr11FO1cePG/XH3AACw3w0dOlRZWVmaOXNmrZn//Oc/OuSQQ+TxeNSpUyfdd999Idd36tRJd911ly699FKlpqaqY8eOevLJJ8Pe7tKlS+VyufTf//5Xffv2VUJCgo466iitWbMmmNm9e7fGjBmjdu3aKSkpSX369NHzzz8fsp0hQ4Zo8uTJmjJlilq1aqXhw4dLku6//3716dNHycnJ6tChg6666ioVFxdb3TdPP/20Dj74YCUkJKhnz556/PHHrb4eAICmxOPxKCsrS7m5uZo4caKGDh2qN954I2KnVj3vfuONN9SrVy95PB5t2rRJn3/+uU466SS1atVK6enpOu644/Tll19arWnNmjUaMWKEUlJS1LZtW1100UXatWtXXe860CgwZAaitGfPHr377ru66qqrlJiYGHJdVlaWLrjgAr344osyxkiS/v73v6tfv35atWqVpk2bVm17eXl5Ouuss3Taaafpf//7n6644orgkDqcjRs36rXXXtNbb72lt956S8uWLQv5Z8IlJSW67rrr9MUXX2jx4sVyu906/fTT+UssAKBZiomJ0V133aVHHnlEP//8c7XrV65cqXPOOUfnnXeevv76a82YMUPTpk3TvHnzQnL33XefjjjiCK1atUpXXXWVJk6cqHXr1kW8/RtvvFH33XefPv/8c7Vu3VqjRo1SRUWFJKm8vFz9+/fXf//7X61Zs0YTJkzQRRddpM8++yxkG/Pnz1d8fLw+/vhjzZkzR5Lkdrv18MMPa+3atZo/f74++OAD/fnPf476fnnuued0yy236M4779S3336ru+66S9OmTdP8+fOj3gYAAE1ZYmKifD5fVJ1aWlqqWbNm6emnn9batWvVpk0bFRUVaezYsfroo4+0YsUKde/eXSNHjgx5kVc4+fn5OuGEE3TYYYfpiy++0MKFC7V9+3adc8459bG7QMMzAKKyYsUKI8m8+uqrNV5///33G0lm+/btJjc315x22mkh1+fl5RlJZtWqVcYYY2666SbTu3fvkMzNN99sJJm9e/caY4yZO3euSU9PD14/ffp0k5SUZAoLC4OX3XjjjWbgwIG1rnvnzp1Gkvn6669rXAcAAE3V2LFjzejRo40xxhx11FHm0ksvNcYY8+qrr5qqX3PPP/98c9JJJ4V83Y033mh69eoV/Dw3N9dceOGFwc8DgYBp06aNmT17dq23vWTJEiPJvPDCC8HLdu/ebRITE82LL75Y69edcsop5vrrrw9+ftxxx5nDDjss4r6+/PLLpmXLlsHPa/odoV+/fsHPu3btahYsWBCyjdtvv90MGjTIGMPvAwCA5uW3vxMEAgHz/vvvG4/HY2644YZq2Zo6VZJZvXp12Nvw+/0mNTXVvPnmm8HLfjsj+H233n777WbYsGEh29i8ebORZNatW2eM+fX3gD/96U+Wews0TrySGbBk/v9XKkdyxBFHhL1+3bp1OvLII0MuGzBgQMTtdurUSampqcHPs7OztWPHjuDn69ev15gxY9SlSxelpaWpU6dOkqRNmzZFtW4AAJqiWbNmaf78+fr2229DLv/222919NFHh1x29NFHa/369fL7/cHL+vbtG/xvl8ulrKysYL9W/TPXlJQUHXLIISHbGjRoUPC/MzMz1aNHj+Aa/H6/br/9dvXp00eZmZlKSUnRu+++W62T+/fvX21/Fi1apBNPPFHt2rVTamqqLrroIu3evVulpaUR74uSkhJt3LhR48ePD647JSVFd9xxB6fQAgA0W2+99ZZSUlKUkJCgESNG6Nxzz9WMGTOi6tT4+PiQ3wUkafv27br88svVvXt3paenKy0tTcXFxVE/t/7f//6nJUuWhHRxz549JYk+RrMU/TuQAQe4bt26yeVy6dtvv9Xpp59e7fpvv/1WLVq0UOvWrSVJycnJ9bKOuLi4kM9dLlfIqTBGjRql3NxcPfXUU8rJyVEgEFDv3r15IyEAQLM2ePBgDR8+XFOnTtW4ceOsvz5cvz799NMqKyurMRfOvffeq4ceekgPPvhg8FyQU6ZMqdbJv/+d4ccff9Spp56qiRMn6s4771RmZqY++ugjjR8/Xj6fT0lJSWFvt+o8k0899ZQGDhwYcl1MTEzU6wcAoCk5/vjjNXv2bMXHxysnJ0exsbFRd2piYqJcLlfI9saOHavdu3froYceUm5urjwejwYNGhT1c+vi4mKNGjVKs2bNqnZddnb2vu8w0MgwZAai1LJlS5100kl6/PHHde2114acl3nbtm167rnndPHFF1crptr06NFDb7/9dshln3/++T6tcffu3Vq3bp2eeuopHXvssZKkjz76aJ+2CQBAU3H33Xfr0EMPVY8ePYKXHXzwwfr4449Dch9//LEOOuigqAeu7dq1q/W6FStWqGPHjpKkvXv36vvvv9fBBx8cvJ3Ro0frwgsvlCQFAgF9//336tWrV9jbW7lypQKBgO677z653b/+w8OXXnopqrVKUtu2bZWTk6MffvhBF1xwQdRfBwBAU5acnKxu3bqFXLYvnfrxxx/r8ccf18iRIyVJmzdvtnrTvsMPP1z/+c9/1KlTJ8XGMn5D88fpMgALjz76qLxer4YPH64PP/xQmzdv1sKFC3XSSSepXbt2uvPOO6Pe1hVXXKHvvvtON910k77//nu99NJLwTchinZQ/XstWrRQy5Yt9eSTT2rDhg364IMPdN111znaFgAATU2fPn10wQUX6OGHHw5edv3112vx4sW6/fbb9f3332v+/Pl69NFHdcMNN9TJbd52221avHix1qxZo3HjxqlVq1Y67bTTJEndu3fX+++/r08++UTffvutrrjiCm3fvj3iNrt166aKigo98sgj+uGHH/Tss88G3xAwWrfeeqtmzpyphx9+WN9//72+/vprzZ07V/fff7+T3QQAoEnal07t3r27nn32WX377bf69NNPdcEFF4S82CySSZMmac+ePRozZow+//xzbdy4Ue+++64uueSSkFN2Ac0FQ2bAQvfu3fXFF1+oS5cuOuecc9S1a1dNmDBBxx9/vJYvX67MzMyot9W5c2f9+9//1iuvvKK+fftq9uzZuvnmmyVJHo/H0frcbrdeeOEFrVy5Ur1799a1116re++919G2AABoim677baQ00gdfvjheumll/TCCy+od+/euuWWW3Tbbbc5OqVGTe6++2796U9/Uv/+/bVt2za9+eabio+PlyT97W9/0+GHH67hw4dryJAhysrKCg6gw+nXr5/uv/9+zZo1S71799Zzzz2nmTNnWq3rsssu09NPP625c+eqT58+Ou644zRv3jx17tzZyW4CANAk7UunPvPMM9q7d68OP/xwXXTRRbrmmmvUpk2bqG87JydHH3/8sfx+v4YNG6Y+ffpoypQpysjICL6qGmhOXCbadzEDUO/uvPNOzZkzR5s3b27opQAAgDCWLl2q448/Xnv37lVGRkZDLwcAAABoUJwUBmhAjz/+uI488ki1bNlSH3/8se69915Nnjy5oZcFAAAAAAAARI0hM9CA1q9frzvuuEN79uxRx44ddf3112vq1KkNvSwAAAAAAAAgapwuAwAAAAAAAADgGGcaBwAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjjFkBgAAAAAAAAA4xpAZAAAAAAAAAOAYQ2YAAAAAAAAAgGMMmQEAAAAAAAAAjv1/6nyY4TF/Fy4AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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3WrUqFHI5erVq0uS9uzZExzb4/EU+qR0q1atXC+zKE2bNi3R8Q4XHx8fPG/zIdWrVw+uo62bb75ZLVu2VK9evdSgQQMNHTpUH330UZG1kbZvOC1atAi57DiOmjdvro0bN7qaNwAAAAAAAEoGTWaUurS0NNWrV0+rVq0qdFunTp3UrVs3nXHGGUXe1+fzyeNxd5ge/onZw/35B+4OV9zpHmzOi1wSijrHsJv1KUqkU1rYqlOnjlauXKkPPvggeK7sXr16afDgwVEv+2hvXwAAAAAAAJQcmsw4Ki644AKtX79eX3/99RGP1bhxY23evFn79u0LuX716tXB26X/+5Ts3r17Q+qO5JPOjRs3ViAQ0IYNG0KuX7Nmjesxo1W9evVC6yIVXp/imtGlKS4uTn369NGkSZO0YcMG3XjjjXr11Ve1fv36ElvGunXrQi4bY7R+/fqIp/MAAAAAAABA6aLJjKPirrvuUmJiooYOHapt27YVut3mk6y9e/eW3+/X888/H3L9008/Lcdx1KtXL0lSamqqatWqpc8//zykbtKkSS7W4A+Hxn722WdDrp84caLrMaPVrFkzrV69Wjt27Ahe9+2332rRokUhdYmJiZIKN9dtbdmyRatXr1Z+fn7Yul27doVc9ng8at++vaQ/frixpLz66qshbyy888472rJlS3CfAAAAAAAAoGzElPUEUDW0aNFCM2fO1IABA9SqVSsNGjRIHTp0kDFGGRkZmjlzpjweT6HzLxelT58+6tq1q+655x5t3LhRHTp00CeffKJZs2Zp1KhRIedLvu666/Too4/quuuu08knn6zPP/9ca9eudb0eJ5xwggYMGKBJkyYpMzNTp59+uubNm1ein9gtztChQ/XUU0+pR48eGjZsmLZv364pU6aoTZs2wR8mlP441Ubr1q315ptvqmXLlqpRo4batm2rtm3bWi1vzJgxmj59ujIyMsJ+Wvi6667T7t27de6556pBgwb65Zdf9Nxzz+mEE04Inie7JNSoUUNnnnmmrr32Wm3btk0TJ05U8+bNdf3115fYMgAAAAAAAGCPJjOOmosvvljff/+9nnzySX3yySf65z//Kcdx1LhxY11wwQUaPny4OnToEHEcj8ejDz74QPfdd5/efPNNTZ06VU2aNNHjjz+uO+64I6T2vvvu044dO/TOO+/orbfeUq9evfThhx+qTp06rtfjn//8p2rXrq0ZM2bo/fff17nnnqv//Oc/atiwoesxo3H88cfr1Vdf1X333afRo0erdevWeu211zRz5kwtWLAgpPbll1/WLbfcottvv115eXkaO3asdZM5WldddZVefPFFTZo0SXv37lV6erquvPJKjRs3zvX5tIvyt7/9Td99953Gjx+vffv26bzzztOkSZOCn9wGAAAAAABA2XAMv7gFoBxbsGCBunbtqrfffluXX355WU8HAAAAAAAAf8I5mQEAAAAAAAAArtFkBgAAAAAAAAC4RpMZAAAAAAAAAOAaTWYA5VqXLl1kjOF8zACAci83N1dDhw5Vo0aNlJqaqtNOO02LFy8u62kBAAAL5DngDk1mAAAAoAQUFBSoSZMm+uKLL7R3716NGjVKffr00f79+8t6agAAIErkOeAOTWYAAAAUadq0aXIcRxs3bqwQ45aGxx57TMcdd5wCgUDE2qSkJN13331q1KiRPB6P+vfvr7i4OK1Zs+YozLTqmTJliho1aqTc3NyyngoAlGvkOXlenpHnlQdNZiCMJk2aaMiQIcHLCxYskOM4WrBgQYktw3EcjRs3rsTGK20bN26U4zh64oknItaOGzdOjuMchVkBAFDysrKyNGHCBN19993yeEL/2zxt2jQdd9xxuv3224u9/7p167R79241b968ROaTm5uru+++W/Xr11dCQoI6deqkTz/9NKr7Hvo/TFF/S5YsCdb98MMPuuKKK3TssccqMTFRtWrV0tlnn63Zs2cXGnP//v0aO3asevbsqRo1ashxHE2bNu2Ilr906VKNHDlSbdq0UVJSkho1aqR+/fpp7dq1hcYcMmSI8vLy9I9//COqbQAAqJqqYp7b5Omhdezfv78aNGigxMREHXfccXrggQeUnZ1dqHb58uXq2bOnUlNTlZKSou7du2vlypUhNUOGDCl2no7j6Pfffw+pJc8rh5iyngBQnGnTpunaa68NXvb5fGrUqJG6d++ue++9V3Xr1i3D2dn573//q6+//rpCNZMBALj66qvVv39/+Xy+sp5KmfjnP/+pgoICDRgwIOT69evXa/jw4br77rv1+uuv6+mnny5034MHD+qqq67SmDFjlJaWViLzGTJkiN555x2NGjVKLVq00LRp09S7d2/Nnz9fZ555ZlRj3HrrrTrllFNCrjv8RfMvv/yiffv2afDgwapfv76ys7P173//WxdddJH+8Y9/6IYbbgjW7ty5Uw888IAaNWqkDh06RPUmfKTlT5gwQYsWLdIVV1yh9u3ba+vWrXr++ed10kknacmSJWrbtm2wNj4+XoMHD9ZTTz2lW265hTe2AaAY5HnVy3ObPN20aZNOPfVUpaWlaeTIkapRo4YWL16ssWPHavny5Zo1a1awdsWKFTrzzDPVsGFDjR07VoFAQJMmTdI555yjr7/+Wq1atZIk3XjjjerWrVvI/IwxGj58uJo0aaJjjjkmeD15XokYoJyaOnWqkWQeeOAB89prr5mXXnrJDB482Hg8HtO0aVNz4MCBUp9D48aNzeDBg4OX/X6/OXjwoPH7/VbjjBgxwhT3cDt48KDJz88/kmkeVRkZGUaSefzxxyPW5ufnm4MHDx6FWQEAysr+/fut73Mo4zMyMkp+QiWoffv25qqrrip0/T333GMuvPBC89xzz5lOnToVuj0vL89ccMEFZuDAgSYQCJTIXL766qtC+Xvw4EHTrFkz07lz54j3nz9/vpFk3n77betlFxQUmA4dOphWrVqFXJ+Tk2O2bNlijDFm6dKlRpKZOnXqES1/0aJFJjc3N+S6tWvXGp/PZwYNGlSoftmyZUaSmTdvnsUaAQD+jDyvXHluk6cPP/ywkWRWrVoVcv0111xjJJndu3cHr+vdu7epXr262blzZ/C6zZs3m+TkZHPppZeGndP//vc/I8k8/PDDhW4jzysHTpeBcq9Xr1666qqrdN1112natGkaNWqUMjIyQt5N+7MDBw6Uylw8Ho/i4+MLfcXmSMTHxysmpnJ+qSAmJkbx8fFlPQ0AwJ/88ssvuvnmm9WqVSslJCSoZs2auuKKKwqdU/HP51o8dBqkH3/8UQMHDlT16tWDn7g5dNvq1avVr18/paamqmbNmrrtttuUk5NTIvM5fDnr16/XkCFDVK1aNaWlpenaa68t9JXO33//XUOHDlXdunXl8/nUpk0b/fOf/4xqG2VkZOi7774r9CkcSZo9e7YuuugiLV26VCeeeGLIbYFAQFdffbUcx9H06dNL7NM477zzjrxeb8gniePj4zVs2DAtXrxYmzZtinqsffv2qaCgIOp6r9erhg0bau/evSHX+3w+paenRz1ONMs//fTTFRcXF3JdixYt1KZNG/3000+F6jt27KgaNWqE/X8hAFRW5HlkVTXPbfI0KytLkgp9W7xevXryeDwh4/zvf/9Tt27dVLNmzZC6c845R3PmzAn744gzZ86U4zgaOHBgodvI88qBJjMqnHPPPVfSH2Eh/fFVk+TkZG3YsEG9e/dWSkqKBg0aJOmPYJg4caLatGmj+Ph41a1bVzfeeKP27NkTMqYxRg899FDw/ENdu3bVDz/8UGjZxZ2T+auvvlLv3r1VvXp1JSUlqX379nrmmWeC83vhhRckKeQcRIcUdU7mb775Rr169VJqaqqSk5N13nnnhZxfSfq//ygsWrRIo0ePVu3atZWUlKRLLrlEO3bsCKnNzMzU6tWrlZmZGXH7Llu2TD169FCtWrWUkJCgpk2baujQoUXWvvjii2rWrJl8Pp9OOeUULV26NOT2os7J7DiORo4cqRkzZqhVq1aKj49Xx44d9fnnn0ecGwCgZCxdulRffvml+vfvr2effVbDhw/XvHnz1KVLlyLPvfdnV1xxhbKzs/XII4/o+uuvD7mtX79+ysnJ0fjx49W7d289++yzIS+kSmo+/fr10759+zR+/Hj169dP06ZN0/333x+8fdu2bTrttNM0d+5cjRw5Us8884yaN2+uYcOGaeLEiRHX8csvv5QknXTSSSHX79y5U99//73OOecczZ07V+edd17I7TfeeKO2bNmit99+u8g3kfPz87Vz586o/g7/caJvvvlGLVu2VGpqash4p556qiQVOhdica699lqlpqYqPj5eXbt21bJly4qsO3DggHbu3KkNGzbo6aef1ocfflhoXd2IdvmHM8Zo27ZtqlWrVpG3n3TSSVq0aNERzw0AKhryfGLEdazqeX644vK0S5cukqRhw4Zp5cqV2rRpk958801NnjxZt956q5KSkoK1ubm5SkhIKDR2YmKi8vLytGrVqiKXnZ+fr7feekunn366mjRpUmQNeV4JlPEnqYFiHfrqzdKlS0Ouf+aZZ4wkM2XKFGOMMYMHDzY+n880a9bMDB482EyZMsW8+uqrxhhjrrvuOhMTE2Ouv/56M2XKFHP33XebpKQkc8opp5i8vLzgmH//+9+NJNO7d2/z/PPPm6FDh5r69eubWrVqhZwu49BXU+bPnx+87pNPPjFxcXGmcePGZuzYsWby5Mnm1ltvNd26dTPGGPPll1+a888/30gyr732WvDvEElm7NixwcurVq0ySUlJpl69eubBBx80jz76qGnatKnx+XxmyZIlhbbPiSeeaM4991zz3HPPmTvuuMN4vV7Tr1+/IrdlcV9hPWTbtm2mevXqpmXLlubxxx83L730krnnnnvM8ccfH6w5dLqME0880TRv3txMmDDBPPbYY6ZWrVqmQYMGIdt17NixhU4TIsm0bdvW1KpVyzzwwANmwoQJpnHjxiYhIcF8//33YecHACgZ2dnZha5bvHixkRTMUGMKfw320PP6gAEDCt3/0G0XXXRRyPU333yzkWS+/fbbYseNdj6HL2fo0KEh119yySWmZs2awcvDhg0z9erVC/k6pzHG9O/f36SlpRW5zMMd+r/Bvn37Qq7/4IMPTPXq1c38+fMLjbNx40YjycTHx5ukpKTg3+effx6sOfR/iWj+Dv/6cZs2bcy5555baJ4//PBDyP+LirNo0SJz2WWXmVdeecXMmjXLjB8/3tSsWdPEx8ebFStWFKq/8cYbg/PweDzm8ssvD/m67J9FOl2G7fIP99prrxlJ5pVXXiny9htuuMEkJCSEHQMAKiPynDwvqTx98MEHTUJCQsi877nnnkJ17dq1My1btjQFBQXB63Jzc02jRo2MJPPOO+8UuezZs2cbSWbSpEnFzo88r/gq53f0UalkZmZq586dysnJ0aJFi/TAAw8oISFBF154YbAmNzdXV1xxhcaPHx+87osvvtDLL7+sGTNmhHwdo2vXrurZs6fefvttDRw4UDt27NBjjz2mCy64QLNnzw5+8vaee+7RI488EnZufr9fN954o+rVq6eVK1eqWrVqwduMMZKkzp07q2XLlvr000911VVXRVzfv//978rPz9cXX3yhY489VpJ0zTXXqFWrVrrrrru0cOHCkPqaNWvqk08+Cc47EAjo2WefVWZmpvUPE3z55Zfas2ePPvnkE5188snB6x966KFCtb/++qvWrVun6tWrS5JatWqliy++WB9//HHIvinKqlWrtGzZMnXs2FGS1L9/f7Vq1Ur33Xef3n33Xas5AwDsHf4JlPz8fGVlZal58+aqVq2aVqxYoauvvjrs/YcPH17sbSNGjAi5fMstt2jSpEn673//q/bt25fYfP48h7POOkvvvfeesrKylJKSon//+9/q16+fjDHauXNnsK5Hjx564403tGLFCp1xxhnFrseuXbsUExOj5OTkkOuXLl2qdu3aacqUKRo0aFDI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tmypTz75RGvXrg35JPK//vUveTyeiJ8Wf/PNN7V06VI98cQThT78t2PHDs2dO1cDBgyIeJ5l8rzio8mMSqtfv36aNGmSHnzwwULnVi4oKND+/ftVrVo1devWTbGxsXruuefUvXv3YEN44sSJEZdx0kknqWnTppo4caKGDBlS6If/Do2VlJQkSdq7d2+hczcfzuv1qnv37po1a5Y2btwYfLdz27Ztmjlzps4888ywX2UtTmZmprZs2aJ69eqF/THAPXv2qFq1aiFN8UPvpBZ13ii3Fi9erBUrVuikk06SJG3atEmzZs1Sz549OR8zABwFF154oV577TWlpaWpdevWWrx4sebOnRv2PHnRysjI0EUXXaSePXtq8eLFev311zVw4EB16NDhqM7n0Ucf1fz589WpUyddf/31at26tXbv3q0VK1Zo7ty5Eb/ieuyxx6pt27aaO3euhg4dKkl66aWXtHLlSkl/fGPqb3/7m04++WRdfvnlUc/L7TkcO3XqpCuuuEJjxozR9u3b1bx5c02fPl0bN27UK6+8ElL79ddfq2vXrho7dqzGjRsn6Y/fUkhISNDpp5+uOnXq6Mcff9SLL76oxMREPfroo8H73njjjcrKytLZZ5+tY445Rlu3btWMGTO0evVqPfnkk4U+8fz8889r79692rx5syRp9uzZ+u233yT98bXpQ//viHb5d9xxhz744AP16dNHu3fv1uuvvx6yvD//iPLy5cu1e/duXXzxxdbbFAAqOvKcPC+JPP3LX/6iDz/8UGeddZZGjhypmjVras6cOfrwww913XXXhZxi4/PPP9cDDzyg7t27q2bNmlqyZImmTp2qnj176rbbbiu0vm+++aYKCgoiNo/J88qBJjMqrXPOOUc33nijxo8fr5UrV6p79+6KjY3VunXr9Pbbb+uZZ57R5Zdfrtq1a+vOO+/U+PHjdeGFF6p379765ptv9OGHHwbfLSyOx+PR5MmT1adPH51wwgm69tprVa9ePa1evVo//PCDPv74Y0kK/sjdrbfeqh49esjr9ap///5FjvnQQw/p008/1Zlnnqmbb75ZMTEx+sc//qHc3Fw99thjrrbFe++9p2uvvVZTp04N+wOA06dP16RJk3TJJZeoWbNm2rdvn1566SWlpqaqd+/erpZdlLZt26pHjx669dZb5fP5NGnSJEnS/fffX2LLAAAU75lnnpHX69WMGTOUk5OjM844Q3PnzlWPHj2OeOw333xT9913n/76178qJiZGI0eO1OOPP37U51O3bl19/fXXeuCBB/Tuu+9q0qRJqlmzptq0aaMJEyZENcbQoUN133336eDBg3IcR//97381Z84cPfnkk2rWrJl69+6tSy65xPUcbb366qu699579dprr2nPnj1q37695syZo7PPPjviffv27asZM2boqaeeUlZWlmrXrq1LL71UY8eOVfPmzYN1V155pV555RVNnjxZu3btUkpKijp27KgJEyYU+TXXJ554Ivj7FpL07rvv6t1335X0xwvYQ03maJd/6EX/7NmzNXv27ELL+3OT+e2331ajRo107rnnRtwGAFDZkOfkeUnk6dlnn60vv/xS48aN06RJk7Rr1y41bdpUDz/8sO66666Q+x1zzDHyer16/PHHtW/fPjVt2lQPPfSQRo8erZiYwi3GGTNmqE6dOhEb8uR5JWGAcmrq1KlGklm6dGnYusGDB5ukpKRib3/xxRdNx44dTUJCgklJSTHt2rUzd911l9m8eXOwxu/3m/vvv9/Uq1fPJCQkmC5duphVq1aZxo0bm8GDBwfr5s+fbySZ+fPnhyzjiy++MOeff75JSUkxSUlJpn379ua5554L3l5QUGBuueUWU7t2beM4jjn8oSfJjB07NmS8FStWmB49epjk5GSTmJhounbtar788suotk9RczxUO3Xq1GK306HlDhgwwDRq1Mj4fD5Tp04dc+GFF5ply5YFazIyMowk8/jjjxe6/5/XZezYsebPTzOSzIgRI8zrr79uWrRoYXw+nznxxBMLbVMAQMVy6Dl/x44dZT2VErN3715To0YN8/LLL5f1VFCEnJwck56ebiZOnFjWUwGASoM8x9FGnlcejjH8SheAo8dxHI0YMULPP/98WU8FAFCCxo0bp/vvv187duyI+E2gimTChAmaOnWqfvzxx0LnGUTZmjJlih555BGtW7dOPp+vrKcDAJUCeY6jjTyvPHhkAQAAAMW4++67gz++i/Jl+PDh+vXXX3lBCgCIiDwvv8jzyoNHFwAAAAAAAADANU6XAeCo4nQZAAAAAAAAlUvhn34EgFLE+1oAAAAAAACVC6fLAAAAAAAAAAC4Vu4+yRwIBLR582alpKTIcZyyng4A4Cgzxmjfvn2qX78+P8xRwZHpAFB1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PffcI0lq0qSJHnzwQV1zzTVKTU3VDTfcUORXZz744AO1aNFC8fHx6tq1q6ZPny7HcbR3715JxX8V9rXXXlOTJk2Ulpam/v37a9++fcGajz76SGeeeaaqVaummjVr6sILL9SGDRuOxuYBAOCo69atm9LT0zV+/Phia/7973+rTZs28vl8atKkiZ588smQ25s0aaJHHnlEQ4cOVUpKiho1aqQXX3wx7HIXLFggx3H0n//8R+3bt1d8fLxOO+00rVq1Kliza9cuDRgwQMccc4wSExPVrl07/etf/woZp0uXLho5cqRGjRqlWrVqqUePHpKkp556Su3atVNSUpIaNmyom2++Wfv377faNi+//LKOP/54xcfH67jjjtOkSZOs7g8AQEXi8/mUnp6uxo0b66abblK3bt30wQcfRMzUQ6+7P/jgA7Vu3Vo+n0+//vqrli5dqvPPP1+1atVSWlqazjnnHK1YscJqTqtWrVKvXr2UnJysunXr6uqrr9bOnTtLetWBcoEmMxCl3bt36+OPP9bNN9+shISEkNvS09M1aNAgvfnmmzLGSJKeeOIJdejQQd98843uvffeQuNlZGTo8ssvV9++ffXtt9/qxhtvDDapw9mwYYPef/99zZkzR3PmzNHChQtDviZ84MABjR49WsuWLdO8efPk8Xh0ySWX8E4sAKBS8nq9euSRR/Tcc8/pt99+K3T78uXL1a9fP/Xv31/ff/+9xo0bp3vvvVfTpk0LqXvyySd18skn65tvvtHNN9+sm266SWvWrIm4/L/85S968skntXTpUtWuXVt9+vRRfn6+JCknJ0cdO3bUf/7zH61atUo33HCDrr76an399dchY0yfPl1xcXFatGiRpkyZIknyeDx69tln9cMPP2j69On67LPPdNddd0W9XWbMmKH77rtPDz/8sH766Sc98sgjuvfeezV9+vSoxwAAoCJLSEhQXl5eVJmanZ2tCRMm6OWXX9YPP/ygOnXqaN++fRo8eLC++OILLVmyRC1atFDv3r1DPuQVzt69e3XuuefqxBNP1LJly/TRRx9p27Zt6tevX2msLlD2DICoLFmyxEgy7733XpG3P/XUU0aS2bZtm2ncuLHp27dvyO0ZGRlGkvnmm2+MMcbcfffdpm3btiE199xzj5Fk9uzZY4wxZurUqSYtLS14+9ixY01iYqLJysoKXveXv/zFdOrUqdh579ixw0gy33//fZHzAACgoho8eLC5+OKLjTHGnHbaaWbo0KHGGGPee+89c+i/uQMHDjTnn39+yP3+8pe/mNatWwcvN27c2Fx11VXBy4FAwNSpU8dMnjy52GXPnz/fSDJvvPFG8Lpdu3aZhIQE8+abbxZ7vwsuuMDccccdwcvnnHOOOfHEEyOu69tvv21q1qwZvFzU/xE6dOgQvNysWTMzc+bMkDEefPBB07lzZ2MM/x8AAFQuh/+fIBAImE8//dT4fD5z5513FqotKlMlmZUrV4Zdht/vNykpKWb27NnB6w7vEfw5Wx988EHTvXv3kDE2bdpkJJk1a9YYY/74f8Btt91mubZA+cQnmQFL5v9/UjmSk08+Oezta9as0SmnnBJy3amnnhpx3CZNmiglJSV4uV69etq+fXvw8rp16zRgwAAde+yxSk1NVZMmTSRJv/76a1TzBgCgIpowYYKmT5+un376KeT6n376SWeccUbIdWeccYbWrVsnv98fvK59+/bBfzuOo/T09GC+Hvqaa3Jystq0aRMyVufOnYP/rlGjhlq1ahWcg9/v14MPPqh27dqpRo0aSk5O1scff1wokzt27FhofebOnavzzjtPxxxzjFJSUnT11Vdr165dys7OjrgtDhw4oA0bNmjYsGHBeScnJ+uhhx7iFFoAgEprzpw5Sk5OVnx8vHr16qUrr7xS48aNiypT4+LiQv4vIEnbtm3T9ddfrxYtWigtLU2pqanav39/1K+tv/32W82fPz8ki4877jhJIo9RKUX/C2RAFde8eXM5jqOffvpJl1xySaHbf/rpJ1WvXl21a9eWJCUlJZXKPGJjY0MuO44TciqMPn36qHHjxnrppZdUv359BQIBtW3blh8SAgBUameffbZ69OihMWPGaMiQIdb3D5evL7/8sg4ePFhkXTiPP/64nnnmGU2cODF4LshRo0YVyuQ//59h48aNuvDCC3XTTTfp4YcfVo0aNfTFF19o2LBhysvLU2JiYtjlHjrP5EsvvaROnTqF3Ob1eqOePwAAFUnXrl01efJkxcXFqX79+oqJiYk6UxMSEuQ4Tsh4gwcP1q5du/TMM8+ocePG8vl86ty5c9Svrffv368+ffpowoQJhW6rV6/eka8wUM7QZAaiVLNmTZ1//vmaNGmSbr/99pDzMm/dulUzZszQNddcUyiYitOqVSv997//Dblu6dKlRzTHXbt2ac2aNXrppZd01llnSZK++OKLIxoTAICK4tFHH9UJJ5ygVq1aBa87/vjjtWjRopC6RYsWqWXLllE3XI855phib1uyZIkaNWokSdqzZ4/Wrl2r448/Priciy++WFdddZUkKRAIaO3atWrdunXY5S1fvlyBQEBPPvmkPJ4/vnj41ltvRTVXSapbt67q16+vn3/+WYMGDYr6fgAAVGRJSUlq3rx5yHVHkqmLFi3SpEmT1Lt3b0nSpk2brH6076STTtK///1vNWnSRDExtN9Q+XG6DMDC888/r9zcXPXo0UOff/65Nm3apI8++kjnn3++jjnmGD388MNRj3XjjTdq9erVuvvuu7V27Vq99dZbwR8hirZR/WfVq1dXzZo19eKLL2r9+vX67LPPNHr0aFdjAQBQ0bRr106DBg3Ss88+G7zujjvu0Lx58/Tggw9q7dq1mj59up5//nndeeedJbLMBx54QPPmzdOqVas0ZMgQ1apVS3379pUktWjRQp9++qm+/PJL/fTTT7rxxhu1bdu2iGM2b95c+fn5eu655/Tzzz/rtddeC/4gYLTuv/9+jR8/Xs8++6zWrl2r77//XlOnTtVTTz3lZjUBAKiQjiRTW7Rooddee00//fSTvvrqKw0aNCjkw2aRjBgxQrt379aAAQO0dOlSbdiwQR9//LGuvfbakFN2AZUFTWbAQosWLbRs2TIde+yx6tevn5o1a6YbbrhBXbt21eLFi1WjRo2ox2ratKneeecdvfvuu2rfvr0mT56se+65R5Lk8/lczc/j8eiNN97Q8uXL1bZtW91+++16/PHHXY0FAEBF9MADD4ScRuqkk07SW2+9pTfeeENt27bVfffdpwceeMDVKTWK8uijj+q2225Tx44dtXXrVs2ePVtxcXGSpL///e866aST1KNHD3Xp0kXp6enBBnQ4HTp00FNPPaUJEyaobdu2mjFjhsaPH281r+uuu04vv/yypk6dqnbt2umcc87RtGnT1LRpUzerCQBAhXQkmfrKK69oz549Oumkk3T11Vfr1ltvVZ06daJedv369bVo0SL5/X51795d7dq106hRo1StWrXgp6qBysQx0f6KGYBS9/DDD2vKlCnatGlTWU8FAACEsWDBAnXt2lV79uxRtWrVyno6AAAAQJnipDBAGZo0aZJOOeUU1axZU4sWLdLjjz+ukSNHlvW0AAAAAAAAgKjRZAbK0Lp16/TQQw9p9+7datSoke644w6NGTOmrKcFAAAAAAAARI3TZQAAAAAAAAAAXONM4wAAAAAAAAAA12gyAwAAAAAAAABco8kMAAAAAAAAAHCNJjMAAAAAAAAAwDWazAAAAAAAAAAA12gyAwAAAAAAAABco8kMAAAAAAAAAHCNJjMAAAAAAAAAwLX/B7EOW+gQkrwrAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZkAAAHrCAYAAACtlpOGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABrSklEQVR4nO3deXhTZd7/8U+WNt0LhUIpS9kURRZHHBncAEERlBF1HBdUcEUER8RtGBVwxRUVFVwHGEdcn3EddxR8RGAEdRQVBCyKsghIW+je5P79wY88xLZJ7tOGdHm/rivXRZNv7nOfJf003xxOXMYYIwAAAAAAAAAAHHDHewIAAAAAAAAAgMaLJjMAAAAAAAAAwDGazAAAAAAAAAAAx2gyAwAAAAAAAAAco8kMAAAAAAAAAHCMJjMAAAAAAAAAwDGazAAAAAAAAAAAx2gyAwAAAAAAAAAco8kMAAAAAAAAAHCMJjMQIy6XS9OnT4/3NMIaO3as0tLS4j0NAAAAAAAANGI0mRFX+fn5mjhxog488EClpKQoJSVFPXv21IQJE/Tll1/Ge3oxNWjQILlcroi3ujaqS0pKNH36dC1atKhe5r2/zZ49W/PmzYv3NAAAAAAAAFALb7wngObrjTfe0Jlnnimv16vRo0erb9++crvdWr16tf71r39pzpw5ys/PV15eXrynGhM33HCDLr744uDPn376qWbNmqW//e1vOvjgg4P39+nTp07LKSkp0c033yxpT2O7sZk9e7Zat26tsWPHxnsqAAAAAAAAqAFNZsTF+vXrddZZZykvL08LFy5Uu3btQh6/6667NHv2bLnd4U+2Ly4uVmpqaiynGjPHH398yM9JSUmaNWuWjj/++LDN4Ma8zgAAAAAAAGh6uFwG4uLuu+9WcXGx5s6dW63BLEler1d/+ctf1LFjx+B9e68fvH79eo0YMULp6ekaPXq0pD2N16uvvlodO3aUz+dTjx49dO+998oYE3z+hg0b5HK5arz0wm8vSzF9+nS5XC6tW7dOY8eOVYsWLZSZmakLLrhAJSUlIc8tLy/XVVddpezsbKWnp+uPf/yjfvrppzpuodB5fPPNNzrnnHPUsmVLHX300ZL2nJVcUzN67Nix6ty5c3Cds7OzJUk333xzrZfg+PnnnzVq1CilpaUpOztb11xzjfx+f0jN5s2btXr1alVWVkacdyAQ0IMPPqjevXsrKSlJ2dnZOvHEE7VixYpgzdy5c3XcccepTZs28vl86tmzp+bMmRMyTufOnfX1119r8eLFwbk3xrOxAQAAAAAAmjLOZEZcvPHGG+revbv69+9v9byqqioNGzZMRx99tO69916lpKTIGKM//vGP+vDDD3XRRRfp0EMP1TvvvKNrr71WP//8s+6//37H8/zzn/+sLl26aMaMGfrss8/05JNPqk2bNrrrrruCNRdffLH++c9/6pxzztGRRx6pDz74QCeddJLjZdbkjDPO0AEHHKA77rgjpHEeSXZ2tubMmaPx48fr1FNP1WmnnSYp9BIcfr9fw4YNU//+/XXvvffq/fff13333adu3bpp/PjxwbopU6Zo/vz5ys/PDzaxa3PRRRdp3rx5Gj58uC6++GJVVVXpf//3f7Vs2TIdfvjhkqQ5c+bokEMO0R//+Ed5vV69/vrruvzyyxUIBDRhwgRJ0gMPPKArrrhCaWlpuuGGGyRJbdu2jXr9AQAAAAAAEHs0mbHfFRUVadOmTRo1alS1xwoKClRVVRX8OTU1VcnJycGfy8vLdcYZZ2jGjBnB+1599VV98MEHuu2224KNyAkTJuiMM87Qgw8+qIkTJ6pbt26O5vq73/1OTz31VPDnHTt26Kmnngo2mf/73//qn//8py6//HI98sgjwWWPHj26Xr+4sG/fvlqwYIH181JTU/WnP/1J48ePV58+fXTuuedWqykrK9OZZ56pm266SZJ02WWX6bDDDtNTTz0V0mSO1ocffqh58+bpL3/5ix588MHg/VdffXVIg3zx4sUh+3bixIk68cQTNXPmzGCTedSoUbrxxhvVunXrGucOAAAAAACA+ONyGdjvioqKJElpaWnVHhs0aJCys7ODt72N2339tvH55ptvyuPx6C9/+UvI/Xubmm+99ZbjuV522WUhPx9zzDHasWNHcB3efPNNSaq27EmTJjleZjTzqG81ref3338fct+8efNkjIl4FvP//M//yOVyadq0adUec7lcwX/v22AuLCzU9u3bNXDgQH3//fcqLCx0sBYAAAAAAACIB85kxn6Xnp4uSdq9e3e1xx577DHt2rVLW7durfHMVa/Xqw4dOoTc98MPPyg3Nzc47l4HH3xw8HGnOnXqFPJzy5YtJUk7d+5URkaGfvjhB7nd7mpnSvfo0cPxMmvSpUuXeh1vX3uvmbyvli1baufOnY7GW79+vXJzc5WVlRW2bsmSJZo2bZqWLl1a7TrXhYWFyszMdLR8AAAAAAAA7F80mbHfZWZmql27dlq1alW1x/Zeo3nDhg01Ptfn88ntdnYC/r5n0e7rt19wty+Px1Pj/TbXRa4P+571u5fL5apxHuHWpya1rWMsrV+/XkOGDNFBBx2kmTNnqmPHjkpMTNSbb76p+++/X4FAYL/PCQAAAAAAAM5wuQzExUknnaR169bpP//5T53HysvL06ZNm7Rr166Q+1evXh18XPq/s5ALCgpC6upypnNeXp4CgYDWr18fcv+aNWscjxmtli1bVlsXqfr61NZcj5Vu3bpp06ZN+vXXX2utef3111VeXq7XXntN48aN04gRIzR06NBam+kAAAAAAABouGgyIy6uu+46paSk6MILL9TWrVurPW5zpvCIESPk9/v18MMPh9x///33y+Vyafjw4ZKkjIwMtW7dWh999FFI3ezZsx2swR57x541a1bI/Q888IDjMaPVrVs3rV69Wtu2bQve99///ldLliwJqUtJSZFUvblua/PmzVq9erUqKyvD1p1++ukyxujmm2+u9tje/br37Ol993NhYaHmzp1b7Tmpqal1njsAAAAAAABih8tlIC4OOOAALViwQGeffbZ69Oih0aNHq2/fvjLGKD8/XwsWLJDb7a52/eWajBw5UoMHD9YNN9ygDRs2qG/fvnr33Xf16quvatKkSSHXS7744ot155136uKLL9bhhx+ujz76SN99953j9Tj00EN19tlna/bs2SosLNSRRx6phQsXat26dY7HjNaFF16omTNnatiwYbrooov0yy+/6NFHH9UhhxwS/GJCac+lNnr27Knnn39eBx54oLKystSrVy/16tXLanlTpkzR/PnzlZ+fH/bL/wYPHqzzzjtPs2bN0tq1a3XiiScqEAjof//3fzV48GBNnDhRJ5xwghITEzVy5EiNGzdOu3fv1hNPPKE2bdpo8+bNIeP169dPc+bM0W233abu3burTZs2Ou6446zmDgAAAAAAgNihyYy4OeWUU/TVV1/pvvvu07vvvqu///3vcrlcysvL00knnaTLLrtMffv2jTiO2+3Wa6+9pqlTp+r555/X3Llz1blzZ91zzz26+uqrQ2qnTp2qbdu26aWXXtILL7yg4cOH66233lKbNm0cr8ff//53ZWdn65lnntErr7yi4447Tv/+97/VsWNHx2NG4+CDD9Y//vEPTZ06VZMnT1bPnj319NNPa8GCBVq0aFFI7ZNPPqkrrrhCV111lSoqKjRt2jTrJrONuXPnqk+fPnrqqad07bXXKjMzU4cffriOPPJISXu+GPGll17SjTfeqGuuuUY5OTkaP368srOzdeGFF4aMNXXqVP3www+6++67tWvXLg0cOJAmMwAAAAAAQAPiMvv7G8wAAAAAAAAAAE0G12QGAAAAAAAAADhGkxkAAAAAAAAA4BhNZgAAAAAAAACAYzSZAQAAgN8oLy/XhRdeqE6dOikjI0N/+MMftHTp0nhPCwAAWCLTgf2DJjMAAADwG1VVVercubM+/vhjFRQUaNKkSRo5cqR2794d76kBAAALZDqwf9BkBgAAQIM3ffp0uVwubd++3dHz7777bh100EEKBAJR1aempmrq1Knq1KmT3G63zjrrLCUmJmrNmjWOlo/wHn30UXXq1Enl5eXxngoAIMbI9KaNTG++aDID++jcubPGjh0b/HnRokVyuVxatGhRvS3D5XJp+vTp9TZeU9C5c2edfPLJ8Z4GAKAWn3zyiaZPn66CgoJ4T8WRoqIi3XXXXbr++uvldlf/83fevHk66KCDdNVVV9U6xtq1a/Xrr7+qe/fu9TKn8vJyXX/99crNzVVycrL69++v9957r96fb7OceI45duxYVVRU6LHHHot6GwAA7JHpZHqsxyTTmy+azGgw5s2bJ5fLFbwlJSXpwAMP1MSJE7V169Z4T8/Km2++2WgaybNnz9a8efPiPQ0AQAP2ySef6Oabb260b0j//ve/q6qqSmeffXa1x9atW6fLLrtMZ555pl577bUan19aWqpzzz1XU6ZMUWZmZr3MaezYsZo5c6ZGjx6tBx98UB6PRyNGjNDHH39cr8+3WU48x0xKStKYMWM0c+ZMGWOi2gYAAHtkOpke6zHJ9GbMAA3E3LlzjSRzyy23mKeffto88cQTZsyYMcbtdpsuXbqY4uLimM8hLy/PjBkzJviz3+83paWlxu/3W40zYcIEU9vLq7S01FRWVtZlmvXqkEMOMQMHDozrHPLy8sxJJ50U1zkAAGp3zz33GEkmPz8/Yu3u3btjModp06YZSWbbtm3Wz+3Tp48599xza3zshhtuMCeffLJ56KGHTP/+/as9XlFRYU466SRzzjnnmEAgYL3smixfvtxIMvfcc0/wvtLSUtOtWzczYMCAenu+zXLiOeZeK1asMJLMwoULI24DAIAzZDqZHssx9yLTmyfOZEaDM3z4cJ177rm6+OKLNW/ePE2aNEn5+fl69dVXa31OcXFxTObidruVlJRU43/DcSopKUler7fextufYrWdAQAN1/Tp03XttddKkrp06RL8H0cbNmwIXlPxm2++0TnnnKOWLVvq6KOPlrTnbJfOnTvXOJ7L5Qq57+eff9ZFF12k3Nxc+Xw+denSRePHj1dFRUXYuf3www/q3r27evXqVev/esrPz9eXX36poUOH1vj466+/rj/+8Y/69NNP9bvf/S7ksUAgoPPOO08ul0vz58+vNm+nXnrpJXk8Hl166aXB+5KSknTRRRdp6dKl2rhxY70832Y58Rxzr379+ikrKyvs33wAAOfIdDKdTEcs0WRGg3fcccdJ2hMo0p6AS0tL0/r16zVixAilp6dr9OjRkvYExwMPPKBDDjlESUlJatu2rcaNG6edO3eGjGmM0W233aYOHTooJSVFgwcP1tdff11t2bVdk3n58uUaMWKEWrZsqdTUVPXp00cPPvhgcH6PPPKIJIVc/mOvmq7J/Pnnn2v48OHKyMhQWlqahgwZomXLloXU7L2cyJIlSzR58mRlZ2crNTVVp556qrZt2xZSW1hYqNWrV6uwsDDstu3cubO+/vprLV68ODjPQYMGhSxv8eLFuvzyy9WmTRt16NAhuI7R/pEhSf/85z91xBFHKCUlRS1bttSxxx6rd999N+zc5s+fL6/XG/wjCAAQH6eddlrwv6Tef//9evrpp/X0008rOzs7WHPGGWeopKREd9xxhy655BKr8Tdt2qQjjjhCzz33nM4880zNmjVL5513nhYvXqySkpJan7d+/Xode+yxSk9P16JFi9S2bdsa6z755BNJ0mGHHVbtse3bt+urr77SwIED9f7772vIkCEhj48bN06bN2/Wiy++WOMHxJWVldq+fXtUt32/nOjzzz/XgQceqIyMjJDxjjjiCEnSF198Uet62zzfZjnxHHNfhx12mJYsWVLTagMA6ohMJ9NjPea+yPTmp3GeTolmZf369ZKkVq1aBe+rqqrSsGHDdPTRR+vee+9VSkqKpD3BMW/ePF1wwQX6y1/+ovz8fD388MP6/PPPtWTJEiUk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AAAAAAAAADhGkxkAAAAAAAAA4BhNZgAAAAAAAACAYzSZAQAAAAAAAACO0WQGAAAAAAAAADhGkxkAAAAAAAAA4BhNZmA/2bBhg1wul7744ouonzNv3jy1aNEi7vMAAAANy2//Rpg+fboOPfTQqJ/P3wMAANSdy+XSK6+8IslZtg4aNEiTJk2KydyA/Y0mM2Bp48aNuvDCC5Wbm6vExETl5eXpyiuv1I4dO8I+r2PHjtq8ebN69eoV9bLOPPNMfffdd3WdMgAATdLYsWPlcrl05513htz/yiuvyOVyxWlWAABgf9v7N4HL5VJiYqK6d++uW265RVVVVfGeGtBs0GQGLHz//fc6/PDDtXbtWj377LNat26dHn30US1cuFADBgzQr7/+WuPzKioq5PF4lJOTI6/XG/XykpOT1aZNm/qaPgAATU5SUpLuuusu7dy5M95TqRcVFRXxngIAAI3SiSeeqM2bN2vt2rW6+uqrNX36dN1zzz3W4/j9fgUCgRjMEGjaaDIDFiZMmKDExES9++67GjhwoDp16qThw4fr/fff188//6wbbrhBktS5c2fdeuutOv/885WRkaFLL720xv8689prr+mAAw5QUlKSBg8erPnz58vlcqmgoEBS7f8V9umnn1bnzp2VmZmps846S7t27QrWvP322zr66KPVokULtWrVSieffLLWr1+/PzYPAAD73dChQ5WTk6MZM2bUWvM///M/OuSQQ+Tz+dS5c2fdd999IY937txZd9xxhy688EKlp6erU6dOevzxx8Mud9GiRXK5XPr3v/+tPn36KCkpSX/4wx+0atWqYM2OHTt09tlnq3379kpJSVHv3r317LPPhowzaNAgTZw4UZMmTVLr1q01bNgwSdLMmTPVu3dvpaamqmPHjrr88su1e/duq23z5JNP6uCDD1ZSUpIOOuggzZ492+r5AAA0Jj6fTzk5OcrLy9P48eM1dOhQvfbaaxEzde/77tdee009e/aUz+fTjz/+qE8//VTHH3+8WrdurczMTA0cOFCfffaZ1ZxWrVql4cOHKy0tTW3bttV5552n7du31/eqAw0CTWYgSr/++qveeecdXX755UpOTg55LCcnR6NHj9bzzz8vY4wk6d5771Xfvn31+eef66abbqo2Xn5+vv70pz9p1KhR+u9//6tx48YFm9ThrF+/Xq+88oreeOMNvfHGG1q8eHHIfxMuLi7W5MmTtWLFCi1cuFBut1unnnoqn8QCAJokj8ejO+64Qw899JB++umnao+vXLlSf/7zn3XWWWfpq6++0vTp03XTTTdp3rx5IXX33XefDj/8cH3++ee6/PLLNX78eK1Zsybi8q+99lrdd999+vTTT5Wdna2RI0eqsrJSklRWVqZ+/frp3//+t1atWqVLL71U5513nv7zn/+EjDF//nwlJiZqyZIlevTRRyVJbrdbs2bN0tdff6358+frgw8+0HXXXRf1dnnmmWc0depU3X777fr22291xx136KabbtL8+fOjHgMAgMYsOTlZFRUVUWVqSUmJ7rrrLj355JP6+uuv1aZNG+3atUtjxozRxx9/rGXLlumAAw7QiBEjQk7yCqegoEDHHXecfve732nFihV6++23tXXrVv35z3+OxeoC8WcARGXZsmVGknn55ZdrfHzmzJlGktm6davJy8szo0aNCnk8Pz/fSDKff/65McaY66+/3vTq1Suk5oYbbjCSzM6dO40xxsydO9dkZmYGH582bZpJSUkxRUVFwfuuvfZa079//1rnvW3bNiPJfPXVVzXOAwCAxmrMmDHmlFNOMcYY84c//MFceOGFxhhjXn75ZbP3z9xzzjnHHH/88SHPu/baa03Pnj2DP+fl5Zlzzz03+HMgEDBt2rQxc+bMqXXZH374oZFknnvuueB9O3bsMMnJyeb555+v9XknnXSSufrqq4M/Dxw40Pzud7+LuK4vvviiadWqVfDnmv5G6Nu3b/Dnbt26mQULFoSMceutt5oBAwYYY/h7AADQtOz7N0EgEDDvvfee8fl85pprrqlWW1OmSjJffPFF2GX4/X6Tnp5uXn/99eB9+/YIfputt956qznhhBNCxti4caORZNasWWOM2fN3wJVXXmm5tkDDxJnMgCXz/89UjuTwww8P+/iaNWv0+9//PuS+I444IuK4nTt3Vnp6evDndu3a6Zdffgn+vHbtWp199tnq2rWrMjIy1LlzZ0nSjz/+GNW8AQBojO666y7Nnz9f3377bcj93377rY466qiQ+4466iitXbtWfr8/eF+fPn2C/3a5XMrJyQnm697/5pqWlqZDDjkkZKwBAwYE/52VlaUePXoE5+D3+3Xrrbeqd+/eysrKUlpamt55551qmdyvX79q6/P+++9ryJAhat++vdLT03Xeeedpx44dKikpibgtiouLtX79el100UXBeaelpem2227jEloAgCbrjTfeUFpampKSkjR8+HCdeeaZmj59elSZmpiYGPK3gCRt3bpVl1xyiQ444ABlZmYqIyNDu3fvjvq99X//+199+OGHIVl80EEHSRJ5jCYp+m8gA5q57t27y+Vy6dtvv9Wpp55a7fFvv/1WLVu2VHZ2tiQpNTU1JvNISEgI+dnlcoVcCmPkyJHKy8vTE088odzcXAUCAfXq1YsvEgIANGnHHnushg0bpilTpmjs2LHWzw+Xr08++aRKS0trrAvnnnvu0YMPPqgHHnggeC3ISZMmVcvk3/7NsGHDBp188skaP368br/9dmVlZenjjz/WRRddpIqKCqWkpIRd7t7rTD7xxBPq379/yGMejyfq+QMA0JgMHjxYc+bMUWJionJzc+X1eqPO1OTkZLlcrpDxxowZox07dujBBx9UXl6efD6fBgwYEPV76927d2vkyJG66667qj3Wrl27uq8w0MDQZAai1KpVKx1//PGaPXu2rrrqqpDrMm/ZskXPPPOMzj///GrBVJsePXrozTffDLnv008/rdMcd+zYoTVr1uiJJ57QMcccI0n6+OOP6zQmAACNxZ133qlDDz1UPXr0CN538MEHa8mSJSF1S5Ys0YEHHhh1w7V9+/a1PrZs2TJ16tRJkrRz50599913Ovjgg4PLOeWUU3TuuedKkgKBgL777jv17Nkz7PJWrlypQCCg++67T273nv94+MILL0Q1V0lq27atcnNz9f3332v06NFRPw8AgMYsNTVV3bt3D7mvLpm6ZMkSzZ49WyNGjJAkbdy40epL+w477DD9z//8jzp37iyvl/Ybmj4ulwFYePjhh1VeXq5hw4bpo48+0saNG/X222/r+OOPV/v27XX77bdHPda4ceO0evVqXX/99fruu+/0wgsvBL+EKNpG9W+1bNlSrVq10uOPP65169bpgw8+0OTJkx2NBQBAY9O7d2+NHj1as2bNCt539dVXa+HChbr11lv13Xffaf78+Xr44Yd1zTXX1Msyb7nlFi1cuFCrVq3S2LFj1bp1a40aNUqSdMABB+i9997TJ598om+//Vbjxo3T1q1bI47ZvXt3VVZW6qGHHtL333+vp59+OviFgNG6+eabNWPGDM2aNUvfffedvvrqK82dO1czZ850spoAADRKdcnUAw44QE8//bS+/fZbLV++XKNHjw452SySCRMm6Ndff9XZZ5+tTz/9VOvXr9c777yjCy64IOSSXUBTQZMZsHDAAQdoxYoV6tq1q/785z+rW7duuvTSSzV48GAtXbpUWVlZUY/VpUsXvfTSS/rXv/6lPn36aM6cObrhhhskST6fz9H83G63nnvuOa1cuVK9evXSVVddpXvuucfRWAAANEa33HJLyGWkDjvsML3wwgt67rnn1KtXL02dOlW33HKLo0tq1OTOO+/UlVdeqX79+mnLli16/fXXlZiYKEm68cYbddhhh2nYsGEaNGiQcnJygg3ocPr27auZM2fqrrvuUq9evfTMM89oxowZVvO6+OKL9eSTT2ru3Lnq3bu3Bg4cqHnz5qlLly5OVhMAgEapLpn61FNPaefOnTrssMN03nnn6S9/+YvatGkT9bJzc3O1ZMkS+f1+nXDCCerdu7cmTZqkFi1aBM+qBpoSl4n2W8wAxNztt9+uRx99VBs3boz3VAAAQBiLFi3S4MGDtXPnTrVo0SLe0wEAAADiiovCAHE0e/Zs/f73v1erVq20ZMkS3XPPPZo4cWK8pwUAAAAAAABEjSYzEEdr167Vbbfdpl9//VWdOnXS1VdfrSlTpsR7WgAAAAAAAEDUuFwGAAAAAAAAAMAxrjQOAAAAAAAAAHCMJjMAAAAAAAAAwDGazAAAAAAAAAAAx2gyAwAAAAAAAAAco8kMAAAAAAAAAHCMJjMAAAAAAAAAwDGazAAAAAAAAAAAx2gyAwAAAAAAAAAc+3+nO9wVwmgA0gAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for i in range(len(x_train)):\n", + " f, ax = plt.subplots(1,3, constrained_layout = False)\n", + "\n", + " nonparallel_perturbation = np.linalg.norm(x_train[[i]] - nonparallel_adv[[i]])\n", + " parallel_perturbation = np.linalg.norm(x_train[[i]] - parallel_adv[[i]])\n", + "\n", + " ax[0].set_title(f'Ground Truth: {labels[np.argmax(y_train[i])]}\\nPrediction: {labels[np.argmax(classifier.predict(x_train[i]))]}')\n", + " ax[0].imshow(x_train[i].transpose(1,2,0))\n", + " ax[0].set_xlabel('Original')\n", + "\n", + " ax[1].set_title(f'{labels[np.argmax(classifier.predict(nonparallel_adv[i]))]} ($\\\\ell ^{2}$={nonparallel_perturbation:.5f})')\n", + " ax[1].imshow(x_train[i].transpose(1,2,0))\n", + " ax[1].set_xlabel('Non-parallel')\n", + "\n", + " ax[2].set_title(f'{labels[np.argmax(classifier.predict(parallel_adv[i]))]} ($\\\\ell ^{2}$={parallel_perturbation:.5f})')\n", + " ax[2].imshow(x_train[i].transpose(1,2,0))\n", + " ax[2].set_xlabel('Parallel')\n", + " f.set_figwidth(15)\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example with MNIST" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from tests.utils import get_image_classifier_pt\n", + "from art.attacks.evasion.deepfool import DeepFool\n", + "from art.attacks.evasion.auto_attack import AutoAttack, AutoProjectedGradientDescent\n", + "\n", + "# Load MNIST data\n", + "(x_train_mnist, y_train_mnist), (x_test_mnist, y_test_mnist), _, _ = load_dataset('mnist')\n", + "\n", + "x_train_mnist = np.reshape(x_train_mnist, (x_train_mnist.shape[0],) + (1, 28, 28)).astype(np.float32)\n", + "x_test_mnist = np.reshape(x_test_mnist, (x_test_mnist.shape[0],) + (1, 28, 28)).astype(np.float32)\n", + "\n", + "n_train = 10\n", + "n_test = 10\n", + "x_train_mnist = x_train_mnist[:n_train]\n", + "y_train_mnist = y_train_mnist[:n_train]\n", + "x_test_mnist = x_test_mnist[:n_test]\n", + "y_test_mnist = y_test_mnist[:n_test]\n", + "\n", + "classifier = get_image_classifier_pt(from_logits=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Define attacks to run in parallel " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/kieranfraser/git/personal/art/env/lib/python3.9/multiprocessing/resource_tracker.py:216: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown\n", + " warnings.warn('resource_tracker: There appear to be %d '\n" + ] + } + ], + "source": [ + "norm = np.inf\n", + "eps = 0.3\n", + "eps_step = 0.1\n", + "batch_size = 32\n", + "\n", + "attacks = list()\n", + "attacks.append(\n", + " AutoProjectedGradientDescent(\n", + " estimator=classifier,\n", + " norm=norm,\n", + " eps=eps,\n", + " eps_step=eps_step,\n", + " max_iter=100,\n", + " targeted=True,\n", + " nb_random_init=5,\n", + " batch_size=batch_size,\n", + " loss_type=\"cross_entropy\",\n", + " verbose=False,\n", + " )\n", + ")\n", + "attacks.append(\n", + " AutoProjectedGradientDescent(\n", + " estimator=classifier,\n", + " norm=norm,\n", + " eps=eps,\n", + " eps_step=eps_step,\n", + " max_iter=100,\n", + " targeted=False,\n", + " nb_random_init=5,\n", + " batch_size=batch_size,\n", + " loss_type=\"difference_logits_ratio\",\n", + " verbose=False,\n", + " )\n", + ")\n", + "attacks.append(DeepFool(classifier=classifier, max_iter=100, epsilon=1e-6, nb_grads=3, batch_size=batch_size, verbose=False,))\n", + "sa = SquareAttack(estimator=classifier, norm=norm, max_iter=5000, eps=eps, p_init=0.8, nb_restarts=5, verbose=False,)\n", + "\n", + "attacks.append(sa)\n", + "\n", + "attack = AutoAttack(\n", + " estimator=classifier,\n", + " norm=norm,\n", + " eps=eps,\n", + " eps_step=eps_step,\n", + " attacks=attacks,\n", + " batch_size=batch_size,\n", + " estimator_orig=None,\n", + " targeted=True,\n", + " parallel=True,\n", + ")\n", + "\n", + "x_train_mnist_adv = attack.generate(x=x_train_mnist, y=y_train_mnist)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Visualize the performance" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAFLCAYAAAB2qu6tAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA690lEQVR4nO3deVhU9f4H8PewDfsIyJps7guiicrFJTVJ0vSqlXslZrnhgmZ6vZmamuRyzTISra54LVu8V7R8TFNRvJiaEpimEhooyeKS7ArIfH9/+GMuI3CYgeEMy/v1PPM8zvl8zzkfjvLxM99z5hyFEEKAiIiISCYmxk6AiIiImhc2H0RERCQrNh9EREQkKzYfREREJCs2H0RERCQrNh9EREQkKzYfREREJCs2H0RERCQrNh9EREQkKzYfpMXHxwehoaGa98ePH4dCocDx48cNtg+FQoEVK1YYbHtERNS4sPloQKKjo6FQKDQvS0tLtG/fHrNnz0Z2drax09PLgQMHGmWDUVpais6dO0OhUGDDhg3GToeIqEkyM3YCVNnKlSvh6+uLBw8eID4+Hlu2bMGBAwdw8eJFWFtby5rLU089hfv378PCwkKv9Q4cOIDIyMgqG5D79+/DzKxh/tPbvHkzbty4Yew0iIiaNM58NEBDhw7FSy+9hNdeew3R0dEIDw9Hamoq9u3bV+06hYWF9ZKLiYkJLC0tYWJiuH8qlpaWDbL5uHXrFlauXInFixcbOxUioiaNzUcj8PTTTwMAUlNTAQChoaGwtbXFtWvXMGzYMNjZ2WHSpEkAALVajU2bNqFLly6wtLSEq6srpk+fjnv37mltUwiB1atXo1WrVrC2tsagQYPw66+/Vtp3ddd8nDlzBsOGDYODgwNsbGzg7++PDz74QJNfZGQkAGidRipX1TUfiYmJGDp0KOzt7WFra4vBgwfj9OnTWmPKT0udPHkSCxYsgLOzM2xsbDB69Gjcvn1ba2xubi6uXLmC3NxcXQ4xAOBvf/sbOnTogJdeeknndYiISH8N7+MnVXLt2jUAgJOTk2bZw4cPERISgn79+mHDhg2a0zHTp09HdHQ0pkyZgrlz5yI1NRUfffQREhMTcfLkSZibmwMAli1bhtWrV2PYsGEYNmwYfv75ZwwZMgQlJSU15nP48GEMHz4c7u7umDdvHtzc3HD58mXs378f8+bNw/Tp05GRkYHDhw9j586dNW7v119/Rf/+/WFvb49FixbB3NwcW7duxcCBAxEXF4fAwECt8XPmzIGDgwOWL1+OtLQ0bNq0CbNnz8bXX3+tGRMTE4MpU6Zg+/btWhfQVuenn37Cjh07EB8fr9UoERGR4bH5aIByc3Nx584dPHjwACdPnsTKlSthZWWF4cOHa8YUFxdjzJgxiIiI0CyLj4/Hp59+ii+++AITJ07ULB80aBCeffZZ7N69GxMnTsTt27exbt06PPfcc/juu+80/9m+9dZbWLNmjWRuZWVlmD59Otzd3ZGUlIQWLVpoYkIIAEBQUBDat2+Pw4cP6zSLsHTpUpSWliI+Ph6tW7cGALzyyivo0KEDFi1ahLi4OK3xTk5O+OGHHzR5q9VqfPjhh8jNzYVKpapxf48TQmDOnDkYN24cgoKCkJaWpvc2iIhIdzzt0gAFBwfD2dkZnp6eGD9+PGxtbRETE4MnnnhCa9zMmTO13u/evRsqlQrPPPMM7ty5o3kFBATA1tYWx44dAwAcOXIEJSUlmDNnjtan/PDw8BpzS0xMRGpqKsLDw7UaDwC1mjEoKyvDDz/8gFGjRmkaDwBwd3fHxIkTER8fj7y8PK11pk2bprWv/v37o6ysDNevX9csCw0NhRBCp1mP6OhoXLhwAWvXrtU7fyIi0h9nPhqgyMhItG/fHmZmZnB1dUWHDh0qXfBpZmaGVq1aaS1LSUlBbm4uXFxcqtzurVu3AEDzn3S7du204s7OznBwcJDMrfwUkJ+fn+4/kITbt2+jqKgIHTp0qBTr1KkT1Go10tPT0aVLF81yLy8vrXHlOT9+XYsu8vLysGTJErz55pvw9PTUe30iItIfm48GqHfv3ujZs6fkGKVSWakhUavVcHFxwRdffFHlOs7OzgbL0ZhMTU2rXF5+2kcfGzZsQElJCcaNG6c53fLHH38AeNTMpKWlwcPDQ++vGhMRUfXYfDQhbdq0wZEjR9C3b19YWVlVO87b2xvAo5mSiqc6bt++XePsQZs2bQAAFy9eRHBwcLXjdD0F4+zsDGtrayQnJ1eKXblyBSYmJvU6I3Hjxg3cu3dPa2al3Jo1a7BmzRokJiaie/fu9ZYDEVFzw2s+mpCxY8eirKwMq1atqhR7+PAhcnJyADy6psTc3BybN2/Wmi3YtGlTjfvo0aMHfH19sWnTJs32ylXclo2NDQBUGvM4U1NTDBkyBPv27dO60DM7Oxu7du1Cv379YG9vX2Nej9P1q7Zz585FTEyM1mvr1q0AHl03EhMTA19fX733T0RE1ePMRxMyYMAATJ8+HREREUhKSsKQIUNgbm6OlJQU7N69Gx988AFefPFFODs7Y+HChYiIiMDw4cMxbNgwJCYm4vvvv0fLli0l92FiYoItW7ZgxIgR6N69O6ZMmQJ3d3dcuXIFv/76Kw4dOgQACAgIAPDoP/eQkBCYmppi/PjxVW5z9erVOHz4MPr164dZs2bBzMwMW7duRXFxMdatW1erY6HrV2179OiBHj16aC0rb4K6dOmCUaNG1Wr/RERUPTYfTUxUVBQCAgKwdetW/P3vf4eZmRl8fHzw0ksvoW/fvppxq1evhqWlJaKionDs2DEEBgbihx9+wHPPPVfjPkJCQnDs2DG88847+Mc//gG1Wo02bdrg9ddf14x5/vnnMWfOHHz11Vf4/PPPIYSotvno0qUL/vvf/2LJkiWIiIiAWq1GYGAgPv/880r3+CAiosZPIWpzlR4RERFRLfGaDyIiIpIVmw8iIiKSFZsPIiIikhWbDyIiAvDomVGvvvoqvLy8YG9vj7/85S84deqUsdOiJojNBxERAXh0PyAfHx/Ex8cjJycH4eHhGDFiBAoKCoydGjUxbD6IiJqodevWoWPHjlCr1TqNt7GxwbJly+Dl5QUTExOMHz8eFhYWVd6BmOouKioKXl5eKC4uNnYqsmPzQXpTKBRYsWKFsdOQFBoaCltbW2OnQWQw7777LhQKhc4PdczLy8PatWuxePHiSs+BAh49zbljx46YP39+tdtISUnBn3/+ibZt29Y674qKi4uxePFieHh4wMrKCoGBgTh8+LDB19dnP8bcZmhoKEpKSjR3VW5WBNWL33//XYSFhYl27doJKysrYWVlJTp16iRmzZolzp8/b+z06gSAWL58ebXxAQMGCAA1vqS2oYvCwkKxfPlycezYsUqxyZMnCxsbmzptvzr79u0TTz75pFAqlcLT01MsW7ZMlJaW1su+iIQQIj09XVhbWwsbGxvRpUsXndZ5//33hb29vbh//36lWEpKilAqlWLZsmWidevWVa5fVFQkevfuLVasWFGn3CsaP368MDMzEwsXLhRbt24VQUFBwszMTPz3v/816Pr67MeY2xRCiEWLFglvb2+hVqt1OgZNBZuPevDdd98Ja2trYW9vL2bOnCmioqLEtm3bxIIFC4SPj49QKBQiLS3N2GnWWk2Nww8//CB27typec2dO1cAEH//+9+1lte1Cbt9+3a1udRX83HgwAGhUCjEoEGDxLZt28ScOXOEiYmJmDFjhsH3RVRu3Lhx4umnnxYDBgzQufnw9/cXL730UpWxt956SwwfPlxs3rxZBAYGVoqXlJSI5557TkycONFg/ymeOXNGABDr16/XLLt//75o06aNCAoKMtj6+uzHmNssd+7cOQFAHD16tMZj0JSw+TCwq1evChsbG9GpUyeRkZFRKV5aWio++OADcePGDcntFBQU1FeKdabvrMXu3bsFgCpnKCrS92c2RvPRuXNn0a1bN62ZjrfeeksoFApx+fJlg++PKC4uTpiamopffvlF5+bj999/FwBEdHR0lXF/f3+xbds28corr1RqnMvKysS4cePE8OHDDTqj9+abbwpTU1ORm5urtXzNmjUCQI01Udf19dmPMbdZkaOjo5g7d67kz9/U8JoPA1u3bh0KCwuxfft2uLu7V4qbmZlh7ty5Wo+JL78+4dq1axg2bBjs7OwwadIkAEBhYSHeeOMNeHp6QqlUokOHDtiwYYPWE2TT0tKgUCgQHR1daX+PX5+xYsUKKBQKXL16FaGhoWjRogVUKhWmTJmCoqIirXWLi4sxf/58ODs7w87ODn/961/xxx9/1PEIaedx6dIlTJw4EQ4ODujXrx8AYODAgRg4cGCldUJDQ+Hj46P5mZ2dnQEA77zzDhQKRZXXoty8eROjRo2Cra2t5oF6ZWVlWmMyMzNx5coVlJaWSuZ86dIlXLp0CdOmTYOZ2f8eizRr1iwIIfDvf/9bz6NAJK2srAxz5szBa6+9hq5du+q83o8//ggAlR6aCAB37tzBhQsXMGDAABw5cgSDBw/Wik+fPh2ZmZnYvXu31r/zcqWlpbhz545Or4oXuiYmJqJ9+/aVnlLdu3dvAEBSUpLkz6Tr+vrsx5jbrKhHjx44efJkVT92k8UHyxnY/v370bZtW70fiPbw4UOEhISgX79+2LBhA6ytrSGEwF//+lccO3YMU6dORffu3XHo0CG8+eabuHnzJt5///1a5zl27Fj4+voiIiICP//8Mz799FO4uLhg7dq1mjGvvfYaPv/8c0ycOBF9+vRBbGysTg+e08eYMWPQrl07rFmzRquhqomzszO2bNmCmTNnYvTo0Xj++ecBAP7+/poxZWVlCAkJQWBgIDZs2IAjR47gH//4B9q0aYOZM2dqxi1ZsgQ7duxAamqqprmpSmJiIgCgZ8+eWss9PDzQqlUrTZzIUKKionD9+nUcOXJEr/WuXLkCAPD19a0UO3XqFFq0aIGMjAwUFhZq/U5fv34dn376KSwtLbWecP3999+jf//+AICTJ09i0KBBOuVR8XcqMzOzyg9k5csyMjIkt6Xr+vrsx5jbrKh169bYuXNnpeVNGZsPA8rLy0NGRkaVj2HPycnBw4cPNe9tbGxgZWWleV9cXIwxY8YgIiJCs2zfvn2IjY3F6tWr8dZbbwEAwsLCMGbMGHzwwQeYPXs22rRpU6tcn3zySXz22Wea93fv3sVnn32maT7Onz+Pzz//HLNmzUJkZKRm35MmTcIvv/xSq31WpVu3bti1a5fe69nY2ODFF1/EzJkz4e/vj5deeqnSmAcPHmDcuHF4++23AQAzZsxAjx498Nlnn2k1H7rKzMwEgGoLS03Fk0gfd+/exbJly/D2229rZvn0WdfMzKzKb3ydPXsWXbt2RVRUFCZNmqRVh7y9vWv8ENCtWzedv6Hi5uam+fP9+/ehVCorjbG0tNTEpei6vj77MeY2K3JwcMD9+/dRVFQEa2vrSvGmiM2HAeXl5QFAlb/wAwcOxPnz5zXv169fj4ULF2qNefw/xAMHDsDU1BRz587VWv7GG2/g3//+N77//nvMnj27VrnOmDFD633//v0RExODvLw82Nvb48CBAwBQad/h4eG1ahZ0zcPQqvo5H/+EER0dXeUpq8eVF43qCkv53z+RISxduhSOjo6YM2eOQbebnJwMU1NT7Nu3D5cuXdJ7fQcHBwQHB+u9npWVVZX3s3jw4IEmboj19dmPMbdZUXnDp1AoKsWaKjYfBmRnZwcAVd4NcOvWrcjPz0d2dnaVn9LNzMzQqlUrrWXXr1+Hh4eHZrvlOnXqpInXlpeXl9Z7BwcHAMC9e/dgb2+P69evw8TEpNLMSocOHWq9z6pUNS1sKJaWlpU+MTo4OODevXu12l550aiusNRUPIl0lZKSgm3btmHTpk1aM2oPHjxAaWkp0tLSYG9vD0dHxyrXd3JywsOHD5Gfn1+pfty5cwcnTpzAyy+/XKvfv5KSEvz55586jXV2doapqSmAR7ODN2/erDSmfEbRw8NDclu6rq/Pfoy5zYru3bsHa2vrZlVDeMGpAalUKri7u+PixYuVYoGBgQgODkbfvn2rXFepVFZ5IyBdVNctP35hZUXlBeFx+lx3YQhV/bLV5uepSnU/Y22Vn24pLyIVZWZm1lg8iXR18+ZNqNVqzJ07F76+vprXmTNn8Ntvv8HX1xcrV66sdv2OHTsCeHTNxeNMTEygVCqxevXqWuX2448/wt3dXadXenq6Zr3u3bvjt99+qzRDeObMGU1ciq7r67MfY26zotTUVM2HyuaCzYeBPffcc7h69Sp++umnOm/L29sbGRkZyM/P11pefjGZt7c3gP/NWuTk5GiNq8vMiLe3N9RqNa5du6a1XI7bLDs4OFT6WYDKP4/cU5TlRePcuXNayzMyMvDHH3/UWDyJdOXn54eYmJhKry5dusDLywsxMTGYOnVqtesHBQUBqPxvVQiBe/fu4ZVXXsETTzxRq9zKr/nQ5VXxmo8XX3wRZWVl2LZtm2ZZcXExtm/fjsDAQM03AIuKinDlyhXcuXNHa7+6rq/rOGNvs6Kff/4Zffr0qenQNy3G+5Zv0/Tbb78Ja2tr0aVLF5GVlVUpXv79+4o3oKnunhR79+4VAMSaNWu0lo8bN04oFApx9epVzbKWLVuK0aNHa4174403Kt0HY/ny5QKAuH37ttbY7du3CwAiNTVVCCFEYmKiACBmzZqlNW7ixIkGuc9HdXkIIcTChQuFUqkUt27d0ixLSkoSJiYmwtvbW7OsqKhIABDz5s2rtI3qjmn5fivKyMgQly9fFiUlJTX+LB07dhTdunUTDx8+1CxbunSpUCgU4tKlSzWuT1QX+txkzM/PT0yYMEFr2datW4WpqakICAgQZWVlYvHixWL37t31kWqVxowZI8zMzMSbb74ptm7dKvr06SPMzMxEXFycZsyxY8eqrTG6rK/POGNvU4j/3WTsyJEjuh7GJoHNRz3Yu3evsLKyEiqVSsyaNUts3bpVREVFicWLFwtPT09hYmIivvzyS8346v6jLCsrE4MGDRIKhUJMmzZNREZGipEjRwoAIjw8XGvs3/72NwFATJ06VWzZskVMmDBBBAQE1Lr5EEKICRMmCABi0qRJIjIyUjz//PPC39+/3puPS5cuCRMTE/Hkk0+Kjz76SCxbtky4uLiIrl27ajUfQjy66Zebm5uIjIwUX375pbhw4YIQQr/mY/LkyZV+9up89913QqFQiKefflps27ZNzJ07V5iYmIjXX3+95gNBVEf6NB8bN24Utra2oqioSAjx6C6bI0eOFN9//70IDg4WPj4+YtasWVqNdH27f/++WLhwoXBzcxNKpVL06tVLHDx4UGuMVPOhy/r6jDP2NoUQYvHixcLLy4u3VyfDuHr1qpg5c6Zo27atsLS0FFZWVqJjx45ixowZIikpSWus1N048/Pzxfz584WHh4cwNzcX7dq1E+vXr6/0D7WoqEhMnTpVqFQqYWdnJ8aOHStu3bpVp+bj/v37Yu7cucLJyUnY2NiIESNGiPT09HpvPoQQ4vPPPxetW7cWFhYWonv37uLQoUNi8uTJlZqPH3/8UQQEBAgLCwutvOqr+RBCiJiYGNG9e3ehVCpFq1atxNKlS3WaNSGSU05OjnB0dBSffvqpsVOhajx48EC4ubmJTZs2GTsV2SmEkPkKQyIiksXatWuxfft2XLp0qdYXtFP9iYqKwpo1a5CSklLlV/ibMjYfREREJCu2wkRERCQrNh9EREQkKzYfREREJCs2H0RERCSrBvdsF7VajYyMDNjZ2TWrh+wQNSRCCOTn58PDw6PRfEuCtYPIuPSpGw2u+cjIyKjy9rNEJL/09PRKDzxsqFg7iBoGXepGvTUfkZGRWL9+PbKystCtWzds3rwZvXv3rnG98icw9sMwmMG8vtIjIgkPUYp4HKj0RNT6Vtu6AbB2EBmbPnWjXpqPr7/+GgsWLEBUVBQCAwOxadMmhISEIDk5GS4uLpLrlk+XmsEcZgoWECKj+P+7/8h5+qIudQNg7SAyOj3qRr2czN24cSNef/11TJkyBZ07d0ZUVBSsra3xz3/+s9LY4uJi5OXlab2IqPnRp24ArB1EjZnBm4+SkhIkJCQgODj4fzsxMUFwcDBOnTpVaXxERARUKpXmxXO2RM2PvnUDYO0gaswM3nzcuXMHZWVlcHV11Vru6uqKrKysSuOXLFmC3NxczSs9Pd3QKRFRA6dv3QBYO4gaM6N/20WpVDa7B+oQUd2xdhA1Xgaf+WjZsiVMTU2RnZ2ttTw7Oxtubm6G3h0RNQGsG0TNi8GbDwsLCwQEBODo0aOaZWq1GkePHkVQUJChd0dETQDrBlHzUi+nXRYsWIDJkyejZ8+e6N27NzZt2oTCwkJMmTKlPnZHRE0A6wZR81Evzce4ceNw+/ZtLFu2DFlZWejevTsOHjxY6WIyIqJyrBtEzYdCCCGMnURFeXl5UKlUGIiRvFEQkZE8FKU4jn3Izc2Fvb29sdPRCWsHkXHpUzcaxxOjiIiIqMlg80FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESyMjN2AtQ8PXw6QDKeOatYMn4+aIdkvNupyZJxj0gLybjpsZ8l40RkHKwdTQNnPoiIiEhWbD6IiIhIVmw+iIiISFZsPoiIiEhWbD6IiIhIVmw+iIiISFZsPoiIiEhWvM8H1Qv1gCcl4x/+8yPJeFtz6X+a6hr2nxi0XTKe3LNMMv6mz19q2AMR1QfWjubB4DMfK1asgEKh0Hp17NjR0LshoiaEdYOoeamXmY8uXbrgyJEj/9uJGSdYiEga6wZR81Evv91mZmZwc3Orj00TURPFukHUfNTLBacpKSnw8PBA69atMWnSJNy4caPascXFxcjLy9N6EVHzo0/dAFg7iBozgzcfgYGBiI6OxsGDB7Flyxakpqaif//+yM/Pr3J8REQEVCqV5uXp6WnolIiogdO3bgCsHUSNmcGbj6FDh2LMmDHw9/dHSEgIDhw4gJycHHzzzTdVjl+yZAlyc3M1r/T0dEOnREQNnL51A2DtIGrM6v2KrhYtWqB9+/a4evVqlXGlUgmlUlnfaRBRI1JT3QBYO4gas3pvPgoKCnDt2jW8/PLL9b0rklHpkJ6S8UUf75SMtze3kIyra/g2/u+lpZLxXLX0f0pP1vB/VvHQXpJxq2MXpDcAQP3gQY1jqGqsG00XawdrB1APp10WLlyIuLg4pKWl4ccff8To0aNhamqKCRMmGHpXRNREsG4QNS8Gn/n4448/MGHCBNy9exfOzs7o168fTp8+DWdnZ0PvioiaCNYNoubF4M3HV199ZehNElETx7pB1LzwwXJEREQkKzYfREREJCs2H0RERCQrNh9EREQkKz42spkytbeXjBc+Jf048/nv75KMD7IqqCGDuvW90ff6SMaPfhwkGT+54kPJ+OFPoyTjnT+fLRkHgNaLT9U4hqixYe1g7TAEznwQERGRrNh8EBERkazYfBAREZGs2HwQERGRrNh8EBERkazYfBAREZGs2HwQERGRrNh8EBERkax4k7Fm6o9/PSEZP9srUqZMamely1nJ+EFb6RsJTUkbIhnf4XNEMm7f+a5knKipYu1g7TAEznwQERGRrNh8EBERkazYfBAREZGs2HwQERGRrNh8EBERkazYfBAREZGs2HwQERGRrHifjybq4dMBkvEvu38kGTeBRZ32P+X6YMn4uSOdJOMXpkrnd+y+pWTc5dx9yfjVex0l4+ZrjknGTRSSYaJGi7WDtUMOnPkgIiIiWbH5ICIiIlmx+SAiIiJZsfkgIiIiWbH5ICIiIlmx+SAiIiJZsfkgIiIiWel9n48TJ05g/fr1SEhIQGZmJmJiYjBq1ChNXAiB5cuX45NPPkFOTg769u2LLVu2oF27dobMu9lTD3hSMv7hP6W/697WXPqvXg21ZPyvV0ZLxk1fLJSMt3hOSMY775wtGW8fmS4ZN0lPlIw7/FcyjNJ3yyTj//H/p/QGALw6aK5k3PTYzzVuo6lg3Wg4WDtYOxoCvWc+CgsL0a1bN0RGRlYZX7duHT788ENERUXhzJkzsLGxQUhICB48eFDnZImocWLdIKKK9J75GDp0KIYOHVplTAiBTZs2YenSpRg5ciQA4F//+hdcXV2xd+9ejB8/vm7ZElGjxLpBRBUZ9JqP1NRUZGVlITg4WLNMpVIhMDAQp06dqnKd4uJi5OXlab2IqPmoTd0AWDuIGjODNh9ZWVkAAFdXV63lrq6umtjjIiIioFKpNC9PT09DpkREDVxt6gbA2kHUmBn92y5LlixBbm6u5pWeLn0xEBERwNpB1JgZtPlwc3MDAGRnZ2stz87O1sQep1QqYW9vr/UiouajNnUDYO0gaswM2nz4+vrCzc0NR48e1SzLy8vDmTNnEBQUZMhdEVETwbpB1Pzo/W2XgoICXL16VfM+NTUVSUlJcHR0hJeXF8LDw7F69Wq0a9cOvr6+ePvtt+Hh4aH1nX6qmSKgi2T8zoL7kvH25haS8YRi6f3HFnSWjN/9Svr8utO96i8UBADV56el45JR4GEN8frmaqqscczd8CLJuMsxQ2XT8LFuyIe1QxprR8Ogd/Nx7tw5DBo0SPN+wYIFAIDJkycjOjoaixYtQmFhIaZNm4acnBz069cPBw8ehKWlpeGyJqJGhXWDiCrSu/kYOHAghKj+DnMKhQIrV67EypUr65QYETUdrBtEVJHRv+1CREREzQubDyIiIpIVmw8iIiKSFZsPIiIikhWbDyIiIpKV3t92obozsbaucczDddIPyTrdcY9kPPVhiWR8wd/fkIw7/PeGZNzF5pZkvEwy2jz0dr8uGU+TJw1qQlg7mofmUDs480FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESy4n0+jOD+gC41jjnU8eM67eO1efMl43Z7T0vGH9Zp70RUH1g7qKngzAcRERHJis0HERERyYrNBxEREcmKzQcRERHJis0HERERyYrNBxEREcmKzQcRERHJivf5MAL/VUk1jjGpoS+ccn2wZNxq70/6pESPMVeYSsZLRc3bMFXoMIhID6wdDR9rh24480FERESyYvNBREREsmLzQURERLJi80FERESyYvNBREREsmLzQURERLJi80FERESy0vs+HydOnMD69euRkJCAzMxMxMTEYNSoUZp4aGgoduzYobVOSEgIDh48WOdkG4ucl4Mk40tdN9S4DTUsJOMJP3SWjHvhxxr3QdUrFWWScTXUNW7j4GXpv6N2+FmvnBoz1g3dsHY0fqwdutF75qOwsBDdunVDZGRktWOeffZZZGZmal5ffvllnZIkosaNdYOIKtJ75mPo0KEYOnSo5BilUgk3N7daJ0VETQvrBhFVVC/XfBw/fhwuLi7o0KEDZs6cibt371Y7tri4GHl5eVovImp+9KkbAGsHUWNm8Obj2Wefxb/+9S8cPXoUa9euRVxcHIYOHYqysqrPg0VEREClUmlenp6ehk6JiBo4fesGwNpB1JgZ/MFy48eP1/y5a9eu8Pf3R5s2bXD8+HEMHlz5gUZLlizBggULNO/z8vJYRIiaGX3rBsDaQdSY1ftXbVu3bo2WLVvi6tWrVcaVSiXs7e21XkTUvNVUNwDWDqLGrN6bjz/++AN3796Fu7t7fe+KiJoI1g2ipk3v0y4FBQVan0ZSU1ORlJQER0dHODo64p133sELL7wANzc3XLt2DYsWLULbtm0REhJi0MQbsodW0nGVifT38AHg1AOlZLz1vzKkc6hxD02bibW1ZPzKBr8atpAgGZ30u/Q3NwCg47xUybj03QCaFtYN3bB2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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAFLCAYAAAB2qu6tAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABEJklEQVR4nO3deXxMZ/s/8M9Mlsk+2bfKQuwUteWhFC0iSm21txVdKEFR5dGNWppaHrSaii6PeJQu+ivKo9SuFEUtpfYmBEms2ffM/fvDN/MYSe6ZySQn2+f9es3r1ZzrPudcczRXrjnnzH1UQggBIiIiIoWoKzsBIiIiql3YfBAREZGi2HwQERGRoth8EBERkaLYfBAREZGi2HwQERGRoth8EBERkaLYfBAREZGi2HwQERGRoth8kIHg4GBERETof967dy9UKhX27t1bbvtQqVSYPXt2uW2PiIiqFzYfVUhsbCxUKpX+ZWdnh4YNG2LChAlITk6u7PTMsnXr1mrZYOTn56Np06ZQqVRYvHhxZadDRFQjWVd2AlTcnDlzULduXeTk5ODAgQNYsWIFtm7dijNnzsDBwUHRXJ566ilkZ2fD1tbWrPW2bt2K6OjoEhuQ7OxsWFtXzf/1li9fjmvXrlV2GkRENRrPfFRB4eHheOGFF/Dqq68iNjYWkydPRlxcHDZt2lTqOpmZmRWSi1qthp2dHdTq8vtfxc7Orko2H7du3cKcOXMwY8aMyk6FiKhGY/NRDTz99NMAgLi4OABAREQEnJyccOXKFfTu3RvOzs4YOXIkAECn02HZsmVo1qwZ7Ozs4OPjg7Fjx+L+/fsG2xRCYN68eahTpw4cHBzQrVs3nD17tti+S7vn48iRI+jduzfc3Nzg6OiIFi1a4OOPP9bnFx0dDQAGl5GKlHTPx4kTJxAeHg4XFxc4OTnhmWeeweHDhw3GFF2WOnjwIKZOnQovLy84OjpiwIABuH37tsHY1NRUnD9/HqmpqaYcYgDAP//5TzRq1AgvvPCCyesQEZH5qt7HTyrmypUrAAAPDw/9soKCAoSFhaFTp05YvHix/nLM2LFjERsbi9GjR2PSpEmIi4vDp59+ihMnTuDgwYOwsbEBALz//vuYN28eevfujd69e+OPP/5Az549kZeXZzSfHTt2oE+fPvDz88Mbb7wBX19fnDt3Dlu2bMEbb7yBsWPH4ubNm9ixYwfWrFljdHtnz55F586d4eLigunTp8PGxgYrV65E165dsW/fPoSGhhqMnzhxItzc3DBr1izEx8dj2bJlmDBhAr777jv9mA0bNmD06NFYtWqVwQ20pfn999+xevVqHDhwwKBRIiKi8sfmowpKTU3FnTt3kJOTg4MHD2LOnDmwt7dHnz599GNyc3MxePBgREVF6ZcdOHAAX375JdauXYsRI0bol3fr1g29evXC+vXrMWLECNy+fRsLFy7Es88+i82bN+v/2L7zzjv48MMPpbkVFhZi7Nix8PPzw8mTJ+Hq6qqPCSEAAB06dEDDhg2xY8cOk84ivPvuu8jPz8eBAwdQr149AMBLL72ERo0aYfr06di3b5/BeA8PD/zyyy/6vHU6HT755BOkpqZCq9Ua3d+jhBCYOHEihg4dig4dOiA+Pt7sbRARkel42aUK6t69O7y8vBAQEIBhw4bByckJGzZswGOPPWYwbty4cQY/r1+/HlqtFj169MCdO3f0rzZt2sDJyQl79uwBAOzcuRN5eXmYOHGiwaf8yZMnG83txIkTiIuLw+TJkw0aDwBlOmNQWFiIX375Bf3799c3HgDg5+eHESNG4MCBA0hLSzNYZ8yYMQb76ty5MwoLC3H16lX9soiICAghTDrrERsbiz///BMLFiwwO38iIjIfz3xUQdHR0WjYsCGsra3h4+ODRo0aFbvh09raGnXq1DFYdunSJaSmpsLb27vE7d66dQsA9H+kGzRoYBD38vKCm5ubNLeiS0DNmzc3/Q1J3L59G1lZWWjUqFGxWJMmTaDT6ZCQkIBmzZrplwcGBhqMK8r50ftaTJGWloaZM2firbfeQkBAgNnrExGR+dh8VEHt27dH27ZtpWM0Gk2xhkSn08Hb2xtr164tcR0vL69yy7EyWVlZlbi86LKPORYvXoy8vDwMHTpUf7nl+vXrAB40M/Hx8fD39zf7q8ZERFQ6Nh81SEhICHbu3Iknn3wS9vb2pY4LCgoC8OBMycOXOm7fvm307EFISAgA4MyZM+jevXup40y9BOPl5QUHBwdcuHChWOz8+fNQq9UVekbi2rVruH//vsGZlSIffvghPvzwQ5w4cQKtWrWqsByIiGob3vNRgwwZMgSFhYWYO3dusVhBQQFSUlIAPLinxMbGBsuXLzc4W7Bs2TKj+2jdujXq1q2LZcuW6bdX5OFtOTo6AkCxMY+ysrJCz549sWnTJoMbPZOTk7Fu3Tp06tQJLi4uRvN6lKlftZ00aRI2bNhg8Fq5ciWAB/eNbNiwAXXr1jV7/0REVDqe+ahBunTpgrFjxyIqKgonT55Ez549YWNjg0uXLmH9+vX4+OOP8fzzz8PLywvTpk1DVFQU+vTpg969e+PEiRP4+eef4enpKd2HWq3GihUr0LdvX7Rq1QqjR4+Gn58fzp8/j7Nnz2L79u0AgDZt2gB48Mc9LCwMVlZWGDZsWInbnDdvHnbs2IFOnTph/PjxsLa2xsqVK5Gbm4uFCxeW6ViY+lXb1q1bo3Xr1gbLipqgZs2aoX///mXaPxERlY7NRw0TExODNm3aYOXKlXj77bdhbW2N4OBgvPDCC3jyySf14+bNmwc7OzvExMRgz549CA0NxS+//IJnn33W6D7CwsKwZ88efPDBB/jXv/4FnU6HkJAQvPbaa/oxAwcOxMSJE/Htt9/i66+/hhCi1OajWbNm+PXXXzFz5kxERUVBp9MhNDQUX3/9dbE5PoiIqPpTibLcpUdERERURrzng4iIiBTF5oOIiIgUxeaDiIiIFMXmg4iIADx4ZtTLL7+MwMBAuLi44B//+AcOHTpU2WlRDcTmg4iIADyYDyg4OBgHDhxASkoKJk+ejL59+yIjI6OyU6Mahs0HEVENtXDhQjRu3Bg6nc6k8Y6Ojnj//fcRGBgItVqNYcOGwdbWtsQZiMkyMTExCAwMRG5ubmWnUinYfJDZVCoVZs+eXdlpSEVERMDJyamy0yAqs4iICKhUqlJfN27ckK6flpaGBQsWYMaMGcWeAwU8eJpz48aNMWXKlFK3cenSJdy7dw/169e3+P1kZGRg1qxZ6NWrF9zd3aFSqRAbG2vWNnJzczFjxgz4+/vD3t4eoaGh2LFjR4l5Dxs2DHXq1IGDgwMaN26MOXPmICsrq9jY48ePo1evXnBxcYGzszN69uyJkydPGow5evQoJkyYgGbNmsHR0RGBgYEYMmQILl68WOY8IyIikJeXp59RudYRVCH+/vtvERkZKRo0aCDs7e2Fvb29aNKkiRg/frw4depUZadnEQBi1qxZpca7dOkiABh9ybZhiszMTDFr1iyxZ8+eYrFRo0YJR0dHi7ZvzOXLl4VGoxEAxNGjRyt0X1T7/Pbbb2LNmjUGr//85z/CwcFBNG3a1Oj6S5cuFS4uLiI7O7tY7NKlS0Kj0Yj3339f1KtXr8T1s7KyRPv27cXs2bMtfi9CCBEXFycAiMDAQNG1a1cBQKxatcqsbQwbNkxYW1uLadOmiZUrV4oOHToIa2tr8euvv+rHXLt2Tbi6uoqgoCARFRUlVq5cKSIiIgQA8dxzzxls7/jx48LOzk40aNBALF68WCxcuFAEBwcLFxcXcf78ef24QYMGCV9fXzFx4kTxxRdfiLlz5wofHx/h6Ogo/vzzzzLlKYQQ06dPF0FBQUKn05l1HGoCNh8VYPPmzcLBwUG4uLiIcePGiZiYGPH555+LqVOniuDgYKFSqUR8fHxlp1lmxhqHX375xaBgTpo0SQAQb7/9tsFyS5uw27dvl5qLEs1H3759haOjI5sPUsyvv/4qAIj58+cbHduiRQvxwgsvlBh75513RJ8+fcTy5ctFaGhosXheXp549tlnxYgRI8rtD2NOTo5ITEwUQghx9OhRs5uPI0eOCABi0aJF+mXZ2dkiJCREdOjQQb9s/vz5AoA4c+aMwfovvfSSACDu3bunX9a7d2/h5uYm7ty5o1928+ZN4eTkJAYOHKhfdvDgQZGbm2uwvYsXLwqNRiNGjhxZpjyFEOLYsWMCgNi1a5fJx6Gm4PTq5ezKlSsYNmwYgoKCsGvXLvj5+RnEFyxYgM8++6zE06APy8zM1D+crbrp0aOHwc92dnb45JNP0KNHD3Tt2rXU9arTe96+fTu2b9+O6dOnY968eZWdDtUS69atg0qlwogRI6Tj4uLicPr0aUydOrXE+ObNmzFhwgQcOHAATzzxhEFMp9PhxRdfhEqlwurVq01+QrUxGo0Gvr6+ZV7/hx9+gJWVFcaMGaNfZmdnh1deeQVvv/02EhISEBAQgLS0NACAj4+Pwfp+fn5Qq9WwtbXVL/v111/Rq1cveHh4GIzr0qULtmzZgoyMDDg5OaFjx47F8mnQoAGaNWuGc+fOlSlP4MEzsNzd3bFp0yY8/fTTZT421RHv+ShnCxcuRGZmJlatWlWs8QAAa2trTJo0yeAx8UX3J1y5cgW9e/eGs7MzRo4cCeDBH+Q333wTAQEB0Gg0aNSoERYvXmzwBNn4+PhSr58+en/G7NmzoVKpcPnyZURERMDV1RVarRajR48udj00NzcXU6ZMgZeXF5ydnfHcc8/h+vXrFh4hwzz++usvjBgxAm5ubujUqRMAoGvXriU2KREREQgODta/Zy8vLwDABx98oL8O/ui9KDdu3ED//v3h5OSkf6BeYWGhwZjExEScP38e+fn5JuWen5+PN954A2+88QZCQkLMe+NEZZSfn4/vv/8eHTt21P8elOa3334DgGIPTQSAO3fu4M8//0SXLl2wc+dOPPPMMwbxsWPHIjExEevXr4e1dfHPp/n5+bhz545JL1NvdDXFiRMn0LBhw2JPuW7fvj0A6O/TKKodr7zyCk6ePImEhAR89913WLFiBSZNmmTwASc3Nxf29vbF9uXg4IC8vDycOXOm1HyEEEhOTi72ME5T8yzSunVrHDx4sPQ3XkOx+ShnW7ZsQf369c1+IFpBQQHCwsLg7e2NxYsXY9CgQRBC4LnnnsPSpUvRq1cvLFmyBI0aNcJbb71V6icaUw0ZMgTp6emIiorCkCFDEBsbiw8++MBgzKuvvoply5ahZ8+e+Oijj2BjY2PSg+fMMXjwYGRlZeHDDz80eDCdMV5eXlixYgUAYMCAAVizZg3WrFmDgQMH6scUFhYiLCwMHh4eWLx4Mbp06YJ//etf+Pzzzw22NXPmTDRp0sToDXxFli1bhvv37+Pdd981OV8iS23fvh13797VfzCROX/+PACgbt26xWKHDh2Cq6srbt68iczMTIPf6atXr+LLL7/E77//Dk9PTzg5OcHJyQm//vqrfszBgwfh5eVl0uvatWvl8M4fSExMLPEDXdGymzdvAgB69eqFuXPnYseOHXjiiScQGBiIYcOGYeLEiVi6dKnBuo0aNcLhw4cNPpDk5eXhyJEjACCtCWvXrsWNGzcwdOjQMuVZpF69evjrr79K3U9Nxcsu5SgtLQ03b94s8THsKSkpKCgo0P/s6Oho0HHn5uZi8ODBiIqK0i/btGkTdu/ejXnz5uGdd94BAERGRmLw4MH4+OOPMWHChDJ/8n7iiSfw1Vdf6X++e/cuvvrqKyxYsAAAcOrUKXz99dcYP348oqOj9fseOXIkTp8+XaZ9lqRly5ZYt26d2es5Ojri+eefx7hx49CiRQu88MILxcbk5ORg6NCheO+99wAAr7/+Olq3bo2vvvoK48aNK1O+SUlJmDt3LhYvXlzskw1RRVq3bh1sbGwwZMgQo2Pv3r0La2vrEr/xdfToUTz++OOIiYnByJEjDepQUFCQwVnVkrRs2bLEb5iUxJLLLI/Kzs6GRqMpttzOzk4fLxIcHIynnnoKgwYNgoeHB/773//iww8/hK+vLyZMmKAfN378eIwbNw6vvPIKpk+fDp1Oh3nz5iExMbHYNh92/vx5REZGokOHDhg1alSZ8wQANzc3ZGdnIysrCw4ODqYcihqBzUc5KrrWWNIvfNeuXXHq1Cn9z4sWLcK0adMMxjz6B3Hr1q2wsrLCpEmTDJa/+eab+OGHH/Dzzz8b/CKZ4/XXXzf4uXPnztiwYQPS0tLg4uKCrVu3AkCxfU+ePLlMzYKpeZS3kt7nmjVrDJbFxsaa/JW/GTNmoF69enj11VfLK0UiozIyMrBp0yb9mTxLXLhwAVZWVti0aVOZPnG7ubmhe/fuFuVQFvb29iXOiZGTk6OPA8C3336LMWPG4OLFi6hTpw4AYODAgdDpdJgxYwaGDx+uP4avv/46EhISsGjRIqxevRoA0LZtW0yfPh3z588vsZYnJSXh2WefhVar1d/fUZY8ixQ1e+V1b011wcsu5cjZ2RkASpwNcOXKldixYwe+/vrrEte1trbW/6IUuXr1Kvz9/fXbLdKkSRN9vKwCAwMNfnZzcwMA3L9/X79ttVpd7MxKo0aNyrzPkpR0Wri82NnZ6e8LKeLm5qZ/j+Y6fPgw1qxZg6VLlxq9YZioPG3cuBFZWVkmXXIBAA8PDxQUFCA9Pb1Y7M6dO9i/fz+GDRtWpt+/vLw8JCUlmfR69P4qS/j5+enPSDysaJm/vz8A4LPPPsMTTzxRrJ4+99xzyMrKwokTJwyWz58/H8nJyfj1119x+vRpHD16VH+vSsOGDQ3GpqamIjw8HCkpKdi2bZt+n2XJs8j9+/fh4OBQ4r0nNRnPfJQjrVYLPz+/Em9SKroHJD4+vsR1NRpNmf+gldYxy37xH+3Wixg75VreSvqFU6lUJeZhbiEr7T2W1fTp09G5c2fUrVtX/+94584dAA8Ky7Vr14o1dUTlYe3atXBycsJzzz1n0vjGjRsDePCtlxYtWhjE1Go1NBpNmb+l9dtvv6Fbt24mjY2LizN6c6ypWrVqhT179ujPzhYpuj+jVatWAIDk5GT9h6mHFd1Q/vDl7yIP3/AOADt37kSdOnX0xxF4cOaib9++uHjxInbu3ImmTZtalGeRuLg4/QfK2oTNRzl79tln9TdsFd3dXFZBQUHYuXMn0tPTDc5+FN1MFhQUBOB/Zy1SUlIM1rfkzEhQUBB0Oh2uXLlicLZDiWmW3dzc8Pfffxdb/uj7Ufo05bVr13D16tUSPy0+99xz0Gq1xf4NiCx1+/Zt7Ny5E8OHDzf5noAOHToAAI4dO2bQfAghcP/+fbz00kt47LHHypRPRd/zkZWVhWvXrsHT09PgmyTPP/88Fi9ejM8//1x/yTo3NxerVq1CaGio/huEDRs2xC+//IKLFy8anLn45ptvoFarizVjj/ruu+9w9OhRLF68WP+BsLCwEEOHDsWhQ4ewadMm/fEtial5Fvnjjz9MPqNVk7D5KGfTp0/HunXr8PLLL2PXrl3FvmtuzpmF3r174/PPP8enn36KmTNn6pcvXboUKpUK4eHhAAAXFxd4enpi//79mDx5sn7cZ599Vub3ER4ejrfffhuffPKJ/oZT4ME3PSpaSEgItm7ditu3b+svm5w6dQoHDx40+MUtKsSW/sFPTExEamoqQkJCYGNjU+q4zz//vNjXkXfv3o3ly5dj8eLFBp+SiMrLd999h4KCArP+QNWrVw/NmzfHzp078fLLL+uXf/HFF/qveup0Orz99tto27Ytnn/+eZO3bck9H59++ilSUlL03/jYvHmz/uv7EydOhFarxe+//45u3bph1qxZBl+dDw0NxeDBgzFz5kzcunUL9evXx+rVqxEfH29w8/xbb72Fn3/+GZ07d8aECRPg4eGBLVu24Oeff8arr75qcNlj//79mDNnDnr27AkPDw8cPnwYq1atQq9evfDGG2/ox7355pv46aef0LdvX9y7d6/Y5fOHb3g3NU/gwdTu9+7dQ79+/cp0PKu1SpverAbbuHGjsLe3F1qtVowfP16sXLlSxMTEiBkzZoiAgAChVqvFN998ox9f2mychYWFolu3bkKlUokxY8aI6Oho0a9fPwFATJ482WDsP//5TwFAvPLKK2LFihVi+PDhok2bNsVmAJ01a5YAIG7fvm2w/qpVqwQAERcXp182fPhwAUCMHDlSREdHi4EDB4oWLVqYPTX6+vXrBQCDadBLy0MIIf766y+hVqvFE088IT799FPx/vvvC29vb/H444+LoKAgg7FNmzYVvr6+Ijo6WnzzzTf6qY5LO6ZF+33YqFGjir13UxUdN85wShXlH//4h/D29hYFBQVmrbdkyRLh5OQksrKyhBAPZtns16+f+Pnnn0X37t1FcHCwGD9+vNnbtURQUFCpj1so+v3bs2dPqTUmOztbTJs2Tfj6+gqNRiPatWsntm3bVmzckSNHRHh4uPD19RU2NjaiYcOGYv78+SI/P99g3OXLl0XPnj2Fp6en0Gg0onHjxiIqKqrYbKbGHhlR1jxnzJghAgMDOb06lZ/Lly+LcePGifr16ws7Ozthb28vGjduLF5//XVx8uRJg7GyqcDT09PFlClThL+/v7CxsRENGjQQixYtKvY/a1ZWlnjllVeEVqsVzs7OYsiQIeLWrVsWNR/Z2dli0qRJwsPDQzg6Ooq+ffuKhISECm8+hBDi66+/FvXq1RO2traiVatWYvv27WLUqFHFmo/ffvtNtGnTRtja2hrkxeaDaruUlBTh7u4uvvzyy8pOhUqQk5MjfH19xbJlyyo7lUqhEkLhOwyJiEgRCxYswKpVq/DXX3/xG1pVTExMDD788ENcunSpxHlBajo2H0RERKQotsJERESkKDYfREREpCg2H0RERKQoNh9ERESkqCo3yZhOp8PNmzfh7Oxc6x60Q1RVCCGQnp4Of3//avMtCdYOosplTt2ocs3HzZs3i00/S0SVIyEhodgDuqoq1g6iqsGUulFhzUd0dDQWLVqEpKQktGzZEsuXLzfpWSdFzzDphN6wRulTXRNRxSlAPg5ga7EnKle0stYNgLXDZMbOClX12Reqe/41mDl1o0Kaj++++w5Tp05FTEwMQkNDsWzZMoSFheHChQvw9vaWrlt0utQaNrBWsYAQVYr/q99KXr6wpG4ArB0mM/pvWsX/eFf3/GsyM+pGhVzMXbJkCV577TWMHj0aTZs2RUxMDBwcHPDvf/+72Njc3FykpaUZvIio9jGnbgCsHUTVWbk3H3l5eTh+/LjBUw/VajW6d++OQ4cOFRsfFRUFrVarf/GaLVHtY27dAFg7iKqzcm8+7ty5g8LCwmKPkvfx8UFSUlKx8TNnzkRqaqr+lZCQUN4pEVEVZ27dAFg7iKqzSv+2i0ajqZUP1SEiy7B2EFVf5X7mw9PTE1ZWVkhOTjZYnpycDF9f3/LeHRHVAKwbRLVLuTcftra2aNOmDXbt2qVfptPpsGvXLnTo0KG8d0dENQDrhoJUavnL2OoajfRlfAMqC1+W5W90+6SICrnsMnXqVIwaNQpt27ZF+/btsWzZMmRmZmL06NEVsTsiqgFYN4hqjwppPoYOHYrbt2/j/fffR1JSElq1aoVt27YVu5mMiKgI6wZR7aESompNB5eWlgatVouu6MeJgogqSYHIx15sQmpqKlxcXCo7HZOwdphIbSWP6wqlYWOXVkRurnz7ll7aMHZpxUj+nCG14phTN6rHE6OIiIioxmDzQURERIpi80FERESKYvNBREREiqr0GU6JiGoKlY2tNC7y8yo8Bys3N/kAoZOGddk58tWN3FCqspb/WREFBdK42tHRov0beXvGbyi18IZcMg3PfBAREZGi2HwQERGRoth8EBERkaLYfBAREZGi2HwQERGRoth8EBERkaLYfBAREZGiOM8HEZGJjM3jobKSf54T+eWZTckK79+XxtV2dtK40Xk68uRzlRibx8MYkWfkIBl7sJylLJzHw9jxNeX4qOztpXFderpZOVVFPPNBREREimLzQURERIpi80FERESKYvNBREREimLzQURERIpi80FERESKYvNBREREiuI8H0RUa1i5uFi2AbVKGhb5ls1xYXT3zs5Gx4jsbIv2ocvMtGh9lUYjj1tZSeNqX29pvODveHkCavn2jc7joZL/G0MIedzI+xM5OfL1AYgaMI+HMTzzQURERIpi80FERESKYvNBREREimLzQURERIpi80FERESKYvNBREREimLzQURERIriPB9EJch8PlQaX7BwhdFtzB3ykjQujp0xKyeyXGFamnyAkTkerLTyeUJUNkZKqoVzSOgyMuTrA7AOCpBv4+59+QZMmIdCxsrdTRovSEySxlV37lm0f6PzeBhjbB4PI7KfaiqNfxQdY3Qbczv2lsYLkpLNyqkqKvczH7Nnz4ZKpTJ4NW7cuLx3Q0Q1COsGUe1SIWc+mjVrhp07d/5vJ9Y8wUJEcqwbRLVHhfx2W1tbw9fXtyI2TUQ1FOsGUe1RITecXrp0Cf7+/qhXrx5GjhyJa9eulTo2NzcXaWlpBi8iqn3MqRsAawdRdVbuzUdoaChiY2Oxbds2rFixAnFxcejcuTPSS3lQTlRUFLRarf4VECC/WYqIah5z6wbA2kFUnZV78xEeHo7BgwejRYsWCAsLw9atW5GSkoLvv/++xPEzZ85Eamqq/pWQkFDeKRFRFWdu3QBYO4iqswq/o8vV1RUNGzbE5cuXS4xrNBpojDyCmYhqF2N1A2DtIKrOKrz5yMjIwJUrV/Diiy9W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cOGCwThvb2/s3r0b58+fx/Dhw2vNnCqWYvNRCzVo0ADHjh1DvXr1MGTIEISEhGDMmDHo1q0bDh06BHd3d5O3VbduXfzwww/48ccf0aJFC6xYsUL/bReNRlOm/NRqNb799lscP34czZs3x5QpU8o8TwERPdCyZUssWbIECxYsQPPmzbF27VpERUWZtO5XX32F+/fvo3Xr1njxxRcxadIks+bu8ff3x8GDB1FYWIiePXvi8ccfx+TJk+Hq6qo/C2PMkiVL4Obmho4dO6Jv374ICwtD69atDcbMmTMH8fHxCAkJ0V867tixI15//XUMHToUXl5eWLhwIby8vBAbG4v169ejadOm+Oijj7B48WKDbXl4eGD37t3IyMhAly5d0KZNG3zxxRclntnw9fXF7t278eeff2LkyJEmN1S1mUqYeuchkYnmz5+PmJgYJCQkVHYqRERUBfGeD7LYZ599hnbt2sHDwwMHDx7EokWLMGHChMpOi4iIqig2H2SxS5cuYd68ebh37x4CAwPx5ptvYubMmZWdFhERVVG87EJERESK4g2nREREpCg2H0RERKQoNh9ERESkKDYfREREpCg2H0RERKQoNh9ERESkKDYfREREpCg2H0RERKSo/w8TOr2k9DTffAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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rT7fpya1fUei5uDmzZtQKBSYO3cuPD09lcvZs2dx7do1eHp6YuXKlbVu37VrVwCPrrl4koGBAeRyOVavXl2v2H755Rc4OztrtGRmZiq369mzJ65du6acJX7s7NmzynZ1NN1em/3oc8yq0tPTlT8sWwsWHzr00ksvITU1Fb/++muDx3J3d0dWVhYKCwtV1j++kMzd3R3A/2Yt8vLyVPo1ZGbE3d0dCoUCaWlpKuuleMSyjY1Nte8CVP8+rWl6klofb29vREdHV1u6d+8ONzc3REdHY9q0abVu7+fnBwA4f/68ynohBO7fv4833ngDTz31VL1ie3zNhyZL1Ws+XnvtNVRWVuKLL75QrisrK0NERAR8fX2VdwGWlJTg6tWruHPnjsp+Nd1e0376HrOq3377Df369avr0LcserzNt8W5du2aMDMzE927dxc5OTnV2h/fe1/14TO1PZNi3759AoBYs2aNyvpx48YJmUwmUlNTlevatWsnRo8erdLvvffeq/YcjOXLlwsA4vbt2yp9IyIiBACRnp4uhBAiMTFRABCzZs1S6Tdx4kSdPOejtjiEEGLBggVCLpeLW7duKdclJSUJAwMD4e7urlxXUlIiAIh333232hi1HdPH+60qKytLXLlyRZSXl2v8nYTgcz5IP7R5yJi3t7eYMGGCyrrt27cLQ0ND0bt3b1FZWSkWLVokoqKiGiPUGo0ZM0YYGRmJ999/X2zfvl3069dPGBkZibi4OGWf48eP15pnNNlem376HlOI/z1k7NixY5oexhaB13zokJeXF3bv3o0JEyagS5cumDRpEnr06AEhBNLT07F7924YGBhUu76jJiNHjsTgwYOxZMkSZGRkoEePHjhy5Aj279+P0NBQlesx3nrrLaxduxZvvfUW+vTpg5MnT+LatWv1/h49e/bEhAkT8PnnnyM/Px/9+vVDTEwMUlNT6z2mpt58801s2rQJQUFBmDZtGm7duoXw8HB0795dZRrT1NQUTz/9NL7//nt07twZtra28Pb21vi9F49pc6vtyZMncfLkSQDA7du3UVxcrJy6HjRoEAYNGqTdlyVqRG+++SaWLVuGBw8ewNTUFKWlpTh48CAOHDigvO18+PDhGD16tGQxffPNN1i6dCl27tyJ+/fvw8fHBwcOHND4b0fT7bXZjz7HBICoqCi4ubnh+eef1+gYtBj6rn5a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fvrpJ0RFRSEuLg5ZWVl45ZVXdB44ETUfzB1EVJVWp10OHTqk8jkyMhIODg5ISEjAoEGDkJ+fj6+//hq7d+/G888/DwCIiIhAt27dcObMGfztb3+rNmZZWRnKysqUnwsKCurzPYioCWPuIKKqGnTBaX5+PgDA1tYWAJCQkICKigoEBgYq+3Tt2hVubm44ffp0jWOEhYXB2tpaubi6ujYkJCJqBpg7iFq3ehcfCoUCoaGh6N+/P7y9H13AlJOTA2NjY7Rt21alr6OjI3JycmocZ/HixcjPz1cumZmZ9Q2JiJoB5g4iqvfdLiEhIbh06RLi4+MbFIBcLodcLm/QGETUfDB3EFG9Zj5mz56NAwcO4Pjx42jfvr1yvZOTE8rLy5GXl6fSPzc3F05OTg0KlIiaP+YOIgK0nPkQQmDOnDmIjo7GiRMn4OnpqdLeu3dvtGnTBjExMXj11VcBAMnJybhx4wb8/Px0F3UroPB/Vm375p7fqm2vEOrvls9XlKptf+7nULXtXa//obadqCrmDukwd1BzoFXxERISgt27d2P//v2wtLRUnou1traGqakprK2tMW3aNMyfPx+2trawsrLCnDlz4OfnV+PV6kTUOjB3EFFVWhUf27ZtAwAEBASorI+IiEBwcDAA4JNPPoGBgQFeffVVlJWVISgoCJ9//rlOgiWi5om5g4iq0vq0S11MTEywdetWbN26td5BEVHLwtxBRFXxxXJEREQkKRYfREREJCkWH0RERCQpFh9EREQkKRYfREREJKl6P16dGleprbHa9gEmxWrbAUO1rYdL3NS2d37nnNp2RR17JyL9YO6g5oAzH0RERCQpFh9EREQkKRYfREREJCkWH0RERCQpFh9EREQkKRYfREREJCkWH0RERCQpFh9EREQkKRYfREREJCkWH0RERCQpFh9EREQkKRYfREREJCkWH0RERCQpFh9EREQkKRYfREREJCkjfQdANbNKylHbPuev59W2h7vG6TIcImommDuoOeDMBxEREUmKxQcRERFJisUHERERSYrFBxEREUmKxQcRERFJisUHERERSYrFBxEREUlKq+d8hIWFYe/evbh69SpMTU3Rr18/rFu3Dl26dFH2CQgIQFyc6n3i06dPR3h4uG4ibiUepl9X2/7X39RvPwK9dRgNUcMwd0iHuYOaA61mPuLi4hASEoIzZ87g6NGj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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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lVbHoKVg8dEIfvjhB2FhYSFsbGzErFmzRGRkpNixY4dYsGCB8PLyEgqFQqSnpxt6mPVWV+Hw008/id27d6uXefPmCQDi73//u8b6hhZht27dqnUsjVF8PHjwQPTv31+YmpqK+fPni61bt4oxY8YIAGL16tV63RdRVRMnThRPPfWUGDJkiNbFh5+fn3j55ZdrjL3zzjti1KhRYvPmzcLf379avKysTDz33HMiODhYb/8onjt3TgAQ69evV6+7f/++6NixowgICNDb9rrsx5B9Vjp//rwAII4dO1bnMWhJWHzoWUpKirC0tBTdunUTmZmZ1eLl5eXi448/FtevX5fsp7CwsLGG2GC6zlpER0cLADXOUFSl63eWu/j49ttvBQDx+eefa6wfP368MDMzEzk5OXrdH5EQQsTGxgpjY2Nx8eJFrYuPP//8UwAQUVFRNcb9/PzEjh07xKuvvipmzpypEauoqBATJ04Uo0aNEuXl5Xr5DkII8fbbbwtjY2ORl5ensX7NmjUCQJ05UdvtddmPIfusyt7eXsybN0/y+7c0vOZDz9atW4eioiLs3LkTrq6u1eImJiaYN2+exmviK69PSE1NxciRI2FtbY0pU6YAAIqKivDWW2/B3d0dSqUSXbp0wYYNGzTeIJueng6FQoGoqKhq+3v0+owVK1ZAoVAgJSUFoaGhaNu2LWxtbTF16lQUFxdrbFtaWor58+fD0dER1tbWeP755/HXX3818AhpjuM///kPgoODYWdnh4EDBwIAhg4diqFDh1bbJjQ0FF5eXurv7OjoCAB47733oFAoarwW5caNGxg7diysrKzUL9SrqKjQaJOVlYUrV66gvLxccsz//ve/AaDaO2omTZqEkpIS7N+/X9uvT6SViooKzJ07F6+//jp69Oih9XY///wzAFR7aSIA3L59G7///juGDBmCo0eP4umnn9aIz5gxA1lZWYiOjoaJSfXXf5WXl+P27dtaLVUvdE1ISEDnzp2rvaW6X79+AIDExETJ76Tt9rrsx5B9VtW7d2+cPn26pq/dYvHFcnp24MABdOrUSecXoj148ABBQUEYOHAgNmzYAAsLCwgh8Pzzz+PEiROYNm0aevXqhcOHD+Ptt9/GjRs38NFHH9V7nC+99BK8vb0RERGB3377DZ999hmcnJywdu1adZvXX38dX375JYKDg9G/f38cP35c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nJwcjfU5OTlwcXHR9+6IqAVg3iBqXfRefJiamqJPnz44duyYep1KpcKxY8cQEBCg790RUQvAvEHUujTKaZcFCxYgJCQEffv2Rb9+/bBp0yYUFRVh6tSpjbE7ImoBmDeIWo9GKT4mTpyIW7duYdmyZcjOzkavXr1w6NChaheTERFVYt4gaj0UQghh6EFUlZ+fD1tbWwzFGD4oiMhAHohynMR+5OXlwcbGxtDD0QpzB5Fh6ZI3mscbo4iIiKjFYPFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREsmLxQURERLJi8UFERESyYvFBREREstJ78bFixQooFAqNpWvXrvreDRG1IMwbRK2LSWN02r17dxw9evS/OzFplN0QUQvCvEHUejTKX7eJiQlcXFwao2siaqGYN4haj0a55iM5ORlubm7o0KEDpkyZguvXr9fatrS0FPn5+RoLEbU+uuQNgLmDqDnTe/Hh7++PqKgoHDp0CNu2bUNaWhoGDRqEgoKCGttHRETA1tZWvbi7u+t7SETUxOmaNwDmDqLmTCGEEI25g9zcXHh6emLjxo2YNm1atXhpaSlKS0vVn/Pz8+Hu7o6hGAMTRZvGHBoR1eKBKMdJ7EdeXh5sbGxk339deQNg7iBqanTJG41+RVfbtm3RuXNnpKSk1BhXKpVQKpWNPQwiakbqyhsAcwdRc9boz/koLCxEamoqXF1dG3tXRNRCMG8QtWx6Lz4WLlyI2NhYpKen4+eff8a4ceNgbGyMyZMn63tXRNRCMG8QtS56P+3y119/YfLkybhz5w4cHR0xcOBAnD17Fo6OjvreFRG1EMwbRK2L3ouPr7/+Wt9dElELx7xB1Lrw3S5EREQkKxYfREREJCsWH0RERCQrFh9EREQkK742kmpUFtRXMn5tikoyPqt3rGQ83O6qzmOqqsdncyXjFlnSD+7N7V8qGffcI12Xmx4+Lxknaq2YO5g7tMGZDyIiIpIViw8iIiKSFYsPIiIikhWL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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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pKR6jUnUUlhaWqJDhw7V1p89exZKpRIff/wxjIweTXr/8MMPWo15/PhxfP755wgNDQXw6Kf6ulws2q1bN/zv//4vPD09dfql3JMxhIWFqb40FRcXIzMzU62PqakpKisrn7ru8uXLuHPnDj766CO4ubkBAM6cOaPWx8/PD1u3bkVFRUWtRUWbNm2wa9cuBAUFYfTo0fjhhx9YgGiJp11aoH/84x8oKytDSEgIjh07hqysLOzfvx8vvPACnnnmGXz44YdajzV16lRcvnwZCxYswJUrV/DDDz8gJiYGALQuYJ5ka2sLe3t7fPHFF0hPT0dCQgLmzp1bp7GI6JEOHTqgoqICGzduxB9//IFt27YhOjpaq229vb2xbds2XLp0CadOncKECRO0mhl4LDw8HHfv3sW4ceNw+vRpXL16FQcOHMDkyZOrFQaaYti1axdSUlJw7tw5jB8/vtoF6J6enjh27Bhu3LihKo48PT1RXFyM+Ph43L59G6WlpXB3d4epqanqWPz4449YsWKF2lgzZ85EYWEhxo4dizNnziAtLQ3btm1DamqqWj9HR0ckJCTg8uXLGDduXIu5p0p9sfhogby9vXHmzBm0a9cOo0ePRvv27fH2229j4MCBOHHiBOzs7LQey8vLC//617+wa9cu+Pn5YdOmTapfu8jl8jrFZ2RkhJ07d+Ls2bPw9fXFnDlz6nyfAiJ6xN/fH+vXr8fq1avh6+uL7du3IzIyUqttv/76a9y7dw/dunXDa6+9htmzZ+t07x5XV1ccP34clZWVGDx4MJ577jlERESgdevWqlmYp1m/fj1sbW3Ru3dvDB8+HCEhIejWrZtan+XLlyMzMxPt27dXnTru3bs3pk2bhjFjxsDBwQFr1qyBg4MDYmJiEBsbi2effRYfffQR1q1bpzaWvb09EhISUFxcjAEDBqB79+748ssva5zZcHZ2RkJCAn777TdMmDBB64KqJZMJba88JNLShx9+iOjoaGRlZRk6FCIiaoR4zQfV2+eff46ePXvC3t4ex48fx9q1azFz5kxDh0VERI0Uiw+qt7S0NKxcuRJ3796Fu7s73n33XSxcuNDQYRERUSPF0y5EREQkKV5wSkRERJJi8UFERESSYvFBREREkmLxQURERJJi8UFERESSYvFBREREkmLxQURERJJi8UFERESS+j+5MAjJlrzoJwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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9XjttdewZ88e+Pj4mMQtObLQrVs3rFq1Cp9++immTZtmXL548WJoNBp07doVAODm5gZPT08cPHgQERERxnafffZZid9H165d8c477+CTTz4xXnAKPLjTo6zVrVsX27dvx82bN42nTU6fPo3Dhw+b/JIXTBLW/sFPTExEamoq6tatCzs7u2LbrVq1qtDtyHv37sWyZcuwcOHCYr9REZVESEgI+vXrh2nTpuHGjRuoV68e1qxZg/j4eJOLxYtSp04dNGnSBLt378Zrr71mXP75558bbws1GAx455130KpVK/Tt29fscVlzzcenn36KlJQU490hW7duNd6+P27cOOj1evz222/o2LEjpk+fbnLr/MiRI5GWlobnn38ejz32GJKSkrBu3TpcuHABH3/8sfFajoMHD2LmzJno3LkzPDw8cPToUaxevRpdunTBhAkTihzX999/j7y8vGITAEs+C3PHWSAmJgZ37txBz549S7RPK61yqzBShW3evFk4OjoKvV4vxowZI1auXCmioqLE1KlThb+/v9BqteLbb781ti+uIFZ+fr7o2LGj0Gg04s033xTLly8XPXv2FABERESESdv//Oc/AoB4/fXXxYoVK8SgQYNEy5YtCxXhmj59ugAgbt68abL+6tWrBQCTwjqDBg0SAMSQIUPE8uXLRZ8+fUTTpk1LpchYceMQQog///xTaLVa8dRTT4lPP/1UfPDBB8Lb21s8+eSTIjAw0KRt48aNha+vr1i+fLn49ttvxR9//CGEKH6fFmz3YcOHDy/03s1VsN9YZIzKwr1798TkyZOFr6+v0Ol04umnnxY7duwwa91FixYJFxcXkZWVZeyrZ8+e4qeffhKdOnUSQUFBYsyYMSIvL68s34KJwMBAAaDIV8Hv3759+4qcY7799lvRqVMn4ePjI2xtbYW7u7vo1KmT2LJli0m7y5cvi86dOwtPT0+h0+lEo0aNRGRkpMjOzi52XM8++6zw9vaW7gtzPwtzx1lg6tSpIiAgoNQqyVYWTD7KyOXLl8Xo0aNFvXr1hIODg3B0dBSNGjUSo0aNEqdOnTJpK6vGmZ6eLiZOnCj8/PyEnZ2dqF+/vliwYEGh/6hZWVni9ddfF3q9Xri6uor+/fuLGzduWJV83Lt3T4wfP154eHgIZ2dn0aNHD5GQkFDmyYcQQnzzzTeiTp06wt7eXjRv3lzs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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "labels = [0,1,2,3,4,5,6,7,8,9]\n", + "\n", + "for i in range(len(x_train_mnist)):\n", + " f, ax = plt.subplots(1,2)\n", + "\n", + " perturbation = np.linalg.norm(x_train_mnist[[i]] - x_train_mnist_adv[[i]])\n", + "\n", + " ax[0].set_title(f'Prediction: {labels[np.argmax(classifier.predict(x_train_mnist[i]))]}\\nGround Truth: {labels[np.argmax(y_train_mnist[[i]])]} ')\n", + " ax[0].imshow(x_train_mnist[i].transpose(1,2,0))\n", + " ax[0].set_xlabel('Original')\n", + "\n", + " ax[1].set_title(f'{labels[np.argmax(classifier.predict(x_train_mnist_adv[i]))]} ($\\\\ell ^{2}$={perturbation:.5f})')\n", + " ax[1].imshow(x_train_mnist_adv[i].transpose(1,2,0))\n", + " ax[1].set_xlabel('Parallel attack')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/huggingface_notebook.ipynb b/notebooks/huggingface_notebook.ipynb new file mode 100644 index 0000000000..6b11d12750 --- /dev/null +++ b/notebooks/huggingface_notebook.ipynb @@ -0,0 +1,824 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8093e27a-33f6-4cd9-a47b-ea94c3d0c514", + "metadata": {}, + "source": [ + "# Huggingface with ART\n", + "\n", + "In this notebook we will go over how to use the Huggingface AIP with ART. This can enable us to train robust foundation models which act over images. \n", + "\n", + "Currently this is a developing feature, and so not all ART tools are supported. Further tools and development is planned. As of ART 1.16 we support: \n", + "+ Using a Pytorch backend.\n", + "+ Evasion attacks and defences on classical classification tasks such as image classification, but not tasks such as object detection.\n", + "\n", + "If you have a use case that is not supported (or find a bug in this new feature!) please raise an issiue on ART.\n", + "\n", + "Let's look at how we can use ART to secure Huggingface models!\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "549129e5-f6f4-4995-b334-d22cb4cae76e", + "metadata": {}, + "outputs": [], + "source": [ + "# Relevant imports for the notebook\n", + "\n", + "import transformers\n", + "import torch\n", + "from torch.optim import Adam\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import numpy as np\n", + "from torchvision import datasets\n", + "from art.estimators.classification.hugging_face import HuggingFaceClassifierPyTorch" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0adf2bba-efc1-4db7-8041-698105537b08", + "metadata": {}, + "outputs": [], + "source": [ + "# We will use CIFAR data for the notebook.\n", + "def get_cifar_data():\n", + " \"\"\"\n", + " Get CIFAR-10 data.\n", + " :return: cifar train/test data.\n", + " \"\"\"\n", + " train_set = datasets.CIFAR10('./data', train=True, download=True)\n", + " test_set = datasets.CIFAR10('./data', train=False, download=True)\n", + "\n", + " x_train = train_set.data.astype(np.float32)\n", + " y_train = np.asarray(train_set.targets)\n", + "\n", + " x_test = test_set.data.astype(np.float32)\n", + " y_test = np.asarray(test_set.targets)\n", + "\n", + " x_train = np.moveaxis(x_train, [3], [1])\n", + " x_test = np.moveaxis(x_test, [3], [1])\n", + "\n", + " x_train = x_train / 255.0\n", + " x_test = x_test / 255.0\n", + "\n", + " return (x_train, y_train), (x_test, y_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5f797586-480b-432e-8b55-26a4ac2360f5", + "metadata": {}, + "outputs": [], + "source": [ + "def train_base_model(architecture='google/vit-base-patch16-224'):\n", + " \"\"\"\n", + " Train a cifar classifier\n", + " \"\"\"\n", + "\n", + " (x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + "\n", + " # Here we load a Huggingface model using the transformers library.\n", + " model = transformers.AutoModelForImageClassification.from_pretrained(architecture,\n", + " ignore_mismatched_sizes=True,\n", + " num_labels=10)\n", + "\n", + " # The HuggingFaceClassifierPyTorch follows broadly the same API as the PyTorchClassifier\n", + " # So we can supply the loss function, the input shape of the data we will supply, the optimizer, etc.\n", + " # Note, frequently we will be performing fine-tuning or transfer learning with vision transformners and \n", + " # so we may be fine-tuning on differently sized inputs. \n", + " # The input_shape argument refers to the shape of the supplied input data which may be different to \n", + " # the shape required by the model. \n", + " # To handle this HuggingFaceClassifierPyTorch has an extra argument of processor which will act on \n", + " # every batch to process the data into the correct form required by the supplied model.\n", + " # This needs to be manually specified by the user. For many attacks and defences to work it needs to be a \n", + " # differentiable funciton. \n", + " # Here the processor is a simple upsampler to enlarge the cifar images into the right size.\n", + " upsampler = torch.nn.Upsample(scale_factor=7, mode='nearest')\n", + "\n", + " optimizer = Adam(model.parameters(), lr=1e-4)\n", + "\n", + " hf_model = HuggingFaceClassifierPyTorch(model, \n", + " loss=torch.nn.CrossEntropyLoss(),\n", + " optimizer=optimizer,\n", + " input_shape=(3, 32, 32),\n", + " nb_classes=10,\n", + " clip_values=(0, 1),\n", + " processor=upsampler)\n", + "\n", + " hf_model.fit(x_train, y_train, nb_epochs=2)\n", + " return hf_model" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "01f68d34-e777-48b4-bdca-7c52a63e6e50", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Some weights of ViTForImageClassification were not initialized from the model checkpoint at google/vit-base-patch16-224 and are newly initialized because the shapes did not match:\n", + "- classifier.weight: found shape torch.Size([1000, 768]) in the checkpoint and torch.Size([10, 768]) in the model instantiated\n", + "- classifier.bias: found shape torch.Size([1000]) in the checkpoint and torch.Size([10]) in the model instantiated\n", + "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n" + ] + } + ], + "source": [ + "hf_base_model = train_base_model()\n", + "torch.save(hf_base_model.model.state_dict(), 'hf_base_model.pt')\n", + "del hf_base_model # Clear models we no longer need." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1a370096-d145-42cf-a59e-c553d81b0967", + "metadata": {}, + "outputs": [], + "source": [ + "def test_pgd(architecture, model_to_test='hf_base_model.pt'):\n", + " \"\"\"\n", + " Here we can test the model we trained against a PGD attack.\n", + " \"\"\"\n", + " \n", + " import os\n", + " from art.attacks.evasion import ProjectedGradientDescentPyTorch\n", + " (x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + " model = transformers.AutoModelForImageClassification.from_pretrained(architecture,\n", + " ignore_mismatched_sizes=True,\n", + " num_labels=10)\n", + " \n", + " # Load the model state dict from the training loop we just performed.\n", + " model.load_state_dict(torch.load(os.path.join('..', model_to_test)))\n", + " optimizer = Adam(model.parameters(), lr=1e-4)\n", + "\n", + " # Set it up as a HuggingFaceClassifierPyTorch\n", + " hf_model = HuggingFaceClassifierPyTorch(model, \n", + " loss=torch.nn.CrossEntropyLoss(),\n", + " optimizer=optimizer,\n", + " input_shape=(3, 32, 32),\n", + " nb_classes=10,\n", + " clip_values=(0, 1),\n", + " processor=torch.nn.Upsample(scale_factor=7, mode='nearest'))\n", + "\n", + " # Let's just use 100 samples for quick demo purposes\n", + " num_samples = 100\n", + " outputs = hf_model.predict(x_test[:num_samples])\n", + " acc = np.sum(np.argmax(outputs, axis=1) == y_test[:num_samples]) / len(y_test[:num_samples])\n", + " print('clean acc ', acc)\n", + "\n", + " # The backend of the HuggingFaceClassifierPyTorch is the existing PyTorchClassifier. \n", + " # Thus we can interface HuggingFaceClassifierPyTorch with aleadry existing attacks in ART which support pytorch.\n", + " # Here we use ProjectedGradientDescentPyTorch.\n", + " attacker = ProjectedGradientDescentPyTorch(hf_model, eps=8/255, eps_step=1/255)\n", + " x_test_adv_robust = attacker.generate(x_test[:num_samples])\n", + " outputs = hf_model.predict(x_test_adv_robust)\n", + " acc = np.sum(np.argmax(outputs, axis=1) == y_test[:num_samples]) / len(y_test[:num_samples])\n", + " print('adv acc ', acc)\n", + "\n", + " # We can display the adversarial examples to highlight the added perturbation to the original sample.\n", + " x_test_adv_robust = np.moveaxis(x_test_adv_robust, [1], [3])\n", + " x_test = np.moveaxis(x_test, [1], [3])\n", + "\n", + " delta = ((x_test[:num_samples] - x_test_adv_robust) + 8/255) * 10 # shift to have min 0 and make perturbations 10x larger to visualise them.\n", + "\n", + " fig, axs = plt.subplots(3, 3)\n", + " for i in range(3):\n", + " axs[i, 0].imshow(x_test_adv_robust[i])\n", + " axs[i, 1].imshow(x_test[i])\n", + " axs[i, 2].imshow(delta[i])\n", + " plt.tight_layout()\n", + " del hf_model # clear memory of unneeded models" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2f4625ff-103f-49d2-90f9-503f1ea43e84", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Some weights of ViTForImageClassification were not initialized from the model checkpoint at google/vit-base-patch16-224 and are newly initialized because the shapes did not match:\n", + "- classifier.weight: found shape torch.Size([1000, 768]) in the checkpoint and torch.Size([10, 768]) in the model instantiated\n", + "- classifier.bias: found shape torch.Size([1000]) in the checkpoint and torch.Size([10]) in the model instantiated\n", + "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "clean acc 0.96\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "PGD - Batches: 0%| | 0/4 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "test_pgd(architecture='google/vit-base-patch16-224', model_to_test='hf_base_model.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fa81f918-e716-457d-a665-835542cf5d5e", + "metadata": {}, + "outputs": [], + "source": [ + "# We can see that we can attack the Huggingface transformer, so now let's use one of the defences in ART!\n", + "\n", + "def adversarial_train():\n", + " from art.defences.trainer import AdversarialTrainerMadryPGD\n", + " (x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + " model = transformers.AutoModelForImageClassification.from_pretrained('google/vit-base-patch16-224',\n", + " ignore_mismatched_sizes=True,\n", + " num_labels=10)\n", + "\n", + " upsampler = torch.nn.Upsample(scale_factor=7, mode='nearest')\n", + "\n", + " optimizer = Adam(model.parameters(), lr=1e-4)\n", + "\n", + " hf_model = HuggingFaceClassifierPyTorch(model, \n", + " loss=torch.nn.CrossEntropyLoss(), \n", + " input_shape=(3, 32, 32),\n", + " nb_classes=10,\n", + " optimizer=optimizer,\n", + " clip_values=(0, 1),\n", + " processor=upsampler)\n", + "\n", + " # We can now use adversarial training with Madry's protocol.\n", + " trainer = AdversarialTrainerMadryPGD(hf_model,\n", + " nb_epochs=10,\n", + " eps=8/255,\n", + " eps_step=1/255,\n", + " max_iter=10)\n", + "\n", + " trainer.fit(x_train, y_train)\n", + " torch.save(trainer._classifier.model.state_dict(), 'hf_adv_model.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "76373745-f886-4f1f-9452-ec648753b04c", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the below to run the adverarial training, it can take some time depending on available hardware. \n", + "# The expected runtime is around 15 hours using a Nvidia V100 GPU. More training could be conducted if better performance is desired.\n", + "\n", + "# adversarial_train()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "51c859e2-c18a-4598-999b-b7621772a2d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Some weights of ViTForImageClassification were not initialized from the model checkpoint at google/vit-base-patch16-224 and are newly initialized because the shapes did not match:\n", + "- classifier.weight: found shape torch.Size([1000, 768]) in the checkpoint and torch.Size([10, 768]) in the model instantiated\n", + "- classifier.bias: found shape torch.Size([1000]) in the checkpoint and torch.Size([10]) in the model instantiated\n", + "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "clean acc 0.88\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "PGD - Batches: 0%| | 0/4 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# We now test the adversariallty trained model and we can see we have done from 0% robustness to 43%.\n", + "test_pgd(architecture='google/vit-base-patch16-224', model_to_test='hf_adv_model.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cfeead39-4b74-4b60-bd4b-a933fb7eebc0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Some weights of SwinForImageClassification were not initialized from the model checkpoint at microsoft/swin-tiny-patch4-window7-224 and are newly initialized because the shapes did not match:\n", + "- classifier.weight: found shape torch.Size([1000, 768]) in the checkpoint and torch.Size([10, 768]) in the model instantiated\n", + "- classifier.bias: found shape torch.Size([1000]) in the checkpoint and torch.Size([10]) in the model instantiated\n", + "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n" + ] + } + ], + "source": [ + "# We can also try with different models, for example here we try the functions with a different architecture\n", + "\n", + "hf_base_model = train_base_model(architecture='microsoft/swin-tiny-patch4-window7-224')\n", + "torch.save(hf_base_model.model.state_dict(), 'swin_tiny_base_model.pt')\n", + "del hf_base_model" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "86b1f346-6c7e-4546-ae10-becbdd442b43", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Some weights of SwinForImageClassification were not initialized from the model checkpoint at microsoft/swin-tiny-patch4-window7-224 and are newly initialized because the shapes did not match:\n", + "- classifier.weight: found shape torch.Size([1000, 768]) in the checkpoint and torch.Size([10, 768]) in the model instantiated\n", + "- classifier.bias: found shape torch.Size([1000]) in the checkpoint and torch.Size([10]) in the model instantiated\n", + "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "clean acc 0.94\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "PGD - Batches: 0%| | 0/4 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "test_pgd(architecture='microsoft/swin-tiny-patch4-window7-224', model_to_test='./swin_tiny_base_model.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4d6ccae2-4f26-402f-be21-bc3003f5a59a", + "metadata": {}, + "outputs": [], + "source": [ + "# We can also try different architectures, for example one of the most popular models on Huggingface is the resn\n", + "def train_using_timm_model():\n", + " import timm\n", + " \n", + " model = timm.create_model('resnet50.a1_in1k', pretrained=True)\n", + " upsampler = torch.nn.Upsample(scale_factor=7, mode='nearest')\n", + " \n", + " optimizer = Adam(model.parameters(), lr=1e-3)\n", + " \n", + " hf_model = HuggingFaceClassifierPyTorch(model, \n", + " loss=torch.nn.CrossEntropyLoss(), \n", + " input_shape=(3, 32, 32),\n", + " nb_classes=10,\n", + " optimizer=optimizer,\n", + " clip_values=(0, 1),\n", + " processor=upsampler)\n", + " (x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + " hf_model.fit(x_train, y_train, nb_epochs=2)\n", + "\n", + " return hf_model" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6584e1db-bc18-453d-9c8b-a313d1c9267e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + } + ], + "source": [ + "hf_timm_model = train_using_timm_model()\n", + "torch.save(hf_timm_model.model.state_dict(), 'timm_resnet_50.pt')\n", + "del hf_timm_model" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "dff3de85-965a-45fe-9870-7858e6527d2d", + "metadata": {}, + "outputs": [], + "source": [ + "def adversarial_train_timm():\n", + " from art.defences.trainer import AdversarialTrainerMadryPGD\n", + " import timm\n", + " (x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + "\n", + " model = timm.create_model('resnet50.a1_in1k', pretrained=True)\n", + "\n", + " upsampler = torch.nn.Upsample(scale_factor=7, mode='nearest')\n", + "\n", + " optimizer = Adam(model.parameters(), lr=1e-4)\n", + "\n", + " hf_model = HuggingFaceClassifierPyTorch(model, \n", + " loss=torch.nn.CrossEntropyLoss(), \n", + " input_shape=(3, 32, 32),\n", + " nb_classes=10,\n", + " optimizer=optimizer,\n", + " clip_values=(0, 1),\n", + " processor=upsampler)\n", + "\n", + " # We can now use adversarial training with Madry's protocol.\n", + " trainer = AdversarialTrainerMadryPGD(hf_model,\n", + " nb_epochs=10,\n", + " eps=8/255,\n", + " eps_step=1/255,\n", + " max_iter=10)\n", + "\n", + " trainer.fit(x_train, y_train)\n", + " torch.save(trainer._classifier.model.state_dict(), 'timm_resnet_50_adv_model.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "122ba410-7352-494b-8029-c747e5378855", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the below to run the adverarial training, it can take some time depending on available hardware. \n", + "# The expected runtime is around 10 hours using a Nvidia V100 GPU. More training could be conducted if better performance is desired.\n", + "\n", + "# adversarial_train_timm()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b8a93fba-3d05-4316-91e6-de54869c22fe", + "metadata": {}, + "outputs": [], + "source": [ + "def test_on_pgd_timm():\n", + " from art.attacks.evasion import ProjectedGradientDescentPyTorch\n", + " import timm\n", + "\n", + " (x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + " \n", + " model = timm.create_model('resnet50.a1_in1k', pretrained=True)\n", + "\n", + " model.load_state_dict(torch.load('timm_resnet_50_adv_model.pt'))\n", + " loss_fn = torch.nn.CrossEntropyLoss()\n", + " from torch.optim import Adam\n", + " optimizer = Adam(model.parameters(), lr=1e-4)\n", + " hf_model = HuggingFaceClassifierPyTorch(model, \n", + " loss=torch.nn.CrossEntropyLoss(),\n", + " optimizer=optimizer,\n", + " input_shape=(3, 32, 32),\n", + " nb_classes=10,\n", + " clip_values=(0, 1),\n", + " processor=torch.nn.Upsample(scale_factor=7, mode='nearest'))\n", + "\n", + " outputs = hf_model.predict(x_test[:100])\n", + " acc = np.sum(np.argmax(outputs, axis=1) == y_test[:100]) / len(y_test[:100])\n", + " print('clean acc ', acc)\n", + "\n", + " attacker = ProjectedGradientDescentPyTorch(hf_model, eps=8/255, eps_step=1/255)\n", + " x_test_adv_robust = attacker.generate(x_test[:100])\n", + " outputs = hf_model.predict(x_test_adv_robust)\n", + " acc = np.sum(np.argmax(outputs, axis=1) == y_test[:100]) / len(y_test[:100])\n", + " print('adv acc ', acc)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "9fee39b0-5be1-4bb1-978d-b982f0642881", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n", + "clean acc 0.69\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "PGD - Batches: 0%| | 0/4 [00:00 $\\Delta = m + s - 1$

\n", + "\n", + "Based on this relationship we can derive a simple but effective criterion that if we are making many predictions for an image and the highest predicted class $c_t$ has been predicted $k_t$ times and the second most predicted class $c_{t-1}$ has been predicted $k_{t-1}$ times then we have a certified prediction for $c_t$ if: \n", + "\n", + "\n", + "

$k_t - k_{t-1} > 2\\Delta$

\n", + "\n", + "Intuitivly we are saying that even if $k$ predictions were adversarially influenced and those predictions were to change, then the model will *still* have predicted class $c_t$.\n", + "\n", + "### What's special about Vision Transformers?\n", + "\n", + "The formulation above is very generic and it can be applied to any nerual network model, in fact the original paper which proposed it (https://arxiv.org/abs/2110.07719) considered the case with convolutional nerual networks. \n", + "\n", + "However, Vision Transformers (ViTs) are well siuted to this task of predicting with vision ablations for two key reasons: \n", + "\n", + "+ ViTs first tokenize the input into a series of image regions which get embedded and then processed through the neural network. Thus, by considering the input as a set of tokens we can drop tokens which correspond to fully masked (i.e ablated)regions significantly saving on the compute costs. \n", + "\n", + "+ Secondly, the ViT's self attention layer enables sharing of information globally at every layer. In contrast convolutional neural networks build up the receptive field over a series of layers. Hence, ViTs can be more effective at classifying an image based on its small unablated regions.\n", + "\n", + "Let's see how to use these tools!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "aeb27667", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import numpy as np\n", + "import torch\n", + "\n", + "sys.path.append(\"..\")\n", + "from torchvision import datasets\n", + "from matplotlib import pyplot as plt\n", + "\n", + "# The core tool is PyTorchSmoothedViT which can be imported as follows:\n", + "from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing\n", + "\n", + "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "80541a3a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + } + ], + "source": [ + "# Function to fetch the cifar-10 data\n", + "def get_cifar_data():\n", + " \"\"\"\n", + " Get CIFAR-10 data.\n", + " :return: cifar train/test data.\n", + " \"\"\"\n", + " train_set = datasets.CIFAR10('./data', train=True, download=True)\n", + " test_set = datasets.CIFAR10('./data', train=False, download=True)\n", + "\n", + " x_train = train_set.data.astype(np.float32)\n", + " y_train = np.asarray(train_set.targets)\n", + "\n", + " x_test = test_set.data.astype(np.float32)\n", + " y_test = np.asarray(test_set.targets)\n", + "\n", + " x_train = np.moveaxis(x_train, [3], [1])\n", + " x_test = np.moveaxis(x_test, [3], [1])\n", + "\n", + " x_train = x_train / 255.0\n", + " x_test = x_test / 255.0\n", + "\n", + " return (x_train, y_train), (x_test, y_test)\n", + "\n", + "\n", + "(x_train, y_train), (x_test, y_test) = get_cifar_data()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2ac0c5b3", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['vit_base_patch8_224',\n", + " 'vit_base_patch16_18x2_224',\n", + " 'vit_base_patch16_224',\n", + " 'vit_base_patch16_224_miil',\n", + " 'vit_base_patch16_384',\n", + " 'vit_base_patch16_clip_224',\n", + " 'vit_base_patch16_clip_384',\n", + " 'vit_base_patch16_gap_224',\n", + " 'vit_base_patch16_plus_240',\n", + " 'vit_base_patch16_rpn_224',\n", + " 'vit_base_patch16_xp_224',\n", + " 'vit_base_patch32_224',\n", + " 'vit_base_patch32_384',\n", + " 'vit_base_patch32_clip_224',\n", + " 'vit_base_patch32_clip_384',\n", + " 'vit_base_patch32_clip_448',\n", + " 'vit_base_patch32_plus_256',\n", + " 'vit_giant_patch14_224',\n", + " 'vit_giant_patch14_clip_224',\n", + " 'vit_gigantic_patch14_224',\n", + " 'vit_gigantic_patch14_clip_224',\n", + " 'vit_huge_patch14_224',\n", + " 'vit_huge_patch14_clip_224',\n", + " 'vit_huge_patch14_clip_336',\n", + " 'vit_huge_patch14_xp_224',\n", + " 'vit_large_patch14_224',\n", + " 'vit_large_patch14_clip_224',\n", + " 'vit_large_patch14_clip_336',\n", + " 'vit_large_patch14_xp_224',\n", + " 'vit_large_patch16_224',\n", + " 'vit_large_patch16_384',\n", + " 'vit_large_patch32_224',\n", + " 'vit_large_patch32_384',\n", + " 'vit_medium_patch16_gap_240',\n", + " 'vit_medium_patch16_gap_256',\n", + " 'vit_medium_patch16_gap_384',\n", + " 'vit_small_patch16_18x2_224',\n", + " 'vit_small_patch16_36x1_224',\n", + " 'vit_small_patch16_224',\n", + " 'vit_small_patch16_384',\n", + " 'vit_small_patch32_224',\n", + " 'vit_small_patch32_384',\n", + " 'vit_tiny_patch16_224',\n", + " 'vit_tiny_patch16_384']" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# There are a few ways we can interface with PyTorchSmoothedViT. \n", + "# The most direct way to get setup is by specifying the name of a supported transformer.\n", + "# Behind the scenes we are using the timm library (link: https://github.com/huggingface/pytorch-image-models).\n", + "\n", + "\n", + "# We currently support ViTs generated via: \n", + "# https://github.com/huggingface/pytorch-image-models/blob/main/timm/models/vision_transformer.py\n", + "# Support for other architectures can be added in. Consider raising a feature or pull request to have \n", + "# additional models supported.\n", + "\n", + "# We can see all the models supported by using the .get_models() method:\n", + "PyTorchDeRandomizedSmoothing.get_models()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e8bac618", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Running algorithm: salman2021\n", + "INFO:root:Converting Adam Optimiser\n", + "WARNING:art.estimators.certification.derandomized_smoothing.pytorch: ViT expects input shape of: (3, 224, 224) but (3, 32, 32) specified as the input shape. The input will be rescaled to (3, 224, 224)\n", + "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n", + "INFO:art.estimators.certification.derandomized_smoothing.pytorch:PyTorchViT(\n", + " (patch_embed): PatchEmbed(\n", + " (proj): Conv2d(3, 384, kernel_size=(16, 16), stride=(16, 16))\n", + " (norm): Identity()\n", + " )\n", + " (pos_drop): Dropout(p=0.0, inplace=False)\n", + " (patch_drop): Identity()\n", + " (norm_pre): Identity()\n", + " (blocks): Sequential(\n", + " (0): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (1): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (2): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (3): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (4): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (5): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (6): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (7): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (8): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (9): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (10): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (11): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " )\n", + " (norm): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (fc_norm): Identity()\n", + " (head_drop): Dropout(p=0.0, inplace=False)\n", + " (head): Linear(in_features=384, out_features=10, bias=True)\n", + ")\n" + ] + } + ], + "source": [ + "import timm\n", + "\n", + "# We can setup the PyTorchSmoothedViT if we start with a ViT model directly.\n", + "\n", + "vit_model = timm.create_model('vit_small_patch16_224')\n", + "optimizer = torch.optim.Adam(vit_model.parameters(), lr=1e-4)\n", + "\n", + "art_model = PyTorchDeRandomizedSmoothing(model=vit_model, # Name of the model acitecture to load\n", + " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n", + " optimizer=optimizer, # the optimizer to use: note! this is not initialised here we just supply the class!\n", + " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n", + " nb_classes=10,\n", + " ablation_size=4, # Size of the retained column\n", + " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n", + " load_pretrained=True) # if to load pre-trained weights for the ViT" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "353ef5a6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Running algorithm: salman2021\n", + "INFO:timm.models._builder:Loading pretrained weights from Hugging Face hub (timm/vit_small_patch16_224.augreg_in21k_ft_in1k)\n", + "INFO:timm.models._hub:[timm/vit_small_patch16_224.augreg_in21k_ft_in1k] Safe alternative available for 'pytorch_model.bin' (as 'model.safetensors'). Loading weights using safetensors.\n", + "WARNING:art.estimators.certification.derandomized_smoothing.pytorch: ViT expects input shape of: (3, 224, 224) but (3, 32, 32) specified as the input shape. The input will be rescaled to (3, 224, 224)\n", + "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n", + "INFO:art.estimators.certification.derandomized_smoothing.pytorch:PyTorchViT(\n", + " (patch_embed): PatchEmbed(\n", + " (proj): Conv2d(3, 384, kernel_size=(16, 16), stride=(16, 16))\n", + " (norm): Identity()\n", + " )\n", + " (pos_drop): Dropout(p=0.0, inplace=False)\n", + " (patch_drop): Identity()\n", + " (norm_pre): Identity()\n", + " (blocks): Sequential(\n", + " (0): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (1): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (2): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (3): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (4): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (5): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (6): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (7): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (8): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (9): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (10): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " (11): Block(\n", + " (norm1): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (attn): Attention(\n", + " (qkv): Linear(in_features=384, out_features=1152, bias=True)\n", + " (q_norm): Identity()\n", + " (k_norm): Identity()\n", + " (attn_drop): Dropout(p=0.0, inplace=False)\n", + " (proj): Linear(in_features=384, out_features=384, bias=True)\n", + " (proj_drop): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls1): Identity()\n", + " (drop_path1): Identity()\n", + " (norm2): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (mlp): Mlp(\n", + " (fc1): Linear(in_features=384, out_features=1536, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (drop1): Dropout(p=0.0, inplace=False)\n", + " (norm): Identity()\n", + " (fc2): Linear(in_features=1536, out_features=384, bias=True)\n", + " (drop2): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (ls2): Identity()\n", + " (drop_path2): Identity()\n", + " )\n", + " )\n", + " (norm): LayerNorm((384,), eps=1e-06, elementwise_affine=True)\n", + " (fc_norm): Identity()\n", + " (head_drop): Dropout(p=0.0, inplace=False)\n", + " (head): Linear(in_features=384, out_features=10, bias=True)\n", + ")\n" + ] + } + ], + "source": [ + "# Or we can just feed in the model name and ART will internally create the ViT.\n", + "\n", + "art_model = PyTorchDeRandomizedSmoothing(model='vit_small_patch16_224', # Name of the model acitecture to load\n", + " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n", + " optimizer=torch.optim.SGD, # the optimizer to use: note! this is not initialised here we just supply the class!\n", + " optimizer_params={\"lr\": 0.01}, # the parameters to use\n", + " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n", + " nb_classes=10,\n", + " ablation_size=4, # Size of the retained column\n", + " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n", + " load_pretrained=True) # if to load pre-trained weights for the ViT" + ] + }, + { + "cell_type": "markdown", + "id": "c7a4255f", + "metadata": {}, + "source": [ + "Creating a PyTorchSmoothedViT instance with the above code follows many of the general ART patterns with two caveats: \n", + "+ The optimizer would (normally) be supplied initialised into the estimator along with a pytorch model. However, here we have not yet created the model, we are just supplying the model architecture name. Hence, here we pass the class into PyTorchDeRandomizedSmoothing with the keyword arguments in optimizer_params which you would normally use to initialise it.\n", + "+ The input shape will primiarily determine if the input requires upsampling. The ViT model such as the one loaded is for images of 224 x 224 resolution, thus in our case of using CIFAR data, we will be upsampling it." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "44975815", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The shape of the ablated image is (10, 4, 224, 224)\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# We can see behind the scenes how PyTorchDeRandomizedSmoothing processes input by passing in the first few CIFAR\n", + "# images into art_model.ablator.forward along with a start position to retain pixels from the original image.\n", + "original_image = np.moveaxis(x_train, [1], [3])\n", + "\n", + "ablated = art_model.ablator.forward(torch.from_numpy(x_train[0:10]).to(device), column_pos=6)\n", + "ablated = ablated.cpu().detach().numpy()\n", + "\n", + "# Note the shape:\n", + "# - The ablator adds an extra channel to signify the ablated regions of the input.\n", + "# - The input is reshaped to be 224 x 224 to match the image shape that the ViT is expecting\n", + "print(f\"The shape of the ablated image is {ablated.shape}\")\n", + "\n", + "ablated_image = ablated[:, 0:3, :, :]\n", + "\n", + "# shift the axis to disply\n", + "ablated_image = np.moveaxis(ablated_image, [1], [3])\n", + "\n", + "# plot the figure: Note the axis scale!\n", + "f, axarr = plt.subplots(1,2)\n", + "axarr[0].imshow(original_image[0])\n", + "axarr[1].imshow(ablated_image[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7253ce1", + "metadata": {}, + "outputs": [], + "source": [ + "# We can now train the model. This can take some time depending on hardware.\n", + "from torchvision import transforms\n", + "\n", + "scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)\n", + "art_model.fit(x_train, y_train, \n", + " nb_epochs=30, \n", + " update_batchnorm=True, \n", + " scheduler=scheduler,\n", + " transform=transforms.Compose([transforms.RandomHorizontalFlip()]))\n", + "torch.save(art_model.model.state_dict(), 'trained.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "046b8168", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Normal Acc 0.902 Cert Acc 0.703: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████| 79/79 [02:06<00:00, 1.61s/it]\n" + ] + } + ], + "source": [ + "# Perform certification\n", + "art_model.model.load_state_dict(torch.load('trained.pt'))\n", + "acc, cert_acc = art_model.eval_and_certify(x_test, y_test, size_to_certify=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a2683f52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Running algorithm: salman2021\n", + "INFO:timm.models._builder:Loading pretrained weights from Hugging Face hub (timm/vit_small_patch16_224.augreg_in21k_ft_in1k)\n", + "INFO:timm.models._hub:[timm/vit_small_patch16_224.augreg_in21k_ft_in1k] Safe alternative available for 'pytorch_model.bin' (as 'model.safetensors'). Loading weights using safetensors.\n", + "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n", + "INFO:root:Running algorithm: salman2021\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The shape of the ablated image is (10, 4, 224, 224)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:timm.models._builder:Loading pretrained weights from Hugging Face hub (timm/vit_small_patch16_224.augreg_in21k_ft_in1k)\n", + "INFO:timm.models._hub:[timm/vit_small_patch16_224.augreg_in21k_ft_in1k] Safe alternative available for 'pytorch_model.bin' (as 'model.safetensors'). Loading weights using safetensors.\n", + "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The shape of the ablated image is (10, 4, 224, 224)\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAACbCAYAAADvEdaMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA71klEQVR4nO29e5Bc9XXvu/aj39OPefWMRho9kYSExMNCjwEfh8S6yJjEJpbrxLdcAae4pkxG3MJKOYlSjl2hUlEdn5wyZUeGuqkEkrqhcHFywY6MyeFKtmSMACODQW+EHjN6zGhGMz09/e7e+3f+6NFev9UI2xLz6On5fqpUWr336r1/e8/u1b/+rZehlFIEAAAAAABmPeZMDwAAAAAAAEwOmNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNgBAAAAADQImNiBhmLXrl20ePFiCgaDtHHjRnrjjTdmekgAADAlwN6BqzFlEzs8cGC6+f73v0/bt2+nb37zm/TLX/6SbrnlFtqyZQtdunRppocGGhzYOzDdwN6BD8NQSqnJPuj3v/99uv/+++nJJ5+kjRs30uOPP07PPfccHT9+nJLJ5K99r+u6dOHCBYpGo2QYxmQPDUwiSikaHx+nrq4uMs2ZX/zduHEjrV+/nv7hH/6BiKrPUnd3Nz3yyCP0l3/5l7/2vXjuZg/19tzB3s0N6u25g72bG1zXc6emgA0bNqje3l7vteM4qqurS+3cufM3vre/v18REf7Non/9/f1T8RhdE8ViUVmWpZ5//nmx/f7771ef+cxnPqBfKBTU2NiY9+/IkSMzfh/xb/Y9d0rB3s21f/Xw3MHezb1/1/Lc2TTJlEolOnjwIO3YscPbZpombd68mQ4cOPAB/WKxSMVi0XutJhYQ123YSLZt09jYqNAPmK4nN/uVJy9oDgu9thZ+3RqPeLLf9Ak9KxDSXlieOJoaE3rlCp8rEY/ztTlleT0lvpZCgeVgKCD0HHI8OZ/Pin2xeJRfKNYrleS5LOI/n6WNvSnSJPQiYb4Xti/I4yuWhJ4ytF8DJh+7VJJ6FWV47//r7/wbRaNRmmmGh4fJcRzq6OgQ2zs6OujYsWMf0N+5cyf9zd/8zXQND0wB9fDcTZa9+2//9AwFw2G6cOJtoT989rgnOw5/JpMLVgi9BUtWenKiY4EnB0PSxJ88+ron95065MmVjLRBlnauaCLmyXZA2tl1m+7w5KU38JgKaWm3jx55x5NdV9qTcqXgyceOHvHk8bHLQk+3rZWyZqtH8kIvk+PjVRw+V1tbs9BLNPP3gqsy/J6KUKNCvvo3Kpcr9PJ/7q+L5w72bu5xLc/dpE/sJuuBs22bbNsWExYiIsvkZWPb4smW3yf1Aj6+tKCfJ3N+S07s7ID22uL35P1SzzT5XEHtPaYj1MggnniSyzuDNcdztPBG15F/Bv34pFjPJCX0LGI9/T6FAvJcoaDfk30+lmtX4D9sYmfV6F2Z2PFxZt9S/o4dO2j79u3e63Q6Td3d3TM4InCt1MNzN1n2LhgOUygcoUAwKLb7/fx51Sd2tXoh7cdbWPthVzuxC4b4h2wgwD82zdofjfq5ND07KH+ghiM8OWrSvnhsVx4vHObzuq601aUy/x0DAb7eYo3NVJptNYiPYdvyXLatXbPBNtjnk/fCrx3f0dY4ah8rpyLtbj08d9cK7N3s51qeu0mf2F0rH/bAHTt2lAzTpNTwsNBv0eyZ0cov2hw5mzVCHNuSdUc8OePID6ky2JDkCvzrLpcvCr2yw0ZlWJvpBG15vEqF9Szz6saxei7+hVyp+QVrFFo92dRsYLkoxxSy+foz2urbiCN/cobDbHwNbcXSqJnkkua/zxXYWFbKNUbfrl5LsVzz03YGaWtrI8uyaHBwUGwfHBykzs7OD+gHAoEP/E0AmGo+zN6Np0apXCxSa6JF6Kt2njAqm1fO5i1cKvQcbSJlujlPdnPyM1oY5VUwleeVrfltMhZwYfcNntx9wyJP7pq/QOglkzw+n48/T5WEXNnrXsCfwUpF2rtCgVfcUqO8cjY8PCL0bL9u/NkwNrfKz3Ewwscb01YOA0H5decqvjc+m4+RHksJvVKxauMrsHdgljDpEaDX88DFYjHxD4Brxe/307p162jPnj3eNtd1ac+ePdTT0zODIwONDOwdmAlg78CvY9IndnjgwEyxfft2+sd//Ef6l3/5Fzp69Cg9/PDDlM1m6U/+5E9memigQYG9AzMF7B34MKbEFbt9+3Z64IEH6Pbbb6cNGzbQ448/jgcOTDl/9Ed/RENDQ/SNb3yDBgYG6NZbb6WXXnrpA/FPAEwmsHdgJoC9Ax/GlEzsJuOBC9oGmaZBVBMWsEiLq1vcwdmpyXYZmxLS48q0oMN8sSD0CmUtQ03T82tBxkREpAXQKpffE2+RsSSVsp7QwcdwapIsLD9fWLEkx1Su8DjCmp4dkWMKavsqBsfsmcoVehXi4+mJEE0ROfZMlmNzyhUtZqcmZnM8Xc0YLpVrLqoO2LZtG23btm2mhwHmEJPyBVsuE9llKhVlPGsux/Foi1fM9+RMVmaxlspsQ1ra2C7aPumUWb6cM1fv2HS7J8/vkLFz8Xg7D83mz3m4JnlCDzE2tHTSfDYj9IpanG44JO1Oc4Lj+5YtXe3JR48eF3pk8DGKRbZV8ZjMdtXyw2gszS5yRTK2z3V58KOjfD/zORnLfKXSa8Wpnxi7K8DegasxZckTeOAAAHMF2DsAQL0w8+WzAQAAAADApDDj5U4+jKDhkGm4FI3KIa6Yz8vurSFOefe50p2ZGeFld8fl+Wu+Jv3f1JbtYwmu/2T7pcshNTbO+7QhtUSlW2E8zUv6Ja2kSb4gXSxKc482abWgiIjKJU7XN7V6Ur6adHVHK45saz7WYo07x6/5JkyXr7+YkUVESSsFE9DKrFRc6dody1ZdFaWK3A4AuD4qhQJVDIOMigxvCPg5/GJMK/3U2ildpwtv4vIkye4uT9brVlZPxLZBFAa+KIsB504NsZ7JtvT4u78SeutXsev0ExvWe7Kq6VSZTnPB976zF8Q+v1Y03e/nLOG29vlCr6//PdYLst3N1BR4T6f5Ptk+touxmLTV+Ty7c3Uva6XGrnm19eQlAVC3YMUOAAAAAKBBwMQOAAAAAKBBqFtXbCJgkWWaFKpxP8a1zND2mNYSxpUuDP2VZWt+RVPOZYtaxXa9FY1dk1nqFNk9qiw+xqVLKamnZYqO53ipP+fIjKymkFaYtCjHbmmtc0yD1/+tgGwjlM+yKyXs0/o51rhBClpHjbxWPd2t8S2kMny8VI7vS6a2en25ev0VB65YACaDYj5HhnKpKSQ/47EWzk792C23enL30uVCb1zLSD1+qt+T05oNIiLKpFKefDnF7teLAzIsI6ZlxZLJWaK7v//vQs/3X9kW/k7Px3m7T4aDdHaye5iU7CaUGuUwl1++xT1lbZ+0/ZEo27iKFjZSyqSEnmaeqV2rluDU2ODLIzwOk7R+2rb8WkwkqlnG5ZoOPADUK1ixAwAAAABoEDCxAwAAAABoEDCxAwAAAABoEOo2xq4tHiTbMinqs8T2YJBfmxbHWYRqOkWUtbIBrlZaRCkZZ1HSOko4JY6hcFVNeRItPkPZXEJgvCRT7R2Hx5fTYtBq49HGs3z88yPyGD6TdWMZHnt5QMam5Mc4fmZhm1buIClLIRhRLjVQHOW4mkxGnndsnGPshsc4pvBM/5jQc6zqY+Mq5P8DMBkEAjYFAj4qW1GxPR/iEkyn0/yZfPuVN4TeyGXu9HD+Andb8FmybYxuW4oVtml6HC4R0bx2/mq4NHDWk2MBWT5lPJX25BOnT/P757XJ8/r4ePO6O8W+Lu113wDHBx5/t1/oJedx3N+ZPs0WlqVtdUv82tG6ZgRrSlgFbI7RzhdYLxaLCT3brr5PuVgHAbMDPKkAAAAAAA0CJnYAAAAAAA1C3bpiO9vC5LctivllqY2mMLsCDOEulW5BQytXUtQqjJskXROtUW6YHYlwqYH0mHR7xrXl+XGti8TZ81IvU2RXrF/zEMwPy1tt+zRX5+WU2FdUWkcNrdxJPCbdNHes5ibe6YvsSlA5eS/ibexyKOZ4HJmMnNcHfKzX3cnnSiZlM/PBdNVlW3Fc6jt0jgAAH41QKEmhUJgupaS9O9nP7sgjhw95sumT9sTRus3kxznEwjKlmzJfZNdpapzl8WxG6J05d9STIyG2BSuXrZQD19y5P//ZTz150ZIlQm3FyhWe3NoaF/sCQb6WeIzdpWZFhoBki3oHIS7Bkk+NCz3H4ZCSYIhtWiYt9WJa+ZSAFuJTKskwnNxEyZhyWf5tAKhXsGIHAAAAANAgYGIHAAAAANAg1K0rtrkpRAGfRXYpJbYHNBdEOMDVwot5uXxe1prdJxLNnlzbnLrk8Ny2XNY6OTQ1Cb0LQ7z0//5ZdhEMjcvleb1Jw6IQL+/f919uFXoL5vHx/+fBU2LfgZMDnlxx2dVhm3Ls4ylu1J3L8PiiUZ/QI4fdz8Eg7/MHZcZx2OB9Fa0r9kKtqTgRUXSk6tIolR3aD1csAB+ZRHMrhcIROtl/Qmy/eIYzTcM+/oyPZWWniEz6kicbLrtfU+PSxZrKs42zA/x5b+tICr2QFqIyf/EtntxdYzNO/+qAJ1sG26qyI7vpDA1zNv7atavEvhuWL+Xja5mvTZtuE3rvHOvz5GKBw2aKvpqsWGIXq6vYjg0MXBB6fq2rUbxZv35ZLSCfr4bNwBULZgtYsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBDqNsauvbmFgn6b8iMFsd00tHIdOS3FvyTjH2xD6wBR5niP2plsvsxxIYlmjs0oOTKe7dQ5js8YSWulRWxZid2y+AyxIOslbZlqHxzheJnlMVmJ/WILH2MwxbEzxZysDv/WCY7HMSscZ1KOyMrpFNfKlZhaaYF4WKhFXb7mgpbyr0ppobe4PTKhg5gTACaD06cPUiAYpGPvnxTbL1x835MdrYxJNB4ReiuXL/bkNavWePLFobzQOzvEx2jvZLuwaJksTxJt5ZizwVF+jxo+LfT6znLc21CK4+hWrRZq9H+s4Li6bEaOydXC8VSJbdzh1w4IveUrb/XkjvkJT37tjf1Cb2CQ7ZUeF1fIS/s5Oso2OdTEx3OVjNnL5qrXX6nIuEEA6hWs2AEAAAAANAiY2AEAAAAANAh164pNtLZRKOCj5qaQ2G6anKKfSnPKf7mmcrqppdu7xEvrqqZie1MTp82XieWjp2TZgWyR3RHBIKfJB/3yeKEIuzebLXYDHDw5KPQqJX5fMS5dse3NPA5DS90vV6RbOldil0ZW6zZRqkgXqaG5m/XGGz5TduFQptbxwubxVYpFqTfhplY17moAwPXxi5//hGyfTXaH7OywbNVaTw5pze1XrV4u9FauWODJToE/x8qUbs8scacc28d2xrISQq9cYRuXHR/x5HhN+EVFswF9l9geB5vOC714jEtOLV22WOxT2vpCPsVdgo69/rbUy/P1r9nyKU9ee/NSoZd/k12x758848nhsCxhFU+0aq/4+yKdlqVkisXqmOCKBbMFrNgBAAAAADQImNgBAAAAADQIdeuKJdMmMn1k+HwfqhLQuiiESWaJ2dqc1TS17hIkM54CIa6wPjzAWVK5Ybkcv7RFq3SueUSDEZlZunLZfD6vplix5HXoy/22JZtdR/18La3Nyzx52fKFQu903y88+dgJdn347RrXqWI3daXCf3KzJqPX5+cxulr1epeky9YwTPE/AOCjMXT+MlmWRbfdcq/YHghwJ4YWrenDvC6Z+T6SYtvVf5JdpyU3IPRMg92Jls2fcUdJm0GanXCK7M5VjrSfTfE2T76c4XAV0y/tsSs6/tSEcGiHbArydS3u6hZqQYvfZxLbtLVrZEZvIpHw5B/m/5cnD1yUNn1+kjvqOAbbal9NuE46XXXtVjNsZYgOAPUIvpkBAAAAABoETOwAAAAAABoETOwAAAAAABqEuo2xKxQqRMogo5yv2cPp9tksp7WXynKOWjE5Ji6T4/iTdE52gJjfzbdAVXjfojYZV7asi+PPcgXeN3/FLULPrzhWY3SMuzeERGo9EV3mgJnuznliVyrLsSpLb+SyBrFmGc8Xa+Zq7qNDPPbRMRmz59PiXUzFMTdlV6bva2F15GgV22uqopCaiJdRCuVOAJgMQpFmsm2bfDUfqZTWeSbQkvDkXEXGuhW0uN9Qc5Tf49Z8eAt61xxtczkn1IIhLRbX4HJJrllTLqqV49T8imP7rFCz0FN+tneuIc9lOJp9svj4voiMAQ418etKke3d5fOylFRrhOMSP/vpLZ785q/OCL2M1omiUBzy5GJefuckogkiIipp3XgAqGewYgcAAAAA0CBgYgcAAAAA0CDUrSvWMRxyDJOUIyud6+6/UJC7UjRFpZvygtb8+vQ5Xma3a3wd/sELnlwYZL3lSVme5JN3sUv0/fPscojObxd6ba3cReLSELsIEgmZ/m+6fHy/1vGh+j4uXWIHU548lLoo9M5f5JR/n4+vPxGTbpp8nq9Z2TyXN2p8rK7mmjUNQ9OT8380nABgcunsXkQ+n/8Dn7VCgcNNBtNsrv2JNqFXrrCbUi8Rlc/Ijjxlxce3bQ7LqFiyLEo4xmVHkq0pT1Yj0k1Z0kI2DJePHQrVdAzSTJyrpE13tC5Bpk/rmmHJe5HJsvvV0OJGAjX3LK3Z3VC4xZM/0XOz0Dv+/llPPnRkgM+Tzgo9/0SHjnJZjhuAegUrdgAAAAAADQImdgAAAAAADULdumLj8QiFgn6q2HL5O5Ph9C9V5iX8sXGZCXq2b1B7D7sjQkE5l714ml0dHUF2Z8yfv0joJbq4urlvXHN1BqXLdsEtG3jXALtUQ5UhoecQX0c2WxD75oXZvVvSKr0bEdnEekGEM9KiCXYBj18eEHqXBi97ctng8RZKNdXmTfaxRgKcVVzKS3fOlQ4VTk1HCgDA9aEMi5RhfcDdlxtn92NAc2+Op0eEXqnAn+Vcmt/jq/mIRiPscm1vZjdlrEWGirQn+FyOzd158gE5vpFFbIOKjhYqUpNl61S0zNqaTF3H1Gyc5opNtMjMWtfhY+pZ+/G4dPv6DbZjqfGUJ6uytGO3rmKbmYjyfdm9+38JvaHBYSIiqlRkFQEA6hWs2AEAAAAANAiY2IEZZ//+/fQHf/AH1NXVRYZh0AsvvCD2K6XoG9/4Bs2bN49CoRBt3ryZ3nvvPaEzMjJCX/ziFykWi1EikaAHH3xQrNQCAEA9AHsHphpM7MCMk81m6ZZbbqFdu3Zddf+3vvUt+s53vkNPPvkkvf766xSJRGjLli1U0KqyfvGLX6TDhw/Tyy+/TLt376b9+/fTQw89NF2XAAAAvxWwd2CqqdsYu8zYCFUKPrJLslOEz9DmoloKvW3JkiG5DMfcNUc5fiQRCQq9/CjH2CW7uDvE/Jt/R+gdOscxIidOsnzHvBahl0rxvo5l3JXCJBlzUtIqnSeULE+SvsQxcSGt2vm8lppzORwX4ruZ41HyNWVRfv7iDz35XD+f1/LL+EDSYua0CilUrpn/m+XqmAqTlP5/zz330D333HPVfUopevzxx+nrX/86ffaznyUion/913+ljo4OeuGFF+gLX/gCHT16lF566SX6xS9+QbfffjsREX33u9+lT3/60/T3f//31NXV9YHjFotFKhY5LimdTn9AB4Bpo1IiMohstyQ2xzVz1R3nz+eNSxNCr0kr/WRpNjKbTgm9Qo7tYijCtmXlcmlbuhct8GTTx/HGmZQ8Xvc87pqz8jR3yYi1SDvb0szlU2xbdpRwNVujNDMejMgSVpWC1g1He4+vtkQM8ee6tY3jkjM5aYOzKY5Fnt/Occ33/cHdQu+FH/3/RDR55U5g78BUgxU7UNecPn2aBgYGaPPmzd62eDxOGzdupAMHDhAR0YEDByiRSHhGjoho8+bNZJomvf7661c97s6dOykej3v/uru7p/ZCAADgNwB7ByYDTOxAXTMwUP1V3dHRIbZ3dHR4+wYGBiiZTIr9tm1TS0uLp1PLjh07aGxszPvX398/BaMHAIDfHtg7MBnUrSvWNIgsg8ipKbWhNHehSbw07hjSFTuq9WtOp7XOC0Xp6pgXZzft+t/9XU9esHKT0Pv/nvpnT+7Uyo5YJVmJ/fyp91lv6WpPDrbeIPQiil3MuZFLYl/IZbdqKc/ug+Fx6UpItHMJltbOxZ6cz8SEnqm9dPwcp1HbeaJc5ntjaKn9hpJp/pVK9bEpz+IWFIFAgAKBwG9WBGAauHPDrRQKhmjp6lvE9gvnuWTS/C52l65YvkzodbbzF72l+HM9rpX7ICIqamVI9M9/U0SWO2lqYleq5Wc3r6/GVZzPcmjHx9awy3bxisVCr+yyQVY16wkVl+24snhMlk9+PZULbG9czS1q2vJ4RlCza9q+Yrks9GyLQ1GcUsqT29tkWamP/5f1RESULxTp+R/+hGYjsHdzC6zYgbqms7Naa2pwcFBsHxwc9PZ1dnbSpUtyclypVGhkZMTTAQCAegf2DkwGmNiBumbJkiXU2dlJe/bs8bal02l6/fXXqaenh4iIenp6KJVK0cGDBz2dvXv3kuu6tHHjxmkfMwAAXA+wd2AyqFtXrKGq/5ya5XO9Sba+Aq/yNXpaomlLK2dXdYZlZtPHbl/hyavuYPfr6CXpAg5UOJts6QLOGHMNmdHameTsKj2LK5eSLoxShfeV8/LP4BC7At4/f86T3z30ptC7YxMfs7WTM3rT4/LXnE9LLmtbzC4XtyabzCmxy7WiuazHhlJCrzhePWCxPDmV2DOZDJ08edJ7ffr0aXr77beppaWFFi5cSI8++ij97d/+LS1fvpyWLFlCf/3Xf01dXV103333ERHRqlWr6FOf+hR9+ctfpieffJLK5TJt27aNvvCFL1w1QwyAeuO2m1ZQJBKhm26Trtj8Gna5RuIcUyGtDpEytBAVzcXYEpErOEr7yOuffteVR6zoGaCaDS4WZejJshsWenLIz7Yln5WdgJSp2ThD2juldYpwFcuOIUNFXC19tpTncTiudCObth6uw1c5flmGspw9zXFmd378Nk/OlWUlhvCEa9dQk9NpB/YOTDV1O7EDc4c333yTfleLb9y+fTsRET3wwAP09NNP05//+Z9TNpulhx56iFKpFH384x+nl156iYJBjgP6t3/7N9q2bRt98pOfJNM0aevWrfSd73xn2q8FAAB+HbB3YKrBxA7MOHfddRcp9eGJGIZh0GOPPUaPPfbYh+q0tLTQM888MxXDAwCASQP2Dkw1iLEDAAAAAGgQ6nbFzq045Fom5Ysy9sOvlRqxbY4lsUwZw3ZDJ5cMCYZ4/rp4kSzMeMvHeUl83sqbPfntA08JvYXdfLzOm9byeNpl2QE7HPfkXIHj9PJpGbcxeIHjO0YHz4l9jlaSIBTl5fe2Ntkpov/CW57cMW++J1dyNSVi8lxx3MiO8nmUjJfRY11CAT6Xv1OeNx2oxpoUSpMTcwLAXCcYiVAoEqGmoCxJEQlrJtrmkk5uzYKPocfYabJb09XGLbvaPj6IURNvW9Gi+PSqSMqQek0JLsFScfg9jivLT5HLB1EkY3NN/QQOy44t7Y4i7aIrWmkmVx4voJ3b5/B4IwU5JjXI9m/oFGehLli5QOgNmxP21Jy95Z3A3AIrdgAAAAAADQImdgAAAAAADULdumJ9lk0+y6bRmm4LToGX6kNhrfF1zTJ5Uitx0n8x5cnLPvYpobdgrf6a3a3l8azQi0fZxdq+4lZPztqyefbht37hycU8HyNd04x7+Hwfj92RbuRgkP8s85ewi/XmFbJ7RcXiNH+flWDZX1NhvcDdJnJnuZK9W6npKKFN8zMWuy3CrbKcQEdXtbRKvjA55U4AmOs0xZop2tREypLux5xWdkhpTdyLNR10shm2NSWtg0yxKG1BpcLu0rJWxkTvOkNElMux3c1lOYykUlMWJdrCdjEaT3hyItom9IJ+vyc7Nd0ryNC6SGjdhKJaGAoR0eVL/L6C1pHI1Tr1EBEZxOdyHb5nsah0cy9ayG278jm+f8qVJbHi0ar981k17mUA6hSs2AEAAAAANAiY2AEAAAAANAiY2AEAAAAANAh1G2NXKhTJdB0KB+QQjaCWym5yLIRyZFxEqIn1PvNHn/HkO+75pNCLtXGcxeCpo55smfJ4qXFukTN05rgnXxiXcWY/feEFT24KcbxMoShLkHR2cGxKLCpj2E6f41IoJW0cLV2Lhd6Ktev4hcPxIyMpWT4lp8Uljub5eIaS97aQ5/iZjFYKQWUKQm9VYkK/JlQGAHB9/OjFlykYDJLj+5nYPjrKZTgyY8OeXFt5Q4+50xvIOzV1UVrak57c3MZtCAOWtAXZkZQnn3iP7WI6I+1Y95JFnmz52N7Foq1Cb8kSbj22oFu2OVuylOOIWwJsq6JBGW/oai3VSIt3K9fYfkvrNWlpx+tYXBP3F2ObWVZsxy2/UKOWlup5AwE5HgDqlWtasdu5cyetX7+eotEoJZNJuu++++j48eNCp1AoUG9vL7W2tlJTUxNt3bpVGBoAAJgNwN4BAGYj1zSx27dvH/X29tJrr71GL7/8MpXLZbr77rspm+WMoq9+9av0H//xH/Tcc8/Rvn376MKFC/S5z31u0gcOAABTCewdAGA2ck2u2Jdeekm8fvrppymZTNLBgwfpE5/4BI2NjdE//dM/0TPPPEO/93u/R0RETz31FK1atYpee+012rRp0299LleVqlXTa6qKG1q6fkVxur5hSJdDMMDL9reuY5dlwCeX04+8zd0bRi+878nFonQ/jo+OeHL/ySOenFEhoedz+H1NWqX4WFC6W9ub2RV7cXBA7KtoZQhy4+z66D/dR5LDPI4MlyQI2vJeVALsfrlc4fsSCslyAuEoX0vIZjfFeC4tjzdRDqDiotwJaFym09795Gevk237KLFgpdiuHP78v/XqTzx50QLZHaGtlV2f58+xPan9jIZbEp5cMtmWDmrhH0REn9zQ48m33nyTJ+dq7KLp46+Q031nPfnEe+8LvXcPsZ1NxJvEvq2f/0NPvvOmFZ7sV3LdYcE87hpU0lyxhik74OgdNcpalwvTrulQkWD7F9I6b7iWjDG58o1h123gEgCSj5Q8MTZWjTtraanWcjt48CCVy2XavHmzp3PjjTfSwoUL6cCBA1c9RrFYpHQ6Lf4BAEC9AXsHAJgNXPfEznVdevTRR+nOO++kNWvWEBHRwMAA+f1+SiQSQrejo4MGBgaucpRqHEs8Hvf+dXd3X1UPAABmCtg7AMBs4boXl3t7e+nQoUP0yiuvfKQB7Nixg7Zv3+69TqfTE8bOJSKX3IpcFrd93FHC0TonlEhmRnXEuRr5f/5wtye3dBwWekl9eT/Hma8+n6xS3hRhF6ZtshsgUuPa7UyySyQ/PurJIUse7/IQZ7iVS9JFEA2yS7SkZaG999abQu/isROeXKxwQ2vyyQrpjj7eBZpLOCLvrRlgN0tQq77eTNLdvOqmJURElMuXiehXBECjM9X27r7P/58UCoUpkFwu9HPjPEF8713+rM3rlBNCU3MlhoJsq0puXuitWMPHb57HIRq5Ntm94ffv4VVIPUQjW+OKdTUvaEWxa7dQkXqXLnEoy9nTF8S+cJjHO3DusiefOfye0DO1DjqnBi558oa7bxd6ixZ3ebKeMWsGa9JdfWx3Db3bhCHtsd+oXpffV5OKDECdcl0Tu23bttHu3btp//79tECL9ejs7KRSqUSpVEr8ih0cHKTOzs6rHIkoEAhQIBC46j4AAJhpYO8AALOJa3LFKqVo27Zt9Pzzz9PevXtpyZIlYv+6devI5/PRnj17vG3Hjx+nvr4+6unpqT0cAADULbB3AIDZyDWt2PX29tIzzzxDP/jBDygajXpxJPF4nEKhEMXjcXrwwQdp+/bt1NLSQrFYjB555BHq6em5pgwxAACYaWDvAACzkWua2D3xxBNERHTXXXeJ7U899RR96UtfIiKib3/722SaJm3dupWKxSJt2bKFvve9713zwFzXINc1yG/LeLGgzXEcpKW5K0uWE3FLXDJkeJjjVDJDMqg5VOasNJf4XC3NsnJ6oqvdkytO0ZPPX5DHU8RxGKbJt7dUqamObnBsXiQYFvu0ii5k6S9qSro4JY4JNLVgl3RuVOiVAhxnE+3isWdDKaE37nLMXSHLi7mtsaVCr20ijjCbResJ0LhMp70L+EwK+E06ceyQ2J4eY/ui9DIeJfnZy2S4tp5hsC0I1nRLKOe4LNLYEB9vsE+WO/nxf/7Yk0fHtfdkxoReNMbxcfHmFk+OxKS7+dw5jqtLts0X+4IxjvX72Y/4vCPvvSP0HM2mnxzgItDnsuNCb/kqjiOMx9i2xrUSU0REoTCXO4lH+D75gvI7JxyuXktJt8UA1DHXNLHTDcuHEQwGadeuXbRr167rHhQAAMw0sHcAgNnIR6pjBwAAAAAA6oe6raVtGgEyDZuCAVlqQ2llTSIhXmaPRGWD51yZU+Nbo5zmbteURSmN8ZK+a7JezieX3Ts6OHDa1dwgK2+WFeBf/QkHUpdUzpN9hqyOns/wvlg0Jvb5tRLnlsHjyBRkCYHTF9nlmkrxdRWNrNBrX8Hz9/kJrZSKkun/o8M8Jn9BcxXPl27pfK5aDiCfR+cJACaD8ZFBquRDtPcHPxLb+wfOebJZ5pCKd96pKWys2ZeKHvZhSDv28u69nuzXSjrdetvHhF7JH/XkdJHtwqm+S0Lv8uWj/J4Cn+vCwBmhd/oM691+2zqx7//u5fIvb7zGhZ0rY5eFXrrIYSR5LeTl1JvSjfyzgxc9OWKz+9bnly5WS8tOjmqu2AWLFgu9z279AhER5XIodwJmB1ixAwAAAABoEDCxAwAAAABoEOrWFeuzDfLbJuW05XciIivI2a+u1s0hV5YV1i2tSnjAz+5Hn09mz/rDnCkVj/G+gaFBoZebzy7XZPcNnnz+0rDQu2n9nZ6cGeJMsFMnZMeLbCblybYlxx6Ps2vWIHZvXDwvK7b3ndWyYgM89liHzLJtb9GOp7lzjRF5L5pH+XGYn+QMtwUJ6W4+eaSaqZcvlAlcHZ9t0v+15SaKhqouHtP0kTnRAcQ0bQpMZEIbBlEoGCBzIsP73/e9Q6+8c2pmBg1mjM5kB4XDEVq+WNbKU9rn3zZZtmpCO0yLf6Mrl22fPyg/4+TjTNCuLs5OvWvLFqEWDWvZpEHuSnHkkOw0c+Lk+3wN8xd7ckHJNQNLC5s5dOKY2HfkBHfQCS9e5ckXLshuGM0Jfp30cxhJuEmG64wMnPXky+dPevLQsLTpBUfLMtaqClxMya/FOz5Z3ZfPy3sOGNM0adVNt3sdmyyb7R0ZBpFVtYMGGZRoTpA1se/wO69T3+ljVz0muH7qdmIHwGzGNAxa1hmj5mj1i9Sy/GSavgnZplA46pWliDaFvJZQ+95+/+oHBACAOsUwDIrFWig40Q7Tsv1k2RNxi4ZZndhN2LtksoPsiTjyM+8fmZHxNjpwxQIAAAAANAhYsQNginCVQe6E96y6Ilf9HaUUkVOZcGMbRE7FT8qsuoV0NxoAAMwGTNOkG5Yvp6amaja1ZbErVpFB6krxf4MoHAmTYVRtYSgUvOrxwEejbid2yVaTwkGTypdlynve4TiTrFbVQ5my9IatlQyJxbhch98nK7Hns1w2IOTTbkdJ3po3X33Vk5eu1Kqen5OdJ0ytG0ZYq/puWbISeyjEsS/ZjIyxy+f5daXCpVWaQvIYd9y2wpODWsmUiiVLujhlLleQ7+cYO3NcfqiSYS5xcNuKm3h7okPoHbx4moiICiV5HiBx3WoHFSIiUiYZE8ZNuYpKxYJXoaLiD5BlXdmH6vZzkdHhUSqEirRp4x1i+x2/8zueHAhwuQ7bks6WK658IiJXabF4JEt8lEtsJ/MltguXz50WeiNa/OzI8IgnnzopQwUuXGL715Ts4h0BaVsMP8fYlSoybvrlfa948qJlaz25u6WmQ4XWySeslWopFmTniVNpjmdu0uyio6S9GhjNeHJb22JPzpXlZ3DvvjeIiKhcRqedD8O0LFq/YSO1tFbLjhlkkDnxQ9Z1FTkTXTuUUlQsF7wOTbFo08wMuMGBKxYAAAAAoEGo2xU7AGY7SilyJ1bgXOWQ61ZXDAyj6o5VisigakFZTw8rdgCA2YYiqlTKXg9jkwwyVNUloZS2YkeKlHKqbyAigxB6MhXU7cRuwQI/NYV8FDfkkv7JfnYfDGpNrEuOdFM2NfGlZXNcFsRxM0LP0hYtR4bY7Tuekcv2hTIfw1IsR5tkSv7gALstzmXZ7ekqmSrf0c7uYcOVZUNGU9xRIhDh60rEo0LPr7ljipqLhWzpbs4WWa+U0TpKuHLB9obuTk/u6uTx9Z+TZQIuD1X/BsUyOk98GEopKhQylLeqhq7iZMma+HsZRJ6bgohobDRFauL5GB8f/8CxQOMTDgcoHArQ5bTsLvPWOwc9OZlkW9ORlJ12ymW2IaOjKd5R063G1mzN/CXsOu1ulrbl/Anu3pDNsOs02dEp9MKtCU+2guz2zOXleefNW+jJAxfOiX3Dl9mezuvi+BqjpldvpqjZSZvtYtmVdiighbkEtLIwpctDQo9MtoUdWqmWUlG6XK8M47doHTxnUcql/jOnKHUldKpSIXKqfxdDKbLUlR+1BrV3tZFlV0MEDKdw1eOBj0bdTuwAmO3o3wPVhvLK267IJYMMUqoagyL1AABgduG6iu1XNcC4KiuXXOWSQUTKICKlCBUBpxbE2AEAAAAANAh1u2IXS/ioKeyj/FBObG9OalleEc60Gh6UmVaFEi+n2352EZRqEptczZ1YdvgYY/lRoRfRMlILOV4+zhdk54mSdjxHk5WS2WmZNF9XLCYrp8di3A0jn2e94ctyTE1N7HIwtKw4oyJXffw2H19PVvPXNMVefMNiPq/W8Hr/fllE8p0T1UbgFQfxYL8O13HIueKOMEz+NauIyL0Sf0JUcSre8p7jwr09FwnYLgV8LhULKbH91Vf3eLIqs92JhaXNKJc5dKSgZdXbNb/dFy3u9uQ1m1Z78rKFXUIv1c/u0oFRtnH+msz8Za3smh0a4jCXtSvXCL2b1q705Gf/338V+2ziLhJlLXylVJJuOlXRPhtBvl4rIMe0eMlST77Uf5x3mNLehbQwl1WruMJAISfDdbrnJYmIqFiE2/DDcCoOvfnqfgpMfMEYStFEBSfy+33UnKh+B1uWRd3L51N44rvbF/Bd9Xjgo1G3EzsAZjtKsWvCdZXXaUK5RGqinZFSihzdFUuYLAMAZhdKKRoeGiTfRHy3SQaZEw7XQDBAllWNr7Msi0zb9CZ0epkeMHngroIZZefOnbR+/XqKRqOUTCbpvvvuo+PHjwudQqFAvb291NraSk1NTbR161YaHJQJHX19fXTvvfdSOBymZDJJX/va16hSQZ09AED9AHsHpgOs2IEZZd++fdTb20vr16+nSqVCf/VXf0V33303HTlyhCKRqqv5q1/9Kv3oRz+i5557juLxOG3bto0+97nP0c9//nMiInIch+69917q7OykV199lS5evEj3338/+Xw++ru/+7sZuS7XVfSrsykKT7i7Tcsk02D3q1ZDthp0POGLvTQGdw8AjUqj2jtFisbGx7xC6yYZXoKEnfdRya2GOZmmSa/8/FUKBqsu23Pnz8/EcBseQ9VZGl46naZ4PE5v/c//StGwjy6feVnsH9NKdKTzPC9NXZYurPSoNmd12j0xEpTp+o7eyaKY8uTxnOx4Edbi1JrCHANXVPK8Oa2LRLnI+wwlF0cjAc4LamqSJV1srVxJ2eEU/4uDNV0utPiEeILjCG2/X+ppVdqHszy+8bQsrfGpzbfzPq2tx3//Hz8UeoMTYX+uq+jsaIHGxsYoFovRZDA0NETJZJL27dtHn/jEJ2hsbIza29vpmWeeoc9//vNERHTs2DFatWoVHThwgDZt2kQ//vGP6fd///fpwoUL1NFR7ZLx5JNP0l/8xV/Q0NAQ+Wvux9W48tyB2cNkPnczxZXn7v/5zhMUDoVoMCUn9heGOb7NLfFn0irL1RlXs2PK4lgyy5bPflCLS563ZJ4nR0jGdo5oJY4OnePY3ldfe0XoXR7iEiJLl3Ac3fo7ZAeNiGbjfvwfPxD7VJm/gjq1siOmJdcdXIev2a91CbL9Mk5r5UqOsTtz7G0+jyM7/Lxx8C1PvvljGz05r7c0IqKuZPX7o1Qu0bP//izsHZgRruW5gysW1BVjY9WaVi0tLUREdPDgQSqXy7R582ZP58Ybb6SFCxfSgQMHiIjowIEDtHbtWs/IERFt2bKF0uk0HT58mK5GsVikdDot/gEAwHQCewemAkzsQN3gui49+uijdOedd9KaNdWsuoGBAfL7/ZRIJIRuR0cHDQwMeDq6kbuy/8q+q7Fz506Kx+Pev+7u7qvqAQDAVAB7B6aKuo2xy2ZsMlwfkSWbBDdF2FXhC/ESfqSm6XQ8zq6JTDqvyTIINZPTyp0UWI76W4Ve0MfL/ZUil0WxbTk39msvfVrTbsOQemGtM4ZZ81eo6C6HEO+MJcJCb2SEXanjmks41iLHnqtwjZf3zrCL+di7/UKvo4WXeTsWaOcypbu5baIDhuO6dHZ08mLCent76dChQ/TKK6/8ZuWPyI4dO2j79u3e63Q6DWMHZoxIxEfhsJ/iNYEx0XYuw1HU7E6w5je532D3mwpp5Y3C0i3nFriUx/g4r9pYYeniSS5LePKyMLuD3zv9vhygwTbOF+aQj/MX+4Raa1vzVWUiolKeXZ/FInehyGalbSlqZUjKRS4DZQelXezo4tCbsxfZ3g/2ybEXMnyu9w+/zeNrbRd6qrm6mqbKk5uxDnsHpoq6ndiBucW2bdto9+7dtH//flqwYIG3vbOzk0qlEqVSKfErdnBwkDo7Oz2dN954QxzvShbZFZ1aAoEABWrqXwEAwHQAewemErhiwYyilKJt27bR888/T3v37qUlS5aI/evWrSOfz0d79nCh1uPHj1NfXx/19PQQEVFPTw+9++67dOnSJU/n5ZdfplgsRqtXryYAAKgHYO/AdFC3K3YX+onCQaJiSrpYo+3spgyGOGM0Lj221NLCl5bJ8rJ9KiU7WYxe9msyb7dcWaXc1ZKHr3QTqO6Q2WT6TNkwOfPVsuWtzjusqWrKD/m0Rt2V3AifNy/H7mjZs6kM7yvVNC8Y0VzRZ07yRaYuy+yvUpbf2BnnX36rFs0XelcOV3Zc+uWZEfoo9Pb20jPPPEM/+MEPKBqNejEi8XicQqEQxeNxevDBB2n79u3U0tJCsViMHnnkEerp6aFNmzYREdHdd99Nq1evpj/+4z+mb33rWzQwMEBf//rXqbe3F79SwawglzlJ5ASJXPlb22ewYRscZNfhe0fOCL2glrXvjyc8uS0p3Z5dbZwJaWvFYVvjMnxDbypT0LrwJJPSZTu/q8WTL2rxXSdOHBV6i0s8gdFdykRE4+N8Xbkcu07TYzLAX3fFOiW2aVYgIvQOH2rz5FKRw1CSSRmXNv9m7o6RbOd9be1y1Ss4cfzCJHSegL0D00HdTuzA3OCJJ54gIqK77rpLbH/qqafoS1/6EhERffvb3ybTNGnr1q1ULBZpy5Yt9L3vfc/TtSyLdu/eTQ8//DD19PRQJBKhBx54gB577LHpugwAAPiNwN6B6QATOzCj/DZlFIPBIO3atYt27dr1oTqLFi2iF198cTKHBgAAkwrsHZgOEGMHAAAAANAg1O2KneNrJccXoLL/drG96HJ8hlnhNPxg3BB6iXaOzWs2OYitJSdT1lMjHJuSGua4unxW3hqnopUN0LpIuBV5vEKe4zD0CuCWLWP2xgv8vnxGxm74FMeFRM0on8uUMSflMo8xEOFfgkGfjLNI+Pl4SynhyWtvkbEpK2++xZMX33CDJ2/YJGP7zl2oxroUSxWiX54hAMBHQ5WK5FpEZs1vbbvMdiPmY5tx8LV9Qm9gkG2hoX3+N2xYJ/Q+3sP29EpxXCKid375utDLFtgmnejjskinzpwRevkc2wal2AYHY7JkSFrrcjM+Oiz2ZdMcw6dbcduSNj0e5bImXVrSQXPrPKGX7OIYua7b1npyS0zaO7/eoUOT9RIuROTZe70jEAD1DFbsAAAAAAAahLpbsbsSg5ArVFeZ8oWS2G/4OGPUdXklzszJX3d2lvXI5GzPbF6usGXzrJfTV9EKMhbCFZmrv2bFrsjHc7RfsJYjU1XzRT5+oVQW+5Ti17a22lgoyfTZov7S4ONZSv7iLGp9JUsVHoevpt9kTrvXGa04aL4ox1ecGMeV49ZZu+HrohGuYa7RCH+zK9eQL1Q9EeWa39oV7bNcKLC3wnGl3dGz9g2tWHm5Ij/jBS0jtahljBZL0s6WNJtU0Y7h1pxXaa/1FTu3plqAq/WiVbXH+JC/Y+1m/dx6ZYJKzTWWy9p1addbKNZUOjCvbcXuSlZsIz13YPZwLX8zQ9XZX/jcuXOoiD3L6O/vF0U2ZyOnTp2iZcuWzfQwwDXQCM8d7N3sA88dmAmu5bmru4md67p04cIFUkrRwoULqb+/n2Kx2G9+YwNzpf1Lvd0LpRSNj49TV1cXmebs9uqnUilqbm6mvr4+isfjv/kNYFK5lme8kZ472LsPAns39biuS8ePH6fVq1fX3X2eC0y1vas7V6xpmrRgwQJKp6uJArFYDA/dBPV4LxplEnTlAxOPx+vuHs8lfttnvJGeO9i7q1OP96KRnrv586uF5+vxPs8Vpsreze6fHQAAAAAAwAMTOwAAAACABqFuJ3aBQIC++c1vovcd4V5MB7jHM8tcv/9z/fp1cC+mB9znmWOq733dJU8AAAAAAIDro25X7AAAAAAAwLWBiR0AAAAAQIOAiR0AAAAAQIOAiR0AAAAAQIOAiR0AAAAAQINQlxO7Xbt20eLFiykYDNLGjRvpjTfemOkhTTk7d+6k9evXUzQapWQySffddx8dP35c6BQKBert7aXW1lZqamqirVu30uDg4AyNuLGYi8/cVDNZz3RfXx/de++9FA6HKZlM0te+9rUPNH6fzczFZw/2bmaZi8/cVFNX9k7VGc8++6zy+/3qn//5n9Xhw4fVl7/8ZZVIJNTg4OBMD21K2bJli3rqqafUoUOH1Ntvv60+/elPq4ULF6pMJuPpfOUrX1Hd3d1qz5496s0331SbNm1Sd9xxxwyOujGYq8/cVDMZz3SlUlFr1qxRmzdvVm+99ZZ68cUXVVtbm9qxY8dMXNKkM1efPdi7mWOuPnNTTT3Zu7qb2G3YsEH19vZ6rx3HUV1dXWrnzp0zOKrp59KlS4qI1L59+5RSSqVSKeXz+dRzzz3n6Rw9elQRkTpw4MBMDbMhwDM3PVzPM/3iiy8q0zTVwMCAp/PEE0+oWCymisXi9F7AFIBnrwrs3fSBZ256mEl7V1eu2FKpRAcPHqTNmzd720zTpM2bN9OBAwdmcGTTz9jYGBERtbS0EBHRwYMHqVwui3tz44030sKFC+fcvZlM8MxNH9fzTB84cIDWrl1LHR0dns6WLVsonU7T4cOHp3H0kw+ePQb2bnrAMzd9zKS9q6uJ3fDwMDmOIy6KiKijo4MGBgZmaFTTj+u69Oijj9Kdd95Ja9asISKigYEB8vv9lEgkhO5cuzeTDZ656eF6n+mBgYGr/m2u7JvN4NmrAns3feCZmx5m2t7ZH2HsYIro7e2lQ4cO0SuvvDLTQwFgUsAzDT4MPBug0ZjpZ7quVuza2trIsqwPZIkMDg5SZ2fnDI1qetm2bRvt3r2bfvKTn9CCBQu87Z2dnVQqlSiVSgn9uXRvpgI8c1PPR3mmOzs7r/q3ubJvNoNnD/ZuusEzN/XUg72rq4md3++ndevW0Z49e7xtruvSnj17qKenZwZHNvUopWjbtm30/PPP0969e2nJkiVi/7p168jn84l7c/z4cerr62v4ezOVzOVnbqqZjGe6p6eH3n33Xbp06ZKn8/LLL1MsFqPVq1dPz4VMEXP52YO9mxnm8jM31dSVvZuE5I9J5dlnn1WBQEA9/fTT6siRI+qhhx5SiURCZIk0Ig8//LCKx+Pqpz/9qbp48aL3L5fLeTpf+cpX1MKFC9XevXvVm2++qXp6elRPT88MjroxmKvP3FQzGc/0lfT/u+++W7399tvqpZdeUu3t7Q1V7mQuPnuwdzPHXH3mppp6snd1N7FTSqnvfve7auHChcrv96sNGzao1157baaHNOUQ0VX/PfXUU55OPp9Xf/qnf6qam5tVOBxWf/iHf6guXrw4c4NuIObiMzfVTNYzfebMGXXPPfeoUCik2tra1J/92Z+pcrk8zVczdczFZw/2bmaZi8/cVFNP9s6YGBAAAAAAAJjl1FWMHQAAAAAAuH4wsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBAwsQMAAAAAaBD+N0XSy/Q4P/1ZAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# We can also support different types of ablations. For example, we can use block or column ablations.\n", + "\n", + "(x_train, y_train), (x_test, y_test) = get_cifar_data()\n", + "for ablation_type in ['block', 'row']:\n", + " art_model = PyTorchDeRandomizedSmoothing(model='vit_small_patch16_224', # Name of the model acitecture to load\n", + " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n", + " optimizer=torch.optim.SGD, # the optimizer to use: note! this is not initialised here we just supply the class!\n", + " optimizer_params={\"lr\": 0.01}, # the parameters to use\n", + " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n", + " nb_classes=10,\n", + " verbose=False,\n", + " ablation_type=ablation_type,\n", + " ablation_size=4, # Size of the retained column\n", + " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n", + " load_pretrained=True) # if to load pre-trained weights for the ViT\n", + " \n", + " # We can see behind the scenes how PyTorchDeRandomizedSmoothing processes input by passing in the first few CIFAR\n", + " # images into art_model.ablator.forward along with a start position to retain pixels from the original image.\n", + " original_image = np.moveaxis(x_train, [1], [3])\n", + "\n", + " ablated = art_model.ablator.forward(torch.from_numpy(x_train[0:10]).to(device), column_pos=6)\n", + " ablated = ablated.cpu().detach().numpy()\n", + "\n", + " # Note the shape:\n", + " # - The ablator adds an extra channel to signify the ablated regions of the input.\n", + " # - The input is reshaped to be 224 x 224 to match the image shape that the ViT is expecting\n", + " print(f\"The shape of the ablated image is {ablated.shape}\")\n", + "\n", + " ablated_image = ablated[:, 0:3, :, :]\n", + " \n", + " # shift the axis to disply\n", + " ablated_image = np.moveaxis(ablated_image, [1], [3])\n", + "\n", + " # plot the figure: Note the axis scale!\n", + " f, axarr = plt.subplots(1,4)\n", + " axarr[0].imshow(original_image[0])\n", + " axarr[1].imshow(ablated_image[0])\n", + " axarr[2].imshow(original_image[1])\n", + " axarr[3].imshow(ablated_image[1])\n", + " plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6ddf5329", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Running algorithm: levine2020\n", + "INFO:art.estimators.classification.pytorch:Inferred 6 hidden layers on PyTorch classifier.\n", + "INFO:art.estimators.certification.derandomized_smoothing.pytorch:MNISTModel(\n", + " (conv_1): Conv2d(2, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1))\n", + " (conv_2): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1))\n", + " (fc1): Linear(in_features=6272, out_features=500, bias=True)\n", + " (fc2): Linear(in_features=500, out_features=100, bias=True)\n", + " (fc3): Linear(in_features=100, out_features=10, bias=True)\n", + " (relu): ReLU()\n", + ")\n", + "Normal Acc 0.965 Cert Acc 0.494: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████| 79/79 [00:02<00:00, 33.61it/s]\n" + ] + } + ], + "source": [ + "# The algorithm is general such that we do not have to supply only ViTs. \n", + "# We can use regular CNNs as well, howevever we will loose the advantages \n", + "# that were discussed at the start of the notebook. Here we will demonstrate it for a simple MNIST case \n", + "# and also illustrate the use of the algorithm in https://arxiv.org/pdf/2002.10733.pdf\n", + "\n", + "class MNISTModel(torch.nn.Module):\n", + "\n", + " def __init__(self):\n", + " super(MNISTModel, self).__init__()\n", + "\n", + " self.device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n", + "\n", + " self.conv_1 = torch.nn.Conv2d(in_channels=2, # input channels are doubled as per https://arxiv.org/pdf/2002.10733.pdf\n", + " out_channels=64,\n", + " kernel_size=4,\n", + " stride=2,\n", + " padding=1)\n", + "\n", + " self.conv_2 = torch.nn.Conv2d(in_channels=64,\n", + " out_channels=128,\n", + " kernel_size=4,\n", + " stride=2, padding=1)\n", + "\n", + " self.fc1 = torch.nn.Linear(in_features=128*7*7, out_features=500)\n", + " self.fc2 = torch.nn.Linear(in_features=500, out_features=100)\n", + " self.fc3 = torch.nn.Linear(in_features=100, out_features=10)\n", + "\n", + " self.relu = torch.nn.ReLU()\n", + "\n", + " def forward(self, x: \"torch.Tensor\") -> \"torch.Tensor\":\n", + " \"\"\"\n", + " Computes the forward pass though the neural network\n", + " :param x: input data of shape (batch size, N features)\n", + " :return: model prediction\n", + " \"\"\"\n", + " x = self.relu(self.conv_1(x))\n", + " x = self.relu(self.conv_2(x))\n", + " x = torch.flatten(x, 1)\n", + " x = self.relu(self.fc1(x))\n", + " x = self.relu(self.fc2(x))\n", + " x = self.fc3(x)\n", + " return x\n", + "\n", + "def get_mnist_data():\n", + " \"\"\"\n", + " Get the MNIST data.\n", + " \"\"\"\n", + " train_set = datasets.MNIST('./data', train=True, download=True)\n", + " test_set = datasets.MNIST('./data', train=False, download=True)\n", + "\n", + " x_train = train_set.data.numpy().astype(np.float32)\n", + " y_train = train_set.targets.numpy()\n", + "\n", + " x_test = test_set.data.numpy().astype(np.float32)\n", + " y_test = test_set.targets.numpy()\n", + "\n", + " x_train = np.expand_dims(x_train, axis=1)\n", + " x_test = np.expand_dims(x_test, axis=1)\n", + "\n", + " x_train = x_train / 255.0\n", + " x_test = x_test / 255.0\n", + "\n", + " return (x_train, y_train), (x_test, y_test)\n", + "\n", + "\n", + "model = MNISTModel()\n", + "(x_train, y_train), (x_test, y_test) = get_mnist_data()\n", + "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0005)\n", + "\n", + "art_model = PyTorchDeRandomizedSmoothing(model=model,\n", + " loss=torch.nn.CrossEntropyLoss(),\n", + " optimizer=optimizer,\n", + " input_shape=(1, 28, 28),\n", + " nb_classes=10,\n", + " ablation_type='column',\n", + " algorithm='levine2020', # Algorithm selection\n", + " threshold=0.3, # Requires a threshold\n", + " ablation_size=2,\n", + " logits=True)\n", + "\n", + "scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[200], gamma=0.1)\n", + "\n", + "# Uncomment to train.\n", + "'''\n", + "art_model.fit(x_train, y_train,\n", + " nb_epochs=400,\n", + " scheduler=scheduler)\n", + "torch.save(art_model.model.state_dict(), 'trained_mnist.pt')\n", + "\n", + "'''\n", + "art_model.model.load_state_dict(torch.load('trained_mnist.pt'))\n", + "acc, cert_acc = art_model.eval_and_certify(x_test, y_test, size_to_certify=5)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/targeted_universal_perturbation.ipynb b/notebooks/targeted_universal_perturbation.ipynb new file mode 100644 index 0000000000..0016543804 --- /dev/null +++ b/notebooks/targeted_universal_perturbation.ipynb @@ -0,0 +1,268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "pPUFFtGZRKhi" + }, + "source": [ + "# ART Targeted Universal Perturbation\n", + "Train a PyTorch classifier on MNIST dataset, then attack it with targeted universal adversarial perturbations.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "UqgvEhS7ReTV" + }, + "outputs": [], + "source": [ + "from torch import nn\n", + "from torch import optim\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from art.attacks.evasion import TargetedUniversalPerturbation\n", + "from art.estimators.classification import PyTorchClassifier\n", + "from art.utils import load_mnist\n", + "\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UXk56AdlRiKg" + }, + "source": [ + "Load the MNIST dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "z4K19tJURgKv" + }, + "outputs": [], + "source": [ + "(x_train, y_train), (x_test, y_test), min_pixel_value, max_pixel_value = load_mnist()\n", + "\n", + "# Swap axes to PyTorch's NCHW format\n", + "x_train = np.transpose(x_train, (0, 3, 1, 2)).astype(np.float32)\n", + "x_test = np.transpose(x_test, (0, 3, 1, 2)).astype(np.float32)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rMFPiLy-SPYB" + }, + "source": [ + "Create the model and train the ART classifier" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "d72RLBmVSo6b" + }, + "outputs": [], + "source": [ + "model = nn.Sequential(\n", + " nn.Conv2d(1, 4, 5),\n", + " nn.ReLU(),\n", + " nn.MaxPool2d(2, 2),\n", + " nn.Conv2d(4, 10, 5),\n", + " nn.ReLU(),\n", + " nn.MaxPool2d(2, 2),\n", + " nn.Flatten(),\n", + " nn.Linear(4 * 4 * 10, 100),\n", + " nn.Linear(100, 10),\n", + ")\n", + "\n", + "criterion = nn.CrossEntropyLoss()\n", + "optimizer = optim.Adam(model.parameters(), lr=0.01)\n", + "\n", + "classifier = PyTorchClassifier(\n", + " model=model,\n", + " clip_values=(min_pixel_value, max_pixel_value),\n", + " loss=criterion,\n", + " optimizer=optimizer,\n", + " input_shape=(1, 28, 28),\n", + " nb_classes=10\n", + ")\n", + "\n", + "classifier.fit(x_train, y_train, batch_size=64, nb_epochs=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0ySgYKa6UPlI" + }, + "source": [ + "Run Targeted Universal Perturbation attack" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gm0L8jpfUToS", + "outputId": "bd9b6d20-fbd3-4f30-db39-14acc440cfc2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Executed in: 2.46 minutes\n" + ] + } + ], + "source": [ + "# Create a one-hot encoded target label array, specifying a specific class as the target for the attack\n", + "TARGET = 0\n", + "y_target = np.zeros([len(x_train), 10])\n", + "for i in range(len(x_train)):\n", + " y_target[i, TARGET] = 1.0\n", + "\n", + "attack = TargetedUniversalPerturbation(\n", + " classifier,\n", + " max_iter=1,\n", + " attacker=\"fgsm\",\n", + " attacker_params={\"delta\": 0.6, \"eps\": 0.01, \"targeted\": True, \"verbose\": False},\n", + ")\n", + "\n", + "start_time = time.time()\n", + "x_train_adv = attack.generate(x_train, y=y_target)\n", + "print(\"Executed in:\", round((time.time()-start_time)/60, 2), \"minutes\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cc2gjvqzUV02" + }, + "source": [ + "Print attack statistics" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Yl880Qs6TVMw", + "outputId": "2eaa573d-ba18-441c-c894-ff0460d7b47e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Attack statistics:\n", + "Fooling rate: 87.79%\n", + "Targeted success rate: 97.41%\n", + "Converged: False\n", + "\n", + "Misclassified train samples: 52728\n", + "Misclassified test samples: 8632\n" + ] + } + ], + "source": [ + "print(\"Attack statistics:\")\n", + "print(f\"Fooling rate: {attack.fooling_rate:.2%}\")\n", + "print(f\"Targeted success rate: {attack.targeted_success_rate:.2%}\")\n", + "print(f\"Converged: {attack.converged}\")\n", + "\n", + "# Evaluate the attack results\n", + "train_y_pred = np.argmax(classifier.predict(x_train_adv), axis=1)\n", + "print(\"\\nMisclassified train samples:\", np.sum(np.argmax(y_train, axis=1) != train_y_pred))\n", + "\n", + "# Generate adversarial examples for test set\n", + "x_test_adv = x_test + attack.noise\n", + "\n", + "# Evaluate the attack results on the test set\n", + "test_y_pred = np.argmax(classifier.predict(x_test_adv), axis=1)\n", + "print(\"Misclassified test samples:\", len(x_test_adv[np.argmax(y_test, axis=1) != test_y_pred]))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AqLt3NQyTW8Q" + }, + "source": [ + "Plot some misclassified samples" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 896 + }, + "id": "vjtAGMrzPe7U", + "outputId": "1cece8ce-1f29-4924-acd6-ee1aebf16583" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA8oAAAP6CAYAAABIFaSZAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAADfaUlEQVR4nOzdd5hV5dn+/WvtMr1Sh6EPIMWGigULWMEeW+yx608Ua6yoNGs0aiwYfTSI3WhiD3bFAmrsDUTpbegwffbscr9/+DLPGmnngj2O+Hw/x5HjiDPnXKvd69rr2hsGzznnDAAAAAAAmJlZqKV3AAAAAACA3xIGZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQD/Z2y//fbmeZ5lZmbaihUrNquW53nmeV6a9uz3b8KECeZ5np122mlpqTd69GjzPM9Gjx6dlnpYW7qv2ZbktNNOM8/zbMKECfLPpPt8bco+bI69997bPM+zSZMm/SrbA4DfOgZlAP8nfPrpp/bNN9+YmVlDQ4M9/vjjLbxHAAAA+K1iUAbwf8I//vEPMzPr2LFjk//Gr+PII4+0adOm2c0339zSuwIAALBRDMoAfvdqa2vtqaeeMjOzxx57zPLy8uzbb7+1Tz/9tIX37P+OwsJC69Onj3Xo0KGldwUAAGCjGJQB/O49++yzVllZadtss43ts88+dtxxx5nZxj9V/uijj+yggw6yoqIiy8vLswEDBtj48ePXmf3hhx/M8zwrLi62+vr69dYcMGCAeZ5nL774YpOvJxIJe+ihh2zvvfe2Vq1aWWZmpnXv3t2GDRtm8+fPX6vOpEmTzPM823vvva22ttZGjhxpffv2tZycHOvWrVtj7qeffrIzzjjDunfvbpmZmZaXl2ddu3a1Qw45xB5++OEmNePxuD3++ON20kknWZ8+faygoMCys7Otd+/eduGFF9qiRYvWeUz+v9v4wQcf2GGHHWZt27a1UCjU+PcrN/T3N5977jk766yzbJtttrHi4mLLysqy7t272xlnnGHTp09f77ncVIsWLbJLL7208Xzl5+fbzjvvbPfee68lEokm2QsuuMA8z7O99tprre+ZmV1zzTXmeZ7tuOOOTa771KlTbdSoUbbHHntYx44dLSMjw1q3bm3777+/PfPMM+vcL/81jcViNmbMGNtqq60sKyvLunTpYldeeWXjNioqKuyyyy6zsrIyy8rKsm7dutno0aPXuY/+v+v69ddf21FHHWVt27a17Oxs22677eyuu+6yZDLZrOdxY6qqquzBBx+0o446ynr16mW5ubmWm5tr2267rV1zzTW2evXqdf5ct27dzPM8mzNnjr377rs2ZMgQKy4utuzsbNtxxx3t0UcfXe82V65caRdffLF17drVMjMzrUuXLjZ8+HBbuXJloH1XbO4a35Tr9vnnn9tJJ51kXbp0sczMTGvVqpUNHTrUJk6cGGjfY7GY3XbbbbbTTjtZfn6+ZWRkWElJie288852xRVXNMv5AoDfBAcAv3N77bWXMzN3xx13OOecmzx5sjMzV1hY6Gpra9f5M88884wLh8POzNw222zjTjjhBLfnnns6z/PcpZde6szM/bKFDhw40JmZe+qpp9ZZ85tvvnFm5tq3b+/i8Xjj1ysrK93ee+/tzMzl5eW5wYMHu2OOOcb17t3bmZlr3bq1++KLL5rUevfdd52ZuV133dXtvPPOLjc31x100EHuuOOOc/vvv79zzrlvv/3WFRQUODNzvXv3dkcddZT74x//6AYOHOjy8vLc9ttv36Tm/PnzG8/Lbrvt5v74xz+6gw8+2JWWljozc23btnU//fTTWsc1ePBgZ2buvPPOc6FQyPXr188df/zxbsiQIe7JJ590zjn38MMPOzNzp5566lo/Hw6HXU5OjhswYIA76qij3OGHH+7Kysqcmbnc3Fw3efLktX5m1KhRzszcqFGj1nmu1+e9995zxcXFzsxct27d3OGHH+6GDh3a+LUhQ4a4hoaGxnwsFnMDBgxwZuauvPLKJrVeffVV53meKygoWOu8nHnmmc7MXJ8+fdzQoUPdcccd5wYOHOhCoZAzM3fJJZestW9rrunAgQPd4MGDXUFBgTv88MPdoYce6goLC52ZuUMPPdStWLHC9e7d27Vt29YdffTRbsiQIS4rK8uZmTv33HPXqnvqqac6M3PDhg1zWVlZrlu3bu64445zQ4YMcRkZGc7M3DHHHONSqVSTn9vQNQt6Hjfmgw8+aFxje+65Z+P+tW7d2pmZ69mzp1u+fPlaP9e1a1dnZu66665znue5nXbayR1//PFut912a7xH77zzzrV+bvHixa5Xr17OzFxxcbE76qij3BFHHOGKiopcjx493OGHH+7MzD388MPyMaR7jW/qdXPOub/97W+Na61///7umGOOcXvuuWfjz40ZM2atn1lzH7/77ruNX0smk26//fZzZuYKCgrcQQcd5E444QS3//77N577L7/8Uj5HALAlYVAG8Ls2ffp0Z2YuGo26pUuXNn69T58+zszco48+utbPlJeXu/z8/CbD9RpvvfVW41Dyy0H5wQcfdGbmhg4dus59ueSSS5yZuT//+c9Nvn7iiSc2DkFLlixp8r0777zTmZnr1auXSyQSjV9fM1SZmdtuu+1ceXn5Wts7/fTTnZm5G264Ya3v1dbWuvfee6/J1yorK92LL77oYrFYk683NDS4q6++2pmZO/jgg9eqteYB28zcuHHj1nnsGxoinn76aVddXd3ka6lUyo0bN86Zmdt6663XGgY2ZVAuLy93rVu3dp7nufvuu88lk8nG7y1fvtztu+++6xwiZs2a5YqKipzneW7ixInOuZ/fVGjTpo0zM/fMM8+sta1Jkya5mTNnrvX1H374wXXq1MmZmfvkk0+afM9/TXfZZZcmg+GcOXMah9Btt93WHXbYYa6mpqbx+59++qmLRCIuFAq5uXPnNqm7ZuBa82aG/02a7777zrVt29aZmbv//vub/Nz6rtmmnscNmT9/vnvrrbea1HLOuZqaGnfKKac07vsvrRnWotGoe/nll9e5/+t6Q+yYY45xZub22msvt3r16savr1ixwu26666N5ytdg/KmrPFNvW6vvfaa8zzPtWnTZq17/Jtvvmlcf5MmTWryvXUNyu+9954zM7fDDju4ysrKtY7r008/XecbGADwe8CgDOB37corr3Rm5o4++ugmX7/11ludmbnBgwev9TM33HCDMzO32267rbPmRRddtM5BubKy0uXk5LhQKOQWLFjQ5HsNDQ2ND7bfffdd49enTp3qPM9zpaWl63wQdc65gw8+2JlZk0HAP1S9//77G/y5X34avalKS0tdKBRaaz/XPGDvu+++6/3ZDQ0RG7LmU/rvv/++ydc3ZVBesxaGDx++zu8vWLDARaNR17Zt27WGlhdeeKHx0/1Zs2a5PfbYY4O1NuSBBx5wZuYuv/zyJl9fc009z3PffvvtWj934YUXNv6pg1++oeKcc4cddpgzM/fII480+fqagatDhw6urq5urZ+75557Gt+M8VvfNduc87gpampqXCQScW3btl3re2sG5UsvvXSdP7vmDTH/PTJv3jwXCoWc53lrrSvnnPvyyy/TPihvyPrW+KZetzWD/r/+9a91bu+ZZ55ZZ09c16C8JnvhhRcGOiYA+D2IGAD8TiUSCXvkkUfMzOyMM85o8r1TTjnFRowYYe+//77NnDnTevTo0fi9Nf+O6EknnbTOuqeeeqrddddda309Pz/fjjnmGHv00Uft0Ucftauvvrrxe//5z39s2bJltssuu9jWW2/d+PWJEyeac84OOuggy8/PX+f29t57b5s4caJNmTLFDj300Cbfa9eune21117r/LlddtnFJk6caMOGDbMxY8bY4MGDLSsra51Zv6+//trefvttmz17ttXU1FgqlTKzn89nKpWyGTNm2A477LDWzx1zzDEbrb0+M2bMsNdee81mzJhhVVVVjX/3csmSJWZmNn36dOvXr98m1zf7+RqYWePfUf+ljh07Wq9evWzq1Kn2008/2VZbbdX4vT/84Q926aWX2h133GE77LCDVVRU2IABA+z2229f7/aqq6vt1VdftS+//NKWL19uDQ0NZmZWXl7eeEzr0qVLF9tmm23W+nqvXr3MzGynnXaydu3arff76/u75Mcee+w6r/+pp55qF1xwgf3000+2aNEiKy0tXe8xmW3eedyYKVOm2AcffGDz5s2z2tpac86ZmVlGRoYtW7bMVq1aZcXFxWv93GGHHbbOen379rUffvjBFi5c2Pi1999/31KplO20007rXFP9+/e37bbbrvGfk0uXTV3jQa7b8uXL7b///a9lZ2ev95zsvffeZvbzud6YHXfc0cLhsI0fP9622morO+qoo/iFfAD+z2BQBvC79Z///McWL15sHTt2tKFDhzb5Xvv27e3ggw+2l156ycaPH2833nhj4/cWLFhgZmbdu3dfZ931fd3s54H80UcftQkTJjQZlNf84qzTTz+9SX7WrFlm9vMvFtvYLxdbtmzZWl/z/+KuX7r88svtww8/tLfeessOPPBAi0ajtv3229ugQYPs+OOPt5133rlJvqamxv70pz/Z888/v8H9qKysXOfXN7Qv65NMJm348OH2wAMPNA5FQbYZxJpzvb43FvyWLVu21oD3l7/8xV577TWbOnWq5ebm2jPPPGMZGRnr/PmXX37ZTj/9dFuxYsV6t7G+Y+rSpcs6v56Xl7fB7695o2V9v0xufes2Pz/fWrdubStWrLAFCxZsdFDe3PO4LkuXLrWjjz7aPvzwww3mKisr1zkor++cFBQUmFnTc7Kx+3vN99I1KG/uGg9y3WbPnm3OOaurq7PMzMwN7te6+skv9ejRw+688067/PLLbfjw4TZ8+HDr2rWrDRw40A499FD74x//uN57AAC2dAzKAH631gye9fX1Nnjw4LW+v+ZTpgkTJtjYsWMtHA5v9jYHDRpkPXr0sB9//NGmTJliu+++uy1dutQmTpxoWVlZdvzxxzfJr/m0tn///rb99ttvsPauu+661teys7PXm8/JybE333zTPv30U3vttddsypQpNmXKFPvss8/sjjvusPPOO8/GjRvXmL/66qvt+eeftz59+tgtt9xiO++8s7Vp06bxQXj33Xe3jz76aL0P+xval/W566677P7777eSkhK74447bPfdd7f27ds3foJ24okn2lNPPbXBAUO15lwfc8wxlpubu8Fs69at1/raJ598Yj/++KOZ/fymwrfffrvOIWbhwoV23HHHWV1dnV1xxRV20kknWbdu3SwvL89CoZC98cYbNnTo0PUeUyi04X+QYmPf3xzKed7c87guZ511ln344Yc2cOBAGzNmjG2//fZWXFxs0WjUzMxKS0utvLx8k89ZS/o11vian11zbfLy8uzoo4/e/J23n3/z+7HHHmsvvfSSffjhh/bhhx/a008/bU8//bSNGjXKPvjgAz5lBvC7xKAM4HepvLy88Z9BWbFihU2ePHm92UWLFtlrr71mhxxyiJn9/EdHf/jhB5szZ8468+v7upk1/hNI1113nT388MO2++672+OPP26JRMKOPfZYKyoqapLv3LmzmZntsccedu+99+oHGMDOO+/c+OlxIpGwF154wU455RS777777JhjjrF99tnHzKzxny365z//adttt91adX766ae079uabT7wwAN2+OGHN+s2O3fubD/99JNdeeWVNmDAgEA/u3z5cjv++OMtkUjY6aefbhMmTLDTTjvNvvzyS+vatWuT7Msvv2x1dXV25JFH2l/+8pe1ajXHeVTMnj17nV+vqqpq/OS7U6dOG62zOedxXWpqamzixIkWCoVs4sSJa90jNTU1tnjx4s3ezhodO3Y0sw3fxxv6XlCbu8aDXLc1/cTzPBs/fnza3kBo3769nX322Xb22Web2c//HN4ZZ5xhH330kV111VWNf8UFAH5PfrtvwQLAZpgwYYIlk0nbddddzf38iwvX+b8rrrjCzJr+m8prPn1+4okn1ll7Q/82q9nP/25tKBSyZ555xmpra9f7x67NzA466CAzM3vppZc2+O8vp0skErFjjjmm8Y+if/XVV43fW/Pvof5y8DMze/3112358uVp358NbfP7779vsn+ba825Xt+/Y7w+zjn705/+ZAsWLLBTTjnFxo8fb3/+859t1apVdtxxx1k8Hm+S39AxOefsySef3MQj2DzPPvusxWKxtb7+2GOPmZlZz549G4fIDdnU87g+FRUVlkwmraCgYK0h2czs8ccfT8ufKFhj0KBB5nmeffHFF/bDDz+s9f2vv/46rX8/eXPXeJDrVlpaatttt51VVVXZa6+9tpl7vn59+vSxK6+80swsrfcoAPyWMCgD+F0aP368mf38C2825JRTTjEzs1deeaXx7+ydeeaZlpeXZx999JHdfffdTfKTJk2y+++/f4M1O3XqZAcccIBVVlbaiBEj7LvvvrMuXbrYvvvuu1Z2hx12sKOPPtrmz59vRx111Do/yaqpqbEnnnii8Zf+qO677751/sKoxYsX22effWZmTR/e+/bta2Zm99xzT5P89OnT7dxzzw20bdWabY4bN67xj42a/fwnAk455RRLJBJp29bll19uRUVFdscdd9jtt9/e+Mu1/GbPnm2PP/54k6/dfPPN9tprr1m/fv3svvvua/zawIED7ZNPPml8s+WXx/Svf/2r8Rd3mf38d1VHjhwp/RKl5rBo0SK77LLLGn+JlJnZtGnTbOzYsWZmdskll0h1NvU8rk/79u2tuLjYVq9e3Tj8rfHxxx83+bv+6dClSxc78sgjLZVK2bBhw5r83eBVq1bZeeedl9bBfHPXeNDrdsMNN5jZz2/Mvfzyy2vVc87ZJ598Ym+88cZG9/2dd96xiRMnrvVmkHPOXnnlFTNb9xsAAPB7wKAM4HfnvffesxkzZlhmZuZafyf4l7beemvbcccdLR6PN35SXFpaag8++KCFw2G76KKLbLvttrMTTzzRBg8ebPvuu680NK759HjNb8de8ynzujz88MO233772auvvmq9e/e2XXbZxY477jg79thjbZdddrFWrVrZySefbKtWrQpyGux//ud/rE+fPlZWVmaHH364nXzyyTZ06FArKyuzBQsW2L777tvkj4KOGjXKPM+z6667zrbbbjs74YQTbL/99rNtt93WysrKbPfddw+0fcWIESMsIyPDHnzwQevdu7cdd9xxdtBBB1mPHj0sFovZkUcembZtderUyV588UUrLi62yy67zDp37mz77befnXzyyXbYYYdZz549raysrMkfgX///fdt5MiRlpOTY88++2zj38mNRCL29NNPW6tWrexvf/ubvfjii40/c9hhh9lOO+1kCxYssK222soOPfRQO+6446xHjx72l7/8pfGTuF/bueeeaw899JD16tXLTjjhBDvwwAOtf//+tmTJEjvyyCNt2LBhUp1NOY8bEg6HbeTIkWb28xtXu+22m5144om255572u67726HHnpo2oexcePGWY8ePWzSpEnWvXt3O/roo+2oo46ysrIyW7JkyTr/iPSm2tw1HvS6HXbYYXbXXXfZypUr7fDDD7devXrZoYceaieddJINGTLESkpKbLfddrN33nlno/v+zTff2CGHHGJt2rSxffbZx0466SQ76qijrHv37vbQQw9ZYWFh48AOAL83DMoAfnfW/DHqww47bJ2/IfeX1nyq7P/j18cff7xNmjTJhg4danPnzrUXX3zRqqqq7P7777c77rhjozWPOOIIa9WqlZn9799bXp/8/Hx744037Mknn7T999/f5s2bZ88//7y98847VldXZyeddJI9//zzTf4JK8WNN95ow4YNs6KiIvv444/t2WeftalTp9quu+5qjzzyiL322msWifzvr6o46qij7L333rP99tvPysvL7aWXXrKlS5fa6NGj7dVXX238xUrptOuuu9pnn31mhx9+uNXU1NhLL71kM2fOtAsuuMA++uijxt9anC6DBg2y77//3q677jrr1KmTffrpp/bss8/aV199Ze3bt7dRo0bZgw8+aGY//1bgE044wZLJpI0bN26tf7qnS5cuNmHCBPM8z04//fTGPw0QiURs0qRJNmLECOvYsaO9/fbbNmnSJNthhx3so48+sgMPPDCtx6TaddddbcqUKbbNNtvYm2++aZMmTbJevXrZHXfcYc8884x5nifXCnIeFRdffLG98MILtvvuu9v06dPt5ZdftlgsZuPGjWuWv/9aUlJin3zyiV1wwQWWk5Njr7zyin366ad2/PHH28cffyz1DdXmrvFNuW4XXnih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V+A69Md1oz+ipdAf0+PCCy+0nXbayTIzMzd5KBs9enTjeY9EItatWze75JJLrLq6Or07uw533XWXTZgwQcr+2kPjlrwu5s2bZ4cccojl5ORYu3bt7PLLL/9VXu+aS6Sld2BTlZeXN/7/f/7znzZy5EibPn1649fy8vIa/79zzpLJpEUiW+zhNovjjjvODjzwwCZfO+2006y+vt7atWsXqNaBBx5oDz/8sMViMZs4caKdf/75Fo1G7eqrr14r29DQYBkZGZu172u0atUqLXXwvyorK23IkCG2//772/3332/ffvutnXHGGVZUVGTnnHNOS+8eBPTHzff5559bu3bt7PHHH7fOnTvblClT7JxzzrFwOGzDhw8PVIv++PtBf9zy0R/T54wzzrBPPvnEvvnmm02usfXWW9tbb71liUTCJk+ebGeccYbV1tbaAw88sFY2nf2xsLAwLXXwv5LJpB1yyCFWUlJiU6ZMsfLycjvllFMsGo3aTTfd1NK7t0m22E+US0pKGv9XWFhonuc1/vcPP/xg+fn59uqrrza+2/Xhhx+u849ZXHzxxbb33ns3/ncqlbKbb77ZunfvbtnZ2bb99tvbv/71r83a1yuvvNK22mory8nJsbKyMrvuuussHo+vlXvggQesc+fOlpOTY8cee6xVVFQ0+f5DDz1kffv2taysLOvTp4/dd999m7Vf2dnZTc5jOBy2d955x84888zAtTIzM62kpMS6du1qw4YNs/33399eeuklM/vfP95y4403WmlpqfXu3dvMzObPn2/HHnusFRUVWatWrewPf/iDzZkzp7FmMpm0Sy+91IqKiqx169Z2xRVXmHOuyXZ/+a5bLBazK6+80jp37myZmZnWs2dP+8c//mFz5syxffbZx8zMiouLzfM8O+2008xMu+YTJ060rbbayrKzs22fffZpsp+bIplM2plnntm4zd69e6/306oxY8ZY27ZtraCgwM4991xraGho/F5zrNcnnnjCGhoabPz48bb11lvb8ccfbxdeeKHdcccdm1UXvx764+b3xzPOOMPuuusuGzx4sJWVldnJJ59sp59+uj333HOBa9Efg6E/ojnRHze/P5qZ3X333Xb++edbWVnZZtWJRCJWUlJinTp1suOOO85OOumkxv645o8QP/TQQ9a9e3fLysoyM7PVq1fbWWed1Xjv77vvvvb11183qXvLLbdY+/btLT8/384880yrr69v8v1fXtNUKmW33nqr9ezZ0zIzM61Lly524403mplZ9+7dzcxshx12MM/zmlz3jZ3b//73v7bDDjtYVlaWDRgwwL788svNOl9mv9118cYbb9jUqVPt8ccft/79+9tBBx1k119/vY0bN65Jb96SbLGDsuKqq66yW265xaZNm2bbbbed9DM333yzPfroo3b//ffb999/b5dccomdfPLJ9t57723yfuTn59uECRNs6tSpdtddd9mDDz5od955Z5PMjBkz7JlnnrGXX37ZXnvtNfvyyy/tvPPOa/z+E088YSNHjrQbb7zRpk2bZjfddJNdd9119sgjj6x3u926dQv0R4geffRRy8nJsWOOOSbwMf5SdnZ2k5vi7bfftunTp9ubb75pr7zyisXjcRs6dKjl5+fbBx98YJMnT7a8vDw78MADG3/u9ttvtwkTJtj48ePtww8/tJUrV9rzzz+/we2ecsop9tRTT9ndd99t06ZNswceeMDy8vKsc+fO9u9//9vMzKZPn27l5eWND14bu+bz58+3o446yg477DD76quv7KyzzrKrrrpqs85PKpWyTp062bPPPmtTp061kSNH2ogRI+yZZ55pknv77bdt2rRpNmnSJHvqqafsueeeszFjxjR+f1PWq+d5G/zjRh999JENGjSoybu2Q4cOtenTp9uqVas2/aDxm0J/DNYfzcwqKirS8ikt/XHD6I9oafTH4P0xXX7ZH2fMmGH//ve/7bnnnmv8o89//OMfbenSpfbqq6/a559/bjvuuKPtt99+tnLlSjMze+aZZ2z06NF200032WeffWYdOnTY6BB49dVX2y233GLXXXedTZ061Z588klr3769mf087JqZvfXWW1ZeXt74hunGzm11dbUdeuih1q9fP/v8889t9OjRdtlll232OfqtrouPPvrItt1228bzZvZzf6ysrLTvv/9+0w+4JbnfgYcfftgVFhY2/ve7777rzMy98MILTXKnnnqq+8Mf/tDkaxdddJEbPHiwc865+vp6l5OT46ZMmdIkc+aZZ7oTTjhhvdsfPHiwu+iii+T9ve2229xOO+3U+N+jRo1y4XDYLViwoPFrr776qguFQq68vNw551yPHj3ck08+2aTO9ddf7wYOHOicc2727NnOzNyXX37Z+P19993X3XPPPfJ+9e3b1w0bNkzOr+E/r6lUyr355psuMzPTXXbZZY3fb9++vYvFYo0/89hjj7nevXu7VCrV+LVYLOays7Pd66+/7pxzrkOHDu7WW29t/H48HnedOnVqcg3953769OnOzNybb765zv1csy5WrVrV+DXlml999dWuX79+Tb5/5ZVXrlXrl7p27eruvPPO9X7/l84//3x39NFHN/73qaee6lq1auVqamoav/b3v//d5eXluWQyKe37uo65d+/e7rnnnlvvfhxwwAHunHPOafK177//3pmZmzp1qnw8+G2gP6anP06ePNlFIpHG/qSiP64b/RG/BfTHze+Po0aNcttvv718DBv62c8++8y1adPGHXPMMY3fj0ajbunSpY2ZDz74wBUUFLj6+vomtXr06OEeeOAB55xzAwcOdOedd16T7++6665NtuW/ppWVlS4zM9M9+OCD69zPdZ2jNdvc0Ll94IEHXOvWrV1dXV3j9//+97+vs5bflrouzj77bDdkyJAmX6upqXFm5iZOnCgfz2/J7/ovXQwYMCBQfsaMGVZbW2sHHHBAk683NDTYDjvssMn78c9//tPuvvtumzlzplVXV1sikbCCgoImmS5duljHjh0b/3vgwIGWSqVs+vTplp+fbzNnzrQzzzzTzj777MZMIpHY4N+xePvtt+V9/Oijj2zatGn22GOPBTiy//XKK69YXl6exeNxS6VSduKJJzZ512nbbbdt8g78119/bTNmzLD8/Pwmderr623mzJlWUVFh5eXltuuuuzZ+LxKJ2IABA9b644VrfPXVVxYOh23w4MHyfivXfNq0aU32w+zn67O5xo0bZ+PHj7d58+ZZXV2dNTQ0rPULMbbffnvLyclpst3q6mqbP3++VVdXb9J6/eGHHzZ737Hloz/q/fG7776zP/zhDzZq1CgbMmRIgKP7Gf0xOPojWhL9Ue+Pm+vbb7+1vLw8SyaT1tDQYIcccojde++9jd/v2rWrtW3btvG/v/76a6uurrbWrVs3qVNXV2czZ840s5/70rnnntvk+wMHDrR33313nfswbdo0i8Vitt9++8n7XVNTs9Fzu+ZPJKz5I+Nr9mNz/V9YF78Vv+tBOTc3t8l/h0KhtR4i/H+mf81v2fvPf/7TZHGZ/fx3zDbFRx99ZCeddJKNGTPGhg4daoWFhfb000/b7bffLtdYs18PPvjgWg8k4XB4k/brlx566CHr37+/7bTTTpv08/vss4/9/e9/t4yMDCstLV3rF1/88lpUV1fbTjvtZE888cRatfwNMYjs7OzAP9Mc11zx9NNP22WXXWa33367DRw40PLz8+22226zTz75RK7RXPteUlJiS5YsafK1Nf9dUlKyyXXx20J/1EydOtX2228/O+ecc+zaa6/dpBr0x2Doj2hp9MdfT+/eve2ll16ySCRipaWla/2yrnX1xw4dOtikSZPWqlVUVLRJ+7A5/fHXPre/5XVRUlLS+MfU19jS++PvelD+pbZt29p3333X5GtfffWVRaNRMzPr16+fZWZm2rx58wK9674hU6ZMsa5du9o111zT+LW5c+eulZs3b54tWrTISktLzczs448/tlAoZL1797b27dtbaWmpzZo1y0466aS07JdfdXW1PfPMM3bzzTdvco3c3Fzr2bOnnN9xxx3tn//8p7Vr126td8HW6NChg33yySc2aNAgM/v5na41fxdlXbbddltLpVL23nvv2f7777/W99c032Qy2fg15Zr37du38RdLrPHxxx9v/CA3YPLkybb77rs3+fsia94J9fv666+trq6usYl//PHHjX+nsFWrVmlfr2Y/v+t4zTXXWDweb7w33nzzTevdu7cVFxenbTv4baE/ru3777+3fffd10499dTGX+qyKeiPwdAf8VtDf2w+a/7ZPNWOO+5oixcvbvznpNalb9++9sknn9gpp5zS+LUN9aVevXpZdna2vf3223bWWWetcx/NmvZH5dz27dvXHnvsMauvr2/8VHlz++NveV0MHDjQbrzxRlu6dGnjv57z5ptvWkFBgfXr1y9t2/k1/Z8alPfdd1+77bbb7NFHH7WBAwfa448/bt99913jH4vJz8+3yy67zC655BJLpVK25557WkVFhU2ePNkKCgrs1FNPXW/tZcuWrfXvq3Xo0MF69epl8+bNs6efftp23nln+89//rPOX7iSlZVlp556qv31r3+1yspKu/DCC+3YY49tfAdmzJgxduGFF1phYaEdeOCBFovF7LPPPrNVq1bZpZdeus592m+//ezII4/c6D9l8s9//tMSiYSdfPLJG8yl00knnWS33Xab/eEPf7CxY8dap06dbO7cufbcc8/ZFVdcYZ06dbKLLrrIbrnlFuvVq5f16dPH7rjjjg3+G5/dunWzU0891c444wy7++67bfvtt7e5c+fa0qVL7dhjj7WuXbua53n2yiuv2MEHH2zZ2dnSNT/33HPt9ttvt8svv9zOOuss+/zzz+V/e2/hwoVrrYuuXbtar1697NFHH7XXX3/dunfvbo899ph9+umnjb9ZcY2GhgY788wz7dprr7U5c+bYqFGjbPjw4RYKhTZ5vfbp08duvvlmO/LII9f5/RNPPNHGjBljZ555pl155ZX23Xff2V133bXWL4rA7wv9sanvvvvO9t13Xxs6dKhdeumltnjxYjP7+d32Tf1UV0V/pD/it4X+uLYZM2ZYdXW1LV682Orq6hqPoV+/fmn7J5zWZf/997eBAwfaEUccYbfeeqtttdVWtmjRIvvPf/5jRx55pA0YMMAuuugiO+2002zAgAG2xx572BNPPGHff//9en9Dd1ZWll155ZV2xRVXWEZGhu2xxx62bNky+/777+3MM8+0du3aWXZ2tr322mvWqVMny8rKssLCwo2e2xNPPNGuueYaO/vss+3qq6+2OXPm2F//+lfpOLfEdTFkyBDr16+f/elPf7Jbb73VFi9ebNdee62df/75zfqnkJpVi/4N6TRZ3y9jWNcvEhk5cqRr3769KywsdJdccokbPnx44y9jcO7nX7byt7/9zfXu3dtFo1HXtm1bN3ToUPfee++td/uDBw92ZrbW/66//nrnnHOXX365a926tcvLy3PHHXecu/POO5vs75pfZnDfffe50tJSl5WV5Y455hi3cuXKJtt54oknXP/+/V1GRoYrLi52gwYNavylI+v6S/ddu3Z1o0aN2uj5GzhwoDvxxBPX+b01dd999931/vy6fsmF8v3y8nJ3yimnuDZt2rjMzExXVlbmzj77bFdRUeGc+/mX01x00UWuoKDAFRUVuUsvvdSdcsop6/1lNc45V1dX5y655BLXoUMHl5GR4Xr27OnGjx/f+P2xY8e6kpIS53meO/XUU51z2jV/+eWXXc+ePV1mZqbba6+93Pjx46VfVrOudfHYY4+5+vp6d9ppp7nCwkJXVFTkhg0b5q666qp1/qKJkSNHNq6fs88+u8kvsNjYvq/rXjAz9/DDD693v51z7uuvv3Z77rmny8zMdB07dnS33HLLBvP47aI/blp/HDVq1Dr3u2vXro0Z+uPP6I/0xy0V/XHTnx/Xt++zZ89uzGzsftrYLwJb3/crKyvdBRdc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6KKLbM2aNfXqzpgxw9q1a2fJyck2bNgwq6qqqvf4F49pKBSymTNnWkFBgcXFxVmHDh1s2rRpZmbWqVMnMzM766yzzPO8esf9ZPv2zTfftLPOOsvi4+Otd+/e9u67736p/WV26p4XjTlXnCpO20FZcccdd9iMGTOsuLjYunfvLv2bu+66yxYuXGgPPfSQrV271saMGWM//elP7eWXX27weiQnJ9ujjz5q69ats9mzZ9vDDz9s9913X71MSUmJPfXUU/bss8/aiy++aO+++67deOON4ccXL15sEydOtGnTpllxcbFNnz7dJkyYYH/4wx+Ou9yOHTue8EeIVq9ebWeeeaa1a9cu/L3+/ftbeXm5rV27tsHba2aWkJBQ76JYsWKFbdiwwZYvX27PPfec1dbWWv/+/S05OdleeeUVW7VqlSUlJdmAAQPC/+6ee+6xRx991ObPn2+vvvqq7d271/7yl7+ccLmDBg2yxx9/3B544AErLi62efPmWVJSkuXk5NjTTz9tZmYbNmywsrKy8I3XyY75jh077Morr7Qf/OAH9t5779nw4cPtjjvu+FL7JxQKWXZ2tv3pT3+ydevW2cSJE+1Xv/qVPfXUU/VyK1assOLiYnvppZfs8ccftyVLltjkyZPDjzfkfPU874Q/brR69Wq74IIL6r1q279/f9uwYYPt27ev4RuNUwr98cT90cxs3bp1NmXKFFu4cKEFAo33dEl/PDH6I5ob/fHk/bGpfLE/lpSU2NNPP21LliwJ/+jzj3/8Y9uzZ4+98MIL9s4771ivXr3s4osvtr1795qZ2VNPPWV33nmnTZ8+3d5++23LzMw86RA4btw4mzFjhk2YMMHWrVtnjz32WPj++M033zQzs3/84x9WVlZmS5YsMbOT79uKigr7/ve/b926dbN33nnH7rzzzi/1ousRp+p50ZRzRbNxXwMLFixwqamp4a9XrlzpzMz99a9/rZcbPHiwu/zyy+t9b/To0a5v377OOeeqqqpcYmKie+211+plhg0b5v77v//7uMvv27evGz16tLy+s2bNcmeffXb460mTJrmoqCj30Ucfhb/3wgsvuEAg4MrKypxzzuXn57vHHnusXp2pU6e6Pn36OOec27JlizMz9+6774Yfv+iii9xvf/vb467HiBEj3KWXXlrve4cOHXJm5pYuXSpvj3+/hkIht3z5chcXF+duvfXW8OPt2rVz1dXV4X+zaNEiV1RU5EKhUPh71dXVLiEhwS1btsw551xmZqabOXNm+PHa2lqXnZ1d7xj69/2GDRucmbnly5cfcz2PnBf79u0Lf0855uPGjXPdunWr9/jYsWOPqvVFubm57r777jvu4180atQo91//9V/hrwcPHuxatWrlDh06FP7e3LlzXVJSkgsGg9K6H2ubi4qK3JIlS467Hpdccom77rrr6n1v7dq1zszcunXr5O3BqYH+2LD+WFVV5bp37+4WLVrknDv2taSgPx4b/RGnAvpjw/qj36RJk1yPHj3kbTjRv3377bddenq6+9GPfhR+PCYmxu3ZsyeceeWVV1xKSoqrqqqqVys/P9/NmzfPOedcnz593I033ljv8XPOOafesvzHtLy83MXFxbmHH374mOt5rH10ZJkn2rfz5s1zrVu3dpWVleHH586de8xafqfredFYc8Wp5Gv9Sxe9e/eOKF9SUmKHDx+2Sy65pN73a2pq7Kyzzmrwejz55JP2wAMPWGlpqVVUVFhdXZ2lpKTUy3To0MHat28f/rpPnz4WCoVsw4YNlpycbKWlpTZs2DAbMWJEOFNXV3fC37FYsWJFg9c5Us8995wlJSVZbW2thUIhu+aaa+q96nTmmWfWewV+zZo1VlJSYsnJyfXqVFVVWWlpqR04cMDKysrsnHPOCT8WHR1tvXv3PurHC4947733LCoqyvr27Suvt3LMi4uL662H2efH58uaM2eOzZ8/37Zv326VlZVWU1Nz1Adi9OjRwxITE+stt6Kiwnbs2GEVFRUNOl/Xr1//pdcdpz/644n747hx46xr167205/+tIFb9m/0x8jRH9Gc6I9f3f3jBx98YElJSRYMBq2mpsYuu+wy+93vfhd+PDc319q0aRP+es2aNVZRUWGtW7euV6eystJKS0vN7PO+NHLkyHqP9+nTx1auXHnMdSguLrbq6mq7+OKL5fU+dOjQSfftkZ9IOPIj40fW48v6v3BenCq+1oNyixYt6n0dCASOuonw/0z/kU/Ze/755+udXGaf/45ZQ6xevdoGDhxokydPtv79+1tqaqo98cQTds8998g1jqzXww8/fNQNSVRUVIPWy+zz39M58uMkR3z88cfhxyJx4YUX2ty5cy02NtaysrKO+uCLLx6LiooKO/vss23x4sVH1fI3xEgkJCRE/G+a4pgrnnjiCbv11lvtnnvusT59+lhycrLNmjXL3njjDblGU617RkZG+Dw4oqHnBU5d9McT++c//2kffPBB+HcMj+yb9PR0Gz9+fL0f8T0Z+mNk6I9obvTHr05RUZE988wzFh0dbVlZWUd9WNex+mNmZqa99NJLR9VKS0tr0Dp8mf74Ve/bU/m8aMy54lTxtR6Uv6hNmzb24Ycf1vvee++9ZzExMWZm1q1bN4uLi7Pt27dH9Kr7ibz22muWm5tr48ePD39v27ZtR+W2b99uu3btsqysLDMze/311y0QCFhRUZG1a9fOsrKybPPmzTZw4MBGWS+zz19dmjZtmu3Zs8fatm1rZmbLly+3lJQU69atW0S1WrRoYQUFBXK+V69e9uSTT1rbtm2PehXsiMzMTHvjjTfsggsuMLPPX+k68rsox3LmmWdaKBSyl19+2fr163fU40eabzAYDH9POeZdu3YNf7DEEa+//vrJN/IEVq1aZeeee2693xc58kqo35o1a6yysjLcxF9//fXw7xS2atWq0c9Xs8/Pi/Hjx1ttbW342li+fLkVFRVZy5YtG205OLXQH+t7+umnrbKyMvz1W2+9ZUOHDrVXXnnF8vPzI6pFf4wM/RGnGvpj0znyZ/NUvXr1st27d4f/nNSxdO3a1d544w0bNGhQ+Hsn6kuFhYWWkJBgK1assOHDhx9zHc3q90dl33bt2tUWLVpkVVVV4XeVv2x/PJXPi8acK04V/6cG5YsuushmzZplCxcutD59+tgf//hH+/DDD8M/FpOcnGy33nqrjRkzxkKhkH3729+2AwcO2KpVqywlJcUGDx583NqffPLJUX9fLTMz0woLC2379u32xBNP2H/8x3/Y888/f8wPXImPj7fBgwfb3XffbeXl5XbzzTfbVVddFX4FZvLkyXbzzTdbamqqDRgwwKqrq+3tt9+2ffv22S233HLMdbr44ovtiiuusJtuuumYj1966aXWrVs3+9nPfmYzZ8603bt3269//WsbNWpUk75bYGY2cOBAmzVrll1++eU2ZcoUy87Otm3bttmSJUvs9ttvt+zsbBs9erTNmDHDCgsLrUuXLnbvvfee8G98duzY0QYPHmxDhw61Bx54wHr06GHbtm2zPXv22FVXXWW5ubnmeZ4999xz9r3vfc8SEhKkYz5y5Ei755577LbbbrPhw4fbO++8I//tvZ07dx51XuTm5lphYaEtXLjQli1bZp06dbJFixbZW2+9Ff5kxSNqamps2LBh9utf/9q2bt1qkyZNsptuuskCgUCDz9cuXbrYXXfdZVdcccUxH7/mmmts8uTJNmzYMBs7dqx9+OGHNnv27KM+KAJfL/TH+r44DH/66adm9vmNT0PftVDRH+mPOLXQH49WUlJiFRUVtnv3bqusrAxvQ7du3RrtTzgdS79+/axPnz72wx/+0GbOnGmdO3e2Xbt22fPPP29XXHGF9e7d20aPHm1Dhgyx3r1723nnnWeLFy+2tWvXHvcTuuPj423s2LF2++23W2xsrJ133nn2ySef2Nq1a23YsGHWtm1bS0hIsBdffNGys7MtPj7eUlNTT7pvr7nmGhs/fryNGDHCxo0bZ1u3brW7775b2s7T8bxozrmiyTTnL0g3luN9GMOxPkhk4sSJrl27di41NdWNGTPG3XTTTeEPY3Du8w9buf/++11RUZGLiYlxbdq0cf3793cvv/zycZfft29fZ2ZH/Td16lTnnHO33Xaba926tUtKSnJXX321u+++++qt75EPM3jwwQddVlaWi4+Pdz/60Y/c3r176y1n8eLFrmfPni42Nta1bNnSXXDBBeEPHTnWL93n5ua6SZMmnXDfbd261X33u991CQkJLj093f3yl790tbW14ceP1F25cuVxaxzrQy6Ux8vKytygQYNcenq6i4uLc3l5eW7EiBHuwIEDzrnPP5xm9OjRLiUlxaWlpblbbrnFDRo06LgfVuOcc5WVlW7MmDEuMzPTxcbGuoKCAjd//vzw41OmTHEZGRnO8zw3ePBg55x2zJ999llXUFDg4uLi3Pnnn+/mz58vfVjNsc6LRYsWuaqqKjdkyBCXmprq0tLS3A033ODuuOOOY37QxMSJE8Pnz4gRI+p9gMXJ1v1Y14KZuQULFhx3vZ1zbs2aNe7b3/62i4uLc+3bt3czZsw4YR6nLvpjw/uj37H2G/3xc/RH+uPpiv7Y8P54vHXfsmVLOHOy6+lkHwR2vMfLy8vdz3/+c5eVleViYmJcTk6OGzhwoNu+fXs4M23aNJeenu6SkpLc4MGD3e23337cD/NyzrlgMOh+85vfuNzcXBcTE+M6dOjgpk+fHn784Ycfdjk5OS4QCNQ77ifat845t3r1atejRw8XGxvrevbs6Z5++mnpw7xO1/PiZHPF6cZz7jif/AGY2cqVK+3KK6+0zZs382NlAOBDfwSAY9uyZYt17tzZ1q1bZ4WFhc29OkCDfK3/jjK+vKVLl9qvfvUrbgIB4AvojwBwbEuXLrXrrruOIRmnNd5RBgAAAADAh3eUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAJ9oNdg/eUgTrsbJlT5SIOW8gJNr5g3f0tDVaRyhkBTbPD+/0RedN7S00WtG4sAPzpSzqc9+0IRrcmJNse9PJ+p54sXIrUTmauvk7LKDjzb68iPRP2lwsy6/dL7YHz29ZnP3CHNaL9+8QNv2SORdW9LoNSNx4D+7y9nUZ95vwjU5sabY96cT9TzxopugP9ZF0B8r/tDoy4/EJedObdblb7pR2/+ep98/5j+o3b81FU/sjyWjohp92QVzgo1eMxKfnNVCzrZ591ATrsmJNcW+P52o54kLRHBjIvJC+rW8/LUJJ3ycd5QBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPCJlpMBfabe/EinhqzLiYW0WN7QUr2muE1Nsj0RcCFPzhbcsFWrGcHyN87rLOU6X79Rrrk/Xz+fPn2kQMp5AX2r1PMkovOpmZWK+8k5/XzaPD9fyuVfv02uqfJiYxu9ZpPxGn+fRkQ89fOuLdFritvUJNsTARdBMyu4bqtWM4Llb/yfIinX+boNcs2I+uN8sT/qp6h8nkR0PjWzUnE/uVAE9zoLtJr5I7bKNVVebEyj12wqLoKTr/TGJnj/RrygC+YE9ZLiNjXJ9kQgkuf7gof07VdtvF47TzvPq5VrHs7Ul7+pjzbmeJ7e9dXzJJLzqbltulEcByN4ciwZFSXl8ueKA14EXFTjXXe8owwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgI/nnHNKsPBPUxt94bXV0XK2yy93SDlXVaWvQCikZ0Wb5+fL2dqPE6Rc0fh1+go0wTZZQHs9ZdNDBXLJ4CH92HebJB77ykq5Zum8XCmXN7RUrvl15CXES7nSOe2beE1ObNOPJzTr8puiP9bVRMnZojHbpZyrjKA/ak8NEdm8QO8Rcn8c96G+Ak2wTeZ5UmzTw4VyyWBFjJztNlE89ocj6I8Pd5RyedeWyDW/juT++GB2E6/JiTV3f+y0eHqj1wzV6v2x4A9BKefV6PdPXhP0kpJR+ja5fbFSruBxvec3xTY5sT+WXK/fE7oqfT/lP1kn5QLV2jliZlZ6g3ZPXDBHr/l1FIrW9tPm67VzpKlsGfirEz7OO8oAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4OM555wS7J88pNEXXjKxu5wtnLFeyrmaGrlm6SMFUi5/eIlcMxJeTHQTFNVe+9j4u45yyc6jNku5nUPOkGu2f363nN14Z6qUi4oOyjWbQt7QUi0YiOD1qVBIz6p1I6l5mlh28NFmXX7/pMGNXrNkUg85W3jXOinnamrlmqXzxf44tIn6Y3QT9MeAJ8U2PthJLtl5pNgfr/2GXLP982VyduMUsT9GNe91n3eteJ542jEyMzPt9iWyupHUPE0sq/hDsy7/knOnNnrN0h8lytm8v1RJOS+oXyObbtT6U+GDdXLNSDixl0VEvEZKRkTJJQv+R9v+nX1byDXbvqs/j237iXZMvWbujwVztPtXF0F/9CLoZWrdSGqeLpa/NuGEj/OOMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgA+DMgAAAAAAPp5zzinB/j0n6EV3fCzlXE2NXLMpbJ6f3+g1Q0H9tYeCEZsafflebKwWjI7Wi9bVSbFIjuenV3WXs5/11eoWjVov19z0P52lXFR0UK7ZFPKGlurhgHbulczTz/umOEebwrKDjzbr8iPqj9vLpJyrqW3o6jSKzQsKGr1mRP1x+MZGX74XG6MFm6Q/6sfz06t7yFm5P95YLNfc9IjYH6NCcs2mkHdtiR72PClW8nChXLIpztGmsKziD826/PMvnyVn4/Zq57MXbN5zr2RUVKPXdCHtHDUzK5yr9Z2Ilh+l9WcXra+nVyeNGBEdz93fSpKz5d/QzqfOj+j3r5tv1vZTy9RDcs2mkDIrWc7u6RUv5Tpevlmuuf/eDlIu4eMquWZTWP7aie/feEcZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHw855xTgoV/mtroC8+/fpucdbV1WjDQvLP/nv/+hpxt97dSKedFR8s1N43KlXLJ+q63jGU7pVxo9x65phcfp69ATKyWq63Ra4o2TugqZzv/ZoOUK55WKNcMpNTK2R92XSPlXtjSTa6Z/EyylGv19Ptyzc3z86Vc3l3iNW9my/41Wc42hSbpjyO2yllXJ+4rz2vYyjSSPQO7y9l2fymRcqHy8oauznF5UVFyVn9u0vd9IC6C/hgbo+Vq9F6i2jhJ7yWdpxZLueLpRfoKxEi3L2ZmFptSrdcVpT3fQsq1+rPWm83MNi8okHJ50/XjuezdKXK2KXRaPL3Ra+bPDclZL6SdJ66Z++Puc7Xzycys3duVUi5QqT+PqlyUfp8dqNOOk4tg17toffn7OydKuYocfQUqs7R9+uSAOXLNa1aPkHLLzvudXHNrXaqcvSBeu3+O8fTnxl+U9ZZyxaP055GSUdry2z8tPi+a2atLbjvh47yjDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4MOgDAAAAACAT7QarKuNkouGKmKk3NZRZ8g1a884LOUKhq6Xa66ffaaU6zp2o1yz7cJ35azXupWUWzc5S64ZlVAl5aoOJsg1Q5/tk3Jegl7T1dXJWQtq22ShkFwydGa+lMtZHpRrWhvteMa2FrfHzH7S5R05O7nNWin3r705cs1dl2mvpe37boFcM39oiZw9XTRJf/z5N+Saan/Mv7ZYrrnht92lXNdbN8g12/zhX3LWRYtPTyEn17SAJ8W8hHi55K7ruki5Q+319ew8bZ2cdZViP3H68kPfEPvj3yPo423TpZjXQq/ZZkWcnN3fWTufXMEhveZlWnbf9yLoj9d+/fpjqC6C92SqtF66fYDec2tyq6Vc4ZxauebGodr9TuFCbdlmZpmvHJSzLkrbp14E7dFp7dG8oH6vVdVG66Wf9NCeF83MDnfW92kgWuuPTt14M2vfdr+U+832H8g1ExO1bUqP0s/73356lpytSv1Qyg1I0O41zMzuz3xbyuXdqN1rmJkVzlGfHyK4dz8J3lEGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAJ9oNVh4f61c1KurlnLrb2gh15RXNCpKrtnllnVSrvh3XeSa3aZ+Jmc3jciUcjFJh+SaiauSpFy7374m1/TSUrWgC8k1I1IrnnsxMXLJqA07pFxiYoJc80CfHCnnbdBfn3ohqZucffJvF0i5mg41cs3ouDop55wn19w8P1/K5Q0tlWs2t8L79H3q1VVJufWjtGvZTO+PXiT98Rdrpdz6B7vqNX+5Rc66Ku15xIuRnx2s8jtnSLnt/fX9FJd1UMrp3cnMa6E/N4Y+1Z5zvGh9P0Vt3C7lEhMi6I/n5Uq5olkH5JqBg5/I2cSP20m5bdn6kYqO156bXEjv+ZsXFEi5vGtL5JrNLftZ/dzzQk7K7RgQ1GuqwYD+PNb5Ua2PbxweK9cseFR7vjUz84LafnJR+jaZuO8DNfq+r0nRemn1NyrlmoEIbjVdSNt+L6Btu5nZ7r0pUu7TaL2PfzNnm5Tr9dKNck2vLF7OPtOuu5T7Ze/lcs20qMNaUN/1VjJKO58K5ujn6MnwjjIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+ESrwVCcHLXDHeOkXNfxJXJNq6uTYsWzu8olu03eLeW6jtHXs6ZHnpz18g5Jubi4Wrlm1j8+0ZadmSHXDFVo62mhkFzTS0yUs5aYoNWMiZFLBj/5VAsePizXTNrSUlv2sCq5ZvukA3K2cu5BKeeqquWaJQ92kHKBKP3Y5w0tlXKb5+fLNZtbZP0xXsp1HbdJX4FasT/+tptcstukMinX5WZ9PZ3Yx83MzPO0WKccueSuQdq5n5aoXyOHq2KlXCikbY+Z2ebhHeVsp8Xa861XqW9TcI/YHw9VyjVTVmjL91KS5Jq7B2TL2XaPr5VyXd7Qj1PJQ7lSLqL+eK12v7F5QYFcs7mFovV9WpWmvX9T8Ef93PNCTsptHKpdS2ZmeU9qx7Rwvn7/pq6nmVldC+1+Z/c39W1K3K0tP3Wr3ksCdVrNYGWUXNP008m8GO04BXZqz8tmZi12aivQ4mP9ut9xoLOUy4nSN/6zbno28y9BKfc372K5Zsn12rXsBfTzvmCOtp4loyI4n06Cd5QBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPDxnHNOCebdd49cNNiyTsp1Gb1BrunFxWq56Gi5piqYmS5nd16SKmdrzq6Qcm3+lCDXTHl5s5RT96eZ2YFvZWvLXvOJXDMi+w5ouTrtvDMzC1VWSTmvqJNcc/3NSVIurY123M3MEhenydm01R9JudDefXJN1eb5+Y1eM29oqZxddvDRRl9+JJqkP/68WK7pxcdpwSboj15CvJx1+8Vr2cxcrbafdg3vIdcMfWe/lIuN1nvJiPxVUu6xHd+Ua0Zi12fac07rF/XnkVZ/XqMFPU+uadqthgVSkvWSEfR8L147T5ukPy4oaPSaedeWyNllFX9o9OVHotNsvT+GkoJSrnB+jVzTRWnnqYtu/PeOQrF6zUBtSM5u/qF2DxdIr5ZrtvyHdo2kbNP3/f4CbT3j92v9wcwstlw7R8zMXJRYc3+tXDNQoy0/FMH55In9MRivP4erNc30dY0+pO8nVcko8SBFoGCOfo4sf23CCR/nHWUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8IlWg0V3b5WL1nTOlHKbH+kk11Rlz4uRs3u7xkm5qnR9+cEuh+Rs7d54KZfy8mZ9BerqtFxSolwydfUOKVfVRTvuZmbRB2vkbGD3J1qwtlavmdRCyu09M02uGZ+qHfua11vJNTP/uUHObhlZJOViyzvoy//9GimXN3yLXNNCIT17miiapW9/TWGWlNs8P7+hq3NcOQ/JLd8+66b1p8w/l8g1Q5VVctbrkiflKnpXyjXjgtprw3v3J8s17974Ayl3+XfelGvurEqTsx+L67rn2+Jzg5m1fq2dFtx3QK5ptdryXZV+jlhUlBzdMiRXysWWazkzs8xH3pNyeUNL5ZrmnJ49TXR6Rn++P9wuVsqV3tj47/O0fUFbtpnZwRxt+e3eqpZr7rhIuyc1M4vJ0O434t5MkmumbtWuvVCMvu9bbtK2vyJL3/e1LfTlJ+7StskL6vclwTi978icJ8UCtfp6iiXNzOyjftr+jz6sn6PZKyukXP6D+jZ5zdAfeUcZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAACfaDkZFaUXfXujlMsbKpc0LzZWypU+lCPXLBj7iZTbdnW2XDOhRZWcPbwpUcp5ifFyTQtor324CGq6PZ9Kufh1Ib1mZaWera2Vcl58nFzz4x8VSbl951bLNaODnpTr+LsP5JpOTpp1nLteyhVPz5drVs7XsnnDt8g11XPUQvr51Owi6Y/vbJByedfqi/diY6Rc6bxcuWbB7TukXOhghVwzKr21nD2cmSTl0tIOyDX3fpQm5br+dr9c03Z9LMVWvftNueTH5wflbExKjZZL1nJmZhtubCflutyj98dQ5X4t6Gl91MzMauvkaMc5xVKu+P8VyDUrF2jZvKGlck15+10kzw7Ny0VwTJO3HtZycyJYfpT2nFM6Uj+fcx7Xbp8/OUu/L/GKDsrZ6nKtbuY67f7JzCwUo+2nYKz+HltMudZ3knbJJS1Qo98beMHGv4/wQuK1F8El6onXcyTXUgSd1Dos0+7JS36qzWJmZiWjtPui/AcjmB3E7Vf3p4J3lAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAJ1oNrpuUFUFZLdvlF+vkiq6mRsrlDS2Va4bUoJct16ypk3epuaJDUq7kOn35XlDLZf+zWq7p2nWRcoFaeY9aKEZ/jeZAXqyUK8+TS1ptmrajomP0bYpemyTlAqkpck1XVSVni6drO8CLF0+SCJTMy5eziW8kSrmqcysaujpfuXV3to8grWW7jF4rV3Q1tVIu79oSuabeHz25ZiTns4vScuUHtfPJzCz3Wacte+tHck0vWuv5aX96V665r0svORtK1o69c/pxStyl9Wd3uFKuqe4nL1nro2YR9se7tB4VSKiTa6pKHi6Usy1e187nyvNOn/64+epI3pOJk1KdF+j3MF5Q62YFc+SStrdbvJQ71FO/RkIHtJpmZh3/rPWy2H0R9NwYrenGlOv3RS6g9Z2oQ1ofMzNz0Y3/Hp8X0vanmVnUp9q1F0zTn5vUfR+ME58YLbJ78pKBMVowVq8pL3ukvk0J67VrpKqbft2dDO8oAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgE900VUNSbPP8fLlkbbW2qkU3bJBrquI/c3L2s/0Jcja2RY0WLDgk1zRPW9cdRZ5cMhTUXk/xAtpxNzPzvKCcdaE6KVd7MFaumVIcI+UqOuivJXV88mMpFzpQLtcMpLeSs90m7ZBy23+WJ9ds8552jsa/VSrXdDVazY2dzpRrnk68GLE/LiiQa9bVREm5ztc3fn+sPq+rnI1/faOc/bi3do0Goqrlmp9102pmvxxBf6zWlu9FacfIzKymvfjcYPqTePCw/nSf89cyrea+fXLNQHy8lHP7D+g127SWs90mbpdy2wfp9yVt/qUd+/i3S+SarqZWym3M6y7XPK1EafcwJaP06ylUK/bH/9Gvu8PttB7RNWe3XHPjax3lbEz5YSnnoiJ4Pyyk7Xvn6f3RqxPvCwN6TdNvyWWBSu26MzMLbtTud7yzzohgBbTtj6rUN74uWXu+MzPLf1K7z955vtbHzcxSN2vHPmVLlVzTC1ZIuU0ZiXLNk+EdZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwYVAGAAAAAMCHQRkAAAAAAB8GZQAAAAAAfBiUAQAAAADwiVaDXe/dLxctvTNBygXrouSa3t5YLRjQZ/+ya8+Ucq0/rJZrtnnzsJytbantp31FcXLNgxce0pZ9QK+Z/rp2msRUOrlmSD7zzFo9Vyzltt50hlyzvLBOyrVdrZ+jtudTKRZITdFr1tTK0YreHaRc8vaQXHPrQC2b2KurXLPDn8ukXMdn9W23m/VoU+h69z45WzolUcoFgxG8jvmZeD17nlyybFgPKZf+QZVc00tvJWfbvqNdo2Vd9G06nK2dz15WO7lmbfs0Kbe3i95zLRjBuf+R9jyScFDfTxtv0La/cLx2LZuZebHac7iXnCTXtOoaOVrRO1fKRdQff6Y95yX21p+bcp/aJeU6PnP69Mfcv+nZHf8dlHIugv7oHdRuOJyn79PvX/WalFuzr71cM6qwQs7uPjdZyqWVaH3UzCzhY62Xe0H9Xs/qtOspUKVfyy4mgtnhsHb/7j7W7t/MzKI652vL3q8fz9qsllIuGKdve6BW72UHOmnPTwmf6Mf+4wu1c+9AvnZPZGaW8bp2nrR9Uy55UryjDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+0WrQ21cuF83/dVDKbbwzVa4Z1a5Syq2/u4tcs/XbTsrFlFfLNV2svEst9r1SKdf2nZBcs+0fxWxQO0ZmZhYVpeUCEbzuEtK3STtKZlVt9Jox5do2pa/cLtcMVlZJuYhenfL0dOJLxZFUliRtz5NyVW3q5Jo7v58p5WpbyCWb3/4I+uN4sT9OiaA/ZhyWcuvv6ybXbP2WduVFR9Afg62S5GzSml1Sru7HbeWa0eJ+KpmSLNc0Tzv3PTFnZha7UT/582a8L+W2jekh1wyl10i5XTf0kmtm/u5tKRdRfwx4cjRx5dpIKkuSthVIuaq2+rH/6D+zpNzp1B+jD+v3Gx0Wa/dQ236iP99bS+183jQ4Ti7ZJ1Ar5Q5Ux8s1WyUfkrMf99L2U0UP/RpJKNb6c4sy9a7MrCZFW35suV4z4VP9fIrdrx37mi5t5JoxFdr1HFWlX/eBGnGbnL6fXAT9sWWx9twYiYRPtXO/OkXfpj29YqVcMEGveTK8owwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgE+0GtzXt5Nc9NMfVkq5qEBQrtn2iQQp5zkn10x6eb1WM6mFXPPT7+TI2fQd8VowGJJrhsrL5aysrk6KeZ6n14yKkqMfjeoh5WIzD8o1sx6Jk3Kuukau6UVrl5OL4HhabbUcDaS3lnIVPdvLNataaccpqlq/7rL/+pGU23Ohvp7N7UDfPDn7yZWHpVyUp58n7R4Xe4l+mCzp5XVSzktKkmt+FkF/bLVjj5TruFh/vXfLT2KlXHS81vPMzJzT+l7yqkS5ZuZjxREsXzuo8Z/pBz/q7CopV36mvu/bd8yWcm7Xx3JNV60fpyi1P56lraeZWVVLrT9GR9If/7JDyu25SF/P5ra3i/Z8a2a2/1ztOc/z9H3a8uXG749/3tZXyoV66fcll3TS7knNzF7cr/XdQEC/L6ss0vZ9ZZFcUhfB8fQCetY5rUd5gVq5ZqhGu+5jduuzQ6dnDmnBCM7RQI0+Y9Ulac+N5blazsysNkk79wK1+ka1e0s7Rz/7ht5zToZ3lAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAJ1oNpi0rloumLg01aGVOpGRevpTzPCfXzNuWpdXcuFWuGXdQq2lm5urqtGBUlFzTi43Vll1TI9eURbCeld/ppmd7HtaCdfryYz+rlHKuqkqu2dzcIW0/RR8OyjVbr9ok5bzEBLlmxVnZUq4m1ZNrNrfUF9fJ2ZTn9f2vKnm4UMp5Ab03529rL+VcBP0x9qBW08zMxP4Y/+42uWRBtXbu7emVKNeMqtSeczL+vEGuGUl/drXafspYUirX3NKmQMrFnlEh19x+ZYaUy/ndx3LNSLhDh6RcRP3xVe2Yegl6fzzUK0fKnU79Mf198TnczNLXNP7yS0ZWa8EIdmn7v8ZowfV6L/nX9dqxNzOrLY+TcjHJei/xorTnBxdsgvfYIjmdI8kGtbAXrT83BmK0HhHM0XvJ7m8lSbl2b2h9LFJR1dq6RtXoM1bLf2n32cE4/d69PFebcer0lntSvKMMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIBPtBrc9GAnuWjdoRgp1+WW9XLNwhu3aMFoeZPMZbXRggH99YSDWfryE3MypFxtq3i5ZuybG6WcFxsr1wwktZByrrJSrvlZV+0cMTMLeDVSLuYDbT3NzKI++0jKuXh937uqKi0YCsk1LUbfT+q5H1UdlEsW35Mv5To+KZe0pDW7pFyL9RFs+716tClseihPzsr98Rfr5JqFIzdrwZgI+mP7tlrQ8+SaFVlRcjaxQ6aUi6g/vq4952S9LZe0QHKSlHM1tXpR5yJYAXH/V4r9yczSSrQedaiXvk0HC7RsICVZrhkqPyhn5f5YVSeXLL63UMp1ekIuaS3U/lh8+vTHkuv1695VadnCR7X7AjOzgnnac55TryUzq26tXaPxe6rlmmX/1O4JzcyyN2nbtK9Ivy9K3q5d93t6yyUtuv1hKRes08+RUFA/TuaJvdTpNeMStF5WV6tvU1WbCHq+yEXps4uL1rY/KoKnsU1DtB7V9n/1+5KU7dp1n7QrgnPkJHhHGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8otVg4Y1b5KI1PfOl3OZHOsk186/fJmdVweR4KRfTIlGumbHoAznrxWvLj9taJ9d0ajAUkmuGKg7JWVVFlxo569VGSbm8ZQf0FagWl19drdcUeQnacTczO/Qt7VoyM0tcuVbKRb23Sa7Z5eda7tOruss1K7JypFzb5dvlms2tcORmOVt7ltgf5+vHPn/EVjmrCibFSbnopBZyzXYL35ez6nUSt6UJ+qOTkxY6WCFnm5MLBuVsq5VbpdyBTnlyzdju2n7a/Z/6fUHyR/qxT2iK/niTlvv06h5yzYPtO0i5tn9v/HuiplIwTz/3DubGSrnSG/X3efLn6vc7qmCctvxQnHb/YmaW+Yp+r+ViteVnvaL3Mgtp2cp0vecfbKNtv3OeXDNhvX4P1Xqd1iM+6SmPQxbsqp3PwaB+jqpLd1H6ftpfmCBnWxVr517S9sNyzc6/13K7vxUj16xqrd2XpL/fePfuvKMMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIBPtBrc9GAnuWggqk7Kxa9Okms2hegNO6ScC4bkml6U/trDjsGFUi7nkbVyzUBKspQL7T8g17SQtv3l3/uGXDKmRZWcrT0QJ+UCpaVyzVBVtRaMiZFrqsfeVerbvq+zfIla4ko52ug+u1Dcn2YWs107nrVJuQ1dna/cpofy5GwgKijlTpf+aEFte8zMvKgoObtjSJGUy/mfD+Wacn88UC7XNOf0rFoygn3qarXn20Ccdt2ZmQX37pNyuc/vl2uWnBEr5Q5eeEiueXBLCzmb34z9ce9Fes+P3hYv5Wqv7NjAtfnqlVyvX/deQHsuiV+XEMEa6OeUKrFMW08vFEF/8PTozvO17c/+p77twQTtfiNlu9ZzzMzKe0ewUaLKIv162p2k9b1gh0q5pqvS9pMX0I999CFtPwXq9HnkUJa+71sVy9FGV969Rs7G7NHuyT+O15/vToZ3lAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAJ1oNFlxfKhf14uOlXOnvMuWaJed01JYdcHLNlBdbSLn0P38o1/x44DfkbIcnt2vBpCS5psWIh7QwV6+5aZsUK7usTi6ZGKdn40q14+SCIbmmxcRIsUBKslzS1WnbtG3UGXLN3PvXyFnV5vn5jV6zaOiGRq95qJ++n5pbwYhNctZLEPvjnPZyzZJvddSW7ckl9f741PtyzY9/1l3OdnhC6zuWfHr0x0BGW7mkO1CuZ6uqtVwwqNesqdFya4rlmqn//JaUe3vKXLlmtw9ulLNeXJyUK30oR66p6nxtE/THS/R7jeZW8FAE516s9v7N5uGVcs2SrlFSzvP0+8ektxOkXLs3D8k1y76t9Vwzs8zV2nUfjJdv881Fa08QoSj9iSRus/Z8F9X9gFyzukq7fzMzC+ZUaUH90Jur087RuO2xcs12b2rHc9v39HMkd2mFnFWVjNKupUh0nqM933xOy352hr6fToZ3lAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAx3POOSWY++j/k4t2Gb2hwSt0XKGQlgvos3/p/+Q1cGWOr+DnH8nZw+doy2/x4W59Bapr9KwqLlaKFf8ySy4Z065Szsa/liTl0krr5Jo7v6OdJ4ll+vnU/qE1crYpbJ6f32zLzhtaKmcDrVpKOVepnyMv7nlIzjaFiPrjz4sbfwW0Nm7meXLJ0t83/vlUMCqC/vgtbfkt3i+Ta7qaxu+PXqzWH92BcrmmCwb1bE2tlPOi9F4m3haYhcScmUW1TZdyxb/KkWtGt9Z7RJfMPVJuw+62ck1V3rUlcrZJ+uMn8+RsU8j9/Uw5Wzi/Ca5R8Xx2EfTHkhuiGro6x5X/P+J9rpntL0yQcinb9f3pBfXrWVWTGi3ldvfR92dtK/1ez4vV9qk7rC8/ZaO2TZmrDso1XZR+7jWFklGNfz6rCuboz3d1LWKkXKBar7nif8efuJZcCQAAAACA/wMYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHyi1WC3iWVy0ZJHOkm5/Ou3yTVdKKQF1ZyZ5V+7Qcp58XFyTVdVLWdNXNWanNZyydhtn2qL3n9ArmmHDms5116vGYHa88ulXFkf/XWfxDeTpFwoRi7Z7PKGlkq50kcK5JpewEk51y1PrllyqyflIukPza3bhF1ytmR+vpTLH7FVrunq6sSgdjzNzPIGa/0xEKf3x1B14/fH2g7pcsmYbZ9oi963X67p7JCcbQqBhHgpJ58jZqZdoWZ7BveQa7b94xop1/VuveluuitVzm5+QetRrmeFXNMTd5Q7Q7vmzSLojxH0h+aW97h+X1Z6o/Y8nj9Xr2li2/Mi6I+Fv6vVFh2t35d4dfo2eWK0Ml2/nhI+1bYp6rDeS+L3BKVcx7/JJW1vt0Q5ezhDu54yV+vPTdGHq6TcrguS5ZqZq/S+0xQK5mjHadON8thonqddT4faJ8g1y35YI+Xy5+rX8snwjjIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+ESrwZJ7WstFQ0Fx/vb0OX3z/Hw5q8obvFHKudq6Rl+2mVnianH5VdVyzVBUlJTzEhPlmu7wYSlX8FSVXHPLD1rI2cSPPCnX6fmdck13UMtuGF8o11x/9xlSLvqgdozMzFJK5Ki1fX2flEt4Q9/3VX0qpNyWW7VjFAkXDDV6zaZScm+6nJX7Y0Dfp5sXFMhZVadBG6Scq2ui/rhKW36ouin6Y4Jc0x2u1IKefjw9cT3NzCwhXqtZqfdn1f6uTs7uvfcbUi66XN/2Czp+IGc3t9buYXa+nSXXtLxDUqxp+mOw0Ws2la3X6r3chcR9FcH1VDKq8d8TKvit1ve8kH6NRCJtk3Zf5tVF8DwqPueEYvRrNFCrnacuguPZqljbdjOztFJtXQM1+vXkorTz6VCOvu83DtL6eFSlfi4n7tKzLTdq53NCsTw2WlU37bmx7Ic1ck1ZI152vKMMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAICP55xzSnBAq+Fy0U0PdtIW7kmLjohznpytOxAr5br+apNcs/ShHDmbN7RUCwb01zMCrVtKObf/gFzTS0iQcpvubSfXjOQ4FY7eKWdldXVazoXkkltHnSHl2r98WK752Znavjczi9+rXU8Jn9bKNXcM1/ZT/nWb5ZqbHiqQcoUjS+Sayw7Ml7NNYUDaMDm76aE8KecF9HNP5UJ6L6krF/vjHRvkmqXzcuVs3rXi8ff0XhJIbyXl3L4I+mOi2B/vj6A/RnCcCm/eIWdlan8M6c/hW3/+DSmX/ZLeHz/+ZqKc7ffT16Xc8sXfkmtWfbNCyuUPE5/rzWzTw4VSrnCEfl+y7OCjcrYp9Pv2b+RsyfVRWlC/7HUR3JK6Q9FSrnBxjVyzdKS+UQVzglLORdAf61po2xR9WOwPZhaK047n5mvlkhEdp/z5jT9neHViTW28MjOz7QO0XtZqrV7zYAf9eSTmoFY3VsyZme3pr537BXO1c9nMrOR67RwtmKefo/9Y9esTPs47ygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgw6AMAAAAAIAPgzIAAAAAAD4MygAAAAAA+DAoAwAAAADgE60GXTAkF/U8J+Xyh5fINQNt06VcZWEbuebWH3haMENbtplZ7gP6aw9bxnaXcnn3rZdrusR4Lbj/gFxz082dpFxdTbVcM2mNuJ5m5g4flnJegl5z/f3aNpl4ipiZpb6u5aIrauSaGU9sk7O7ftpVyqV9cFCuWTChVgu206+RzpP2SzmvRaJcs7m5YFDOegGtl+YPbYL+WBBBf7xcPPnb6TVzZ+sX1JY7eki5vHvXyTVdiwQtuC+C/jg6T8rVVev9Mfm9SPpjpZSLqD/OztdqBrTnejOzlNVaLqpC30/tF2yRsyvqviXlsld8Ktf0/ir28giukaJf75dyLqmFXLPZ6aeJ/Jxb+GCdXLI2JVbKHW4r3xLbnnO0japJjZFrZjwbJWe3iP2543NafzAzC8WJ96/aLZmZmW37bpyUc7XivYaZJZZqx9PMLFBdIeVC0fq9+6ah4nkSwf1j0notHF2l32tkvlYlZ3edr91vpezQr7vcxdo+rU3R933u41ouFKdfSyfDO8oAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+DMoAAAAAAPgwKAMAAAAA4MOgDAAAAACAD4MyAAAAAAA+DMoAAAAAAPh4zjmnBPsnD2niVTmxmnO6SLktQ6TNMTOzrrfvlHLlfTrKNWuS9NceWq3ZJ+UOdEuTa6Yt26AF01vKNe1TbT1LH8qRS7qQpy9f5AX0Y6+Kfy1Jzpb3rG705Xeb/ImcDX2mHadAUgu5pqvStsnV1ck1vbhYrWZllVxz2cFH5WxT6J80uFmXX6v2x6H6NVJ0q9YfK5qoP7Z8f7+Ui6g/vlisBdNbyTXt071SrHRerlxSe1aOjNf4LdcSVkXQH3s1fn/sOmmPnA2JxymQrG+Tq9J6lKuNoD/Gx2k1I+mPFX+Qs03hknOnNuvyy/MSpdzufvpxyl8YknL7C7TjaWYWjNcv0pRt2roezI6Sa6a/XynlapNj5JoxB2ulXOlIfduda4L7R68J7h/XJcjZw/k1jb78Tk/o2xR9SDufQvH6+RSo0a4RC+nr6aK0Yx+oE5dtZstfm3DiWnIlAAAAAAD+D2BQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAh0EZAAAAAAAfBmUAAAAAAHwYlAEAAAAA8GFQBgAAAADAx3POueZeCQAAAAAAThW8owwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCgDAAAAAODDoAwAAAAAgA+DMgAAAAAAPgzKAAAAAAD4MCifAu68807r2bPnl6qxdetW8zzP3nvvvUZZp1PVkCFD7Ic//GH46+985zv2i1/84itfj5deesk8z7P9+/c32TK+uK0N8VWsJ9CU6I+6L+6rxughDfFV7G/OC4DrIBLcP0aG+8fPnbaDsud5J/zvzjvv/MrWpbkutsb02WefWXZ2doMuiiFDhoT3e2xsrBUUFNiUKVOsrq6uaVbWZ8mSJTZ16lQp+1Vf9B07drT777//K1lWY3v//fft/PPPt/j4eMvJybGZM2c29yohAvTHxrFixQo799xzLTk52TIyMmzs2LER97U777wzvN+jo6OtY8eONmbMGKuoqGiitf632bNn26OPPiplv+qb5dP5vNi+fbtddtlllpiYaG3btrXbbrvtK3m+Q+OgPzaOY+27J554IqIa3D8e2+l8/+ics7vvvts6d+5scXFx1r59e5s2bVpzr1aDRTf3CjRUWVlZ+P+ffPJJmzhxom3YsCH8vaSkpPD/O+csGAxadPRpu7lNbtiwYda9e3fbuXNng/79gAEDbMGCBVZdXW1Lly61UaNGWUxMjI0bN+6obE1NjcXGxn7ZVTYzs1atWjVKHfxbeXm5XXrppdavXz976KGH7IMPPrChQ4daWlqaXXfddc29ehDQH7+8NWvW2Pe+9z0bP368LVy40Hbu3GkjR460YDBod999d0S1zjjjDPvHP/5hdXV1tmrVKhs6dKgdPnzY5s2bd1S2Mftjampqo9TBvwWDQbvsssssIyPDXnvtNSsrK7NBgwZZTEyMTZ8+vblXDwL6Y+NZsGCBDRgwIPx1WlpaxDW4f/x6GT16tP3973+3u+++284880zbu3ev7d27t7lXq8FO23eUMzIywv+lpqaa53nhr9evX2/Jycn2wgsv2Nlnn21xcXH26quvHvNHEX7xi1/Yd77znfDXoVDI7rrrLuvUqZMlJCRYjx497M9//vOXWtexY8da586dLTEx0fLy8mzChAlWW1t7VG7evHmWk5NjiYmJdtVVV9mBAwfqPf7II49Y165dLT4+3rp06WIPPvjgl1qvI+bOnWv79++3W2+9tcE14uLiLCMjw3Jzc+2GG26wfv362TPPPGNm//4RkGnTpllWVpYVFRWZmdmOHTvsqquusrS0NGvVqpVdfvnltnXr1nDNYDBot9xyi6WlpVnr1q3t9ttvN+dcveV+8dXY6upqGzt2rOXk5FhcXJwVFBTY73//e9u6datdeOGFZmbWsmVL8zzPhgwZYmbaMV+6dKl17tzZEhIS7MILL6y3ng0RDAZt2LBh4WUWFRXZ7Nmzj5mdPHmytWnTxlJSUmzkyJFWU1MTfqwpztfFixdbTU2NzZ8/38444wz7yU9+YjfffLPde++9X6ouvjr0xy/fH5988knr3r27TZw40QoKCqxv3742c+ZMmzNnjh08eDCiWtHR0ZaRkWHZ2dl29dVX28CBA8P98ciPTj7yyCPWqVMni4+PNzOz/fv32/Dhw8PX/kUXXWRr1qypV3fGjBnWrl07S05OtmHDhllVVVW9x794TEOhkM2cOdMKCgosLi7OOnToEH6lv1OnTmZmdtZZZ5nnefWO+8n27ZtvvmlnnXWWxcfHW+/eve3dd9+NaP8cy6l6Xvz973+3devW2R//+Efr2bOnffe737WpU6fanDlz6vVmnLroj413/5iWllZvfx7pX5Hg/jEyp/L9Y3Fxsc2dO9f+9re/2X/+539ap06d7Oyzz7ZLLrnkS9VtTqftoKy44447bMaMGVZcXGzdu3eX/s1dd91lCxcutIceesjWrl1rY8aMsZ/+9Kf28ssvN3g9kpOT7dFHH7V169bZ7Nmz7eGHH7b77ruvXqakpMSeeuope/bZZ+3FF1+0d99912688cbw44sXL7aJEyfatGnTrLi42KZPn24TJkywP/zhD8ddbseOHU/6I0Tr1q2zKVOm2MKFCy0QaLzTISEhod4FuWLFCtuwYYMtX77cnnvuOautrbX+/ftbcnKyvfLKK7Zq1SpLSkqyAQMGhP/dPffcY48++qjNnz/fXn31Vdu7d6/95S9/OeFyBw0aZI8//rg98MADVlxcbPPmzbOkpCTLycmxp59+2szMNmzYYGVlZeHGcrJjvmPHDrvyyivtBz/4gb333ns2fPhwu+OOO77U/gmFQpadnW1/+tOfbN26dTZx4kT71a9+ZU899VS93IoVK6y4uNheeukle/zxx23JkiU2efLk8OMNOV89zzvhj2OuXr3aLrjggnqv2vbv3982bNhg+/bta/hG45RCfzxxf6yurj7qpi8hIcGqqqrsnXfeadjG+ur4+2NJSYk9/fTTtmTJkvCPPv/4xz+2PXv22AsvvGDvvPOO9erVyy6++OLwK/NPPfWU3XnnnTZ9+nR7++23LTMz86Q3v+PGjbMZM2bYhAkTbN26dfbYY49Zu3btzOzzYdfM7B//+IeVlZXZkiVLzOzk+7aiosK+//3vW7du3eydd96xO++880u96HrEqXperF692s4888zwfjP7vD+Wl5fb2rVrG77BOKXQH09+/2hmNmrUKEtPT7dvfvObNn/+/KOG0Ybg/vHETuX7x2effdby8vLsueees06dOlnHjh1t+PDhp/U7yua+BhYsWOBSU1PDX69cudKZmfvrX/9aLzd48GB3+eWX1/ve6NGjXd++fZ1zzlVVVbnExET32muv1csMGzbM/fd///dxl9+3b183evRoeX1nzZrlzj777PDXkyZNclFRUe6jjz4Kf++FF15wgUDAlZWVOeecy8/Pd4899li9OlOnTnV9+vRxzjm3ZcsWZ2bu3XffDT9+0UUXud/+9rfH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GSQnJ9utt95qY8aMsVAoZN/+9rftwIEDtmrVKktJSbHBgwcft/Ynn3xy1N+fzMzMtMLCQtu+fbs98cQT9h//8R/2/PPPH/MDBeLj423w4MF29913W3l5ud1888121VVXhV+hnjx5st18882WmppqAwYMsOrqanv77bdt3759dssttxxznS6++GK74oor7Kabbjrm418chj/99FMz+/zCbui7FqqBAwfarFmz7PLLL7cpU6ZYdna2bdu2zZYsWWK33367ZWdn2+jRo23GjBlWWFhoXbp0sXvvvfeEf8OuY8eONnjwYBs6dKg98MAD1qNHD9u2bZvt2bPHrrrqKsvNzTXP8+y5556z733ve5aQkCAd85EjR9o999xjt912mw0fPtzeeecd+W+T7ty586jzIjc31woLC23hwoW2bNky69Spky1atMjeeuut8CfPHlFTU2PDhg2zX//617Z161abNGmS3XTTTRYIBBp8vnbp0sXuuusuu+KKK475+DXXXGOTJ0+2YcOG2dixY+3DDz+02bNnH/UBIvh6oT8ebdasWTZgwAALBAK2ZMkSmzFjhj311FNN+m6qmVm/fv2sT58+9sMf/tBmzpxpnTt3tl27dtnzzz9vV1xxhfXu3dtGjx5tQ4YMsd69e9t5551nixcvtrVr1x733e74+HgbO3as3X777RYbG2vnnXeeffLJJ7Z27VobNmyYtW3b1hISEuzFF1+07Oxsi4+Pt9TU1JPu22uuucbGjx9vI0aMsHHjxtnWrVvlP591Op4Xl156qXXr1s1+9rOf2cyZM2337t3261//2kaNGtWk7yKhedEf63v22Wft448/tm9961sWHx9vy5cvt+nTpzfKB/mdDPePp+79Y79+/axXr142dOhQu//++y0UCtmoUaPskksuqfcu82mlOX9BurEc78MYjvWL8hMnTnTt2rVzqampbsyYMe6mm24KfxiDc59/mMD999/vioqKXExMjGvTpo3r37+/e/nll4+7/L59+zozO+q/qVOnOuecu+2221zr1q1dUlKSu/rqq919991Xb32PfNjLgw8+6LKyslx8fLz70Y9+5Pbu3VtvOYsXL3Y9e/Z0sbGxrmXLlu6CCy4I/1L9sT6MITc3102aNEnej8fab0fqrly58rj/7lgfcqE8XlZW5gYNGuTS09NdXFycy8vLcyNGjHAHDhxwzn3+4QujR492KSkpLi0tzd1yyy1u0KBBx/0wBuecq6ysdGPGjHGZmZkuNjbWFRQUuPnz54cfnzJlisvIyHCe57nBgwc757Rj/uyzz7qCggIXFxfnzj//fDd//nzpwxiOdV4sWrTIVVVVuSFDhrjU1FSXlpbmbrjhBnfHHXcc84N4Jk6cGD5/RowYUe8Dfk627sc6pmbmFixYcNz1ds65NWvWuG9/+9suLi7OtW/f3s2YMeOEeZy66I8N748XXnihS01NdfHx8e6cc8455oc1nex6+uKHeamPl5eXu5///OcuKyvLxcTEuJycHDdw4EC3ffv2cGbatGkuPT3dJSUlucGDB7vbb7/9uB/m5ZxzwWDQ/eY3v3G5ubkuJibGdejQwU2fPj38+MMPP+xycnJcIBCod9xPtG+dc2716tWuR48eLjY21vXs2dM9/fTT0od5na7nxdatW913v/tdl5CQ4NLT090vf/lLV1tbe8J/g1MT/bFh18ELL7zgevbs6ZKSklyLFi1cjx493EMPPeSCwWA4w/3j5/6v3T/u3LnTXXnllS4pKcm1a9fODRkyxH322Wcn/DenMs+5Rvgsd3xtrVy50q688krbvHkzP3YLAD5btmyxzp0727p166ywsLC5VwcAThncP+Lr4Gv9d5Tx5S1dutR+9atf0eQA4AuWLl1q1113HUMyAHwB94/4OuAdZQAAAAAAfHhHGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8GJQBAAAAAPBhUAYAAAAAwIdBGQAAAAAAHwZlAAAAAAB8/j+U4A/apXH2xAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(3, 3, figsize=(10, 10))\n", + "\n", + "for i, ax in enumerate(axes.flat):\n", + " ax.imshow(x_test_adv[i, ...].squeeze())\n", + " ax.axis(\"off\")\n", + " ax.text(\n", + " 0.5,\n", + " -0.05,\n", + " f\"True Label: {np.argmax(y_test, axis=1)[i]}, Predicted Label: {test_y_pred[i]}\",\n", + " transform=ax.transAxes,\n", + " horizontalalignment=\"center\",\n", + " verticalalignment=\"center\",\n", + " )\n", + "\n", + "plt.tight_layout()\n", + "plt.suptitle(\"Adversarial example and labels\", fontsize=16, y=1.01)\n", + "plt.show()" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/requirements_test.txt b/requirements_test.txt index 7346645fd6..1e9ad346f9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -18,6 +18,7 @@ numba~=0.56.4 opencv-python sortedcontainers==2.4.0 h5py==3.8.0 +multiprocess>=0.70.12 # frameworks @@ -34,6 +35,9 @@ torch==1.13.1 torchaudio==0.13.1+cpu torchvision==0.14.1+cpu +# PyTorch image transformers +timm==0.9.2 + catboost==1.1.1 GPy==1.10.0 lightgbm==3.3.5 diff --git a/run_tests.sh b/run_tests.sh index 9947f646b9..71ae377a42 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -118,9 +118,7 @@ else "tests/attacks/test_targeted_universal_perturbation.py" \ "tests/attacks/test_simba.py" ) - declare -a estimators=("tests/estimators/certification/test_randomized_smoothing.py" \ - "tests/estimators/certification/test_derandomized_smoothing.py" \ - "tests/estimators/classification/test_blackbox.py" \ + declare -a estimators=("tests/estimators/classification/test_blackbox.py" \ "tests/estimators/classification/test_catboost.py" \ "tests/estimators/classification/test_classifier.py" \ "tests/estimators/classification/test_deep_partition_ensemble.py" \ @@ -135,7 +133,6 @@ else "tests/estimators/regression/test_scikitlearn.py" ) declare -a defences=("tests/defences/test_adversarial_trainer.py" \ - "tests/defences/test_adversarial_trainer_madry_pgd.py" \ "tests/defences/test_class_labels.py" \ "tests/defences/test_defensive_distillation.py" \ "tests/defences/test_feature_squeezing.py" \ diff --git a/setup.py b/setup.py index eff1b154b5..fdf9af2298 100644 --- a/setup.py +++ b/setup.py @@ -112,7 +112,9 @@ def get_version(rel_path): "requests", "sortedcontainers", "numba", - ], + "timm", + "multiprocess", + ] }, classifiers=[ "Development Status :: 3 - Alpha", diff --git a/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py index 34ffca6993..9a2c6e3040 100644 --- a/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py +++ b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py @@ -36,7 +36,9 @@ def fix_get_mnist_subset(get_mnist_dataset): yield x_train_mnist[:n_train], y_train_mnist[:n_train], x_test_mnist[:n_test], y_test_mnist[:n_test] -@pytest.mark.skip_framework("tensorflow1", "tensorflow2v1", "keras", "kerastf", "mxnet", "non_dl_frameworks", "pytorch") +@pytest.mark.skip_framework( + "tensorflow1", "tensorflow2v1", "keras", "kerastf", "mxnet", "non_dl_frameworks", "pytorch", "huggingface" +) def test_images_pgd(art_warning, fix_get_mnist_subset, image_dl_estimator_for_attack): try: (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset @@ -55,7 +57,9 @@ def test_images_pgd(art_warning, fix_get_mnist_subset, image_dl_estimator_for_at art_warning(e) -@pytest.mark.skip_framework("tensorflow1", "tensorflow2v1", "keras", "kerastf", "mxnet", "non_dl_frameworks", "pytorch") +@pytest.mark.skip_framework( + "tensorflow1", "tensorflow2v1", "keras", "kerastf", "mxnet", "non_dl_frameworks", "pytorch", "huggingface" +) def test_images_unconstrained_adam(art_warning, fix_get_mnist_subset, image_dl_estimator_for_attack): try: import tensorflow as tf @@ -83,7 +87,7 @@ def test_images_unconstrained_adam(art_warning, fix_get_mnist_subset, image_dl_e art_warning(e) -@pytest.mark.skip_framework("pytorch", "keras", "kerastf", "mxnet", "non_dl_frameworks") +@pytest.mark.skip_framework("pytorch", "huggingface", "keras", "kerastf", "mxnet", "non_dl_frameworks") def test_check_params(art_warning, image_dl_estimator_for_attack): try: classifier = image_dl_estimator_for_attack(FeatureAdversariesTensorFlowV2) diff --git a/tests/attacks/evasion/test_auto_attack.py b/tests/attacks/evasion/test_auto_attack.py index 3c0c367253..9dc71a0895 100644 --- a/tests/attacks/evasion/test_auto_attack.py +++ b/tests/attacks/evasion/test_auto_attack.py @@ -145,6 +145,15 @@ def test_generate_attacks_and_targeted(art_warning, fix_get_mnist_subset, image_ art_warning(e) +@pytest.mark.skip_framework("tensorflow1", "tensorflow2v1", "keras", "non_dl_frameworks", "mxnet", "kerastf") +def test_attack_if_targeted_not_supported(art_warning, fix_get_mnist_subset, image_dl_estimator): + with pytest.raises(ValueError) as excinfo: + classifier, _ = image_dl_estimator(from_logits=True) + attack = SquareAttack(estimator=classifier, norm=np.inf, max_iter=5000, eps=0.3, p_init=0.8, nb_restarts=5) + attack.set_params(targeted=True) + assert str(excinfo.value) == """The attribute "targeted" cannot be set for this attack.""" + + @pytest.mark.skip_framework("tensorflow1", "keras", "pytorch", "non_dl_frameworks", "mxnet", "kerastf") def test_check_params(art_warning, image_dl_estimator_for_attack): try: @@ -182,3 +191,123 @@ def test_classifier_type_check_fail(art_warning): backend_test_classifier_type_check_fail(AutoAttack, [BaseEstimator, ClassifierMixin]) except ARTTestException as e: art_warning(e) + + +@pytest.mark.skip_framework("tensorflow1", "tensorflow2v1", "keras", "non_dl_frameworks", "mxnet", "kerastf") +def test_generate_parallel(art_warning, fix_get_mnist_subset, image_dl_estimator): + try: + classifier, _ = image_dl_estimator(from_logits=True) + + norm = np.inf + eps = 0.3 + eps_step = 0.1 + batch_size = 32 + + attacks = list() + attacks.append( + AutoProjectedGradientDescent( + estimator=classifier, + norm=norm, + eps=eps, + eps_step=eps_step, + max_iter=100, + targeted=True, + nb_random_init=5, + batch_size=batch_size, + loss_type="cross_entropy", + verbose=False, + ) + ) + attacks.append( + AutoProjectedGradientDescent( + estimator=classifier, + norm=norm, + eps=eps, + eps_step=eps_step, + max_iter=100, + targeted=False, + nb_random_init=5, + batch_size=batch_size, + loss_type="difference_logits_ratio", + verbose=False, + ) + ) + attacks.append( + DeepFool( + classifier=classifier, + max_iter=100, + epsilon=1e-6, + nb_grads=3, + batch_size=batch_size, + verbose=False, + ) + ) + attacks.append( + SquareAttack( + estimator=classifier, + norm=norm, + max_iter=5000, + eps=eps, + p_init=0.8, + nb_restarts=5, + verbose=False, + ) + ) + + (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset + + # First test with defined_attack_only=False + attack = AutoAttack( + estimator=classifier, + norm=norm, + eps=eps, + eps_step=eps_step, + attacks=attacks, + batch_size=batch_size, + estimator_orig=None, + targeted=False, + parallel=True, + ) + + attack_noparallel = AutoAttack( + estimator=classifier, + norm=norm, + eps=eps, + eps_step=eps_step, + attacks=attacks, + batch_size=batch_size, + estimator_orig=None, + targeted=False, + parallel=False, + ) + + x_train_mnist_adv = attack.generate(x=x_train_mnist, y=y_train_mnist) + x_train_mnist_adv_nop = attack_noparallel.generate(x=x_train_mnist, y=y_train_mnist) + + assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist)) == pytest.approx(0.0182, abs=0.105) + assert np.max(np.abs(x_train_mnist_adv - x_train_mnist)) == pytest.approx(0.3, abs=0.05) + + noparallel_perturbation = np.linalg.norm(x_train_mnist[[2]] - x_train_mnist_adv_nop[[2]]) + parallel_perturbation = np.linalg.norm(x_train_mnist[[2]] - x_train_mnist_adv[[2]]) + + assert parallel_perturbation < noparallel_perturbation + + # Then test with defined_attack_only=True + attack = AutoAttack( + estimator=classifier, + norm=norm, + eps=eps, + eps_step=eps_step, + attacks=attacks, + batch_size=batch_size, + estimator_orig=None, + targeted=True, + parallel=True, + ) + + x_train_mnist_adv = attack.generate(x=x_train_mnist, y=y_train_mnist) + + assert np.mean(x_train_mnist_adv - x_train_mnist) == pytest.approx(0.0, abs=0.0075) + assert np.max(np.abs(x_train_mnist_adv - x_train_mnist)) == pytest.approx(eps, abs=0.005) + except ARTTestException as e: + art_warning(e) diff --git a/tests/attacks/evasion/test_dpatch_robust.py b/tests/attacks/evasion/test_dpatch_robust.py index d456807ac6..fe2d08c8a3 100644 --- a/tests/attacks/evasion/test_dpatch_robust.py +++ b/tests/attacks/evasion/test_dpatch_robust.py @@ -43,7 +43,7 @@ def test_generate(art_warning, fix_get_mnist_subset, fix_get_rcnn, framework): try: (_, _, x_test_mnist, y_test_mnist) = fix_get_mnist_subset - if framework == "pytorch": + if framework in ["pytorch", "huggingface"]: x_test_mnist = np.transpose(x_test_mnist, (0, 2, 3, 1)) frcnn = fix_get_rcnn @@ -78,7 +78,7 @@ def test_generate_targeted(art_warning, fix_get_mnist_subset, fix_get_rcnn, fram try: (_, _, x_test_mnist, _) = fix_get_mnist_subset - if framework == "pytorch": + if framework in ["pytorch", "huggingface"]: x_test_mnist = np.transpose(x_test_mnist, (0, 2, 3, 1)) frcnn = fix_get_rcnn diff --git a/tests/attacks/inference/attribute_inference/test_baseline.py b/tests/attacks/inference/attribute_inference/test_baseline.py index cfb3565f99..5be3f88d5e 100644 --- a/tests/attacks/inference/attribute_inference/test_baseline.py +++ b/tests/attacks/inference/attribute_inference/test_baseline.py @@ -34,8 +34,8 @@ @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -78,16 +78,19 @@ def transform_feature(x): baseline_inferred_test ) - assert 0.8 <= baseline_train_acc - assert 0.7 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.98, "gb": 0.98, "lr": 0.77, "dt": 0.98, "knn": 0.86, "svm": 0.83} + expected_test_acc = {"nn": 0.62, "rf": 0.86, "gb": 0.82, "lr": 0.86, "dt": 0.84, "knn": 0.82, "svm": 0.93} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_continuous(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_continuous(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -112,16 +115,22 @@ def test_black_box_baseline_continuous(art_warning, get_iris_dataset, model_type baseline_inferred_train = baseline_attack.infer(x_train_for_attack) baseline_inferred_test = baseline_attack.infer(x_test_for_attack) # check accuracy - assert np.allclose(baseline_inferred_train, x_train_feature.reshape(1, -1), atol=0.4) - assert np.allclose(baseline_inferred_test, x_test_feature.reshape(1, -1), atol=0.4) + assert ( + np.count_nonzero(np.isclose(baseline_inferred_train, x_train_feature.reshape(1, -1), atol=0.4)) + > baseline_inferred_train.shape[0] * 0.75 + ) + assert ( + np.count_nonzero(np.isclose(baseline_inferred_test, x_test_feature.reshape(1, -1), atol=0.4)) + > baseline_inferred_test.shape[0] * 0.75 + ) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_slice(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_slice(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -166,16 +175,19 @@ def transform_feature(x): baseline_inferred_test ) - assert 0.8 <= baseline_train_acc - assert 0.7 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.98, "gb": 0.98, "lr": 0.77, "dt": 0.98, "knn": 0.85, "svm": 0.83} + expected_test_acc = {"nn": 0.62, "rf": 0.86, "gb": 0.82, "lr": 0.86, "dt": 0.8, "knn": 0.81, "svm": 0.93} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_values(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_no_values(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -215,16 +227,19 @@ def transform_feature(x): baseline_inferred_test ) - assert 0.8 <= baseline_train_acc - assert 0.7 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.98, "gb": 0.98, "lr": 0.77, "dt": 0.98, "knn": 0.85, "svm": 0.83} + expected_test_acc = {"nn": 0.62, "rf": 0.88, "gb": 0.82, "lr": 0.86, "dt": 0.8, "knn": 0.81, "svm": 0.93} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_encoder(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_encoder(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -311,16 +326,19 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.96, "gb": 0.96, "lr": 0.71, "dt": 0.96, "knn": 0.89, "svm": 0.81} + expected_test_acc = {"nn": 0.62, "rf": 0.8, "gb": 0.77, "lr": 0.75, "dt": 0.82, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_no_encoder(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -398,16 +416,19 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.96, "gb": 0.96, "lr": 0.71, "dt": 0.96, "knn": 0.89, "svm": 0.81} + expected_test_acc = {"nn": 0.62, "rf": 0.8, "gb": 0.77, "lr": 0.75, "dt": 0.82, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_after_feature(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_no_encoder_after_feature(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -488,16 +509,19 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.5 <= baseline_train_acc - assert 0.5 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.94, "gb": 0.95, "lr": 0.8, "dt": 0.94, "knn": 0.87, "svm": 0.8} + expected_test_acc = {"nn": 0.62, "rf": 0.84, "gb": 0.84, "lr": 0.86, "dt": 0.82, "knn": 0.86, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_after_feature_slice(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_no_encoder_after_feature_slice(art_warning, get_iris_dataset, model_type): try: orig_attack_feature = 1 # petal length new_attack_feature = slice(1, 4) # petal length @@ -569,22 +593,27 @@ def transform_other_feature(x): # train attack model baseline_attack.fit(x_train) # infer attacked feature - baseline_inferred_train = baseline_attack.infer(x_train_for_attack) - baseline_inferred_test = baseline_attack.infer(x_test_for_attack) + baseline_inferred_train = np.argmax(baseline_attack.infer(x_train_for_attack), axis=1) + baseline_inferred_test = np.argmax(baseline_attack.infer(x_test_for_attack), axis=1) + x_train_feature = np.argmax(x_train_feature, axis=1) + x_test_feature = np.argmax(x_test_feature, axis=1) # check accuracy baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature) / len(baseline_inferred_train) baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature) / len(baseline_inferred_test) - assert 0.0 <= baseline_train_acc - assert 0.0 <= baseline_test_acc + expected_train_acc = {"nn": 0.96, "rf": 0.99, "gb": 0.99, "lr": 0.96, "dt": 0.98, "knn": 0.97, "svm": 0.96} + expected_test_acc = {"nn": 0.99, "rf": 0.97, "gb": 0.97, "lr": 0.99, "dt": 0.97, "knn": 0.99, "svm": 0.99} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_remove_attack_feature(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_baseline_no_encoder_remove_attack_feature(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -664,8 +693,11 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.58, "rf": 0.96, "gb": 0.96, "lr": 0.71, "dt": 0.96, "knn": 0.89, "svm": 0.81} + expected_test_acc = {"nn": 0.62, "rf": 0.8, "gb": 0.77, "lr": 0.75, "dt": 0.82, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) diff --git a/tests/attacks/inference/attribute_inference/test_black_box.py b/tests/attacks/inference/attribute_inference/test_black_box.py index 071ae9ebe3..15b526caa7 100644 --- a/tests/attacks/inference/attribute_inference/test_black_box.py +++ b/tests/attacks/inference/attribute_inference/test_black_box.py @@ -43,7 +43,7 @@ @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box(art_warning, decision_tree_estimator, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -86,15 +86,16 @@ def transform_feature(x): # check accuracy train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert pytest.approx(0.8285, abs=0.2) == train_acc - assert pytest.approx(0.8888, abs=0.18) == test_acc + assert pytest.approx(0.8285, abs=0.3) == train_acc + assert pytest.approx(0.8888, abs=0.3) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_continuous(art_warning, decision_tree_estimator, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -124,15 +125,21 @@ def test_black_box_continuous(art_warning, decision_tree_estimator, get_iris_dat inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions) inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions) # check accuracy - assert np.allclose(inferred_train, x_train_feature.reshape(1, -1), atol=0.4) - assert np.allclose(inferred_test, x_test_feature.reshape(1, -1), atol=0.4) + assert ( + np.count_nonzero(np.isclose(inferred_train, x_train_feature.reshape(1, -1), atol=0.4)) + > inferred_train.shape[0] * 0.75 + ) + assert ( + np.count_nonzero(np.isclose(inferred_test, x_test_feature.reshape(1, -1), atol=0.4)) + > inferred_test.shape[0] * 0.75 + ) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_slice(art_warning, decision_tree_estimator, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -177,15 +184,16 @@ def transform_feature(x): # check accuracy train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert pytest.approx(0.8285, abs=0.12) == train_acc - assert pytest.approx(0.8888, abs=0.18) == test_acc + assert pytest.approx(0.8285, abs=0.3) == train_acc + assert pytest.approx(0.8888, abs=0.3) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_with_label(art_warning, decision_tree_estimator, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -228,15 +236,16 @@ def transform_feature(x): # check accuracy train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert pytest.approx(0.8285, abs=0.12) == train_acc - assert pytest.approx(0.8888, abs=0.18) == test_acc + assert pytest.approx(0.8285, abs=0.3) == train_acc + assert pytest.approx(0.8888, abs=0.3) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_no_values(art_warning, decision_tree_estimator, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -277,15 +286,16 @@ def transform_feature(x): # check accuracy train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert pytest.approx(0.8285, abs=0.12) == train_acc - assert pytest.approx(0.8888, abs=0.18) == test_acc + assert pytest.approx(0.8285, abs=0.3) == train_acc + assert pytest.approx(0.8888, abs=0.3) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_regressor(art_warning, get_diabetes_dataset, model_type): try: attack_feature = 0 # age @@ -348,15 +358,16 @@ def transform_feature(x): train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert pytest.approx(0.0258, abs=0.12) == train_acc - assert pytest.approx(0.0375, abs=0.12) == test_acc + assert train_acc == pytest.approx(0.1, abs=0.15) + assert test_acc == pytest.approx(0.1, abs=0.15) + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_regressor_label(art_warning, get_diabetes_dataset, model_type): try: attack_feature = 0 # age @@ -419,8 +430,9 @@ def transform_feature(x): train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert pytest.approx(0.0258, abs=0.12) == train_acc - assert pytest.approx(0.0375, abs=0.12) == test_acc + assert pytest.approx(0.1, abs=0.15) == train_acc + assert pytest.approx(0.1, abs=0.15) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @@ -477,17 +489,18 @@ def transform_feature(x): inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions, values=values) # check accuracy # train_acc - _ = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) + train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) # test_acc - _ = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) + test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) # assert train_acc == pytest.approx(0.5523, abs=0.03) # assert test_acc == pytest.approx(0.5777, abs=0.03) + print(train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_one_hot(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -543,15 +556,16 @@ def transform_feature(x): # check accuracy train_acc = np.sum(np.all(inferred_train == train_one_hot, axis=1)) / len(inferred_train) test_acc = np.sum(np.all(inferred_test == test_one_hot, axis=1)) / len(inferred_test) - assert pytest.approx(0.8666, abs=0.12) == train_acc + assert pytest.approx(0.8666, abs=0.3) == train_acc assert pytest.approx(0.8888, abs=0.7) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_one_hot_float(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -613,8 +627,8 @@ def transform_feature(x): attack.fit(x_train) # infer attacked feature values = [[-0.559017, 1.7888544], [-0.47003216, 2.127514], [-1.1774395, 0.84930056]] - inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions, values=values) - inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions, values=values) + inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions, values=values).astype(np.float32) + inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions, values=values).astype(np.float32) # check accuracy train_acc = np.sum( np.all(np.around(inferred_train, decimals=3) == np.around(train_one_hot, decimals=3), axis=1) @@ -624,13 +638,14 @@ def transform_feature(x): ) / len(inferred_test) assert pytest.approx(0.8666, abs=0.12) == train_acc assert pytest.approx(0.8666, abs=0.1) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_one_hot_float_no_values(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -691,8 +706,8 @@ def transform_feature(x): # train attack model attack.fit(x_train) # infer attacked feature - inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions) - inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions) + inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions).astype(np.float32) + inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions).astype(np.float32) # check accuracy train_acc = np.sum( np.all(np.around(inferred_train, decimals=3) == np.around(train_one_hot, decimals=3), axis=1) @@ -702,14 +717,15 @@ def transform_feature(x): ) / len(inferred_test) assert pytest.approx(0.8666, abs=0.12) == train_acc assert pytest.approx(0.8666, abs=0.1) == test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_encoder(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_black_box_encoder(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -797,38 +813,31 @@ def transform_other_feature(x): pipeline.fit(x_train, np.argmax(y_train_iris, axis=1)) classifier = ScikitlearnClassifier(pipeline, preprocessing=None) - baseline_attack = AttributeInferenceBlackBox( + attack = AttributeInferenceBlackBox( classifier, attack_feature=attack_feature, attack_model_type=model_type, encoder=encoder ) # train attack model - baseline_attack.fit(x_train, y_train_iris) + attack.fit(x_train, y_train_iris) # infer attacked feature x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train)]).reshape(-1, 1) x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_for_pred)]).reshape(-1, 1) - baseline_inferred_train = baseline_attack.infer( - x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values - ) - baseline_inferred_test = baseline_attack.infer( - x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values - ) + inferred_train = attack.infer(x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values) + inferred_test = attack.infer(x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values) # check accuracy - baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature.reshape(1, -1)) / len( - baseline_inferred_train - ) - baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature.reshape(1, -1)) / len( - baseline_inferred_test - ) + train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) + test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + assert 0.6 <= train_acc + assert 0.6 <= test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_black_box_no_encoder(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -916,41 +925,34 @@ def transform_other_feature(x): pipeline.fit(x_train, np.argmax(y_train_iris, axis=1)) classifier = ScikitlearnClassifier(pipeline, preprocessing=None) - baseline_attack = AttributeInferenceBlackBox( + attack = AttributeInferenceBlackBox( classifier, attack_feature=attack_feature, attack_model_type=model_type, non_numerical_features=[other_feature], ) # train attack model - baseline_attack.fit(x_train, y_train_iris) + attack.fit(x_train, y_train_iris) # infer attacked feature x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train)]).reshape(-1, 1) x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_for_pred)]).reshape(-1, 1) - baseline_inferred_train = baseline_attack.infer( - x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values - ) - baseline_inferred_test = baseline_attack.infer( - x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values - ) + inferred_train = attack.infer(x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values) + inferred_test = attack.infer(x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values) # check accuracy - baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature.reshape(1, -1)) / len( - baseline_inferred_train - ) - baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature.reshape(1, -1)) / len( - baseline_inferred_test - ) + train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) + test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + assert 0.6 <= train_acc + assert 0.6 <= test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_after_feature(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_black_box_no_encoder_after_feature(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -1034,41 +1036,34 @@ def transform_other_feature(x): pipeline.fit(x_train, np.argmax(y_train_iris, axis=1)) classifier = ScikitlearnClassifier(pipeline, preprocessing=None) - baseline_attack = AttributeInferenceBlackBox( + attack = AttributeInferenceBlackBox( classifier, attack_feature=attack_feature, attack_model_type=model_type, non_numerical_features=[other_feature], ) # train attack model - baseline_attack.fit(x_train, y_train_iris) + attack.fit(x_train, y_train_iris) # infer attacked feature x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train)]).reshape(-1, 1) x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_for_pred)]).reshape(-1, 1) - baseline_inferred_train = baseline_attack.infer( - x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values - ) - baseline_inferred_test = baseline_attack.infer( - x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values - ) + inferred_train = attack.infer(x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values) + inferred_test = attack.infer(x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values) # check accuracy - baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature.reshape(1, -1)) / len( - baseline_inferred_train - ) - baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature.reshape(1, -1)) / len( - baseline_inferred_test - ) + train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) + test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert 0.5 <= baseline_train_acc - assert 0.5 <= baseline_test_acc + assert 0.4 <= train_acc + assert 0.35 <= test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_after_feature_slice(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_black_box_no_encoder_after_feature_slice(art_warning, get_iris_dataset, model_type): try: orig_attack_feature = 1 # petal length new_attack_feature = slice(1, 4) # petal length @@ -1158,33 +1153,34 @@ def transform_other_feature(x): pipeline.fit(x_train, np.argmax(y_train_iris, axis=1)) classifier = ScikitlearnClassifier(pipeline, preprocessing=None) - baseline_attack = AttributeInferenceBlackBox( + attack = AttributeInferenceBlackBox( classifier, attack_feature=new_attack_feature, attack_model_type=model_type, non_numerical_features=[other_feature], ) # train attack model - baseline_attack.fit(x_train, y_train_iris) + attack.fit(x_train, y_train_iris) # infer attacked feature x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train)]).reshape(-1, 1) x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_for_pred)]).reshape(-1, 1) - baseline_inferred_train = baseline_attack.infer(x_train_for_attack, y_train_iris, pred=x_train_predictions) - baseline_inferred_test = baseline_attack.infer(x_test_for_attack, y_test_iris, pred=x_test_predictions) + inferred_train = attack.infer(x_train_for_attack, y_train_iris, pred=x_train_predictions) + inferred_test = attack.infer(x_test_for_attack, y_test_iris, pred=x_test_predictions) # check accuracy - baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature) / len(baseline_inferred_train) - baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature) / len(baseline_inferred_test) + train_acc = np.sum(inferred_train == x_train_feature) / len(inferred_train) + test_acc = np.sum(inferred_test == x_test_feature) / len(inferred_test) - assert 0.0 <= baseline_train_acc - assert 0.0 <= baseline_test_acc + assert 0.0 <= train_acc + assert 0.0 <= test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_remove_attack_feature(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_black_box_no_encoder_remove_attack_feature(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -1272,33 +1268,26 @@ def transform_other_feature(x): pipeline.fit(x_train, np.argmax(y_train_iris, axis=1)) classifier = ScikitlearnClassifier(pipeline, preprocessing=None) - baseline_attack = AttributeInferenceBlackBox( + attack = AttributeInferenceBlackBox( classifier, attack_feature=attack_feature, attack_model_type=model_type, non_numerical_features=[other_feature, attack_feature], ) # train attack model - baseline_attack.fit(x_train, y_train_iris) + attack.fit(x_train, y_train_iris) # infer attacked feature x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train)]).reshape(-1, 1) x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_for_pred)]).reshape(-1, 1) - baseline_inferred_train = baseline_attack.infer( - x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values - ) - baseline_inferred_test = baseline_attack.infer( - x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values - ) + inferred_train = attack.infer(x_train_for_attack, y_train_iris, pred=x_train_predictions, values=values) + inferred_test = attack.infer(x_test_for_attack, y_test_iris, pred=x_test_predictions, values=values) # check accuracy - baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature.reshape(1, -1)) / len( - baseline_inferred_train - ) - baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature.reshape(1, -1)) / len( - baseline_inferred_test - ) + train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train) + test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + assert 0.6 <= train_acc + assert 0.6 <= test_acc + print(model_type, train_acc, test_acc) except ARTTestException as e: art_warning(e) diff --git a/tests/attacks/inference/attribute_inference/test_true_label_baseline.py b/tests/attacks/inference/attribute_inference/test_true_label_baseline.py index 1e63212e11..67d84f8116 100644 --- a/tests/attacks/inference/attribute_inference/test_true_label_baseline.py +++ b/tests/attacks/inference/attribute_inference/test_true_label_baseline.py @@ -34,7 +34,7 @@ @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_true_label_baseline(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -79,15 +79,18 @@ def transform_feature(x): baseline_inferred_test ) - assert 0.8 <= baseline_train_acc - assert 0.7 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.98, "gb": 0.98, "lr": 0.81, "dt": 0.98, "knn": 0.85, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.8, "gb": 0.74, "lr": 0.88, "dt": 0.75, "knn": 0.82, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_true_label_baseline_continuous(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -112,15 +115,21 @@ def test_true_label_baseline_continuous(art_warning, get_iris_dataset, model_typ baseline_inferred_train = baseline_attack.infer(x_train_for_attack, y=y_train_iris) baseline_inferred_test = baseline_attack.infer(x_test_for_attack, y=y_test_iris) # check accuracy - assert np.allclose(baseline_inferred_train, x_train_feature.reshape(1, -1), atol=0.2) - assert np.allclose(baseline_inferred_test, x_test_feature.reshape(1, -1), atol=0.2) + assert ( + np.count_nonzero(np.isclose(baseline_inferred_train, x_train_feature.reshape(1, -1), atol=0.4)) + > baseline_inferred_train.shape[0] * 0.75 + ) + assert ( + np.count_nonzero(np.isclose(baseline_inferred_test, x_test_feature.reshape(1, -1), atol=0.4)) + > baseline_inferred_test.shape[0] * 0.75 + ) except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_true_label_baseline_column(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -167,15 +176,18 @@ def transform_feature(x): baseline_inferred_test ) - assert 0.8 <= baseline_train_acc - assert 0.7 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.98, "gb": 0.98, "lr": 0.81, "dt": 0.98, "knn": 0.87, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.8, "gb": 0.82, "lr": 0.88, "dt": 0.75, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_true_label_baseline_no_values(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -218,8 +230,11 @@ def transform_feature(x): baseline_inferred_test ) - assert 0.8 <= baseline_train_acc - assert 0.7 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.98, "gb": 0.98, "lr": 0.81, "dt": 0.98, "knn": 0.85, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.83, "gb": 0.75, "lr": 0.88, "dt": 0.8, "knn": 0.82, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @@ -276,7 +291,7 @@ def transform_feature(x): @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_true_label_baseline_regression(art_warning, get_diabetes_dataset, model_type): try: attack_feature = 1 # sex @@ -308,16 +323,19 @@ def test_true_label_baseline_regression(art_warning, get_diabetes_dataset, model baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.45, "rf": 0.99, "gb": 0.97, "lr": 0.68, "dt": 0.99, "knn": 0.69, "svm": 0.54} + expected_test_acc = {"nn": 0.45, "rf": 0.6, "gb": 0.65, "lr": 0.68, "dt": 0.54, "knn": 0.45, "svm": 0.47} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_encoder(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_true_label_baseline_encoder(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -404,16 +422,19 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.96, "gb": 0.96, "lr": 0.81, "dt": 0.96, "knn": 0.9, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.77, "gb": 0.77, "lr": 0.88, "dt": 0.81, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_true_label_baseline_no_encoder(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -491,16 +512,19 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.96, "gb": 0.96, "lr": 0.81, "dt": 0.96, "knn": 0.9, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.81, "gb": 0.77, "lr": 0.88, "dt": 0.82, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_after_feature(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_true_label_baseline_no_encoder_after_feature(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -581,16 +605,19 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.5 <= baseline_train_acc - assert 0.5 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.95, "gb": 0.95, "lr": 0.81, "dt": 0.94, "knn": 0.87, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.82, "gb": 0.8, "lr": 0.88, "dt": 0.74, "knn": 0.86, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_after_feature_slice(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_true_label_baseline_no_encoder_after_feature_slice(art_warning, get_iris_dataset, model_type): try: orig_attack_feature = 1 # petal length new_attack_feature = slice(1, 4) # petal length @@ -662,22 +689,27 @@ def transform_other_feature(x): # train attack model baseline_attack.fit(x_train, y_train_iris) # infer attacked feature - baseline_inferred_train = baseline_attack.infer(x_train_for_attack, y_train_iris) - baseline_inferred_test = baseline_attack.infer(x_test_for_attack, y_test_iris) + baseline_inferred_train = np.argmax(baseline_attack.infer(x_train_for_attack, y_train_iris), axis=1) + baseline_inferred_test = np.argmax(baseline_attack.infer(x_test_for_attack, y_test_iris), axis=1) + x_train_feature = np.argmax(x_train_feature, axis=1) + x_test_feature = np.argmax(x_test_feature, axis=1) # check accuracy baseline_train_acc = np.sum(baseline_inferred_train == x_train_feature) / len(baseline_inferred_train) baseline_test_acc = np.sum(baseline_inferred_test == x_test_feature) / len(baseline_inferred_test) - assert 0.0 <= baseline_train_acc - assert 0.0 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.98, "gb": 0.98, "lr": 0.81, "dt": 0.98, "knn": 0.85, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.86, "gb": 0.8, "lr": 0.88, "dt": 0.84, "knn": 0.82, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) @pytest.mark.skip_framework("dl_frameworks") -@pytest.mark.parametrize("model_type", ["nn", "rf"]) -def test_black_box_baseline_no_encoder_remove_attack_feature(art_warning, get_iris_dataset, model_type): +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) +def test_true_label_baseline_no_encoder_remove_attack_feature(art_warning, get_iris_dataset, model_type): try: attack_feature = 2 # petal length @@ -757,8 +789,11 @@ def transform_other_feature(x): baseline_inferred_test ) - assert 0.6 <= baseline_train_acc - assert 0.6 <= baseline_test_acc + expected_train_acc = {"nn": 0.81, "rf": 0.96, "gb": 0.96, "lr": 0.81, "dt": 0.96, "knn": 0.9, "svm": 0.81} + expected_test_acc = {"nn": 0.88, "rf": 0.82, "gb": 0.77, "lr": 0.88, "dt": 0.82, "knn": 0.84, "svm": 0.88} + + assert expected_train_acc[model_type] <= baseline_train_acc + assert expected_test_acc[model_type] <= baseline_test_acc except ARTTestException as e: art_warning(e) diff --git a/tests/attacks/inference/membership_inference/test_black_box.py b/tests/attacks/inference/membership_inference/test_black_box.py index 30573f21e2..0896620404 100644 --- a/tests/attacks/inference/membership_inference/test_black_box.py +++ b/tests/attacks/inference/membership_inference/test_black_box.py @@ -47,7 +47,7 @@ def test_black_box_image(art_warning, get_default_mnist_subset, image_dl_estimat art_warning(e) -@pytest.mark.parametrize("model_type", ["nn", "rf", "gb"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_tabular(art_warning, model_type, tabular_dl_estimator_for_attack, get_iris_dataset): try: classifier = tabular_dl_estimator_for_attack(MembershipInferenceBlackBox) @@ -57,7 +57,7 @@ def test_black_box_tabular(art_warning, model_type, tabular_dl_estimator_for_att art_warning(e) -@pytest.mark.parametrize("model_type", ["nn", "rf", "gb"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_loss_tabular(art_warning, model_type, tabular_dl_estimator_for_attack, get_iris_dataset): try: classifier = tabular_dl_estimator_for_attack(MembershipInferenceBlackBox) @@ -68,7 +68,7 @@ def test_black_box_loss_tabular(art_warning, model_type, tabular_dl_estimator_fo art_warning(e) -@pytest.mark.parametrize("model_type", ["nn", "rf", "gb"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_loss_regression(art_warning, model_type, get_diabetes_dataset): try: from sklearn import linear_model @@ -84,7 +84,7 @@ def test_black_box_loss_regression(art_warning, model_type, get_diabetes_dataset art_warning(e) -@pytest.mark.skip_framework("tensorflow", "pytorch", "scikitlearn", "mxnet", "kerastf") +@pytest.mark.skip_framework("tensorflow", "pytorch", "huggingface", "scikitlearn", "mxnet", "kerastf") @pytest.mark.skipif(keras.__version__.startswith("2.2"), reason="requires Keras 2.3.0 or higher") def test_black_box_keras_loss(art_warning, get_iris_dataset): try: @@ -175,7 +175,7 @@ def test_black_box_with_model_prob( art_warning(e) -@pytest.mark.parametrize("model_type", ["nn", "rf", "gb"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_pred(art_warning, model_type, tabular_dl_estimator_for_attack, get_iris_dataset): try: (x_train, _), (x_test, _) = get_iris_dataset @@ -189,7 +189,7 @@ def test_black_box_pred(art_warning, model_type, tabular_dl_estimator_for_attack art_warning(e) -@pytest.mark.parametrize("model_type", ["nn", "rf", "gb"]) +@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"]) def test_black_box_loss_regression_pred(art_warning, model_type, get_diabetes_dataset): try: from sklearn import linear_model diff --git a/tests/attacks/poison/test_bullseye_polytope_attack.py b/tests/attacks/poison/test_bullseye_polytope_attack.py index fb70cc1692..e5b7227644 100644 --- a/tests/attacks/poison/test_bullseye_polytope_attack.py +++ b/tests/attacks/poison/test_bullseye_polytope_attack.py @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__) -@pytest.mark.skip_framework("non_dl_frameworks", "tensorflow", "mxnet", "keras", "kerastf") +@pytest.mark.skip_framework("non_dl_frameworks", "tensorflow", "mxnet", "keras", "kerastf", "huggingface") def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator): try: (x_train, y_train), (_, _) = get_default_mnist_subset @@ -47,7 +47,7 @@ def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator): art_warning(e) -@pytest.mark.skip_framework("non_dl_frameworks", "tensorflow", "mxnet", "keras", "kerastf") +@pytest.mark.skip_framework("non_dl_frameworks", "tensorflow", "mxnet", "keras", "kerastf", "huggingface") def test_poison_end2end(art_warning, get_default_mnist_subset, image_dl_estimator): try: (x_train, y_train), (_, _) = get_default_mnist_subset diff --git a/tests/attacks/poison/test_clean_label_backdoor_attack.py b/tests/attacks/poison/test_clean_label_backdoor_attack.py index 299afbe652..c853e39e2a 100644 --- a/tests/attacks/poison/test_clean_label_backdoor_attack.py +++ b/tests/attacks/poison/test_clean_label_backdoor_attack.py @@ -30,13 +30,18 @@ logger = logging.getLogger(__name__) -@pytest.mark.skip_framework("non_dl_frameworks", "pytorch", "mxnet") -def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator): +@pytest.mark.skip_framework("non_dl_frameworks", "mxnet") +def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator, framework): try: (x_train, y_train), (_, _) = get_default_mnist_subset classifier, _ = image_dl_estimator() target = to_categorical([9], 10)[0] - backdoor = PoisoningAttackBackdoor(add_pattern_bd) + print(x_train.shape) + + def mod(x): + return add_pattern_bd(x, channels_first=classifier.channels_first) + + backdoor = PoisoningAttackBackdoor(mod) attack = PoisoningAttackCleanLabelBackdoor(backdoor, classifier, target) poison_data, poison_labels = attack.poison(x_train, y_train) @@ -47,7 +52,7 @@ def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator): @pytest.mark.parametrize("params", [dict(pp_poison=-0.2), dict(pp_poison=1.2)]) -@pytest.mark.skip_framework("non_dl_frameworks", "pytorch", "mxnet") +@pytest.mark.skip_framework("non_dl_frameworks", "mxnet") def test_failure_modes(art_warning, image_dl_estimator, params): try: classifier, _ = image_dl_estimator() diff --git a/tests/attacks/poison/test_hidden_trigger_backdoor.py b/tests/attacks/poison/test_hidden_trigger_backdoor.py index c2b011c45a..e73c275738 100644 --- a/tests/attacks/poison/test_hidden_trigger_backdoor.py +++ b/tests/attacks/poison/test_hidden_trigger_backdoor.py @@ -25,6 +25,7 @@ from art.attacks.poisoning import PoisoningAttackBackdoor from art.attacks.poisoning.perturbations import add_pattern_bd from art.estimators.classification.pytorch import PyTorchClassifier +from art.estimators.classification.hugging_face import HuggingFaceClassifierPyTorch from tests.utils import ARTTestException @@ -32,12 +33,15 @@ @pytest.mark.skip_framework("non_dl_frameworks", "tensorflow1", "tensorflow2v1", "mxnet") -def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator): +def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator, framework): try: (x_train, y_train), (_, _) = get_default_mnist_subset - classifier, _ = image_dl_estimator(functional=True) + functional = True + if framework == "huggingface": + functional = False + classifier, _ = image_dl_estimator(functional=functional) - if isinstance(classifier, PyTorchClassifier): + if isinstance(classifier, (PyTorchClassifier, HuggingFaceClassifierPyTorch)): def mod(x): original_dtype = x.dtype @@ -79,10 +83,13 @@ def mod(x): @pytest.mark.skip_framework("non_dl_frameworks", "tensorflow1", "tensorflow2v1", "mxnet") -def test_check_params(art_warning, get_default_mnist_subset, image_dl_estimator): +def test_check_params(art_warning, get_default_mnist_subset, image_dl_estimator, framework): try: (x_train, y_train), (_, _) = get_default_mnist_subset - classifier, _ = image_dl_estimator(functional=True) + functional = True + if framework == "huggingface": + functional = False + classifier, _ = image_dl_estimator(functional=functional) if isinstance(classifier, PyTorchClassifier): diff --git a/tests/attacks/test_attack.py b/tests/attacks/test_attack.py new file mode 100644 index 0000000000..d41b3c85d8 --- /dev/null +++ b/tests/attacks/test_attack.py @@ -0,0 +1,38 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import pytest + + +@pytest.mark.skip_framework("tensorflow1", "tensorflow2v1", "keras", "non_dl_frameworks", "mxnet", "kerastf") +def test_attack_repr(image_dl_estimator): + + from art.attacks.evasion import ProjectedGradientDescentNumpy + + classifier, _ = image_dl_estimator(from_logits=True) + + attack = ProjectedGradientDescentNumpy( + estimator=classifier, + targeted=True, + decay=0.5, + ) + print(repr(attack)) + assert repr(attack) == ( + "ProjectedGradientDescentNumpy(norm=inf, eps=0.3, eps_step=0.1, targeted=True, " + + "num_random_init=0, batch_size=32, minimal=False, summary_writer=None, decay=0.5, " + + "max_iter=100, random_eps=False, verbose=True, )" + ) diff --git a/tests/attacks/test_targeted_universal_perturbation.py b/tests/attacks/test_targeted_universal_perturbation.py index 4982f4e58c..39d5e09ada 100644 --- a/tests/attacks/test_targeted_universal_perturbation.py +++ b/tests/attacks/test_targeted_universal_perturbation.py @@ -74,7 +74,13 @@ def test_2_tensorflow_mnist(self): # Attack up = TargetedUniversalPerturbation( - tfc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True, "verbose": False} + tfc, + max_iter=1, + attacker="fgsm", + attacker_params={ + "eps": 0.3, + "targeted": True, + }, ) x_train_adv = up.generate(self.x_train_mnist, y=y_target) self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) @@ -108,7 +114,13 @@ def test_4_keras_mnist(self): # Attack up = TargetedUniversalPerturbation( - krc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True, "verbose": False} + krc, + max_iter=1, + attacker="fgsm", + attacker_params={ + "eps": 0.3, + "targeted": True, + }, ) x_train_adv = up.generate(self.x_train_mnist, y=y_target) self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) @@ -144,7 +156,13 @@ def test_3_pytorch_mnist(self): # Attack up = TargetedUniversalPerturbation( - ptc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True, "verbose": False} + ptc, + max_iter=1, + attacker="fgsm", + attacker_params={ + "eps": 0.3, + "targeted": True, + }, ) x_train_mnist_adv = up.generate(x_train_mnist, y=y_target) self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) diff --git a/tests/defences/test_adversarial_trainer_madry_pgd.py b/tests/defences/test_adversarial_trainer_madry_pgd.py deleted file mode 100644 index 465dcc50f6..0000000000 --- a/tests/defences/test_adversarial_trainer_madry_pgd.py +++ /dev/null @@ -1,80 +0,0 @@ -# MIT License -# -# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging -import unittest - -import numpy as np - -from art.defences.trainer.adversarial_trainer_madry_pgd import AdversarialTrainerMadryPGD -from art.utils import load_mnist - -from tests.utils import master_seed, get_image_classifier_tf - -logger = logging.getLogger(__name__) - -BATCH_SIZE = 10 -NB_TRAIN = 100 -NB_TEST = 100 - - -class TestAdversarialTrainerMadryPGD(unittest.TestCase): - """ - Test cases for the AdversarialTrainerMadryPGD class. - """ - - @classmethod - def setUpClass(cls): - # MNIST - (x_train, y_train), (x_test, y_test), _, _ = load_mnist() - x_train, y_train, x_test, y_test = ( - x_train[:NB_TRAIN], - y_train[:NB_TRAIN], - x_test[:NB_TEST], - y_test[:NB_TEST], - ) - cls.mnist = ((x_train, y_train), (x_test, y_test)) - - cls.classifier, _ = get_image_classifier_tf() - - def setUp(self): - master_seed(seed=1234) - - def test_fit_predict(self): - (x_train, y_train), (x_test, y_test) = self.mnist - x_test_original = x_test.copy() - - adv_trainer = AdversarialTrainerMadryPGD(self.classifier, nb_epochs=1, batch_size=128) - adv_trainer.fit(x_train, y_train) - - predictions_new = np.argmax(adv_trainer.trainer.get_classifier().predict(x_test), axis=1) - accuracy_new = np.sum(predictions_new == np.argmax(y_test, axis=1)) / NB_TEST - - self.assertEqual(accuracy_new, 0.38) - - # Check that x_test has not been modified by attack and classifier - self.assertAlmostEqual(float(np.max(np.abs(x_test_original - x_test))), 0.0, delta=0.00001) - - def test_get_classifier(self): - adv_trainer = AdversarialTrainerMadryPGD(self.classifier, nb_epochs=1, batch_size=128) - _ = adv_trainer.get_classifier() - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/defences/trainer/test_adversarial_trainer_FBF.py b/tests/defences/trainer/test_adversarial_trainer_FBF.py index 3b3e772138..a08037618e 100644 --- a/tests/defences/trainer/test_adversarial_trainer_FBF.py +++ b/tests/defences/trainer/test_adversarial_trainer_FBF.py @@ -32,7 +32,7 @@ def _get_adv_trainer(): trainer = None if framework in ["tensorflow", "tensorflow2v1"]: trainer = None - if framework == "pytorch": + if framework in ["pytorch", "huggingface"]: classifier, _ = image_dl_estimator() trainer = AdversarialTrainerFBFPyTorch(classifier, eps=0.05) if framework == "scikitlearn": @@ -51,7 +51,7 @@ def fix_get_mnist_subset(get_mnist_dataset): yield x_train_mnist[:n_train], y_train_mnist[:n_train], x_test_mnist[:n_test], y_test_mnist[:n_test] -@pytest.mark.skip_framework("tensorflow", "keras", "scikitlearn", "mxnet", "kerastf") +@pytest.mark.skip_framework("tensorflow", "keras", "scikitlearn", "mxnet", "kerastf", "huggingface") def test_adversarial_trainer_fbf_pytorch_fit_and_predict(get_adv_trainer, fix_get_mnist_subset): (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset x_test_mnist_original = x_test_mnist.copy() @@ -80,7 +80,7 @@ def test_adversarial_trainer_fbf_pytorch_fit_and_predict(get_adv_trainer, fix_ge trainer.fit(x_train_mnist, y_train_mnist, nb_epochs=20, validation_data=(x_train_mnist, y_train_mnist)) -@pytest.mark.skip_framework("tensorflow", "keras", "scikitlearn", "mxnet", "kerastf") +@pytest.mark.skip_framework("tensorflow", "keras", "scikitlearn", "mxnet", "kerastf", "huggingface") def test_adversarial_trainer_fbf_pytorch_fit_generator_and_predict( get_adv_trainer, fix_get_mnist_subset, image_data_generator ): diff --git a/tests/defences/trainer/test_adversarial_trainer_awp_pytorch.py b/tests/defences/trainer/test_adversarial_trainer_awp_pytorch.py new file mode 100644 index 0000000000..3940654be8 --- /dev/null +++ b/tests/defences/trainer/test_adversarial_trainer_awp_pytorch.py @@ -0,0 +1,287 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import pytest +import logging +import numpy as np + +from art.defences.trainer import AdversarialTrainerAWPPyTorch +from art.attacks.evasion import ProjectedGradientDescent + + +@pytest.fixture() +def get_adv_trainer_awppgd(framework, image_dl_estimator): + def _get_adv_trainer_awppgd(): + + if framework == "keras": + trainer = None + if framework in ["tensorflow", "tensorflow2v1"]: + trainer = None + if framework == "pytorch": + classifier, _ = image_dl_estimator(from_logits=True) + proxy_classifier, _ = image_dl_estimator(from_logits=True) + attack = ProjectedGradientDescent( + classifier, + norm=np.inf, + eps=0.2, + eps_step=0.02, + max_iter=20, + targeted=False, + num_random_init=1, + batch_size=128, + verbose=False, + ) + trainer = AdversarialTrainerAWPPyTorch( + classifier, proxy_classifier, attack, mode="PGD", gamma=0.01, beta=6.0, warmup=0 + ) + if framework == "scikitlearn": + trainer = None + + return trainer + + return _get_adv_trainer_awppgd + + +@pytest.fixture() +def get_adv_trainer_awptrades(framework, image_dl_estimator): + def _get_adv_trainer_awptrades(): + + if framework == "keras": + trainer = None + if framework in ["tensorflow", "tensorflow2v1"]: + trainer = None + if framework == "pytorch": + classifier, _ = image_dl_estimator(from_logits=True) + proxy_classifier, _ = image_dl_estimator(from_logits=True) + attack = ProjectedGradientDescent( + classifier, + norm=np.inf, + eps=0.2, + eps_step=0.02, + max_iter=20, + targeted=False, + num_random_init=1, + batch_size=128, + verbose=False, + ) + trainer = AdversarialTrainerAWPPyTorch( + classifier, proxy_classifier, attack, mode="TRADES", gamma=0.01, beta=6.0, warmup=0 + ) + if framework == "scikitlearn": + trainer = None + + return trainer + + return _get_adv_trainer_awptrades + + +@pytest.fixture() +def fix_get_mnist_subset(get_mnist_dataset): + (x_train_mnist, y_train_mnist), (x_test_mnist, y_test_mnist) = get_mnist_dataset + n_train = 100 + n_test = 100 + yield x_train_mnist[:n_train], y_train_mnist[:n_train], x_test_mnist[:n_test], y_test_mnist[:n_test] + + +@pytest.mark.only_with_platform("pytorch") +@pytest.mark.parametrize("label_format", ["one_hot", "numerical"]) +def test_adversarial_trainer_awppgd_pytorch_fit_and_predict(get_adv_trainer_awppgd, fix_get_mnist_subset, label_format): + (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset + x_test_mnist_original = x_test_mnist.copy() + + if label_format == "one_hot": + assert y_train_mnist.shape[-1] == 10 + assert y_test_mnist.shape[-1] == 10 + if label_format == "numerical": + y_train_mnist = np.argmax(y_train_mnist, axis=1) + y_test_mnist = np.argmax(y_test_mnist, axis=1) + + trainer = get_adv_trainer_awppgd() + if trainer is None: + logging.warning("Couldn't perform this test because no trainer is defined for this framework configuration") + return + + predictions = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy = np.sum(predictions == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy = np.sum(predictions == y_test_mnist) / x_test_mnist.shape[0] + + trainer.fit(x_train_mnist, y_train_mnist, nb_epochs=20) + predictions_new = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy_new = np.sum(predictions_new == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy_new = np.sum(predictions_new == y_test_mnist) / x_test_mnist.shape[0] + + np.testing.assert_array_almost_equal( + float(np.mean(x_test_mnist_original - x_test_mnist)), + 0.0, + decimal=4, + ) + + assert accuracy == 0.32 + assert accuracy_new > 0.32 + + trainer.fit(x_train_mnist, y_train_mnist, nb_epochs=20, validation_data=(x_train_mnist, y_train_mnist)) + + +@pytest.mark.only_with_platform("pytorch") +@pytest.mark.parametrize("label_format", ["one_hot", "numerical"]) +def test_adversarial_trainer_awptrades_pytorch_fit_and_predict( + get_adv_trainer_awptrades, fix_get_mnist_subset, label_format +): + (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset + x_test_mnist_original = x_test_mnist.copy() + + if label_format == "one_hot": + assert y_train_mnist.shape[-1] == 10 + assert y_test_mnist.shape[-1] == 10 + if label_format == "numerical": + y_train_mnist = np.argmax(y_train_mnist, axis=1) + y_test_mnist = np.argmax(y_test_mnist, axis=1) + + trainer = get_adv_trainer_awptrades() + if trainer is None: + logging.warning("Couldn't perform this test because no trainer is defined for this framework configuration") + return + + predictions = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy = np.sum(predictions == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy = np.sum(predictions == y_test_mnist) / x_test_mnist.shape[0] + + trainer.fit(x_train_mnist, y_train_mnist, nb_epochs=20) + predictions_new = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy_new = np.sum(predictions_new == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy_new = np.sum(predictions_new == y_test_mnist) / x_test_mnist.shape[0] + + np.testing.assert_array_almost_equal( + float(np.mean(x_test_mnist_original - x_test_mnist)), + 0.0, + decimal=4, + ) + + assert accuracy == 0.32 + assert accuracy_new > 0.32 + + trainer.fit(x_train_mnist, y_train_mnist, nb_epochs=20, validation_data=(x_train_mnist, y_train_mnist)) + + +@pytest.mark.only_with_platform("pytorch") +@pytest.mark.parametrize("label_format", ["one_hot", "numerical"]) +def test_adversarial_trainer_awppgd_pytorch_fit_generator_and_predict( + get_adv_trainer_awppgd, fix_get_mnist_subset, image_data_generator, label_format +): + (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset + x_test_mnist_original = x_test_mnist.copy() + + if label_format == "one_hot": + assert y_train_mnist.shape[-1] == 10 + assert y_test_mnist.shape[-1] == 10 + if label_format == "numerical": + y_test_mnist = np.argmax(y_test_mnist, axis=1) + + generator = image_data_generator() + + trainer = get_adv_trainer_awppgd() + if trainer is None: + logging.warning("Couldn't perform this test because no trainer is defined for this framework configuration") + return + + predictions = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy = np.sum(predictions == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy = np.sum(predictions == y_test_mnist) / x_test_mnist.shape[0] + + trainer.fit_generator(generator=generator, nb_epochs=20) + predictions_new = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy_new = np.sum(predictions_new == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy_new = np.sum(predictions_new == y_test_mnist) / x_test_mnist.shape[0] + + np.testing.assert_array_almost_equal( + float(np.mean(x_test_mnist_original - x_test_mnist)), + 0.0, + decimal=4, + ) + + assert accuracy == 0.32 + assert accuracy_new > 0.32 + + trainer.fit_generator(generator=generator, nb_epochs=20, validation_data=(x_train_mnist, y_train_mnist)) + + +@pytest.mark.only_with_platform("pytorch") +@pytest.mark.parametrize("label_format", ["one_hot", "numerical"]) +def test_adversarial_trainer_awptrades_pytorch_fit_generator_and_predict( + get_adv_trainer_awptrades, fix_get_mnist_subset, image_data_generator, label_format +): + (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset + x_test_mnist_original = x_test_mnist.copy() + + if label_format == "one_hot": + assert y_train_mnist.shape[-1] == 10 + assert y_test_mnist.shape[-1] == 10 + if label_format == "numerical": + y_test_mnist = np.argmax(y_test_mnist, axis=1) + + generator = image_data_generator() + + trainer = get_adv_trainer_awptrades() + if trainer is None: + logging.warning("Couldn't perform this test because no trainer is defined for this framework configuration") + return + + predictions = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy = np.sum(predictions == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy = np.sum(predictions == y_test_mnist) / x_test_mnist.shape[0] + + trainer.fit_generator(generator=generator, nb_epochs=20) + predictions_new = np.argmax(trainer.predict(x_test_mnist), axis=1) + + if label_format == "one_hot": + accuracy_new = np.sum(predictions_new == np.argmax(y_test_mnist, axis=1)) / x_test_mnist.shape[0] + else: + accuracy_new = np.sum(predictions_new == y_test_mnist) / x_test_mnist.shape[0] + + np.testing.assert_array_almost_equal( + float(np.mean(x_test_mnist_original - x_test_mnist)), + 0.0, + decimal=4, + ) + + assert accuracy == 0.32 + assert accuracy_new > 0.32 + + trainer.fit_generator(generator=generator, nb_epochs=20, validation_data=(x_train_mnist, y_train_mnist)) diff --git a/tests/defences/trainer/test_adversarial_trainer_madry_pgd.py b/tests/defences/trainer/test_adversarial_trainer_madry_pgd.py new file mode 100644 index 0000000000..daa01c8d8d --- /dev/null +++ b/tests/defences/trainer/test_adversarial_trainer_madry_pgd.py @@ -0,0 +1,61 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +import pytest + +from art.defences.trainer.adversarial_trainer_madry_pgd import AdversarialTrainerMadryPGD + +logger = logging.getLogger(__name__) + + +@pytest.fixture() +def fix_get_mnist_subset(get_mnist_dataset): + (x_train_mnist, y_train_mnist), (x_test_mnist, y_test_mnist) = get_mnist_dataset + n_train = 100 + n_test = 100 + yield x_train_mnist[:n_train], y_train_mnist[:n_train], x_test_mnist[:n_test], y_test_mnist[:n_test] + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "huggingface", "tensorflow1", "tensorflow2v1") +def test_fit_predict(art_warning, image_dl_estimator, fix_get_mnist_subset): + classifier, _ = image_dl_estimator() + + (x_train, y_train, x_test, y_test) = fix_get_mnist_subset + x_test_original = x_test.copy() + + adv_trainer = AdversarialTrainerMadryPGD(classifier, nb_epochs=1, batch_size=128) + adv_trainer.fit(x_train, y_train) + + predictions_new = np.argmax(adv_trainer.trainer.get_classifier().predict(x_test), axis=1) + accuracy_new = np.mean(predictions_new == np.argmax(y_test, axis=1)) + + assert accuracy_new == pytest.approx(0.375, abs=0.05) + # Check that x_test has not been modified by attack and classifier + assert np.allclose(x_test_original, x_test) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "tensorflow1", "huggingface", "tensorflow2v1") +def test_get_classifier(art_warning, image_dl_estimator): + classifier, _ = image_dl_estimator() + + adv_trainer = AdversarialTrainerMadryPGD(classifier, nb_epochs=1, batch_size=128) + _ = adv_trainer.get_classifier() diff --git a/tests/defences/trainer/test_adversarial_trainer_trades_pytorch.py b/tests/defences/trainer/test_adversarial_trainer_trades_pytorch.py index 8d081f418c..e00bd1d21c 100644 --- a/tests/defences/trainer/test_adversarial_trainer_trades_pytorch.py +++ b/tests/defences/trainer/test_adversarial_trainer_trades_pytorch.py @@ -47,7 +47,8 @@ def _get_adv_trainer(): verbose=False, ) trainer = AdversarialTrainerTRADESPyTorch(classifier, attack, beta=6.0) - if framework == "scikitlearn": + + if framework in ["huggingface", "scikitlearn"]: trainer = None return trainer diff --git a/tests/defences/trainer/test_certified_adversarial_trainer.py b/tests/defences/trainer/test_certified_adversarial_trainer.py index 196d1b8abb..3cbaa984e6 100644 --- a/tests/defences/trainer/test_certified_adversarial_trainer.py +++ b/tests/defences/trainer/test_certified_adversarial_trainer.py @@ -65,7 +65,7 @@ def fix_get_cifar10_data(): @pytest.mark.skip_framework( - "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1" + "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1", "huggingface" ) def test_mnist_certified_training(art_warning, fix_get_mnist_data): """ @@ -115,7 +115,7 @@ def test_mnist_certified_training(art_warning, fix_get_mnist_data): @pytest.mark.skip_framework( - "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1" + "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1", "huggingface" ) def test_mnist_certified_loss(art_warning, fix_get_mnist_data): """ @@ -236,7 +236,7 @@ def test_mnist_certified_loss(art_warning, fix_get_mnist_data): @pytest.mark.skip_framework( - "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1" + "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1", "huggingface" ) def test_cifar_certified_training(art_warning, fix_get_cifar10_data): """ @@ -286,7 +286,7 @@ def test_cifar_certified_training(art_warning, fix_get_cifar10_data): @pytest.mark.skip_framework( - "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1" + "mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2", "tensorflow2v1", "huggingface" ) def test_cifar_certified_loss(art_warning, fix_get_cifar10_data): """ diff --git a/tests/defences/trainer/test_dp_instahide_trainer.py b/tests/defences/trainer/test_dp_instahide_trainer.py index c1a9b4926c..5f77f92e84 100644 --- a/tests/defences/trainer/test_dp_instahide_trainer.py +++ b/tests/defences/trainer/test_dp_instahide_trainer.py @@ -24,6 +24,7 @@ from art.defences.trainer import DPInstaHideTrainer from art.estimators.classification import PyTorchClassifier, TensorFlowV2Classifier, KerasClassifier from tests.utils import ARTTestException +from tests.utils import get_image_classifier_hf logger = logging.getLogger(__name__) @@ -83,6 +84,9 @@ def _get_classifier(): model.compile(optimizer="adam", loss="categorical_crossentropy") classifier = KerasClassifier(model, clip_values=(0, 1), use_logits=True) + elif framework == "huggingface": + classifier = get_image_classifier_hf(from_logits=True) + else: classifier = None @@ -91,11 +95,15 @@ def _get_classifier(): return _get_classifier -@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf", "huggingface") @pytest.mark.parametrize("noise", ["gaussian", "laplacian", "exponential"]) -def test_dp_instahide_single_aug(art_warning, get_mnist_classifier, get_default_mnist_subset, noise): +def test_dp_instahide_single_aug( + art_warning, get_mnist_classifier, get_default_mnist_subset, get_default_cifar10_subset, noise, framework +): classifier = get_mnist_classifier() + (x_train, y_train), (_, _) = get_default_mnist_subset + mixup = Mixup(num_classes=10) try: @@ -105,11 +113,15 @@ def test_dp_instahide_single_aug(art_warning, get_mnist_classifier, get_default_ art_warning(e) -@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf", "huggingface") @pytest.mark.parametrize("noise", ["gaussian", "laplacian", "exponential"]) -def test_dp_instahide_multiple_aug(art_warning, get_mnist_classifier, get_default_mnist_subset, noise): +def test_dp_instahide_multiple_aug( + art_warning, get_mnist_classifier, get_default_mnist_subset, get_default_cifar10_subset, noise, framework +): classifier = get_mnist_classifier() + (x_train, y_train), (_, _) = get_default_mnist_subset + mixup = Mixup(num_classes=10) cutout = Cutout(length=8, channels_first=False) @@ -120,10 +132,13 @@ def test_dp_instahide_multiple_aug(art_warning, get_mnist_classifier, get_defaul art_warning(e) -@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf", "huggingface") @pytest.mark.parametrize("noise", ["gaussian", "laplacian", "exponential"]) -def test_dp_instahide_validation_data(art_warning, get_mnist_classifier, get_default_mnist_subset, noise): +def test_dp_instahide_validation_data( + art_warning, get_mnist_classifier, get_default_mnist_subset, get_default_cifar10_subset, noise, framework +): classifier = get_mnist_classifier() + (x_train, y_train), (x_test, y_test) = get_default_mnist_subset mixup = Mixup(num_classes=10) @@ -134,15 +149,19 @@ def test_dp_instahide_validation_data(art_warning, get_mnist_classifier, get_def art_warning(e) -@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf", "huggingface") @pytest.mark.parametrize("noise", ["gaussian", "laplacian", "exponential"]) -def test_dp_instahide_generator(art_warning, get_mnist_classifier, get_default_mnist_subset, noise): +def test_dp_instahide_generator( + art_warning, get_mnist_classifier, get_default_mnist_subset, get_default_cifar10_subset, noise, framework +): from art.data_generators import NumpyDataGenerator classifier = get_mnist_classifier() + (x_train, y_train), (_, _) = get_default_mnist_subset - generator = NumpyDataGenerator(x_train, y_train, batch_size=len(x_train)) + mixup = Mixup(num_classes=10) + generator = NumpyDataGenerator(x_train, y_train, batch_size=len(x_train)) try: trainer = DPInstaHideTrainer(classifier, augmentations=mixup, noise=noise, loc=0, scale=0.1) diff --git a/tests/estimators/certification/test_derandomized_smoothing.py b/tests/estimators/certification/test_derandomized_smoothing.py index 1c93dfec9e..bcae2c4844 100644 --- a/tests/estimators/certification/test_derandomized_smoothing.py +++ b/tests/estimators/certification/test_derandomized_smoothing.py @@ -118,10 +118,10 @@ def forward(self, x): for dataset, dataset_name in zip([fix_get_mnist_data, fix_get_cifar10_data], ["mnist", "cifar"]): if dataset_name == "mnist": ptc = SmallMNISTModel().to(device) - input_shape = (2, 28, 28) + input_shape = (1, 28, 28) else: ptc = SmallCIFARModel().to(device) - input_shape = (6, 32, 32) + input_shape = (3, 32, 32) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(ptc.parameters(), lr=0.01, momentum=0.9) @@ -137,6 +137,7 @@ def forward(self, x): ablation_type=ablation_type, ablation_size=5, threshold=0.3, + algorithm="levine2020", logits=True, ) classifier.fit(x=dataset[0], y=dataset[1], nb_epochs=1) @@ -152,7 +153,7 @@ def test_tf2_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data): import tensorflow as tf def build_model(input_shape): - img_inputs = tf.keras.Input(shape=input_shape) + img_inputs = tf.keras.Input(shape=(input_shape[0], input_shape[1], input_shape[2] * 2)) x = tf.keras.layers.Conv2D(filters=32, kernel_size=(4, 4), strides=(2, 2), activation="relu")(img_inputs) x = tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2)(x) # tensorflow uses channels last and we are loading weights from an originally trained pytorch model @@ -167,9 +168,9 @@ def build_model(input_shape): for dataset, dataset_name in zip([fix_get_mnist_data, fix_get_cifar10_data], ["mnist", "cifar"]): if dataset_name == "mnist": - input_shape = (28, 28, 2) + input_shape = (28, 28, 1) else: - input_shape = (32, 32, 6) + input_shape = (32, 32, 3) net = build_model(input_shape=input_shape) try: @@ -226,7 +227,6 @@ def forward(self, x): return self.fc2(x) def load_weights(self): - fpath = os.path.join( os.path.dirname(os.path.dirname(__file__)), "../../utils/resources/models/certification/derandomized/" ) @@ -262,21 +262,23 @@ def load_weights(self): clip_values=(0, 1), loss=criterion, optimizer=optimizer, - input_shape=(2, 28, 28), + input_shape=(1, 28, 28), nb_classes=10, ablation_type=ablation_type, ablation_size=ablation_size, threshold=0.3, + algorithm="levine2020", logits=True, ) preds = classifier.predict(np.copy(fix_get_mnist_data[0])) - num_certified = classifier.ablator.certify(preds, size_to_certify=size_to_certify) - + cert, cert_and_correct, top_predicted_class_argmax = classifier.ablator.certify( + preds, label=fix_get_mnist_data[1], size_to_certify=size_to_certify + ) if ablation_type == "column": - assert np.sum(num_certified) == 52 + assert np.sum(cert.cpu().numpy()) == 52 else: - assert np.sum(num_certified) == 22 + assert np.sum(cert.cpu().numpy()) == 22 except ARTTestException as e: art_warning(e) @@ -290,7 +292,7 @@ def test_tf2_mnist_certification(art_warning, fix_get_mnist_data): import tensorflow as tf def build_model(input_shape): - img_inputs = tf.keras.Input(shape=input_shape) + img_inputs = tf.keras.Input(shape=(input_shape[0], input_shape[1], input_shape[2] * 2)) x = tf.keras.layers.Conv2D(filters=32, kernel_size=(4, 4), strides=(2, 2), activation="relu")(img_inputs) x = tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2)(x) # tensorflow uses channels last and we are loading weights from an originally trained pytorch model @@ -322,7 +324,7 @@ def get_weights(): weight_list.append(w) return weight_list - net = build_model(input_shape=(28, 28, 2)) + net = build_model(input_shape=(28, 28, 1)) net.set_weights(get_weights()) loss_object = tf.keras.losses.CategoricalCrossentropy(from_logits=True) @@ -346,7 +348,7 @@ def get_weights(): clip_values=(0, 1), loss_object=loss_object, optimizer=optimizer, - input_shape=(28, 28, 2), + input_shape=(28, 28, 1), nb_classes=10, ablation_type=ablation_type, ablation_size=ablation_size, @@ -358,12 +360,14 @@ def get_weights(): x = np.squeeze(x) x = np.expand_dims(x, axis=-1) preds = classifier.predict(x) - num_certified = classifier.ablator.certify(preds, size_to_certify=size_to_certify) + cert, cert_and_correct, top_predicted_class_argmax = classifier.ablator.certify( + preds, label=fix_get_mnist_data[1], size_to_certify=size_to_certify + ) if ablation_type == "column": - assert np.sum(num_certified) == 52 + assert np.sum(cert) == 52 else: - assert np.sum(num_certified) == 22 + assert np.sum(cert) == 22 except ARTTestException as e: art_warning(e) diff --git a/tests/estimators/certification/test_macer.py b/tests/estimators/certification/test_macer.py new file mode 100644 index 0000000000..cbc6818e2f --- /dev/null +++ b/tests/estimators/certification/test_macer.py @@ -0,0 +1,136 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import pytest +import numpy as np + +from art.estimators.certification.randomized_smoothing import PyTorchMACER, TensorFlowV2MACER +from tests.utils import ARTTestException, get_image_classifier_pt, get_image_classifier_tf + +logger = logging.getLogger(__name__) + + +@pytest.fixture() +def get_mnist_classifier(framework): + def _get_classifier(): + if framework == "pytorch": + import torch + + classifier = get_image_classifier_pt() + optimizer = torch.optim.SGD(classifier.model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4) + scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[200, 400], gamma=0.1) + rs = PyTorchMACER( + model=classifier.model, + loss=classifier._loss, + input_shape=classifier.input_shape, + nb_classes=classifier.nb_classes, + optimizer=optimizer, + clip_values=classifier.clip_values, + channels_first=classifier.channels_first, + sample_size=100, + scale=0.01, + alpha=0.001, + beta=16.0, + gamma=8.0, + lmbda=12.0, + gaussian_samples=16, + ) + + elif framework == "tensorflow2": + import tensorflow as tf + + classifier, _ = get_image_classifier_tf() + optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.9, name="SGD", decay=5e-4) + scheduler = tf.keras.optimizers.schedules.PiecewiseConstantDecay([250, 400], [0.01, 0.001, 0.0001]) + rs = TensorFlowV2MACER( + model=classifier.model, + nb_classes=classifier.nb_classes, + input_shape=classifier.input_shape, + loss_object=classifier.loss_object, + optimizer=optimizer, + train_step=None, + channels_first=classifier.channels_first, + clip_values=classifier.clip_values, + preprocessing_defences=classifier.preprocessing_defences, + postprocessing_defences=classifier.postprocessing_defences, + preprocessing=classifier.preprocessing, + sample_size=100, + scale=0.01, + alpha=0.001, + beta=16.0, + gamma=8.0, + lmbda=12.0, + gaussian_samples=16, + ) + + else: + classifier, scheduler, rs = None, None, None + + return classifier, scheduler, rs + + return _get_classifier + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2") +def test_smoothmix_mnist_predict(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + classifier, _, rs = get_mnist_classifier() + y_test_base = classifier.predict(x=x_test) + y_test_smooth = rs.predict(x=x_test) + + np.testing.assert_array_equal(y_test_smooth.shape, y_test_base.shape) + np.testing.assert_array_almost_equal(np.sum(y_test_smooth, axis=1), np.ones(len(y_test))) + np.testing.assert_array_almost_equal(np.argmax(y_test_smooth, axis=1), np.argmax(y_test_base, axis=1)) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2") +def test_smoothmix_mnist_fit(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, scheduler, rs = get_mnist_classifier() + rs.fit(x=x_test, y=y_test, batch_size=128, nb_epochs=1, scheduler=scheduler) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2") +def test_smoothmix_mnist_certification(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, _, rs = get_mnist_classifier() + pred, radius = rs.certify(x=x_test, n=250) + + np.testing.assert_array_equal(pred.shape, radius.shape) + np.testing.assert_array_less(radius, 1) + np.testing.assert_array_less(pred, y_test.shape[1]) + + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/certification/test_randomized_smoothing.py b/tests/estimators/certification/test_randomized_smoothing.py index 430bae8db7..4035f7f9df 100644 --- a/tests/estimators/certification/test_randomized_smoothing.py +++ b/tests/estimators/certification/test_randomized_smoothing.py @@ -18,126 +18,51 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging -import os -import unittest - +import pytest import numpy as np -import tensorflow as tf -import torch from art.attacks.evasion.fast_gradient import FastGradientMethod -from art.utils import load_dataset, random_targets, compute_accuracy from art.estimators.certification.randomized_smoothing import ( NumpyRandomizedSmoothing, TensorFlowV2RandomizedSmoothing, PyTorchRandomizedSmoothing, ) +from art.utils import load_dataset, random_targets from tests.utils import ( - master_seed, get_image_classifier_pt, - get_image_classifier_kr, get_image_classifier_tf, + get_image_classifier_kr, get_tabular_classifier_pt, + ARTTestException, ) -os.environ["KMP_DUPLICATE_LIB_OK"] = "True" logger = logging.getLogger(__name__) -BATCH_SIZE = 100 -NB_TRAIN = 5000 -NB_TEST = 10 - - -class TestRandomizedSmoothing(unittest.TestCase): - """ - A unittest class for testing Randomized Smoothing as a post-processing step for classifiers. - """ - - @classmethod - def setUpClass(cls): - # Get MNIST - (x_train, y_train), (x_test, y_test), _, _ = load_dataset("mnist") - x_train, y_train = x_train[:NB_TRAIN], y_train[:NB_TRAIN] - x_test, y_test = x_test[:NB_TEST], y_test[:NB_TEST] - cls.mnist = (x_train, y_train), (x_test, y_test) - - def setUp(self): - master_seed(seed=1234) - - def test_3_kr(self): - """ - Test with a Keras Classifier. - :return: - """ - # Build KerasClassifier - classifier = get_image_classifier_kr() - - # Get MNIST - (_, _), (x_test, y_test) = self.mnist - - # First FGSM attack: - fgsm = FastGradientMethod(estimator=classifier, targeted=True) - params = {"y": random_targets(y_test, classifier.nb_classes)} - x_test_adv = fgsm.generate(x_test, **params) - - # Initialize RS object and attack with FGSM - rs = NumpyRandomizedSmoothing( - classifier=classifier, - sample_size=100, - scale=0.01, - alpha=0.001, - ) - fgsm_with_rs = FastGradientMethod(estimator=rs, targeted=True) - x_test_adv_with_rs = fgsm_with_rs.generate(x_test, **params) - # Compare results - # check shapes are equal and values are within a certain range - self.assertEqual(x_test_adv.shape, x_test_adv_with_rs.shape) - self.assertTrue((np.abs(x_test_adv - x_test_adv_with_rs) < 0.75).all()) +@pytest.fixture() +def get_mnist_classifier(framework): + def _get_classifier(): + if framework == "pytorch": + import torch - # Check basic functionality of RS object - # check predict - y_test_smooth = rs.predict(x=x_test) - y_test_base = classifier.predict(x=x_test) - self.assertEqual(y_test_smooth.shape, y_test.shape) - self.assertTrue((np.sum(y_test_smooth, axis=1) <= np.ones((NB_TEST,))).all()) - self.assertTrue((np.argmax(y_test_smooth, axis=1) == np.argmax(y_test_base, axis=1)).all()) - - # check certification - pred, radius = rs.certify(x=x_test, n=250) - self.assertEqual(len(pred), NB_TEST) - self.assertEqual(len(radius), NB_TEST) - self.assertTrue((radius <= 1).all()) - self.assertTrue((pred < y_test.shape[1]).all()) - - # loss gradient - grad = rs.loss_gradient(x=x_test, y=y_test, sampling=True) - assert grad.shape == (10, 28, 28, 1) - - # fit - rs.fit(x=x_test, y=y_test) - - def test_1_tf(self): - """ - Test with a TensorFlow Classifier. - :return: - """ - tf_version = list(map(int, tf.__version__.lower().split("+")[0].split("."))) - if tf_version[0] == 2: + classifier = get_image_classifier_pt() + optimizer = torch.optim.Adam(classifier.model.parameters(), lr=0.01) + rs = PyTorchRandomizedSmoothing( + model=classifier.model, + loss=classifier._loss, + optimizer=optimizer, + input_shape=classifier.input_shape, + nb_classes=classifier.nb_classes, + channels_first=classifier.channels_first, + clip_values=classifier.clip_values, + sample_size=100, + scale=0.01, + alpha=0.001, + ) - # Build TensorFlowV2Classifier + elif framework == "tensorflow2": classifier, _ = get_image_classifier_tf() - - # Get MNIST - (_, _), (x_test, y_test) = self.mnist - - # First FGSM attack: - fgsm = FastGradientMethod(estimator=classifier, targeted=True) - params = {"y": random_targets(y_test, classifier.nb_classes)} - x_test_adv = fgsm.generate(x_test, **params) - - # Initialize RS object and attack with FGSM rs = TensorFlowV2RandomizedSmoothing( model=classifier.model, nb_classes=classifier.nb_classes, @@ -154,156 +79,178 @@ def test_1_tf(self): scale=0.01, alpha=0.001, ) - fgsm_with_rs = FastGradientMethod(estimator=rs, targeted=True) - x_test_adv_with_rs = fgsm_with_rs.generate(x_test, **params) - - # Compare results - # check shapes are equal and values are within a certain range - self.assertEqual(x_test_adv.shape, x_test_adv_with_rs.shape) - self.assertTrue((np.abs(x_test_adv - x_test_adv_with_rs) < 0.75).all()) - - # Check basic functionality of RS object - # check predict - y_test_smooth = rs.predict(x=x_test) - y_test_base = classifier.predict(x=x_test) - self.assertEqual(y_test_smooth.shape, y_test.shape) - self.assertTrue((np.sum(y_test_smooth, axis=1) <= np.ones((NB_TEST,))).all()) - self.assertTrue((np.argmax(y_test_smooth, axis=1) == np.argmax(y_test_base, axis=1)).all()) - - # check certification - pred, radius = rs.certify(x=x_test, n=250) - self.assertEqual(len(pred), NB_TEST) - self.assertEqual(len(radius), NB_TEST) - self.assertTrue((radius <= 1).all()) - self.assertTrue((pred < y_test.shape[1]).all()) - - # loss gradient - grad = rs.loss_gradient(x=x_test, y=y_test, sampling=True) - assert grad.shape == (10, 28, 28, 1) - - # fit - rs.fit(x=x_test, y=y_test) - - def test_2_pt(self): - """ - Test with a PyTorch Classifier. - :return: - """ - # Build KerasClassifier - ptc = get_image_classifier_pt() - - # Get MNIST - (_, _), (x_test, y_test) = self.mnist - - x_test = x_test.transpose(0, 3, 1, 2).astype(np.float32) - - # First FGSM attack: - fgsm = FastGradientMethod(estimator=ptc, targeted=True) - params = {"y": random_targets(y_test, ptc.nb_classes)} - x_test_adv = fgsm.generate(x_test, **params) - # Initialize RS object and attack with FGSM - rs = PyTorchRandomizedSmoothing( - model=ptc.model, - loss=ptc._loss, - optimizer=torch.optim.Adam(ptc.model.parameters(), lr=0.01), - input_shape=ptc.input_shape, - nb_classes=ptc.nb_classes, - channels_first=ptc.channels_first, - clip_values=ptc.clip_values, - sample_size=100, - scale=0.01, - alpha=0.001, - ) - fgsm_with_rs = FastGradientMethod(estimator=rs, targeted=True) - x_test_adv_with_rs = fgsm_with_rs.generate(x_test, **params) + elif framework in ("keras", "kerastf"): + classifier = get_image_classifier_kr() + rs = NumpyRandomizedSmoothing( + classifier=classifier, + sample_size=100, + scale=0.01, + alpha=0.001, + ) + + else: + classifier, rs = None, None + + return classifier, rs + + return _get_classifier + + +@pytest.fixture() +def get_iris_classifier(framework): + def _get_classifier(): + if framework == "pytorch": + classifier = get_tabular_classifier_pt() + rs = PyTorchRandomizedSmoothing( + model=classifier.model, + loss=classifier._loss, + input_shape=classifier.input_shape, + nb_classes=classifier.nb_classes, + channels_first=classifier.channels_first, + clip_values=classifier.clip_values, + sample_size=100, + scale=0.01, + alpha=0.001, + ) - # Compare results - # check shapes are equal and values are within a certain range - self.assertEqual(x_test_adv.shape, x_test_adv_with_rs.shape) - self.assertTrue((np.abs(x_test_adv - x_test_adv_with_rs) < 0.75).all()) + else: + classifier, rs = None, None - # Check basic functionality of RS object - # check predict + return classifier, rs + + return _get_classifier + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +def test_randomized_smoothing_mnist_predict(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + classifier, rs = get_mnist_classifier() + y_test_base = classifier.predict(x=x_test) y_test_smooth = rs.predict(x=x_test) - y_test_base = ptc.predict(x=x_test) - self.assertEqual(y_test_smooth.shape, y_test.shape) - self.assertTrue((np.sum(y_test_smooth, axis=1) <= np.ones((NB_TEST,))).all()) - self.assertTrue((np.argmax(y_test_smooth, axis=1) == np.argmax(y_test_base, axis=1)).all()) - # check certification + np.testing.assert_array_equal(y_test_smooth.shape, y_test_base.shape) + np.testing.assert_array_almost_equal(np.sum(y_test_smooth, axis=1), np.ones(len(y_test))) + np.testing.assert_array_almost_equal(np.argmax(y_test_smooth, axis=1), np.argmax(y_test_base, axis=1)) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +def test_randomized_smoothing_mnist_fit(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, rs = get_mnist_classifier() + rs.fit(x=x_test, y=y_test) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +def test_randomized_smoothing_mnist_certify(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, rs = get_mnist_classifier() pred, radius = rs.certify(x=x_test, n=250) - self.assertEqual(len(pred), NB_TEST) - self.assertEqual(len(radius), NB_TEST) - self.assertTrue((radius <= 1).all()) - self.assertTrue((pred < y_test.shape[1]).all()) - # loss gradient + np.testing.assert_array_equal(pred.shape, radius.shape) + np.testing.assert_array_less(radius, 1) + np.testing.assert_array_less(pred, y_test.shape[1]) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +def test_randomized_smoothing_mnist_loss_gradient(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, rs = get_mnist_classifier() grad = rs.loss_gradient(x=x_test, y=y_test, sampling=True) - assert grad.shape == (10, 1, 28, 28) - # fit - rs.fit(x=x_test, y=y_test) + np.testing.assert_array_equal(grad.shape, x_test.shape) + except ARTTestException as e: + art_warning(e) -class TestRandomizedSmoothingVectors(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Get Iris - (x_train, y_train), (x_test, y_test), _, _ = load_dataset("iris") - cls.iris = (x_train, y_train), (x_test, y_test) - - def setUp(self): - master_seed(seed=1234) - - def test_iris_clipped(self): - (_, _), (x_test, y_test) = self.iris - - ptc = get_tabular_classifier_pt() - rs = PyTorchRandomizedSmoothing( - model=ptc.model, - loss=ptc._loss, - input_shape=ptc.input_shape, - nb_classes=ptc.nb_classes, - channels_first=ptc.channels_first, - clip_values=ptc.clip_values, - sample_size=100, - scale=0.01, - alpha=0.001, - ) - - # Test untargeted attack - attack = FastGradientMethod(ptc, eps=0.1) - x_test_adv = attack.generate(x_test) - self.assertFalse((x_test == x_test_adv).all()) - self.assertTrue((x_test_adv <= 1).all()) - self.assertTrue((x_test_adv >= 0).all()) - preds_smooth = np.argmax(rs.predict(x_test_adv), axis=1) - self.assertFalse((np.argmax(y_test, axis=1) == preds_smooth).all()) - - pred = rs.predict(x_test) - pred2 = rs.predict(x_test_adv) - acc, cov = compute_accuracy(pred, y_test) - acc2, cov2 = compute_accuracy(pred2, y_test) - logger.info("Accuracy on Iris with smoothing on adversarial examples: %.2f%%", (acc * 100)) - logger.info("Coverage on Iris with smoothing on adversarial examples: %.2f%%", (cov * 100)) - logger.info("Accuracy on Iris with smoothing: %.2f%%", (acc2 * 100)) - logger.info("Coverage on Iris with smoothing: %.2f%%", (cov2 * 100)) - - # Check basic functionality of RS object - # check predict +@pytest.mark.only_with_platform("pytorch", "tensorflow2", "keras", "kerastf") +def test_randomized_smoothing_mnist_fgsm(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + classifier, rs = get_mnist_classifier() + fgsm = FastGradientMethod(estimator=classifier, targeted=True) + params = {"y": random_targets(y_test, classifier.nb_classes)} + x_test_adv = fgsm.generate(x_test, **params) + + fgsm_with_rs = FastGradientMethod(estimator=rs, targeted=True) + x_test_adv_with_rs = fgsm_with_rs.generate(x_test, **params) + + np.testing.assert_array_equal(x_test_adv.shape, x_test_adv_with_rs.shape) + np.testing.assert_array_less(np.abs(x_test_adv - x_test_adv_with_rs), 0.75) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_randomized_smoothing_iris_predict(art_warning, get_iris_classifier): + (_, _), (x_test, y_test), _, _ = load_dataset("iris") + + try: + _, rs = get_iris_classifier() y_test_smooth = rs.predict(x=x_test) - self.assertEqual(y_test_smooth.shape, y_test.shape) - self.assertTrue((np.sum(y_test_smooth, axis=1) <= 1).all()) - # check certification + np.testing.assert_array_equal(y_test_smooth.shape, y_test.shape) + assert np.all(np.sum(y_test_smooth, axis=1) <= 1) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_randomized_smoothing_iris_certify(art_warning, get_iris_classifier): + (_, _), (x_test, y_test), _, _ = load_dataset("iris") + + try: + _, rs = get_iris_classifier() pred, radius = rs.certify(x=x_test, n=250) - self.assertEqual(len(pred), len(x_test)) - self.assertEqual(len(radius), len(x_test)) - self.assertTrue((radius <= 1).all()) - self.assertTrue((pred < y_test.shape[1]).all()) + np.testing.assert_array_equal(pred.shape, radius.shape) + np.testing.assert_array_less(radius, 1) + np.testing.assert_array_less(pred, y_test.shape[1]) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_randomized_smoothing_iris_fgsm(art_warning, get_iris_classifier): + (_, _), (x_test, y_test), _, _ = load_dataset("iris") + + try: + classifier, rs = get_iris_classifier() + attack = FastGradientMethod(classifier, eps=0.1) + x_test_adv = attack.generate(x_test) + preds_smooth = np.argmax(rs.predict(x_test_adv), axis=1) + + assert not np.array_equal(x_test, x_test_adv) + assert not np.array_equal(np.argmax(y_test, axis=1), preds_smooth) + assert np.all(x_test_adv <= 1) + assert np.all(x_test_adv >= 0) -if __name__ == "__main__": - unittest.main() + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/certification/test_smooth_adv.py b/tests/estimators/certification/test_smooth_adv.py new file mode 100644 index 0000000000..38a0fb5a8d --- /dev/null +++ b/tests/estimators/certification/test_smooth_adv.py @@ -0,0 +1,136 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import pytest +import numpy as np + +from art.estimators.certification.randomized_smoothing import PyTorchSmoothAdv, TensorFlowV2SmoothAdv +from tests.utils import ARTTestException, get_image_classifier_pt, get_image_classifier_tf + +logger = logging.getLogger(__name__) + + +@pytest.fixture() +def get_mnist_classifier(framework): + def _get_classifier(): + if framework == "pytorch": + import torch + + classifier = get_image_classifier_pt() + optimizer = torch.optim.SGD(classifier.model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4) + scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1) + rs = PyTorchSmoothAdv( + model=classifier.model, + loss=classifier._loss, + input_shape=classifier.input_shape, + nb_classes=classifier.nb_classes, + optimizer=optimizer, + clip_values=classifier.clip_values, + channels_first=classifier.channels_first, + sample_size=100, + scale=0.01, + alpha=0.001, + epsilon=1.0, + num_noise_vec=1, + num_steps=10, + warmup=1, + ) + + elif framework == "tensorflow2": + import tensorflow as tf + + classifier, _ = get_image_classifier_tf() + optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.9, name="SGD", decay=1e-4) + scheduler = tf.keras.optimizers.schedules.PiecewiseConstantDecay([50, 100], [0.01, 0.001, 0.0001]) + rs = TensorFlowV2SmoothAdv( + model=classifier.model, + nb_classes=classifier.nb_classes, + input_shape=classifier.input_shape, + loss_object=classifier.loss_object, + optimizer=optimizer, + train_step=None, + channels_first=classifier.channels_first, + clip_values=classifier.clip_values, + preprocessing_defences=classifier.preprocessing_defences, + postprocessing_defences=classifier.postprocessing_defences, + preprocessing=classifier.preprocessing, + sample_size=100, + scale=0.01, + alpha=0.001, + epsilon=1.0, + num_noise_vec=1, + num_steps=10, + warmup=1, + ) + + else: + classifier, scheduler, rs = None, None, None + + return classifier, scheduler, rs + + return _get_classifier + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2") +def test_smoothmix_mnist_predict(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + classifier, _, rs = get_mnist_classifier() + y_test_base = classifier.predict(x=x_test) + y_test_smooth = rs.predict(x=x_test) + + np.testing.assert_array_equal(y_test_smooth.shape, y_test_base.shape) + np.testing.assert_array_almost_equal(np.sum(y_test_smooth, axis=1), np.ones(len(y_test))) + np.testing.assert_array_almost_equal(np.argmax(y_test_smooth, axis=1), np.argmax(y_test_base, axis=1)) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2") +def test_smoothmix_mnist_fit(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, scheduler, rs = get_mnist_classifier() + rs.fit(x=x_test, y=y_test, batch_size=128, nb_epochs=1, scheduler=scheduler) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch", "tensorflow2") +def test_smoothmix_mnist_certification(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, _, rs = get_mnist_classifier() + pred, radius = rs.certify(x=x_test, n=250) + + np.testing.assert_array_equal(pred.shape, radius.shape) + np.testing.assert_array_less(radius, 1) + np.testing.assert_array_less(pred, y_test.shape[1]) + + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/certification/test_smooth_mix.py b/tests/estimators/certification/test_smooth_mix.py new file mode 100644 index 0000000000..1e53c2c19c --- /dev/null +++ b/tests/estimators/certification/test_smooth_mix.py @@ -0,0 +1,197 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import pytest +import numpy as np + +from art.estimators.certification.randomized_smoothing import PyTorchSmoothMix +from tests.utils import ARTTestException, get_image_classifier_pt, get_cifar10_image_classifier_pt + +logger = logging.getLogger(__name__) + + +@pytest.fixture() +def get_mnist_classifier(framework): + def _get_classifier(): + if framework == "pytorch": + import torch + + classifier = get_image_classifier_pt() + optimizer = torch.optim.SGD(classifier.model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4) + scheduler = torch.optim.lr_scheduler.StepLR(optimizer=optimizer, step_size=50, gamma=0.1) + rs = PyTorchSmoothMix( + model=classifier.model, + loss=classifier._loss, + input_shape=classifier.input_shape, + nb_classes=classifier.nb_classes, + optimizer=optimizer, + clip_values=classifier.clip_values, + channels_first=classifier.channels_first, + sample_size=100, + scale=0.01, + alpha=0.001, + eta=5.0, + num_noise_vec=2, + num_steps=8, + warmup=10, + mix_step=0, + maxnorm_s=None, + maxnorm=None, + ) + + else: + classifier, scheduler, rs = None, None, None + + return classifier, scheduler, rs + + return _get_classifier + + +@pytest.fixture() +def get_cifar10_classifier(framework): + def _get_classifier(): + if framework == "pytorch": + import torch + + classifier = get_cifar10_image_classifier_pt() + optimizer = torch.optim.SGD(classifier.model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4) + scheduler = torch.optim.lr_scheduler.StepLR(optimizer=optimizer, step_size=50, gamma=0.1) + rs = PyTorchSmoothMix( + model=classifier.model, + loss=classifier._loss, + input_shape=classifier.input_shape, + nb_classes=classifier.nb_classes, + optimizer=optimizer, + clip_values=classifier.clip_values, + channels_first=classifier.channels_first, + sample_size=100, + scale=0.01, + alpha=0.001, + eta=5.0, + num_noise_vec=2, + num_steps=8, + warmup=10, + mix_step=0, + maxnorm_s=None, + maxnorm=None, + ) + + else: + classifier, scheduler, rs = None, None, None + + return classifier, scheduler, rs + + return _get_classifier + + +@pytest.mark.only_with_platform("pytorch") +def test_smoothmix_mnist_predict(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + classifier, _, rs = get_mnist_classifier() + y_test_base = classifier.predict(x=x_test) + y_test_smooth = rs.predict(x=x_test) + + np.testing.assert_array_equal(y_test_smooth.shape, y_test_base.shape) + np.testing.assert_array_almost_equal(np.sum(y_test_smooth, axis=1), np.ones(len(y_test))) + np.testing.assert_array_almost_equal(np.argmax(y_test_smooth, axis=1), np.argmax(y_test_base, axis=1)) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_smoothmix_mnist_fit(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, scheduler, rs = get_mnist_classifier() + rs.fit(x=x_test, y=y_test, batch_size=128, nb_epochs=1, scheduler=scheduler) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_smoothmix_mnist_certification(art_warning, get_default_mnist_subset, get_mnist_classifier): + (_, _), (x_test, y_test) = get_default_mnist_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, _, rs = get_mnist_classifier() + pred, radius = rs.certify(x=x_test, n=250) + + np.testing.assert_array_equal(pred.shape, radius.shape) + np.testing.assert_array_less(radius, 1) + np.testing.assert_array_less(pred, y_test.shape[1]) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_smoothmix_cifar10_predict(art_warning, get_default_cifar10_subset, get_cifar10_classifier): + (_, _), (x_test, y_test) = get_default_cifar10_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + classifier, _, rs = get_cifar10_classifier() + y_test_base = classifier.predict(x=x_test) + y_test_smooth = rs.predict(x=x_test) + + np.testing.assert_array_equal(y_test_smooth.shape, y_test_base.shape) + np.testing.assert_array_almost_equal(np.sum(y_test_smooth, axis=1), np.ones((len(y_test)))) + np.testing.assert_array_almost_equal(np.argmax(y_test_smooth, axis=1), np.argmax(y_test_base, axis=1)) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_smoothmix_cifar10_fit(art_warning, get_default_cifar10_subset, get_cifar10_classifier): + (_, _), (x_test, y_test) = get_default_cifar10_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, scheduler, rs = get_cifar10_classifier() + rs.fit(x=x_test, y=y_test, batch_size=128, nb_epochs=1, scheduler=scheduler) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_smoothmix_cifar10_certification(art_warning, get_default_cifar10_subset, get_cifar10_classifier): + (_, _), (x_test, y_test) = get_default_cifar10_subset + x_test, y_test = x_test[:10], y_test[:10] + + try: + _, _, rs = get_cifar10_classifier() + pred, radius = rs.certify(x=x_test, n=250) + + np.testing.assert_array_equal(pred.shape, radius.shape) + np.testing.assert_array_less(radius, 1) + np.testing.assert_array_less(pred, y_test.shape[1]) + + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/certification/test_vision_transformers.py b/tests/estimators/certification/test_vision_transformers.py new file mode 100644 index 0000000000..9a42b8eb97 --- /dev/null +++ b/tests/estimators/certification/test_vision_transformers.py @@ -0,0 +1,616 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import pytest +import os + +import numpy as np + +from art.utils import load_dataset +from tests.utils import ARTTestException + + +@pytest.fixture() +def fix_get_mnist_data(): + """ + Get the first 128 samples of the mnist test set with channels first format + + :return: First 128 sample/label pairs of the MNIST test dataset. + """ + nb_test = 128 + + (_, _), (x_test, y_test), _, _ = load_dataset("mnist") + x_test = np.squeeze(x_test).astype(np.float32) + x_test = np.expand_dims(x_test, axis=1) + y_test = np.argmax(y_test, axis=1) + + x_test, y_test = x_test[:nb_test], y_test[:nb_test] + return x_test, y_test + + +@pytest.fixture() +def fix_get_cifar10_data(): + """ + Get the first 128 samples of the cifar10 test set + + :return: First 128 sample/label pairs of the cifar10 test dataset. + """ + nb_test = 128 + + (_, _), (x_test, y_test), _, _ = load_dataset("cifar10") + y_test = np.argmax(y_test, axis=1) + x_test, y_test = x_test[:nb_test], y_test[:nb_test] + x_test = np.transpose(x_test, (0, 3, 1, 2)) # return in channels first format + return x_test.astype(np.float32), y_test + + +@pytest.mark.only_with_platform("pytorch") +def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data): + """ + Check that the ablation is being performed correctly + """ + import torch + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + from art.estimators.certification.derandomized_smoothing.ablators.pytorch import ColumnAblatorPyTorch + + try: + cifar_data = fix_get_cifar10_data[0] + + col_ablator = ColumnAblatorPyTorch( + ablation_size=4, + channels_first=True, + to_reshape=False, # do not upsample initially + mode="ViT", + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + ) + + cifar_data = torch.from_numpy(cifar_data).to(device) + # check that the ablation functioned when in the middle of the image + ablated = col_ablator.forward(cifar_data, column_pos=10) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, :, 0:10]) == 0 + assert torch.sum(ablated[:, :, :, 10:14]) > 0 + assert torch.sum(ablated[:, :, :, 14:]) == 0 + + # check that the ablation wraps when on the edge of the image + ablated = col_ablator.forward(cifar_data, column_pos=30) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, :, 30:]) > 0 + assert torch.sum(ablated[:, :, :, 2:30]) == 0 + assert torch.sum(ablated[:, :, :, :2]) > 0 + + # check that upsampling works as expected + col_ablator = ColumnAblatorPyTorch( + ablation_size=4, + channels_first=True, + to_reshape=True, + mode="ViT", + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + ) + + ablated = col_ablator.forward(cifar_data, column_pos=10) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, :, : 10 * 7]) == 0 + assert torch.sum(ablated[:, :, :, 10 * 7 : 14 * 7]) > 0 + assert torch.sum(ablated[:, :, :, 14 * 7 :]) == 0 + + # check that the ablation wraps when on the edge of the image + ablated = col_ablator.forward(cifar_data, column_pos=30) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, :, 30 * 7 :]) > 0 + assert torch.sum(ablated[:, :, :, 2 * 7 : 30 * 7]) == 0 + assert torch.sum(ablated[:, :, :, : 2 * 7]) > 0 + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_ablation_row(art_warning, fix_get_mnist_data, fix_get_cifar10_data): + """ + Check that the ablation is being performed correctly + """ + import torch + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + from art.estimators.certification.derandomized_smoothing.ablators.pytorch import ColumnAblatorPyTorch + + try: + cifar_data = fix_get_cifar10_data[0] + + col_ablator = ColumnAblatorPyTorch( + ablation_size=4, + channels_first=True, + to_reshape=False, # do not upsample initially + mode="ViT", + ablation_mode="row", + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + ) + + cifar_data = torch.from_numpy(cifar_data).to(device) + # check that the ablation functioned when in the middle of the image + ablated = col_ablator.forward(cifar_data, column_pos=10) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, 0:10, :]) == 0 + assert torch.sum(ablated[:, :, 10:14, :]) > 0 + assert torch.sum(ablated[:, :, 14:, :]) == 0 + + # check that the ablation wraps when on the edge of the image + ablated = col_ablator.forward(cifar_data, column_pos=30) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, 30:, :]) > 0 + assert torch.sum(ablated[:, :, 2:30, :]) == 0 + assert torch.sum(ablated[:, :, :2, :]) > 0 + + # check that upsampling works as expected + col_ablator = ColumnAblatorPyTorch( + ablation_size=4, + channels_first=True, + to_reshape=True, + mode="ViT", + ablation_mode="row", + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + ) + + ablated = col_ablator.forward(cifar_data, column_pos=10) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, : 10 * 7, :]) == 0 + assert torch.sum(ablated[:, :, 10 * 7 : 14 * 7, :]) > 0 + assert torch.sum(ablated[:, :, 14 * 7 :, :]) == 0 + + # check that the ablation wraps when on the edge of the image + ablated = col_ablator.forward(cifar_data, column_pos=30) + + assert ablated.shape[1] == 4 + assert torch.sum(ablated[:, :, 30 * 7 :, :]) > 0 + assert torch.sum(ablated[:, :, 2 * 7 : 30 * 7, :]) == 0 + assert torch.sum(ablated[:, :, : 2 * 7, :]) > 0 + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data): + """ + Check that the training loop for pytorch does not result in errors + """ + import torch + from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + try: + cifar_data = fix_get_cifar10_data[0][:50] + cifar_labels = fix_get_cifar10_data[1][:50] + + art_model = PyTorchDeRandomizedSmoothing( + model="vit_small_patch16_224", + loss=torch.nn.CrossEntropyLoss(), + optimizer=torch.optim.SGD, + optimizer_params={"lr": 0.01}, + input_shape=(3, 32, 32), + nb_classes=10, + ablation_size=4, + load_pretrained=True, + replace_last_layer=True, + verbose=False, + ) + + scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[1], gamma=0.1) + + head = { + "weight": torch.tensor( + np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/head_weight.npy", + ) + ) + ).to(device), + "bias": torch.tensor( + np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/head_bias.npy", + ) + ) + ).to(device), + } + art_model.model.head.load_state_dict(head) + + art_model.fit(cifar_data, cifar_labels, nb_epochs=2, update_batchnorm=True, scheduler=scheduler) + preds = art_model.predict(cifar_data) + + gt_preds = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/cumulative_predictions.npy", + ) + ) + + np.array_equal(preds, gt_preds) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10_data): + """ + Check that based on a given set of synthetic class predictions the certification gives the expected results. + """ + from art.estimators.certification.derandomized_smoothing.ablators.pytorch import ColumnAblatorPyTorch + import torch + + try: + col_ablator = ColumnAblatorPyTorch( + ablation_size=4, + channels_first=True, + mode="ViT", + to_reshape=True, # do not upsample initially + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + ) + pred_counts = torch.from_numpy(np.asarray([[20, 5, 1], [10, 5, 1], [1, 16, 1]])) + cert, cert_and_correct, top_predicted_class = col_ablator.certify( + pred_counts=pred_counts, + size_to_certify=4, + label=0, + ) + assert torch.equal(cert, torch.tensor([True, False, True])) + assert torch.equal(cert_and_correct, torch.tensor([True, False, False])) + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +@pytest.mark.parametrize("ablation", ["block", "column"]) +def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data, ablation): + """ + Assert implementations matches original with a forward pass through the same model architecture. + There are some differences in architecture between the same model names in timm vs the original implementation. + We use vit_base_patch16_224 which matches. + """ + import torch + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing + + from art.estimators.certification.derandomized_smoothing.ablators import ( + ColumnAblatorPyTorch, + BlockAblatorPyTorch, + ) + + cifar_data = fix_get_cifar10_data[0][:50] + torch.manual_seed(1234) + try: + art_model = PyTorchDeRandomizedSmoothing( + model="vit_base_patch16_224", + loss=torch.nn.CrossEntropyLoss(), + optimizer=torch.optim.SGD, + optimizer_params={"lr": 0.01}, + input_shape=(3, 32, 32), + nb_classes=10, + ablation_size=4, + load_pretrained=True, + replace_last_layer=True, + verbose=False, + ) + + if ablation == "column": + ablator = ColumnAblatorPyTorch( + ablation_size=4, + channels_first=True, + to_reshape=True, + mode="ViT", + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + ) + ablated = ablator.forward(cifar_data, column_pos=10) + madry_preds = torch.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/madry_preds_column.pt", + ) + ) + art_preds = art_model.model(ablated) + assert torch.allclose(madry_preds.to(device), art_preds, rtol=1e-04, atol=1e-04) + + elif ablation == "block": + ablator = BlockAblatorPyTorch( + ablation_size=4, + channels_first=True, + to_reshape=True, + original_shape=(3, 32, 32), + output_shape=(3, 224, 224), + mode="ViT", + ) + ablated = ablator.forward(cifar_data, column_pos=10, row_pos=28) + madry_preds = torch.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/madry_preds_block.pt", + ) + ) + art_preds = art_model.model(ablated) + assert torch.allclose(madry_preds.to(device), art_preds, rtol=1e-04, atol=1e-04) + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +@pytest.mark.parametrize("ablation", ["block", "column"]) +def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data, ablation): + """ + With the forward pass equivalence asserted, we now confirm that the certification functions in the same + way by doing a full end to end prediction and certification test over the data. + """ + import torch + + from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + try: + art_model = PyTorchDeRandomizedSmoothing( + model="vit_small_patch16_224", + loss=torch.nn.CrossEntropyLoss(), + optimizer=torch.optim.SGD, + optimizer_params={"lr": 0.01}, + input_shape=(3, 32, 32), + nb_classes=10, + ablation_type=ablation, + ablation_size=4, + load_pretrained=True, + replace_last_layer=True, + verbose=False, + ) + + head = { + "weight": torch.tensor( + np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/head_weight.npy", + ) + ) + ).to(device), + "bias": torch.tensor( + np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "../../utils/resources/models/certification/smooth_vit/head_bias.npy", + ) + ) + ).to(device), + } + art_model.model.head.load_state_dict(head) + + if torch.cuda.is_available(): + num_to_fetch = 100 + else: + num_to_fetch = 10 + + cifar_data = torch.from_numpy(fix_get_cifar10_data[0][:num_to_fetch]).to(device) + cifar_labels = torch.from_numpy(fix_get_cifar10_data[1][:num_to_fetch]).to(device) + + acc, cert_acc = art_model.eval_and_certify( + x=cifar_data.cpu().numpy(), y=cifar_labels.cpu().numpy(), batch_size=num_to_fetch, size_to_certify=4 + ) + + upsample = torch.nn.Upsample(scale_factor=224 / 32) + cifar_data = upsample(cifar_data) + acc_non_ablation = art_model.model(cifar_data) + acc_non_ablation = art_model.get_accuracy(acc_non_ablation, cifar_labels) + + if torch.cuda.is_available(): + if ablation == "column": + assert np.allclose(cert_acc.cpu().numpy(), 0.29) + assert np.allclose(acc.cpu().numpy(), 0.57) + else: + assert np.allclose(cert_acc.cpu().numpy(), 0.16) + assert np.allclose(acc.cpu().numpy(), 0.24) + assert np.allclose(acc_non_ablation, 0.52) + else: + if ablation == "column": + assert np.allclose(cert_acc.cpu().numpy(), 0.30) + assert np.allclose(acc.cpu().numpy(), 0.70) + else: + assert np.allclose(cert_acc.cpu().numpy(), 0.20) + assert np.allclose(acc.cpu().numpy(), 0.20) + assert np.allclose(acc_non_ablation, 0.60) + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_equivalence(art_warning, fix_get_cifar10_data): + import torch + from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing + from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import PyTorchVisionTransformer + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + try: + + class MadrylabImplementations: + """ + Code adapted from the implementation in https://github.com/MadryLab/smoothed-vit + to check against our own functionality. + + Original License: + + MIT License + + Copyright (c) 2021 Madry Lab + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + """ + + def __init__(self): + pass + + @classmethod + def token_dropper(cls, x, mask): + """ + The implementation of dropping tokens has been done slightly differently in this tool. + Here we check that it is equivalent to the original implementation + """ + + class MaskProcessor(torch.nn.Module): + def __init__(self, patch_size=16): + super().__init__() + self.avg_pool = torch.nn.AvgPool2d(patch_size) + + def forward(self, ones_mask): + B = ones_mask.shape[0] + ones_mask = ones_mask[0].unsqueeze(0) # take the first mask + ones_mask = self.avg_pool(ones_mask)[0] + ones_mask = torch.where(ones_mask.view(-1) > 0)[0] + 1 + ones_mask = torch.cat([torch.IntTensor(1).fill_(0).to(device), ones_mask]).unsqueeze(0) + ones_mask = ones_mask.expand(B, -1) + return ones_mask + + mask_processor = MaskProcessor() + patch_mask = mask_processor(mask) + + # x = self.pos_drop(x) # B, N, C + if patch_mask is not None: + # patch_mask is B, K + B, N, C = x.shape + if len(patch_mask.shape) == 1: # not a separate one per batch + x = x[:, patch_mask] + else: + patch_mask = patch_mask.unsqueeze(-1).expand(-1, -1, C) + x = torch.gather(x, 1, patch_mask) + return x + + @classmethod + def embedder(cls, x, pos_embed, cls_token): + """ + NB, original code used the pos embed from the divit rather than vit + (which we pull from our model) which we use here. + + From timm vit: + self.pos_embed = nn.Parameter(torch.randn(1, embed_len, embed_dim) * .02) + + From timm dvit: + self.pos_embed = nn.Parameter(torch.zeros(1, + self.patch_embed.num_patches + self.num_prefix_tokens, + self.embed_dim)) + + From repo: + self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim)) + """ + x = torch.cat((cls_token.expand(x.shape[0], -1, -1), x), dim=1) + return x + pos_embed + + def forward_features(self, x: torch.Tensor) -> torch.Tensor: + """ + This is a copy of the function in ArtViT.forward_features + except we also perform an equivalence assertion compared to the implementation + in https://github.com/MadryLab/smoothed-vit (see MadrylabImplementations class above) + + The forward pass of the ViT. + + :param x: Input data. + :return: The input processed by the ViT backbone + """ + import copy + + ablated_input = False + if x.shape[1] == self.in_chans + 1: + ablated_input = True + + if ablated_input: + x, ablation_mask = x[:, : self.in_chans], x[:, self.in_chans : self.in_chans + 1] + + x = self.patch_embed(x) + + madry_embed = MadrylabImplementations.embedder(copy.copy(x), self.pos_embed, self.cls_token) + x = self._pos_embed(x) + assert torch.equal(madry_embed, x) + + # pass the x into the token dropping code + madry_dropped = MadrylabImplementations.token_dropper(copy.copy(x), ablation_mask) + + if self.to_drop_tokens and ablated_input: + ones = self.ablation_mask_embedder(ablation_mask) + to_drop = torch.sum(ones, dim=2) + indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0) + x = self.drop_tokens(x, indexes) + + assert torch.equal(madry_dropped, x) + + x = self.norm_pre(x) + x = self.blocks(x) + + return self.norm(x) + + # Replace the forward_features with the forward_features code with checks. + PyTorchVisionTransformer.forward_features = forward_features + + art_model = PyTorchDeRandomizedSmoothing( + model="vit_small_patch16_224", + loss=torch.nn.CrossEntropyLoss(), + optimizer=torch.optim.SGD, + optimizer_params={"lr": 0.01}, + input_shape=(3, 32, 32), + nb_classes=10, + ablation_size=4, + load_pretrained=False, + replace_last_layer=True, + verbose=False, + ) + + cifar_data = fix_get_cifar10_data[0][:50] + cifar_labels = fix_get_cifar10_data[1][:50] + + scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[1], gamma=0.1) + art_model.fit(cifar_data, cifar_labels, nb_epochs=1, update_batchnorm=True, scheduler=scheduler, batch_size=128) + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/classification/test_deeplearning_common.json b/tests/estimators/classification/test_deeplearning_common.json index c4f081e8ca..a868587f35 100644 --- a/tests/estimators/classification/test_deeplearning_common.json +++ b/tests/estimators/classification/test_deeplearning_common.json @@ -425,6 +425,16 @@ "clip_values=array([0., 1.], dtype=float32", "preprocessing_defences=None, postprocessing_defences=None, preprocessing=StandardisationMeanStdPyTorch(mean=0.0, std=1.0, apply_fit=True, apply_predict=True, device=cpu)" ], + "test_repr_huggingface": [ + "art.estimators.classification.hugging_face.HuggingFaceClassifierPyTorch", + "(conv): Conv2d(1, 1, kernel_size=(7, 7), stride=(1, 1))", + "(pool): MaxPool2d(kernel_size=4, stride=4, padding=0, dilation=1, ceil_mode=False)", + "(fullyconnected): Linear(in_features=25, out_features=10, bias=True)", + "loss=CrossEntropyLoss(), optimizer=Adam", + "input_shape=(1, 28, 28), nb_classes=10", + "clip_values=array([0., 1.], dtype=float32", + "preprocessing_defences=None, postprocessing_defences=None, preprocessing=StandardisationMeanStdPyTorch(mean=0.0, std=1.0, apply_fit=True, apply_predict=True, device=cpu)" + ], "test_repr_keras": [ "art.estimators.classification.keras.KerasClassifier", "use_logits=True", diff --git a/tests/estimators/classification/test_deeplearning_common.py b/tests/estimators/classification/test_deeplearning_common.py index 5045e86b38..60eb82ceba 100644 --- a/tests/estimators/classification/test_deeplearning_common.py +++ b/tests/estimators/classification/test_deeplearning_common.py @@ -154,7 +154,7 @@ def test_loss_functions( art_warning(e) -@pytest.mark.skip_framework("non_dl_frameworks") +@pytest.mark.skip_framework("non_dl_frameworks", "huggingface") def test_pickle(art_warning, image_dl_estimator, image_dl_estimator_defended, tmp_path): try: full_path = os.path.join(tmp_path, "my_classifier.p") @@ -176,7 +176,7 @@ def test_pickle(art_warning, image_dl_estimator, image_dl_estimator_defended, tm art_warning(e) -@pytest.mark.skip_framework("non_dl_frameworks", "pytorch") +@pytest.mark.skip_framework("non_dl_frameworks", "pytorch", "huggingface") def test_functional_model(art_warning, image_dl_estimator): try: # Need to update the functional_model code to produce a model with more than one input and output layers... @@ -191,7 +191,7 @@ def test_functional_model(art_warning, image_dl_estimator): art_warning(e) -@pytest.mark.skip_framework("mxnet", "tensorflow", "pytorch", "non_dl_frameworks") +@pytest.mark.skip_framework("mxnet", "tensorflow", "pytorch", "huggingface", "non_dl_frameworks") def test_fit_kwargs(art_warning, image_dl_estimator, get_default_mnist_subset, default_batch_size): try: (x_train_mnist, y_train_mnist), (_, _) = get_default_mnist_subset diff --git a/tests/estimators/object_detection/conftest.py b/tests/estimators/object_detection/conftest.py new file mode 100644 index 0000000000..5e4f600b71 --- /dev/null +++ b/tests/estimators/object_detection/conftest.py @@ -0,0 +1,248 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2021 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +import pytest + +logger = logging.getLogger(__name__) + + +@pytest.fixture() +def get_pytorch_object_detector(get_default_mnist_subset): + """ + This class tests the PyTorchObjectDetector object detector. + """ + import torch + import torchvision + + from art.estimators.object_detection.pytorch_object_detector import PyTorchObjectDetector + + # Define object detector + model = torchvision.models.detection.fasterrcnn_resnet50_fpn( + pretrained=True, progress=True, num_classes=91, pretrained_backbone=True + ) + params = [p for p in model.parameters() if p.requires_grad] + optimizer = torch.optim.SGD(params, lr=0.01) + + object_detector = PyTorchObjectDetector( + model=model, + input_shape=(32, 32, 3), + optimizer=optimizer, + clip_values=(0, 1), + channels_first=False, + attack_losses=["loss_classifier", "loss_box_reg", "loss_objectness", "loss_rpn_box_reg"], + ) + + (_, _), (x_test_mnist, _) = get_default_mnist_subset + + x_test = np.transpose(x_test_mnist[:2], (0, 2, 3, 1)) + x_test = np.repeat(x_test.astype(np.float32), repeats=3, axis=3) + + # Create labels + result = object_detector.predict(x=x_test) + + y_test = [ + { + "boxes": result[0]["boxes"], + "labels": result[0]["labels"], + "scores": np.ones_like(result[0]["labels"]), + }, + { + "boxes": result[1]["boxes"], + "labels": result[1]["labels"], + "scores": np.ones_like(result[1]["labels"]), + }, + ] + + yield object_detector, x_test, y_test + + +@pytest.fixture() +def get_pytorch_object_detector_mask(get_default_mnist_subset): + """ + This class tests the PyTorchObjectDetector object detector. + """ + import torch + import torchvision + + from art.estimators.object_detection.pytorch_object_detector import PyTorchObjectDetector + + # Define object detector + model = torchvision.models.detection.maskrcnn_resnet50_fpn( + pretrained=True, progress=True, num_classes=91, pretrained_backbone=True + ) + params = [p for p in model.parameters() if p.requires_grad] + optimizer = torch.optim.SGD(params, lr=0.01) + + object_detector = PyTorchObjectDetector( + model=model, + input_shape=(32, 32, 3), + optimizer=optimizer, + clip_values=(0, 1), + channels_first=False, + attack_losses=["loss_classifier", "loss_box_reg", "loss_objectness", "loss_rpn_box_reg"], + ) + + (_, _), (x_test_mnist, _) = get_default_mnist_subset + + x_test = np.transpose(x_test_mnist[:2], (0, 2, 3, 1)) + x_test = np.repeat(x_test.astype(np.float32), repeats=3, axis=3) + + # Create labels + result = object_detector.predict(x=x_test) + + y_test = [ + { + "boxes": result[0]["boxes"], + "labels": result[0]["labels"], + "scores": np.ones_like(result[0]["labels"]), + "masks": result[0]["masks"], + }, + { + "boxes": result[1]["boxes"], + "labels": result[1]["labels"], + "scores": np.ones_like(result[1]["labels"]), + "masks": result[0]["masks"], + }, + ] + + yield object_detector, x_test, y_test + + +@pytest.fixture() +def get_pytorch_faster_rcnn(get_default_mnist_subset): + """ + This class tests the PyTorchFasterRCNN object detector. + """ + import torch + + from art.estimators.object_detection.pytorch_faster_rcnn import PyTorchFasterRCNN + + # Define object detector + object_detector = PyTorchFasterRCNN( + input_shape=(32, 32, 3), + clip_values=(0, 1), + channels_first=False, + attack_losses=["loss_classifier", "loss_box_reg", "loss_objectness", "loss_rpn_box_reg"], + ) + params = [p for p in object_detector.model.parameters() if p.requires_grad] + optimizer = torch.optim.SGD(params, lr=0.01) + + object_detector.set_params(optimizer=optimizer) + + (_, _), (x_test_mnist, _) = get_default_mnist_subset + + x_test = np.transpose(x_test_mnist[:2], (0, 2, 3, 1)) + x_test = np.repeat(x_test.astype(np.float32), repeats=3, axis=3) + + # Create labels + result = object_detector.predict(x=x_test) + + y_test = [ + { + "boxes": result[0]["boxes"], + "labels": result[0]["labels"], + "scores": np.ones_like(result[0]["labels"]), + }, + { + "boxes": result[1]["boxes"], + "labels": result[1]["labels"], + "scores": np.ones_like(result[1]["labels"]), + }, + ] + + yield object_detector, x_test, y_test + + +@pytest.fixture() +def get_pytorch_yolo(get_default_cifar10_subset): + """ + This class tests the PyTorchYolo object detector. + """ + import cv2 + import torch + + from pytorchyolo import models + from pytorchyolo.utils.loss import compute_loss + + from art.estimators.object_detection.pytorch_yolo import PyTorchYolo + + model_path = "/tmp/PyTorch-YOLOv3/config/yolov3.cfg" + weights_path = "/tmp/PyTorch-YOLOv3/weights/yolov3.weights" + model = models.load_model(model_path=model_path, weights_path=weights_path) + + class YoloV3(torch.nn.Module): + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, x, targets=None): + if self.training: + outputs = self.model(x) + # loss is averaged over a batch. Thus, for patch generation use batch_size = 1 + loss, _ = compute_loss(outputs, targets, self.model) + + loss_components = {"loss_total": loss} + + return loss_components + else: + return self.model(x) + + model = YoloV3(model) + + params = [p for p in model.parameters() if p.requires_grad] + optimizer = torch.optim.SGD(params, lr=0.01) + + object_detector = PyTorchYolo( + model=model, + input_shape=(3, 416, 416), + optimizer=optimizer, + clip_values=(0, 1), + channels_first=True, + attack_losses=("loss_total",), + ) + + (_, _), (x_test_cifar10, _) = get_default_cifar10_subset + + x_test = cv2.resize( + x_test_cifar10[0].transpose((1, 2, 0)), dsize=(416, 416), interpolation=cv2.INTER_CUBIC + ).transpose((2, 0, 1)) + x_test = np.expand_dims(x_test, axis=0) + x_test = np.repeat(x_test, repeats=2, axis=0) + + # Create labels + + result = object_detector.predict(x=x_test) + + y_test = [ + { + "boxes": result[0]["boxes"], + "labels": result[0]["labels"], + "scores": np.ones_like(result[0]["labels"]), + }, + { + "boxes": result[1]["boxes"], + "labels": result[1]["labels"], + "scores": np.ones_like(result[1]["labels"]), + }, + ] + + yield object_detector, x_test, y_test diff --git a/tests/estimators/object_detection/test_object_seeker_faster_rcnn.py b/tests/estimators/object_detection/test_object_seeker_faster_rcnn.py new file mode 100644 index 0000000000..1a3ca11dfd --- /dev/null +++ b/tests/estimators/object_detection/test_object_seeker_faster_rcnn.py @@ -0,0 +1,119 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +import pytest + +from art.estimators.certification.object_seeker import PyTorchObjectSeeker +from tests.utils import ARTTestException + +logger = logging.getLogger(__name__) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_train(art_warning, get_pytorch_faster_rcnn): + object_detector, x_test, y_test = get_pytorch_faster_rcnn + object_seeker = PyTorchObjectSeeker( + model=object_detector.model, + input_shape=object_detector.input_shape, + channels_first=object_detector.channels_first, + optimizer=object_detector.optimizer, + clip_values=object_detector.clip_values, + attack_losses=object_detector.attack_losses, + detector_type="Faster-RCNN", + num_lines=3, + confidence_threshold=0.3, + iou_threshold=0.4, + prune_threshold=0.5, + device_type="cpu", + ) + + try: + # Compute loss before training + loss1 = object_seeker.compute_loss(x=x_test, y=y_test) + + # Train for one epoch + object_seeker.fit(x_test, y_test, nb_epochs=1) + + # Compute loss after training + loss2 = object_seeker.compute_loss(x=x_test, y=y_test) + + assert loss1 != loss2 + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_predict(art_warning, get_pytorch_faster_rcnn): + object_detector, x_test, _ = get_pytorch_faster_rcnn + object_seeker = PyTorchObjectSeeker( + model=object_detector.model, + input_shape=object_detector.input_shape, + channels_first=object_detector.channels_first, + optimizer=object_detector.optimizer, + clip_values=object_detector.clip_values, + attack_losses=object_detector.attack_losses, + detector_type="Faster-RCNN", + num_lines=3, + confidence_threshold=0.3, + iou_threshold=0.4, + prune_threshold=0.5, + device_type="cpu", + ) + + try: + result = object_seeker.predict(x=x_test) + + assert len(result) == len(x_test) + assert list(result[0].keys()) == ["boxes", "labels", "scores"] + assert np.all(result[0]["scores"] >= 0.3) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_certify(art_warning, get_pytorch_faster_rcnn): + object_detector, x_test, _ = get_pytorch_faster_rcnn + object_seeker = PyTorchObjectSeeker( + model=object_detector.model, + input_shape=object_detector.input_shape, + channels_first=object_detector.channels_first, + optimizer=object_detector.optimizer, + clip_values=object_detector.clip_values, + attack_losses=object_detector.attack_losses, + detector_type="Faster-RCNN", + num_lines=3, + confidence_threshold=0.3, + iou_threshold=0.4, + prune_threshold=0.5, + device_type="cpu", + ) + + try: + result = object_seeker.certify(x=x_test, patch_size=0.01, offset=0.1) + + assert len(result) == len(x_test) + assert np.any(result[0]) + + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/object_detection/test_object_seeker_yolo.py b/tests/estimators/object_detection/test_object_seeker_yolo.py new file mode 100644 index 0000000000..16c701b721 --- /dev/null +++ b/tests/estimators/object_detection/test_object_seeker_yolo.py @@ -0,0 +1,119 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +import pytest + +from art.estimators.certification.object_seeker import PyTorchObjectSeeker +from tests.utils import ARTTestException + +logger = logging.getLogger(__name__) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_train(art_warning, get_pytorch_yolo): + object_detector, x_test, y_test = get_pytorch_yolo + object_seeker = PyTorchObjectSeeker( + model=object_detector.model, + input_shape=object_detector.input_shape, + channels_first=object_detector.channels_first, + optimizer=object_detector.optimizer, + clip_values=object_detector.clip_values, + attack_losses=object_detector.attack_losses, + detector_type="YOLO", + num_lines=3, + confidence_threshold=0.3, + iou_threshold=0.4, + prune_threshold=0.5, + device_type="cpu", + ) + + try: + # Compute loss before training + loss1 = object_seeker.compute_loss(x=x_test, y=y_test) + + # Train for one epoch + object_seeker.fit(x_test, y_test, nb_epochs=1) + + # Compute loss after training + loss2 = object_seeker.compute_loss(x=x_test, y=y_test) + + assert loss1 != loss2 + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_predict(art_warning, get_pytorch_yolo): + object_detector, x_test, _ = get_pytorch_yolo + object_seeker = PyTorchObjectSeeker( + model=object_detector.model, + input_shape=object_detector.input_shape, + channels_first=object_detector.channels_first, + optimizer=object_detector.optimizer, + clip_values=object_detector.clip_values, + attack_losses=object_detector.attack_losses, + detector_type="YOLO", + num_lines=3, + confidence_threshold=0.3, + iou_threshold=0.4, + prune_threshold=0.5, + device_type="cpu", + ) + + try: + result = object_seeker.predict(x=x_test) + + assert len(result) == len(x_test) + assert list(result[0].keys()) == ["boxes", "labels", "scores"] + assert np.all(result[0]["scores"] >= 0.3) + + except ARTTestException as e: + art_warning(e) + + +@pytest.mark.only_with_platform("pytorch") +def test_pytorch_certify(art_warning, get_pytorch_yolo): + object_detector, x_test, _ = get_pytorch_yolo + object_seeker = PyTorchObjectSeeker( + model=object_detector.model, + input_shape=object_detector.input_shape, + channels_first=object_detector.channels_first, + optimizer=object_detector.optimizer, + clip_values=object_detector.clip_values, + attack_losses=object_detector.attack_losses, + detector_type="YOLO", + num_lines=3, + confidence_threshold=0.3, + iou_threshold=0.4, + prune_threshold=0.5, + device_type="cpu", + ) + + try: + result = object_seeker.certify(x=x_test, patch_size=0.01, offset=0.1) + + assert len(result) == len(x_test) + assert np.any(result[0]) + + except ARTTestException as e: + art_warning(e) diff --git a/tests/estimators/object_detection/test_pytorch_faster_rcnn.py b/tests/estimators/object_detection/test_pytorch_faster_rcnn.py index a7eea02c3f..6e1d8befb0 100644 --- a/tests/estimators/object_detection/test_pytorch_faster_rcnn.py +++ b/tests/estimators/object_detection/test_pytorch_faster_rcnn.py @@ -27,44 +27,6 @@ logger = logging.getLogger(__name__) -@pytest.fixture() -def get_pytorch_faster_rcnn(get_default_mnist_subset): - """ - This class tests the PyTorchFasterRCNN object detector. - """ - from art.estimators.object_detection.pytorch_faster_rcnn import PyTorchFasterRCNN - - # Define object detector - object_detector = PyTorchFasterRCNN( - clip_values=(0, 1), - channels_first=False, - attack_losses=["loss_classifier", "loss_box_reg", "loss_objectness", "loss_rpn_box_reg"], - ) - - (_, _), (x_test_mnist, _) = get_default_mnist_subset - - x_test = np.transpose(x_test_mnist[:2], (0, 2, 3, 1)) - x_test = np.repeat(x_test.astype(np.float32), repeats=3, axis=3) - - # Create labels - result = object_detector.predict(x=x_test) - - y_test = [ - { - "boxes": result[0]["boxes"], - "labels": result[0]["labels"], - "scores": np.ones_like(result[0]["labels"]), - }, - { - "boxes": result[1]["boxes"], - "labels": result[1]["labels"], - "scores": np.ones_like(result[1]["labels"]), - }, - ] - - yield object_detector, x_test, y_test - - @pytest.mark.only_with_platform("pytorch") def test_predict(art_warning, get_pytorch_faster_rcnn): try: @@ -94,15 +56,8 @@ def test_predict(art_warning, get_pytorch_faster_rcnn): @pytest.mark.only_with_platform("pytorch") def test_fit(art_warning, get_pytorch_faster_rcnn): try: - import torch - object_detector, x_test, y_test = get_pytorch_faster_rcnn - params = [p for p in object_detector.model.parameters() if p.requires_grad] - optimizer = torch.optim.SGD(params, lr=0.01) - - object_detector.set_params(optimizer=optimizer) - # Compute loss before training loss1 = object_detector.compute_loss(x=x_test, y=y_test) diff --git a/tests/estimators/object_detection/test_pytorch_object_detector.py b/tests/estimators/object_detection/test_pytorch_object_detector.py index 797528e28b..562c56e7bf 100644 --- a/tests/estimators/object_detection/test_pytorch_object_detector.py +++ b/tests/estimators/object_detection/test_pytorch_object_detector.py @@ -27,106 +27,6 @@ logger = logging.getLogger(__name__) -@pytest.fixture() -def get_pytorch_object_detector(get_default_mnist_subset): - """ - This class tests the PyTorchObjectDetector object detector. - """ - import torch - import torchvision - - from art.estimators.object_detection.pytorch_object_detector import PyTorchObjectDetector - - # Define object detector - model = torchvision.models.detection.fasterrcnn_resnet50_fpn( - pretrained=True, progress=True, num_classes=91, pretrained_backbone=True - ) - params = [p for p in model.parameters() if p.requires_grad] - optimizer = torch.optim.SGD(params, lr=0.01) - - object_detector = PyTorchObjectDetector( - model=model, - optimizer=optimizer, - clip_values=(0, 1), - channels_first=False, - attack_losses=["loss_classifier", "loss_box_reg", "loss_objectness", "loss_rpn_box_reg"], - ) - - (_, _), (x_test_mnist, _) = get_default_mnist_subset - - x_test = np.transpose(x_test_mnist[:2], (0, 2, 3, 1)) - x_test = np.repeat(x_test.astype(np.float32), repeats=3, axis=3) - - # Create labels - result = object_detector.predict(x=x_test) - - y_test = [ - { - "boxes": result[0]["boxes"], - "labels": result[0]["labels"], - "scores": np.ones_like(result[0]["labels"]), - }, - { - "boxes": result[1]["boxes"], - "labels": result[1]["labels"], - "scores": np.ones_like(result[1]["labels"]), - }, - ] - - yield object_detector, x_test, y_test - - -@pytest.fixture() -def get_pytorch_object_detector_mask(get_default_mnist_subset): - """ - This class tests the PyTorchObjectDetector object detector. - """ - import torch - import torchvision - - from art.estimators.object_detection.pytorch_object_detector import PyTorchObjectDetector - - # Define object detector - model = torchvision.models.detection.maskrcnn_resnet50_fpn( - pretrained=True, progress=True, num_classes=91, pretrained_backbone=True - ) - params = [p for p in model.parameters() if p.requires_grad] - optimizer = torch.optim.SGD(params, lr=0.01) - - object_detector = PyTorchObjectDetector( - model=model, - optimizer=optimizer, - clip_values=(0, 1), - channels_first=False, - attack_losses=["loss_classifier", "loss_box_reg", "loss_objectness", "loss_rpn_box_reg"], - ) - - (_, _), (x_test_mnist, _) = get_default_mnist_subset - - x_test = np.transpose(x_test_mnist[:2], (0, 2, 3, 1)) - x_test = np.repeat(x_test.astype(np.float32), repeats=3, axis=3) - - # Create labels - result = object_detector.predict(x=x_test) - - y_test = [ - { - "boxes": result[0]["boxes"], - "labels": result[0]["labels"], - "scores": np.ones_like(result[0]["labels"]), - "masks": result[0]["masks"], - }, - { - "boxes": result[1]["boxes"], - "labels": result[1]["labels"], - "scores": np.ones_like(result[1]["labels"]), - "masks": result[0]["masks"], - }, - ] - - yield object_detector, x_test, y_test - - @pytest.mark.only_with_platform("pytorch") def test_predict(art_warning, get_pytorch_object_detector): try: diff --git a/tests/estimators/object_detection/test_pytorch_yolo.py b/tests/estimators/object_detection/test_pytorch_yolo.py index 34183e20dd..a4d88e11bf 100644 --- a/tests/estimators/object_detection/test_pytorch_yolo.py +++ b/tests/estimators/object_detection/test_pytorch_yolo.py @@ -27,82 +27,6 @@ logger = logging.getLogger(__name__) -@pytest.fixture() -def get_pytorch_yolo(get_default_cifar10_subset): - """ - This class tests the PyTorchYolo object detector. - """ - import cv2 - import torch - - from pytorchyolo import models - from pytorchyolo.utils.loss import compute_loss - - from art.estimators.object_detection.pytorch_yolo import PyTorchYolo - - model_path = "/tmp/PyTorch-YOLOv3/config/yolov3.cfg" - weights_path = "/tmp/PyTorch-YOLOv3/weights/yolov3.weights" - model = models.load_model(model_path=model_path, weights_path=weights_path) - - class YoloV3(torch.nn.Module): - def __init__(self, model): - super().__init__() - self.model = model - - def forward(self, x, targets=None): - if self.training: - outputs = self.model(x) - # loss is averaged over a batch. Thus, for patch generation use batch_size = 1 - loss, _ = compute_loss(outputs, targets, self.model) - - loss_components = {"loss_total": loss} - - return loss_components - else: - return self.model(x) - - model = YoloV3(model) - - params = [p for p in model.parameters() if p.requires_grad] - optimizer = torch.optim.SGD(params, lr=0.01) - - object_detector = PyTorchYolo( - model=model, - input_shape=(3, 416, 416), - optimizer=optimizer, - clip_values=(0, 1), - channels_first=True, - attack_losses=("loss_total",), - ) - - (_, _), (x_test_cifar10, _) = get_default_cifar10_subset - - x_test = cv2.resize( - x_test_cifar10[0].transpose((1, 2, 0)), dsize=(416, 416), interpolation=cv2.INTER_CUBIC - ).transpose((2, 0, 1)) - x_test = np.expand_dims(x_test, axis=0) - x_test = np.repeat(x_test, repeats=2, axis=0) - - # Create labels - - result = object_detector.predict(x=x_test) - - y_test = [ - { - "boxes": result[0]["boxes"], - "labels": result[0]["labels"], - "scores": np.ones_like(result[0]["labels"]), - }, - { - "boxes": result[1]["boxes"], - "labels": result[1]["labels"], - "scores": np.ones_like(result[1]["labels"]), - }, - ] - - yield object_detector, x_test, y_test - - @pytest.mark.only_with_platform("pytorch") def test_predict(art_warning, get_pytorch_yolo): try: diff --git a/tests/utils.py b/tests/utils.py index 3da2ca84ed..59b6b78cfe 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -39,7 +39,7 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------------------------------- TEST BASE CLASS -art_supported_frameworks = ["keras", "tensorflow", "tensorflow2v1", "pytorch", "scikitlearn"] +art_supported_frameworks = ["keras", "tensorflow", "tensorflow2v1", "pytorch", "scikitlearn", "huggingface"] class TestBase(unittest.TestCase): @@ -1037,6 +1037,113 @@ def get_image_classifier_kr_tf_with_wildcard(): return krc +def get_image_classifier_hf(from_logits=False, load_init=True, use_maxpool=True): + """ + Standard HF classifier for testing. + + :param from_logits: Flag if model should predict logits (True) or probabilities (False). + :type from_logits: `bool` + :param load_init: Load the initial weights if True. + :type load_init: `bool` + :param use_maxpool: If to use a classifier with maxpool or not + :type use_maxpool: `bool` + :return: HuggingFaceClassifierPyTorch + """ + + import torch + from transformers.modeling_utils import PreTrainedModel + from transformers.configuration_utils import PretrainedConfig + from transformers.modeling_outputs import ImageClassifierOutput + from art.estimators.classification.hugging_face import HuggingFaceClassifierPyTorch + + class ModelConfig(PretrainedConfig): + def __init__( + self, + **kwargs, + ): + super().__init__(**kwargs) + self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + class Model(PreTrainedModel): + """ + Create model for pytorch. + + The weights and biases are identical to the TensorFlow model in get_classifier_tf(). + """ + + def __init__(self, config): + super().__init__(config) + + self.conv = torch.nn.Conv2d(in_channels=1, out_channels=1, kernel_size=7) + self.relu = torch.nn.ReLU() + self.pool = torch.nn.MaxPool2d(4, 4) + self.fullyconnected = torch.nn.Linear(25, 10) + + if load_init: + w_conv2d = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "W_CONV2D_MNIST.npy" + ) + ) + b_conv2d = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "B_CONV2D_MNIST.npy" + ) + ) + w_dense = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "W_DENSE_MNIST.npy" + ) + ) + b_dense = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "B_DENSE_MNIST.npy" + ) + ) + + w_conv2d_pt = w_conv2d.reshape((1, 1, 7, 7)) + + self.conv.weight = torch.nn.Parameter(torch.Tensor(w_conv2d_pt)) + self.conv.bias = torch.nn.Parameter(torch.Tensor(b_conv2d)) + self.fullyconnected.weight = torch.nn.Parameter(torch.Tensor(np.transpose(w_dense))) + self.fullyconnected.bias = torch.nn.Parameter(torch.Tensor(b_dense)) + + # pylint: disable=W0221 + # disable pylint because of API requirements for function + def forward(self, x): + """ + Forward function to evaluate the model + :param x: Input to the model + :return: Prediction of the model + """ + x = self.conv(x) + x = self.relu(x) + x = self.pool(x) + x = x.reshape(-1, 25) + x = self.fullyconnected(x) + if not from_logits: + x = torch.nn.functional.softmax(x, dim=1) + return ImageClassifierOutput( + logits=x, + ) + + config = ModelConfig() + pt_model = Model(config=config) + optimizer = torch.optim.Adam(pt_model.parameters(), lr=0.01) + + hf_classifier = HuggingFaceClassifierPyTorch( + pt_model, + loss=torch.nn.CrossEntropyLoss(reduction="sum"), + optimizer=optimizer, + input_shape=(1, 28, 28), + nb_classes=10, + clip_values=(0, 1), + processor=None, + ) + + return hf_classifier + + def get_image_classifier_pt(from_logits=False, load_init=True, use_maxpool=True): """ Standard PyTorch classifier for unit testing. @@ -1892,6 +1999,107 @@ def __init__(self, message, fixture_name, framework, parameters_dict=""): ) +def get_tabular_classifier_hf(load_init=True): + """ + Standard Huggingface classifier for unit testing on Iris dataset. + + :param load_init: Load the initial weights if True. + :type load_init: `bool` + :return: Huggingface model for Iris dataset. + :rtype: :class:`.HuggingFaceClassifierPyTorch` + """ + import torch + from transformers.modeling_utils import PreTrainedModel + from transformers.configuration_utils import PretrainedConfig + from transformers.modeling_outputs import ImageClassifierOutput + from art.estimators.classification.hugging_face import HuggingFaceClassifierPyTorch + + class ModelConfig(PretrainedConfig): + def __init__( + self, + **kwargs, + ): + super().__init__(**kwargs) + self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + class Model(PreTrainedModel): + """ + Create Iris model. + + The weights and biases are identical to the TensorFlow model in `get_iris_classifier_tf`. + """ + + def __init__(self, config): + super().__init__(config) + + self.fully_connected1 = torch.nn.Linear(4, 10) + self.fully_connected2 = torch.nn.Linear(10, 10) + self.fully_connected3 = torch.nn.Linear(10, 3) + + if load_init: + w_dense1 = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "W_DENSE1_IRIS.npy" + ) + ) + b_dense1 = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "B_DENSE1_IRIS.npy" + ) + ) + w_dense2 = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "W_DENSE2_IRIS.npy" + ) + ) + b_dense2 = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "B_DENSE2_IRIS.npy" + ) + ) + w_dense3 = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "W_DENSE3_IRIS.npy" + ) + ) + b_dense3 = np.load( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), "utils/resources/models", "B_DENSE3_IRIS.npy" + ) + ) + + self.fully_connected1.weight = torch.nn.Parameter(torch.Tensor(np.transpose(w_dense1))) + self.fully_connected1.bias = torch.nn.Parameter(torch.Tensor(b_dense1)) + self.fully_connected2.weight = torch.nn.Parameter(torch.Tensor(np.transpose(w_dense2))) + self.fully_connected2.bias = torch.nn.Parameter(torch.Tensor(b_dense2)) + self.fully_connected3.weight = torch.nn.Parameter(torch.Tensor(np.transpose(w_dense3))) + self.fully_connected3.bias = torch.nn.Parameter(torch.Tensor(b_dense3)) + + # pylint: disable=W0221 + # disable pylint because of API requirements for function + def forward(self, x): + x = self.fully_connected1(x) + x = self.fully_connected2(x) + logit_output = self.fully_connected3(x) + return ImageClassifierOutput(logits=logit_output) + + config = ModelConfig() + pt_model = Model(config=config) + optimizer = torch.optim.Adam(pt_model.parameters(), lr=0.01) + + hf_classifier = HuggingFaceClassifierPyTorch( + pt_model, + loss=torch.nn.CrossEntropyLoss(), + optimizer=optimizer, + input_shape=(4,), + nb_classes=3, + clip_values=(0, 1), + processor=None, + ) + + return hf_classifier + + def get_tabular_classifier_pt(load_init=True): """ Standard PyTorch classifier for unit testing on Iris dataset. diff --git a/utils/resources/models/certification/smooth_vit/cumulative_predictions.npy b/utils/resources/models/certification/smooth_vit/cumulative_predictions.npy new file mode 100644 index 0000000000..71c585b2c5 Binary files /dev/null and b/utils/resources/models/certification/smooth_vit/cumulative_predictions.npy differ diff --git a/utils/resources/models/certification/smooth_vit/head_bias.npy b/utils/resources/models/certification/smooth_vit/head_bias.npy new file mode 100644 index 0000000000..340c4215be Binary files /dev/null and b/utils/resources/models/certification/smooth_vit/head_bias.npy differ diff --git a/utils/resources/models/certification/smooth_vit/head_weight.npy b/utils/resources/models/certification/smooth_vit/head_weight.npy new file mode 100644 index 0000000000..2f718d5fbf Binary files /dev/null and b/utils/resources/models/certification/smooth_vit/head_weight.npy differ diff --git a/utils/resources/models/certification/smooth_vit/madry_preds_block.pt b/utils/resources/models/certification/smooth_vit/madry_preds_block.pt new file mode 100644 index 0000000000..f1d0fb862e Binary files /dev/null and b/utils/resources/models/certification/smooth_vit/madry_preds_block.pt differ diff --git a/utils/resources/models/certification/smooth_vit/madry_preds_column.pt b/utils/resources/models/certification/smooth_vit/madry_preds_column.pt new file mode 100644 index 0000000000..ddb01f7cb0 Binary files /dev/null and b/utils/resources/models/certification/smooth_vit/madry_preds_column.pt differ