Skip to content

Commit 8212437

Browse files
committed
Format fixes + missing imports
Signed-off-by: Álvaro Bacca Peña <[email protected]>
1 parent 96e543e commit 8212437

File tree

10 files changed

+405
-343
lines changed

10 files changed

+405
-343
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ RUN pip3 install tensorflow==2.9.1 keras==2.9.0 numpy==1.22.4 scipy==1.8.1 matpl
99
resampy==0.3.1 ffmpeg-python==0.2.0 cma==3.2.2 pandas==1.4.3 h5py==3.7.0 tensorflow-addons==0.17.1 \
1010
torch==1.12.0 torchaudio==0.12.0 torchvision==0.13.0 catboost==1.0.6 GPy==1.10.0 \
1111
lightgbm==3.3.2 xgboost==1.6.1 kornia==0.6.6 lief==0.12.1 pytest==7.1.2 pytest-pep8==1.0.6 \
12-
pytest-mock==3.8.2 requests==2.28.1 umap-learn==0.5.7
12+
pytest-mock==3.8.2 requests==2.28.1 umap-learn==0.5.7 psutil==7.0.0
1313

1414
RUN apt-get -y install ffmpeg libavcodec-extra vim git
1515

art/defences/detector/poison/activation_defence.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,12 @@ def analyze_clusters(self, **kwargs) -> tuple[dict[str, Any], np.ndarray]:
325325
self.assigned_clean_by_class, self.poisonous_clusters, report = analyzer(self.clusters_by_class)
326326
elif analysis_type == ClusterAnalysisType.DISTANCE:
327327
self.assigned_clean_by_class, self.poisonous_clusters, report = analyzer(
328-
self.clusters_by_class,
329-
separated_activations=self.red_activations_by_class)
328+
self.clusters_by_class, separated_activations=self.red_activations_by_class
329+
)
330330
elif analysis_type == ClusterAnalysisType.SILHOUETTE_SCORES:
331331
self.assigned_clean_by_class, self.poisonous_clusters, report = analyzer(
332-
self.clusters_by_class,
333-
reduced_activations_by_class=self.red_activations_by_class)
332+
self.clusters_by_class, reduced_activations_by_class=self.red_activations_by_class
333+
)
334334
else:
335335
raise ValueError("Unsupported cluster analysis technique " + analysis_type.value)
336336

art/defences/detector/poison/clustering_analyzer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
Class for all methodologies implemented to analyze clusters and determine whether they are poisonous.
3333
"""
3434

35+
3536
@unique
3637
class ClusterAnalysisType(Enum):
3738
SMALLER = "smaller"
@@ -52,7 +53,7 @@ def get_cluster_analyzer(cluster_analysis_type: ClusterAnalysisType):
5253
ClusterAnalysisType.SMALLER: analyze_by_size,
5354
ClusterAnalysisType.RELATIVE_SIZE: analyze_by_relative_size,
5455
ClusterAnalysisType.DISTANCE: analyze_by_distance,
55-
ClusterAnalysisType.SILHOUETTE_SCORES: analyze_by_silhouette_score
56+
ClusterAnalysisType.SILHOUETTE_SCORES: analyze_by_silhouette_score,
5657
}
5758

5859
if cluster_analysis_type not in analyzers:
@@ -75,6 +76,7 @@ def assign_class(clusters: np.ndarray, clean_clusters: np.ndarray, poison_cluste
7576
assigned_clean[np.isin(clusters, poison_clusters)] = 0
7677
return assigned_clean
7778

79+
7880
def analyze_by_size(separated_clusters: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray, dict[str, int]]:
7981
"""
8082
Designates as poisonous the cluster with fewer items on it.
@@ -127,6 +129,7 @@ def analyze_by_size(separated_clusters: list[np.ndarray]) -> tuple[np.ndarray, n
127129
report["suspicious_clusters"] = report["suspicious_clusters"] + np.sum(summary_poison_clusters)
128130
return np.asarray(all_assigned_clean, dtype=object), summary_poison_clusters, report
129131

132+
130133
def analyze_by_distance(
131134
separated_clusters: list[np.ndarray],
132135
separated_activations: list[np.ndarray],
@@ -215,6 +218,7 @@ def analyze_by_distance(
215218
all_assigned_clean_array = np.asarray(all_assigned_clean, dtype=object)
216219
return all_assigned_clean_array, summary_poison_clusters, report
217220

221+
218222
def analyze_by_relative_size(
219223
separated_clusters: list[np.ndarray],
220224
size_threshold: float = 0.35,
@@ -278,6 +282,7 @@ def analyze_by_relative_size(
278282
report["suspicious_clusters"] = report["suspicious_clusters"] + np.sum(summary_poison_clusters).item()
279283
return np.asarray(all_assigned_clean, dtype=object), summary_poison_clusters, report
280284

285+
281286
def analyze_by_silhouette_score(
282287
separated_clusters: list,
283288
reduced_activations_by_class: list,

art/defences/detector/poison/clustering_centroid_analysis.py

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
logger = logging.getLogger(__name__)
3838
tf.get_logger().setLevel(logging.WARN)
3939

40+
4041
def _encode_labels(y: np.array) -> (np.array, set, np.array, dict):
4142
"""
4243
Given the target column, it generates the label encoding and the reverse mapping to use in the classification process
@@ -51,10 +52,12 @@ def _encode_labels(y: np.array) -> (np.array, set, np.array, dict):
5152
unique_classes = set(reverse_mapping.values())
5253
return y_encoded, unique_classes, label_mapping, reverse_mapping
5354

55+
5456
@tf.function(reduce_retracing=True)
5557
def _calculate_centroid_tf(features):
5658
return tf.reduce_mean(features, axis=0)
5759

60+
5861
def _calculate_centroid(selected_indices: np.ndarray, features: np.array) -> np.ndarray:
5962
"""
6063
Returns the centroid of all data within a specific cluster that is classified as a specific class label
@@ -68,6 +71,7 @@ def _calculate_centroid(selected_indices: np.ndarray, features: np.array) -> np.
6871
centroid = _calculate_centroid_tf(features_tf)
6972
return centroid.numpy()
7073

74+
7175
def _class_clustering(y: np.array, features: np.array, label: any, clusterer: ClusterMixin) -> (np.array, np.array):
7276
"""
7377
Given a class label, it clusters all the feature representations that map to that class
@@ -83,10 +87,12 @@ def _class_clustering(y: np.array, features: np.array, label: any, clusterer: Cl
8387
cluster_labels = clusterer.fit_predict(selected_features)
8488
return cluster_labels, selected_indices
8589

90+
8691
@tf.function
8792
def _calculate_features(feature_representation_model, x):
8893
return feature_representation_model(x, training=False)
8994

95+
9096
def _feature_extraction(x_train: np.array, feature_representation_model: Model) -> np.ndarray:
9197
"""
9298
Extract features from the model using the feature representation sub model.
@@ -120,7 +126,9 @@ def _feature_extraction(x_train: np.array, feature_representation_model: Model)
120126
return features.numpy()
121127

122128

123-
def _cluster_classes(y_train: np.array, unique_classes: set[int], features: np.array, clusterer: ClusterMixin) -> (np.array, dict):
129+
def _cluster_classes(
130+
y_train: np.array, unique_classes: set[int], features: np.array, clusterer: ClusterMixin
131+
) -> (np.array, dict):
124132
"""
125133
Clusters all the classes in the given dataset into uniquely identifiable clusters.
126134
@@ -131,7 +139,7 @@ def _cluster_classes(y_train: np.array, unique_classes: set[int], features: np.a
131139
# clustering runs by classes
132140
logging.info("Clustering classes...")
133141
used_cluster_labels = 0
134-
cluster_class_mapping = dict()
142+
cluster_class_mapping = dict()
135143
class_cluster_labels = np.full(len(y_train), -1)
136144

137145
logging.info(f"Unique classes are: {unique_classes}")
@@ -145,8 +153,8 @@ def _cluster_classes(y_train: np.array, unique_classes: set[int], features: np.a
145153
class_cluster_labels[selected_indices] = cluster_labels
146154

147155
# the class (label) corresponding to the cluster is saved for centroid deviation calculation
148-
for l in np.unique(cluster_labels[cluster_labels != -1]):
149-
cluster_class_mapping[l] = class_label
156+
for label in np.unique(cluster_labels[cluster_labels != -1]):
157+
cluster_class_mapping[label] = class_label
150158

151159
return class_cluster_labels, cluster_class_mapping
152160

@@ -170,7 +178,7 @@ class ClusteringCentroidAnalysis(PoisonFilteringDefence):
170178
"final_feature_layer_name",
171179
"misclassification_threshold",
172180
"reducer",
173-
"clsuterer"
181+
"clsuterer",
174182
]
175183
valid_clustering = ["DBSCAN"]
176184
valid_reduce = ["UMAP"]
@@ -184,7 +192,6 @@ def _get_benign_data(self) -> (np.ndarray, np.ndarray):
184192
if len(self.benign_indices) == 0:
185193
raise ValueError(f"Benign indices passed ({len(self.benign_indices)}) are not enough to run the algorithm")
186194

187-
188195
return self.x_train[self.benign_indices], self.y_train[self.benign_indices]
189196

190197
def _extract_submodels(self, final_feature_layer_name: str) -> (Model, Model):
@@ -204,18 +211,21 @@ def _extract_submodels(self, final_feature_layer_name: str) -> (Model, Model):
204211
except ValueError:
205212
raise ValueError(f"Layer with name '{final_feature_layer_name}' not found in the model.")
206213

207-
if not hasattr(final_feature_layer, 'activation') or final_feature_layer.activation != tf.keras.activations.relu:
214+
if (
215+
not hasattr(final_feature_layer, "activation")
216+
or final_feature_layer.activation != tf.keras.activations.relu
217+
):
208218
warnings.warn(f"Final feature layer '{final_feature_layer_name}' must have a ReLU activation.", UserWarning)
209219

210220
# Create a feature representation submodel with weight sharing
211221
feature_representation_model = Model(
212222
inputs=keras_model.inputs,
213223
outputs=keras_model.get_layer(final_feature_layer_name).output,
214-
name="feature_representation_model"
224+
name="feature_representation_model",
215225
)
216226

217227
final_feature_layer_index = keras_model.layers.index(final_feature_layer)
218-
classifier_submodel_layers = keras_model.layers[final_feature_layer_index + 1:]
228+
classifier_submodel_layers = keras_model.layers[final_feature_layer_index + 1 :]
219229

220230
# Create the classifier submodel
221231
classifying_submodel = Sequential(classifier_submodel_layers, name="classifying_submodel")
@@ -245,15 +255,15 @@ def get_clusters(self) -> np.array:
245255
return result
246256

247257
def __init__(
248-
self,
249-
classifier: "CLASSIFIER_TYPE",
250-
x_train: np.ndarray,
251-
y_train: np.ndarray,
252-
benign_indices: np.array,
253-
final_feature_layer_name: str,
254-
misclassification_threshold: float,
255-
reducer = UMAP(n_neighbors=5, min_dist=0),
256-
clusterer = DBSCAN(eps=0.8, min_samples=20)
258+
self,
259+
classifier: "CLASSIFIER_TYPE",
260+
x_train: np.ndarray,
261+
y_train: np.ndarray,
262+
benign_indices: np.array,
263+
final_feature_layer_name: str,
264+
misclassification_threshold: float,
265+
reducer=UMAP(n_neighbors=5, min_dist=0),
266+
clusterer=DBSCAN(eps=0.8, min_samples=20),
257267
):
258268
"""
259269
Creates a :class: `ClusteringCentroidAnalysis` object for the given classifier
@@ -298,13 +308,11 @@ def evaluate_defence(self, is_clean: np.ndarray, **kwargs) -> str:
298308

299309
# Create evaluator and analyze results
300310
errors_by_class, confusion_matrix_json = evaluator.analyze_correctness(
301-
assigned_clean_by_class=assigned_clean_by_class,
302-
is_clean_by_class=is_clean_by_class
311+
assigned_clean_by_class=assigned_clean_by_class, is_clean_by_class=is_clean_by_class
303312
)
304313

305314
return confusion_matrix_json
306315

307-
308316
def _calculate_misclassification_rate(self, class_label: int, deviation: np.array) -> np.float64:
309317
"""
310318
Calculate the misclassification rate when applying a deviation to other classes.
@@ -324,10 +332,12 @@ def _calculate_misclassification_rate(self, class_label: int, deviation: np.arra
324332
sample_features = self.feature_representation_model.predict(sample_data)
325333
feature_shape = sample_features.shape[1:]
326334

327-
@tf.function(input_signature=[
328-
tf.TensorSpec(shape=[None, *feature_shape], dtype=tf.float32),
329-
tf.TensorSpec(shape=deviation.shape, dtype=tf.float32)
330-
])
335+
@tf.function(
336+
input_signature=[
337+
tf.TensorSpec(shape=[None, *feature_shape], dtype=tf.float32),
338+
tf.TensorSpec(shape=deviation.shape, dtype=tf.float32),
339+
]
340+
)
331341
def predict_with_deviation(features, deviation):
332342
# Add deviation to features and pass through ReLu to keep in latent space
333343
deviated_features = tf.nn.relu(features + deviation)
@@ -390,11 +400,12 @@ def predict_with_deviation(features, deviation):
390400
return np.float64(0.0)
391401

392402
all_f_vectors_np = np.concatenate(all_features, axis=0)
393-
logger.info(f"MR --> {class_label} , |f| = {np.linalg.norm(np.mean(all_f_vectors_np, axis=0))}: {misclassified_elements} / {total_elements} = {np.float64(misclassified_elements) / np.float64(total_elements)}")
403+
logger.info(
404+
f"MR --> {class_label} , |f| = {np.linalg.norm(np.mean(all_f_vectors_np, axis=0))}: {misclassified_elements} / {total_elements} = {np.float64(misclassified_elements) / np.float64(total_elements)}"
405+
)
394406

395407
return np.float64(misclassified_elements) / np.float64(total_elements)
396408

397-
398409
def detect_poison(self, **kwargs) -> (dict, list[int]):
399410

400411
# saves important information about the algorithm execution for further analysis
@@ -407,14 +418,13 @@ def detect_poison(self, **kwargs) -> (dict, list[int]):
407418
# FIXME: temporal fix to test other layers
408419
if len(self.features.shape) > 2:
409420
num_samples = self.features.shape[0]
410-
self.features = self.features.reshape(num_samples, -1) # Flattening
421+
self.features = self.features.reshape(num_samples, -1) # Flattening
411422

412423
self.features_reduced = self.reducer.fit_transform(self.features)
413424

414-
self.class_cluster_labels, self.cluster_class_mapping = _cluster_classes(self.y_train,
415-
self.unique_classes,
416-
self.features_reduced,
417-
self.clusterer)
425+
self.class_cluster_labels, self.cluster_class_mapping = _cluster_classes(
426+
self.y_train, self.unique_classes, self.features_reduced, self.clusterer
427+
)
418428

419429
# outliers are poisoned
420430
outlier_indices = np.where(self.class_cluster_labels == -1)[0]
@@ -443,8 +453,7 @@ def detect_poison(self, **kwargs) -> (dict, list[int]):
443453
# for each target class
444454
for class_label in self.unique_classes:
445455
benign_class_indices = np.intersect1d(self.benign_indices, np.where(self.y_train == class_label)[0])
446-
benign_centroids[class_label] = _calculate_centroid(benign_class_indices,
447-
self.features)
456+
benign_centroids[class_label] = _calculate_centroid(benign_class_indices, self.features)
448457

449458
logging.info("Calculating misclassification rates...")
450459
misclassification_rates = dict()
@@ -457,19 +466,22 @@ def detect_poison(self, **kwargs) -> (dict, list[int]):
457466
# MR^k_i
458467
# with unique cluster labels for each cluster in each clustering run, the label already maps to a target class
459468
misclassification_rates[cluster_label] = self._calculate_misclassification_rate(class_label, deviation)
460-
logging.info(f"MR (k={cluster_label}, i={class_label}, |d|={np.linalg.norm(deviation)}) = {misclassification_rates[cluster_label]}")
469+
logging.info(
470+
f"MR (k={cluster_label}, i={class_label}, |d|={np.linalg.norm(deviation)}) = {misclassification_rates[cluster_label]}"
471+
)
461472

462473
report["cluster_data"][cluster_label]["centroid_l2"] = np.linalg.norm(real_centroids[cluster_label])
463474
report["cluster_data"][cluster_label]["deviation_l2"] = np.linalg.norm(deviation)
464475
report["cluster_data"][cluster_label]["class"] = class_label
465476
report["cluster_data"][cluster_label]["misclassification_rate"] = misclassification_rates[cluster_label]
466477

467-
468478
logging.info("Evaluating cluster misclassification...")
469479
for cluster_label, mr in misclassification_rates.items():
470480
if mr >= 1 - self.misclassification_threshold:
471481
cluster_indices = np.where(self.class_cluster_labels == cluster_label)[0]
472482
self.is_clean[cluster_indices] = 0
473-
logging.info(f"Cluster k={cluster_label} i={self.cluster_class_mapping[cluster_label]} considered poison ({misclassification_rates[cluster_label]} >= {1 - self.misclassification_threshold})")
483+
logging.info(
484+
f"Cluster k={cluster_label} i={self.cluster_class_mapping[cluster_label]} considered poison ({misclassification_rates[cluster_label]} >= {1 - self.misclassification_threshold})"
485+
)
474486

475487
return report, self.is_clean.copy()

0 commit comments

Comments
 (0)