lemousehunter
v3: Add DTP + Spectral Decoupling, fix GradNorm OOM, fix _fail_job cancel
283a882
"""
CNNbest Architecture
====================
The convolutional neural network architecture from the ASCAD paper
(Benadjila et al., 2020).
Architecture:
Input(700,1) -> 5 Conv blocks [Conv1D -> SELU -> BatchNorm -> AvgPool]
-> Flatten -> 2 x Dense(4096, SELU) -> Dense(256, Softmax)
Conv filters: [64, 128, 256, 512, 512], kernel_size=11, pool_size=2
Optimizer: RMSprop(lr=1e-5)
"""
from typing import Dict, Any, List
import tensorflow as tf
from .base import BaseModel
from ..constants import CNN_DEFAULTS, NUM_CLASSES, WINDOW_SIZE
class CNNBest(BaseModel):
"""
CNNbest architecture for ASCAD side-channel attacks.
This is the best-performing CNN architecture identified in the original
ASCAD paper. It uses 5 convolutional blocks with increasing filter counts,
SELU activation, batch normalization, and average pooling, followed by
two large fully connected layers.
"""
def __init__(
self,
input_length: int = WINDOW_SIZE,
num_classes: int = NUM_CLASSES,
conv_filters: List[int] = None,
kernel_size: int = CNN_DEFAULTS["kernel_size"],
pool_size: int = CNN_DEFAULTS["pool_size"],
fc_units: int = CNN_DEFAULTS["fc_units"],
num_fc_layers: int = CNN_DEFAULTS["num_fc_layers"],
) -> None:
"""
Args:
input_length: Number of time samples in the input trace.
num_classes: Number of output classes.
conv_filters: List of filter counts for each conv block.
kernel_size: Kernel size for all conv layers.
pool_size: Pool size for average pooling.
fc_units: Number of units in each fully connected layer.
num_fc_layers: Number of fully connected layers.
"""
super().__init__(input_shape=(input_length, 1), num_classes=num_classes)
self.input_length = input_length
self.conv_filters = conv_filters or list(CNN_DEFAULTS["conv_filters"])
self.kernel_size = kernel_size
self.pool_size = pool_size
self.fc_units = fc_units
self.num_fc_layers = num_fc_layers
def build(self) -> tf.keras.Model:
"""Construct the CNNbest Keras model."""
inputs = tf.keras.Input(shape=self.input_shape, name="trace_input")
x = inputs
# Convolutional blocks
for i, filters in enumerate(self.conv_filters):
x = tf.keras.layers.Conv1D(
filters=filters,
kernel_size=self.kernel_size,
padding="same",
kernel_initializer="glorot_uniform",
name=f"conv_{i}",
)(x)
x = tf.keras.layers.Activation("selu", name=f"selu_{i}")(x)
x = tf.keras.layers.BatchNormalization(name=f"bn_{i}")(x)
x = tf.keras.layers.AveragePooling1D(
pool_size=self.pool_size, name=f"avgpool_{i}"
)(x)
x = tf.keras.layers.Flatten(name="flatten")(x)
# Fully connected layers
for i in range(self.num_fc_layers):
x = tf.keras.layers.Dense(
self.fc_units,
activation="selu",
kernel_initializer="glorot_uniform",
name=f"fc_{i}",
)(x)
outputs = tf.keras.layers.Dense(
self.num_classes,
activation="softmax",
name="predictions",
)(x)
self._model = tf.keras.Model(inputs=inputs, outputs=outputs, name="CNNbest")
return self._model
def get_config(self) -> Dict[str, Any]:
"""Return architecture hyperparameters."""
return {
"model_type": "cnn",
"architecture": "CNNbest",
"input_length": self.input_length,
"num_classes": self.num_classes,
"conv_filters": self.conv_filters,
"kernel_size": self.kernel_size,
"pool_size": self.pool_size,
"fc_units": self.fc_units,
"num_fc_layers": self.num_fc_layers,
"total_params": self.model.count_params(),
}