Skip to content

Commit 8508a83

Browse files
committed
[WIP] Tests improved. MNIST results obtained in Notebook
Signed-off-by: alvaro <[email protected]>
1 parent bf39685 commit 8508a83

File tree

3 files changed

+1356
-743
lines changed

3 files changed

+1356
-743
lines changed

art/defences/detector/poison/clustering_centroid_analysis.py

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from __future__ import absolute_import, division, print_function, unicode_literals, annotations
1919

2020
import logging
21+
import warnings
2122
from typing import TYPE_CHECKING
2223

2324
import tensorflow as tf
@@ -165,15 +166,18 @@ class ClusteringCentroidAnalysis(PoisonFilteringDefence):
165166
166167
"""
167168

168-
_DEFENCE_PARAMS = []
169-
_VALID_CLUSTERING = ["DBSCAN"]
170-
_VALID_REDUCE = ["UMAP", "PCA"]
171-
_VALID_ANALYSIS = [
172-
ClusterAnalysisType.SMALLER,
173-
ClusterAnalysisType.RELATIVE_SIZE,
174-
ClusterAnalysisType.DISTANCE,
175-
ClusterAnalysisType.SILHOUETTE_SCORES
169+
defence_params = [
170+
"classifier",
171+
"x_train",
172+
"y_train",
173+
"benign_indices",
174+
"final_feature_layer_name",
175+
"misclassification_threshold",
176+
"reducer",
177+
"clsuterer"
176178
]
179+
valid_clustering = ["DBSCAN"]
180+
valid_reduce = ["UMAP"]
177181

178182
def _get_benign_data(self) -> (np.ndarray, np.ndarray):
179183
"""
@@ -204,8 +208,8 @@ def _extract_submodels(self, final_feature_layer_name: str) -> (Model, Model):
204208
except ValueError:
205209
raise ValueError(f"Layer with name '{final_feature_layer_name}' not found in the model.")
206210

207-
#if not hasattr(final_feature_layer, 'activation') or final_feature_layer.activation != tf.keras.activations.relu:
208-
#raise ValueError(f"Final feature layer '{final_feature_layer_name}' must have a ReLU activation.")
211+
if not hasattr(final_feature_layer, 'activation') or final_feature_layer.activation != tf.keras.activations.relu:
212+
warnings.warn(f"Final feature layer '{final_feature_layer_name}' must have a ReLU activation.", UserWarning)
209213

210214
# Create a feature representation submodel with weight sharing
211215
feature_representation_model = Model(
@@ -479,23 +483,3 @@ def detect_poison(self, **kwargs) -> (dict, list[int]):
479483
logging.info(f"Cluster k={cluster_label} i={self.cluster_class_mapping[cluster_label]} considered poison ({misclassification_rates[cluster_label]} >= {1 - self.misclassification_threshold})")
480484

481485
return report, self.is_clean.copy()
482-
483-
484-
def get_reducer(reduce: ReducerType, nb_dims: int):
485-
"""Initialize the right reducer based on the selected type."""
486-
if reduce == ReducerType.FASTICA:
487-
return FastICA(n_components=nb_dims, max_iter=1000, tol=0.005)
488-
if reduce == ReducerType.PCA:
489-
return PCA(n_components=nb_dims)
490-
if reduce == ReducerType.UMAP:
491-
return UMAP(n_components=nb_dims, random_state=42) # TODO: should I remove the random state?
492-
493-
raise ValueError(f"{reduce} dimensionality reduction method not supported.")
494-
495-
496-
def get_clusterer(clusterer_type: ClustererType) -> ClusterMixin:
497-
"""Initialize the right cluster algorithm (a.k.a., clusterer) based on the selected type. """
498-
if clusterer_type == ClustererType.DBSCAN:
499-
return DBSCAN(eps=0.8, min_samples=20)
500-
501-
raise ValueError(f"{clusterer_type} cluster method not supported.")

notebooks/poisoning_defense_clustering_centroid_analysis.ipynb

Lines changed: 1337 additions & 670 deletions
Large diffs are not rendered by default.

tests/defences/detector/poison/test_clustering_centroid_analysis.py

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@
3737
from tensorflow.keras.layers import Dense
3838
from umap import UMAP
3939

40-
from art.defences.detector.poison.clustering_centroid_analysis import get_reducer, get_clusterer, \
41-
ClusteringCentroidAnalysis, _calculate_centroid, _class_clustering, _feature_extraction, _cluster_classes, \
42-
_encode_labels
40+
from art.defences.detector.poison.clustering_centroid_analysis import ClusteringCentroidAnalysis, _calculate_centroid, _class_clustering, _feature_extraction, _cluster_classes, _encode_labels
4341
from art.defences.detector.poison.utils import ReducerType, ClustererType
4442

4543
logger = logging.getLogger(__name__)
@@ -276,7 +274,7 @@ def test_init_invalid_layer_name(self):
276274

277275
def test_init_invalid_layer_non_relu(self):
278276
"""Test __init__ with an invalid layer that does not have ReLu activation. Check that it raises error."""
279-
with self.assertRaises(ValueError) as e:
277+
with self.assertWarns(UserWarning) as w:
280278
ClusteringCentroidAnalysis(
281279
classifier=self.mock_classifier,
282280
x_train=self.x_train,
@@ -285,7 +283,9 @@ def test_init_invalid_layer_non_relu(self):
285283
final_feature_layer_name=self.non_relu_intermediate_layer_name,
286284
misclassification_threshold=self.misclassification_threshold
287285
)
288-
self.assertEqual(f"Final feature layer '{self.non_relu_intermediate_layer_name}' must have a ReLU activation.", str(e.exception))
286+
self.assertEqual(1, len(w.warnings))
287+
self.assertEqual(f"Final feature layer '{self.non_relu_intermediate_layer_name}' must have a ReLU activation.",
288+
str(w.warnings[0].message))
289289

290290
class TestEncodeLabels(unittest.TestCase):
291291

@@ -650,44 +650,6 @@ def test_integration_with_real_model(self):
650650
self.assertIsInstance(result, np.ndarray)
651651

652652

653-
class TestReducersClusterers(unittest.TestCase):
654-
"""
655-
Suite of tests for the valid and invalid utils used in :class: ``ClusteringCentroidAnalysis``
656-
"""
657-
658-
def test_get_reducer_valid(self):
659-
reducer_cases = [
660-
(ReducerType.FASTICA, FastICA),
661-
(ReducerType.PCA, PCA),
662-
(ReducerType.UMAP, UMAP),
663-
]
664-
for reducer_type, expected in reducer_cases:
665-
with self.subTest(reducer=reducer_type):
666-
reducer = get_reducer(reducer_type, nb_dims=5)
667-
self.assertIsInstance(reducer, expected)
668-
669-
def test_get_reducer_invalid(self):
670-
for invalid in ["INVALID", None]:
671-
with self.subTest(invalid=invalid):
672-
with self.assertRaises(ValueError):
673-
get_reducer(invalid, nb_dims=5)
674-
675-
def test_get_clusterer_valid(self):
676-
clusterer_cases = [
677-
(ClustererType.DBSCAN, DBSCAN),
678-
]
679-
for clusterer_type, expected in clusterer_cases:
680-
with self.subTest(clusterer=clusterer_type):
681-
clusterer = get_clusterer(clusterer_type)
682-
self.assertIsInstance(clusterer, expected)
683-
684-
def test_get_clusterer_invalid(self):
685-
for invalid in ["INVALID", None]:
686-
with self.subTest(invalid=invalid):
687-
with self.assertRaises(ValueError):
688-
get_clusterer(invalid)
689-
690-
691653
class TestDetectPoison(unittest.TestCase):
692654
"""
693655
Unit tests for the detect_poison method in ClusteringCentroidAnalysis

0 commit comments

Comments
 (0)