Skip to content

Commit 0b80b09

Browse files
authored
Merge pull request #1218 from Trusted-AI/dev_1.7.1
Update to ART 1.7.1
2 parents 3d86b8d + ce87dc8 commit 0b80b09

File tree

24 files changed

+571
-222
lines changed

24 files changed

+571
-222
lines changed

.github/actions/deepspeech-v2/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/sh -l
1+
#!/bin/bash
22

33
exit_code=0
44

.github/actions/deepspeech-v3/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/sh -l
1+
#!/bin/bash
22

33
exit_code=0
44

art/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from art import preprocessing
1313

1414
# Semantic Version
15-
__version__ = "1.7.0"
15+
__version__ = "1.7.1.dev0"
1616

1717
# pylint: disable=C0103
1818

art/attacks/evasion/imperceptible_asr/imperceptible_asr_pytorch.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -317,15 +317,12 @@ class only supports targeted attack.
317317
theta_batch = []
318318
original_max_psd_batch = []
319319

320-
for i in range(len(x)):
321-
theta, original_max_psd = self._compute_masking_threshold(original_input[i])
320+
for _, x_i in enumerate(x):
321+
theta, original_max_psd = self._compute_masking_threshold(x_i)
322322
theta = theta.transpose(1, 0)
323323
theta_batch.append(theta)
324324
original_max_psd_batch.append(original_max_psd)
325325

326-
theta_batch = np.array(theta_batch)
327-
original_max_psd_batch = np.array(original_max_psd_batch)
328-
329326
# Reset delta with new result
330327
local_batch_shape = successful_adv_input_1st_stage.shape
331328
self.global_optimal_delta.data = torch.zeros(self.batch_size, self.global_max_length).type(torch.float64)
@@ -485,7 +482,7 @@ def _forward_1st_stage(
485482
return loss, local_delta, decoded_output, masked_adv_input, local_delta_rescale
486483

487484
def _attack_2nd_stage(
488-
self, x: np.ndarray, y: np.ndarray, theta_batch: np.ndarray, original_max_psd_batch: np.ndarray
485+
self, x: np.ndarray, y: np.ndarray, theta_batch: List[np.ndarray], original_max_psd_batch: List[np.ndarray]
489486
) -> "torch.Tensor":
490487
"""
491488
The second stage of the attack.
@@ -544,6 +541,7 @@ class only supports targeted attack.
544541
local_delta_rescale=local_delta_rescale,
545542
theta_batch=theta_batch,
546543
original_max_psd_batch=original_max_psd_batch,
544+
real_lengths=real_lengths,
547545
)
548546

549547
# Total loss
@@ -597,15 +595,17 @@ class only supports targeted attack.
597595
def _forward_2nd_stage(
598596
self,
599597
local_delta_rescale: "torch.Tensor",
600-
theta_batch: np.ndarray,
601-
original_max_psd_batch: np.ndarray,
598+
theta_batch: List[np.ndarray],
599+
original_max_psd_batch: List[np.ndarray],
600+
real_lengths: np.ndarray,
602601
) -> "torch.Tensor":
603602
"""
604603
The forward pass of the second stage of the attack.
605604
606605
:param local_delta_rescale: Local delta after rescaled.
607606
:param theta_batch: Original thresholds.
608607
:param original_max_psd_batch: Original maximum psd.
608+
:param real_lengths: Real lengths of original sequences.
609609
:return: The loss tensor of the second stage of the attack.
610610
"""
611611
import torch # lgtm [py/repeated-import]
@@ -616,7 +616,7 @@ def _forward_2nd_stage(
616616

617617
for i, _ in enumerate(theta_batch):
618618
psd_transform_delta = self._psd_transform(
619-
delta=local_delta_rescale[i, :], original_max_psd=original_max_psd_batch[i]
619+
delta=local_delta_rescale[i, : real_lengths[i]], original_max_psd=original_max_psd_batch[i]
620620
)
621621

622622
loss = torch.mean(relu(psd_transform_delta - torch.tensor(theta_batch[i]).to(self.estimator.device)))

art/attacks/evasion/over_the_air_flickering/over_the_air_flickering_pytorch.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,17 +271,17 @@ def _get_loss_gradients(self, x: "torch.Tensor", y: "torch.Tensor", perturbation
271271

272272
# calculate regularization terms
273273
# thickness - loss term
274-
perturbation = perturbation + eps
275-
norm_reg = torch.mean(perturbation ** 2) + 1e-12
276-
perturbation_roll_right = torch.roll(perturbation, 1, dims=0)
277-
perturbation_roll_left = torch.roll(perturbation, -1, dims=0)
274+
perturbation_i = perturbation[[i]] + eps
275+
norm_reg = torch.mean(perturbation_i ** 2) + 1e-12
276+
perturbation_roll_right = torch.roll(perturbation_i, 1, dims=1)
277+
perturbation_roll_left = torch.roll(perturbation_i, -1, dims=1)
278278

279279
# 1st order diff - loss term
280-
diff_norm_reg = torch.mean((perturbation - perturbation_roll_right) ** 2) + 1e-12
280+
diff_norm_reg = torch.mean((perturbation_i - perturbation_roll_right) ** 2) + 1e-12
281281

282282
# 2nd order diff - loss term
283283
laplacian_norm_reg = (
284-
torch.mean((-2 * perturbation + perturbation_roll_right + perturbation_roll_left) ** 2) + 1e-12
284+
torch.mean((-2 * perturbation_i + perturbation_roll_right + perturbation_roll_left) ** 2) + 1e-12
285285
)
286286

287287
regularization_loss = self.beta_0 * (

art/attacks/evasion/pixel_threshold.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from scipy.optimize import OptimizeResult, minimize
4545
from tqdm.auto import tqdm
4646

47+
from art.config import ART_NUMPY_DTYPE
4748
from art.attacks.attack import EvasionAttack
4849
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
4950
from art.estimators.classification.classifier import ClassifierMixin
@@ -128,7 +129,7 @@ def _check_params(self) -> None:
128129

129130
def rescale_input(self, x):
130131
"""Rescale inputs"""
131-
x = x.astype(np.float32) / 255.0
132+
x = x.astype(ART_NUMPY_DTYPE) / 255.0
132133
x = (x * (self.estimator.clip_values[1] - self.estimator.clip_values[0])) + self.estimator.clip_values[0]
133134
return x
134135

@@ -175,7 +176,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
175176
x = (x - self.estimator.clip_values[0]) / (self.estimator.clip_values[1] - self.estimator.clip_values[0])
176177
x = x * 255.0
177178

178-
x = x.astype(np.uint8)
179+
x = x.astype(ART_NUMPY_DTYPE)
179180

180181
adv_x_best = []
181182
self.adv_th = []

art/attacks/inference/membership_inference/black_box.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
275275
self.attack_model.eval() # type: ignore
276276
inferred: Optional[np.ndarray] = None
277277
test_set = self._get_attack_dataset(f_1=features, f_2=y)
278-
test_loader = DataLoader(test_set, batch_size=self.batch_size, shuffle=True, num_workers=0)
278+
test_loader = DataLoader(test_set, batch_size=self.batch_size, shuffle=False, num_workers=0)
279279
for input1, input2, _ in test_loader:
280280
input1, input2 = to_cuda(input1), to_cuda(input2)
281281
outputs = self.attack_model(input1, input2) # type: ignore

art/defences/detector/poison/spectral_signature_defense.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __init__(
7676
self.batch_size = batch_size
7777
self.eps_multiplier = eps_multiplier
7878
self.expected_pp_poison = expected_pp_poison
79-
self.y_train_sparse = np.argmax(y_train, axis=1)
79+
self.y_train = y_train
8080
self.evaluator = GroundTruthEvaluator()
8181
self._check_params()
8282

@@ -91,9 +91,9 @@ def evaluate_defence(self, is_clean: np.ndarray, **kwargs) -> str:
9191
"""
9292
if is_clean is None or is_clean.size == 0:
9393
raise ValueError("is_clean was not provided while invoking evaluate_defence.")
94-
is_clean_by_class = segment_by_class(is_clean, self.y_train_sparse, self.classifier.nb_classes)
94+
is_clean_by_class = segment_by_class(is_clean, self.y_train, self.classifier.nb_classes)
9595
_, predicted_clean = self.detect_poison()
96-
predicted_clean_by_class = segment_by_class(predicted_clean, self.y_train_sparse, self.classifier.nb_classes)
96+
predicted_clean_by_class = segment_by_class(predicted_clean, self.y_train, self.classifier.nb_classes)
9797

9898
_, conf_matrix_json = self.evaluator.analyze_correctness(predicted_clean_by_class, is_clean_by_class)
9999

@@ -118,7 +118,7 @@ def detect_poison(self, **kwargs) -> Tuple[dict, List[int]]:
118118
self.x_train, layer=nb_layers - 1, batch_size=self.batch_size
119119
)
120120

121-
features_split = segment_by_class(features_x_poisoned, self.y_train_sparse, self.classifier.nb_classes)
121+
features_split = segment_by_class(features_x_poisoned, self.y_train, self.classifier.nb_classes)
122122
score_by_class = []
123123
keep_by_class = []
124124

@@ -134,11 +134,11 @@ def detect_poison(self, **kwargs) -> Tuple[dict, List[int]]:
134134
keep_by_class.append([True])
135135

136136
base_indices_by_class = segment_by_class(
137-
np.arange(len(self.y_train_sparse)),
138-
self.y_train_sparse,
137+
np.arange(self.y_train.shape[0]),
138+
self.y_train,
139139
self.classifier.nb_classes,
140140
)
141-
is_clean_lst = [0] * len(self.y_train_sparse)
141+
is_clean_lst = [0] * self.y_train.shape[0]
142142
report = {}
143143

144144
for keep_booleans, all_scores, indices in zip(keep_by_class, score_by_class, base_indices_by_class):
@@ -171,5 +171,5 @@ def spectral_signature_scores(matrix_r: np.ndarray) -> np.ndarray:
171171
_, _, matrix_v = np.linalg.svd(matrix_m, full_matrices=False)
172172
eigs = matrix_v[:1]
173173
corrs = np.matmul(eigs, np.transpose(matrix_r))
174-
score = np.expand_dims(np.linalg.norm(corrs, axis=1), axis=1)
174+
score = np.expand_dims(np.linalg.norm(corrs, axis=0), axis=1)
175175
return score

art/defences/preprocessor/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44
from art.defences.preprocessor.feature_squeezing import FeatureSqueezing
55
from art.defences.preprocessor.gaussian_augmentation import GaussianAugmentation
6-
from art.defences.preprocessor.inverse_gan import InverseGAN, DefenseGAN
6+
from art.defences.preprocessor.inverse_gan import DefenseGAN, InverseGAN
77
from art.defences.preprocessor.jpeg_compression import JpegCompression
88
from art.defences.preprocessor.label_smoothing import LabelSmoothing
99
from art.defences.preprocessor.mp3_compression import Mp3Compression
10+
from art.defences.preprocessor.mp3_compression_pytorch import Mp3CompressionPyTorch
1011
from art.defences.preprocessor.pixel_defend import PixelDefend
1112
from art.defences.preprocessor.preprocessor import Preprocessor
1213
from art.defences.preprocessor.resample import Resample
@@ -16,3 +17,4 @@
1617
from art.defences.preprocessor.thermometer_encoding import ThermometerEncoding
1718
from art.defences.preprocessor.variance_minimization import TotalVarMin
1819
from art.defences.preprocessor.video_compression import VideoCompression
20+
from art.defences.preprocessor.video_compression_pytorch import VideoCompressionPyTorch

art/defences/preprocessor/mp3_compression.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __call__(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> Tuple[np.nd
7272
Apply MP3 compression to sample `x`.
7373
7474
:param x: Sample to compress with shape `(batch_size, length, channel)` or an array of sample arrays with shape
75-
(length,) or (length, channel). `x` values are recommended to be of type `np.int16`.
75+
(length,) or (length, channel).
7676
:param y: Labels of the sample `x`. This function does not affect them in any way.
7777
:return: Compressed sample.
7878
"""
@@ -84,11 +84,12 @@ def wav_to_mp3(x, sample_rate):
8484
from pydub import AudioSegment
8585
from scipy.io.wavfile import write
8686

87+
x_dtype = x.dtype
8788
normalized = bool(x.min() >= -1.0 and x.max() <= 1.0)
88-
if x.dtype != np.int16 and not normalized:
89+
if x_dtype != np.int16 and not normalized:
8990
# input is not of type np.int16 and seems to be unnormalized. Therefore casting to np.int16.
9091
x = x.astype(np.int16)
91-
elif x.dtype != np.int16 and normalized:
92+
elif x_dtype != np.int16 and normalized:
9293
# x is not of type np.int16 and seems to be normalized. Therefore undoing normalization and
9394
# casting to np.int16.
9495
x = (x * 2 ** 15).astype(np.int16)
@@ -100,7 +101,19 @@ def wav_to_mp3(x, sample_rate):
100101
tmp_wav.close()
101102
tmp_mp3.close()
102103
x_mp3 = np.array(audio_segment.get_array_of_samples()).reshape((-1, audio_segment.channels))
103-
return x_mp3
104+
105+
# WARNING: Sometimes we *still* need to manually resize x_mp3 to original length.
106+
# This should not be the case, e.g. see https://github.com/jiaaro/pydub/issues/474
107+
if x.shape[0] != x_mp3.shape[0]:
108+
logger.warning(
109+
"Lengths original input and compressed output don't match. Truncating compressed result."
110+
)
111+
x_mp3 = x_mp3[: x.shape[0]]
112+
113+
if normalized:
114+
# x was normalized. Therefore normalizing x_mp3.
115+
x_mp3 = x_mp3 * 2 ** -15
116+
return x_mp3.astype(x_dtype)
104117

105118
if x.dtype != np.object and x.ndim != 3:
106119
raise ValueError("Mp3 compression can only be applied to temporal data across at least one channel.")

0 commit comments

Comments
 (0)