1) Why input transformations are used as defenses
Most adversarial attacks (especially on images) rely on adding small, high-frequency perturbations that humans barely notice but models pick up. Input transformations try to break or weaken those perturbations before the model sees them.
Think of input transformations as a security filter placed in front of the model:
Input → Transformation/Filter → Model → Output
They are commonly used when:
retraining is expensive (you can’t do adversarial training immediately)
you want a quick robustness uplift
you want defense-in-depth (layered security)
2) How transformations work (the intuition)
Most transformations work by one (or more) of these mechanisms:
Remove high-frequency noise
(e.g., smoothing, denoising, compression)Disrupt precise perturbation alignment
(e.g., random resizing/padding/cropping)Reduce sensitivity to tiny pixel changes
(e.g., bit-depth reduction, quantization)Add stochasticity so attacker cannot perfectly optimize
(e.g., randomized transformations with expectation-over-transforms evaluation)
3) Common input transformations (what to teach + when to use)
A) Smoothing / Denoising
Goal: reduce small pixel-level noise.
Typical techniques
Gaussian blur
Median filter
Bilateral filter (preserves edges better than Gaussian)
Best for
attacks dominated by high-frequency noise
basic evasion attacks like FGSM
Risk
can reduce clean accuracy (removes useful details too)
B) JPEG compression
Goal: discard high-frequency components through compression.
Why it helps
JPEG compression quantizes frequency components—many adversarial perturbations get weakened in the process.
Best for
image pipelines where compression is acceptable (web, mobile)
Risk
too much compression harms clean performance
adaptive attackers can train through compression (EOT) if they know the defense
C) Resizing + padding / random crop
Goal: perturbation crafted for exact pixel grid may fail when geometry changes.
Best for
models where small geometric changes don’t break semantics
Risk
cropping can remove important features (especially on small objects)
D) Bit-depth reduction / quantization
Goal: reduce precision so tiny perturbations “snap” to the same value.
Example
8-bit → 4-bit input representation
Best for
lightweight defenses in constrained environments
Risk
may reduce accuracy if model needs subtle signals
E) Random noise injection
Goal: add stochasticity that degrades adversarial perturbations.
Best for
combined with robust training or smoothing approaches
Risk
can lower clean accuracy; may require calibration
4) The critical warning (ELMS must include this)
Input transformations can create “illusory robustness”
Some transformations make gradients harder to compute, so simple gradient-based attacks fail, but the model is not truly robust.
How to avoid being fooled
evaluate with stronger attacks (PGD)
evaluate with adaptive methods like EOT (Expectation Over Transformations) if the defense is randomized
test transfer attacks (attack a surrogate, test on your model)
5) Implementation lab (TensorFlow 2): transformations + robustness evaluation
Below is a practical workflow:
Train a CNN on MNIST
Generate adversarial examples (FGSM/PGD)
Evaluate model with and without transformations
This example uses TensorFlow ops only (so it runs anywhere TF runs).
5.1 Train a simple model
import tensorflow as tf
import numpy as np
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype("float32")/255.0)[..., None]
x_test = (x_test.astype("float32")/255.0)[..., None]
y_train_oh = tf.one_hot(y_train, 10)
y_test_oh = tf.one_hot(y_test, 10)
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, 3, activation="relu", input_shape=(28,28,1)),
tf.keras.layers.MaxPool2D(),
tf.keras.layers.Conv2D(64, 3, activation="relu"),
tf.keras.layers.MaxPool2D(),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation="relu"),
tf.keras.layers.Dense(10) # logits
])
loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
model.compile(optimizer=tf.keras.optimizers.Adam(1e-3),
loss=loss_fn,
metrics=[tf.keras.metrics.CategoricalAccuracy(name="acc")])
model.fit(x_train, y_train_oh, epochs=2, batch_size=128, validation_split=0.1)
print("Clean accuracy:", model.evaluate(x_test, y_test_oh, verbose=0)[1])
5.2 Generate adversarial examples (CleverHans FGSM + PGD)
from cleverhans.tf2.attacks.fast_gradient_method import fast_gradient_method
from cleverhans.tf2.attacks.projected_gradient_descent import projected_gradient_descent
# FGSM
x_adv_fgsm = fast_gradient_method(model_fn=model, x=x_test, eps=0.2, norm=np.inf)
# PGD (stronger)
x_adv_pgd = projected_gradient_descent(model_fn=model, x=x_test,
eps=0.3, eps_iter=0.03, nb_iter=20,
norm=np.inf, targeted=False)
print("FGSM accuracy:", model.evaluate(x_adv_fgsm, y_test_oh, verbose=0)[1])
print("PGD accuracy:", model.evaluate(x_adv_pgd, y_test_oh, verbose=0)[1])
6) Add input transformations (defense layer)
6.1 Transformation 1: Gaussian smoothing (blur)
def gaussian_blur(x, ksize=3, sigma=1.0):
# Build Gaussian kernel
ax = tf.range(-ksize//2 + 1, ksize//2 + 1, dtype=tf.float32)
xx, yy = tf.meshgrid(ax, ax)
kernel = tf.exp(-(xx**2 + yy**2) / (2.0 * sigma**2))
kernel = kernel / tf.reduce_sum(kernel)
kernel = kernel[:, :, tf.newaxis, tf.newaxis] # (k,k,1,1)
return tf.nn.depthwise_conv2d(x, kernel, strides=[1,1,1,1], padding="SAME")
6.2 Transformation 2: Random resize + pad (stochastic defense)
def random_resize_pad(x, min_size=24, max_size=28):
# Randomly resize to [min_size, max_size], then pad back to 28x28
size = tf.random.uniform([], minval=min_size, maxval=max_size+1, dtype=tf.int32)
x_resized = tf.image.resize(x, [size, size], method="bilinear")
pad_total = 28 - size
pad_top = tf.random.uniform([], 0, pad_total+1, dtype=tf.int32)
pad_bottom = pad_total - pad_top
pad_left = tf.random.uniform([], 0, pad_total+1, dtype=tf.int32)
pad_right = pad_total - pad_left
x_padded = tf.pad(x_resized, [[0,0],[pad_top,pad_bottom],[pad_left,pad_right],[0,0]])
return x_padded
7) Evaluate “transformed input” robustness
7.1 Deterministic transformation evaluation (blur)
x_test_blur = gaussian_blur(tf.constant(x_test))
x_fgsm_blur = gaussian_blur(tf.constant(x_adv_fgsm))
x_pgd_blur = gaussian_blur(tf.constant(x_adv_pgd))
print("Blur clean accuracy:", model.evaluate(x_test_blur, y_test_oh, verbose=0)[1])
print("Blur FGSM accuracy:", model.evaluate(x_fgsm_blur, y_test_oh, verbose=0)[1])
print("Blur PGD accuracy:", model.evaluate(x_pgd_blur, y_test_oh, verbose=0)[1])
7.2 Stochastic transformation evaluation (random resize+pad)
Because this is randomized, evaluate by averaging accuracy over multiple runs.
def eval_with_random_transform(x, y, runs=10):
accs = []
for _ in range(runs):
x_t = random_resize_pad(tf.constant(x))
accs.append(model.evaluate(x_t, y, verbose=0)[1])
return float(np.mean(accs)), float(np.std(accs))
clean_mean, clean_std = eval_with_random_transform(x_test, y_test_oh, runs=10)
fgsm_mean, fgsm_std = eval_with_random_transform(x_adv_fgsm, y_test_oh, runs=10)
pgd_mean, pgd_std = eval_with_random_transform(x_adv_pgd, y_test_oh, runs=10)
print(f"RandTransform clean acc: {clean_mean:.4f} ± {clean_std:.4f}")
print(f"RandTransform FGSM acc: {fgsm_mean:.4f} ± {fgsm_std:.4f}")
print(f"RandTransform PGD acc: {pgd_mean:.4f} ± {pgd_std:.4f}")
Interpretation note for learners:
If robustness improves against FGSM but not PGD, you may be seeing weak defense or illusory robustness.
Randomized defenses require EOT-style evaluation for honest robustness measurement.
8) Best practices (what to teach as “rules”)
Never rely on input transformations alone for high-stakes systems.
Use them as a layer alongside adversarial training and monitoring.Evaluate with stronger attacks than the ones you used to tune the defense.
Example: tune with FGSM, validate with PGD-20/50.For randomized transforms, report averaged metrics and evaluate adaptively (EOT).
Otherwise, you overestimate robustness.Measure clean accuracy impact and define acceptable degradation upfront.


Comments are closed.