Skip to content

Commit

Permalink
Merge pull request #1869 from swsuggs/speech-commands-explanatory-model
Browse files Browse the repository at this point in the history
Speech commands explanatory model
  • Loading branch information
lcadalzo authored Feb 1, 2023
2 parents b44c5ea + 720d127 commit eb793c6
Show file tree
Hide file tree
Showing 71 changed files with 178 additions and 118 deletions.
1 change: 1 addition & 0 deletions armory/art_experimental/attacks/poison_loader_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def insert(self, x: np.ndarray) -> np.ndarray:
raise ValueError("Shift + Backdoor length is greater than audio's length.")

audio[shift : shift + bd_length] += self.scaled_trigger
audio = np.clip(audio, -1.0, 1.0)
return audio.astype(original_dtype)


Expand Down
26 changes: 17 additions & 9 deletions armory/baseline_models/tf_graph/audio_resnet50.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def get_spectrogram(audio):
return spectrogram # shape (124, 129, 1)


def make_audio_resnet(**kwargs) -> tf.keras.Model:
def make_audio_resnet(sequential=True, **kwargs) -> tf.keras.Model:

inputs = keras.Input(shape=(16000,))
spectrogram = Lambda(lambda audio: get_spectrogram(audio))(inputs)
Expand All @@ -32,8 +32,10 @@ def make_audio_resnet(**kwargs) -> tf.keras.Model:
)

model = keras.Model(resnet.inputs, resnet.outputs)
# ART's TensorFlowV2Classifier get_activations() requires a Sequential model
model = keras.Sequential([model])
if sequential:
# ART's TensorFlowV2Classifier get_activations() requires a Sequential model
model = keras.Sequential([model])

model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
Expand All @@ -47,12 +49,7 @@ def get_art_model(
model_kwargs: dict, wrapper_kwargs: dict, weights_path: Optional[str] = None
):

if weights_path:
raise ValueError(
"This model is implemented for poisoning and does not (yet) load saved weights."
)

model = make_audio_resnet(**model_kwargs)
model = make_audio_resnet(sequential=True, **model_kwargs)

loss_object = losses.SparseCategoricalCrossentropy()

Expand All @@ -73,3 +70,14 @@ def train_step(model, samples, labels):
)

return art_classifier


def get_unwrapped_model(
weights_path: str,
**model_kwargs,
):
# This is used for the explanatory model for the poisoning fairness metrics
model = make_audio_resnet(sequential=False, **model_kwargs)
model.load_weights(weights_path)

return model
120 changes: 85 additions & 35 deletions armory/metrics/poisoning.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,46 @@
from PIL import Image
import numpy as np
import torch
import tensorflow as tf

from armory.data.utils import maybe_download_weights_from_s3

# An armory user may request one of these models under 'adhoc'/'explanatory_model'
EXPLANATORY_MODEL_CONFIGS = explanatory_model_configs = {
"cifar10_silhouette_model": {
"speech_commands_explanatory_model": {
"activation_layer": "avg_pool",
"data_modality": "audio",
"model_framework": "tensorflow",
"module": "armory.baseline_models.tf_graph.audio_resnet50",
"name": "get_unwrapped_model",
"weights_file": "speech_commands_explanatory_model_resnet50_bean.h5",
},
"cifar10_explanatory_model": {
"model_kwargs": {
"data_means": [0.4914, 0.4822, 0.4465],
"data_stds": [0.2471, 0.2435, 0.2616],
"num_classes": 10,
},
"module": "armory.baseline_models.pytorch.resnet18_bean_regularization",
"name": "get_model",
"resize_image": False,
"weights_file": "cifar10_explanatory_model_resnet18_bean.pt",
},
"gtsrb_silhouette_model": {
"model_kwargs": {},
"gtsrb_explanatory_model": {
"module": "armory.baseline_models.pytorch.micronnet_gtsrb_bean_regularization",
"name": "get_model",
"resize_image": False,
"weights_file": "gtsrb_explanatory_model_micronnet_bean.pt",
},
"resisc10_silhouette_model": {
"resisc10_explanatory_model": {
"model_kwargs": {
"data_means": [0.39382024, 0.4159701, 0.40887499],
"data_stds": [0.18931773, 0.18901625, 0.19651154],
"num_classes": 10,
},
"module": "armory.baseline_models.pytorch.resnet18_bean_regularization",
"name": "get_model",
"preprocess_kwargs": {
"resize_image": True,
},
"weights_file": "resisc10_explanatory_model_resnet18_bean.pt",
},
}
Expand All @@ -46,18 +55,44 @@ class ExplanatoryModel:
def __init__(
self,
explanatory_model,
resize_image=True,
size=(224, 224),
resample=Image.BILINEAR,
device=DEVICE,
data_modality="image",
model_framework="pytorch",
activation_layer=None,
preprocess_kwargs=None,
):
"""
explanatory_model: A callable pytorch or tensorflow model used to produce
activations for silhouette analysis
data_modality: one of "image" or "audio" (more options to be added as needed)
model_framework: "pytorch" or "tensorflow"
activation_layer: name of the layer of the model from which to draw activations
(currently only for tensorflow models).
If None, uses the final output layer.
preprocess_kwargs: keyword arguments for the preprocessing function
"""
if not callable(explanatory_model):
raise ValueError(f"explanatory_model {explanatory_model} is not callable")
if model_framework not in ("pytorch", "tensorflow"):
raise ValueError(
f"model_framework should be 'pytorch' or 'tensorflow', not '{model_framework}'"
)
self.explanatory_model = explanatory_model
self.resize_image = bool(resize_image)
self.size = size
self.resample = resample
self.device = device
self.data_modality = data_modality
self.model_framework = model_framework
self.activation_layer = activation_layer
self.preprocess_kwargs = preprocess_kwargs if preprocess_kwargs else {}

if self.activation_layer is not None:
if self.model_framework == "tensorflow":
# Set explanatory_model to return activations from internal layer
self.explanatory_model = tf.keras.Model(
explanatory_model.layers[0].input,
explanatory_model.get_layer(self.activation_layer).output,
)
else:
raise ValueError(
"Currently, 'activation_layer' can only be specified for a tensorflow model, not pytorch."
)

@classmethod
def from_config(cls, model_config, **kwargs):
Expand Down Expand Up @@ -87,7 +122,10 @@ def from_config(cls, model_config, **kwargs):
model_fn = getattr(model_module, name)
explanatory_model = model_fn(weights_path, **model_kwargs)

return cls(explanatory_model, **model_config)
return cls(
explanatory_model,
**model_config,
)

def get_activations(self, x, batch_size: int = None):
"""
Expand All @@ -96,25 +134,32 @@ def get_activations(self, x, batch_size: int = None):
if batch_size, batch inputs and then concatenate
"""
activations = []
with torch.no_grad():
if batch_size:
batch_size = int(batch_size)
if batch_size < 1:
raise ValueError("batch_size must be false or a positive int")
else:
batch_size = len(x)
if batch_size:
batch_size = int(batch_size)
if batch_size < 1:
raise ValueError("batch_size must be false or a positive int")
else:
batch_size = len(x)

for i in range(0, len(x), batch_size):
x_batch = x[i : i + batch_size]

for i in range(0, len(x), batch_size):
x_batch = x[i : i + batch_size]
if self.model_framework == "pytorch":
with torch.no_grad():
x_batch = self.preprocess(x_batch)
activation, _ = self.explanatory_model(x_batch)
activations.append(activation.detach().cpu().numpy())

elif self.model_framework == "tensorflow":
x_batch = self.preprocess(x_batch)
activation, _ = self.explanatory_model(x_batch)
activations.append(activation.detach().cpu().numpy())
activation = self.explanatory_model(x_batch, training=False)
activations.append(activation.numpy())

return np.concatenate(activations)

@staticmethod
def _preprocess(
x, resize_image=True, size=(224, 224), resample=Image.BILINEAR, device=DEVICE
def _preprocess_image(
x, resize_image=False, size=(224, 224), resample=Image.BILINEAR, device=DEVICE
):
if np.issubdtype(x.dtype, np.floating):
if x.min() < 0.0 or x.max() > 1.0:
Expand Down Expand Up @@ -145,10 +190,15 @@ def preprocess(self, x):
"""
Preprocess a batch of images
"""
return type(self)._preprocess(
x,
self.resize_image,
self.size,
resample=self.resample,
device=self.device,
)
if self.data_modality == "image":
return type(self)._preprocess_image(
x,
**self.preprocess_kwargs,
)
elif self.data_modality == "audio":
return x

else:
raise ValueError(
f"There is no preprocessing function for data_modality '{self.data_modality}'. Please set data_modality to 'image' or 'audio', or implement preprocessing for data_modality '{self.data_modality}'"
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "gtsrb_silhouette_model",
"explanatory_model": "gtsrb_explanatory_model",
"poison_dataset": true,
"source_class": 1,
"split_id": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "gtsrb_silhouette_model",
"explanatory_model": "gtsrb_explanatory_model",
"poison_dataset": true,
"source_class": 1,
"split_id": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "gtsrb_silhouette_model",
"explanatory_model": "gtsrb_explanatory_model",
"poison_dataset": true,
"source_class": 1,
"split_id": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "resisc10_silhouette_model",
"explanatory_model": "resisc10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fit_defense_classifier_outside_defense": false,
"fraction_poisoned": 0.1,
"poison_dataset": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fit_defense_classifier_outside_defense": false,
"fraction_poisoned": 0.1,
"poison_dataset": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fraction_poisoned": 0.1,
"poison_dataset": true,
"source_class": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"adhoc": {
"compute_fairness_metrics": true,
"experiment_id": 0,
"explanatory_model": "cifar10_silhouette_model",
"explanatory_model": "cifar10_explanatory_model",
"fit_defense_classifier_outside_defense": false,
"fraction_poisoned": 0.1,
"poison_dataset": true,
Expand Down
Loading

0 comments on commit eb793c6

Please sign in to comment.