FGSM en Redes Convolucionales

Este post es continuación del anterior «Introducción Práctica a FGSM»:

Aquí vamos a centrarnos en una red neuronal convolucional. Las Redes Neuronales Convolucionales representan uno de los avances más significativos en el campo de la inteligencia artificial.

Su historia comenzó en 1998 cuando Yann LeCun presentó LeNet-5, la primera CNN exitosa, utilizada para reconocimiento de dígitos manuscritos.

http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf

Sin embargo, el verdadero punto de inflexión llegó en 2012 con AlexNet, desarrollada por Alex Krizhevsky, que ganó la competencia ImageNet con una diferencia sorprendente sobre los métodos tradicionales.

Esta victoria marcó el inicio de la revolución del deep learning en visión por computador.

https://papers.nips.cc/paper_files/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf

Las CNNs están inspiradas en el córtex visual de los mamíferos, donde las neuronas responden a estímulos en regiones específicas del campo visual, llamadas campos receptivos.

Esta arquitectura permite a las CNNs capturar patrones visuales jerárquicos, desde bordes simples hasta formas y objetos complejos.

Las Redes Convolucionales, o CNNs, son un tipo de red neuronal diseñada específicamente para trabajar con datos que tienen una estructura de cuadrícula, como imágenes. A diferencia de las redes neuronales tradicionales, las CNNs utilizan operaciones de convolución para extraer características locales de los datos, lo que las hace extremadamente eficaces en tareas de visión por computadora. Algunas aplicaciones comunes incluyen el reconocimiento de objetos en imágenes, la clasificación de imágenes y la segmentación semántica. Las CNNs han revolucionado campos como la medicina, los vehículos autónomos y el reconocimiento facial.

Red Neuronal Simple (Fully Connected)
Una red neuronal simple no tiene ningún conocimiento de que la imagen es una matriz bidimensional. Lo primero que hace es aplanarla en un vector de 784 valores (28×28). A partir de ahí, cada uno de esos píxeles se conecta con todas las neuronas de la capa siguiente.

Esto significa que la red no sabe qué píxeles están cerca de otros ni puede identificar bordes, esquinas o formas. Todo lo que aprende está basado en correlaciones globales entre números. Es como aprender a reconocer un gato mirando una lista de números de brillo, sin saber cómo se distribuyen en la imagen.

Red Convolucional (CNN)
Una CNN, en cambio, respeta la estructura espacial de la imagen. Utiliza capas convolucionales que aplican pequeños filtros (kernels) que recorren la imagen buscando patrones locales, como bordes, curvas o zonas oscuras claras.

A medida que la imagen avanza por las capas, estos filtros combinan patrones simples para formar patrones más complejos, permitiendo que la red entienda partes clave de la imagen: la forma del “3”, la curva del “5” o el ángulo cerrado de un “4”.

En este laboratorio vamos a entrenar una CNN y también la pondremos a prueba utilizando FGSM

https://github.com/redteamailabs/ComputerVision/blob/main/CNN_FGSM.ipynb

Como siempre, empezamos importando las librerías necesarias:

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D
from tensorflow.keras.optimizers import Adam

Importa las librerías necesarias:

  • tensorflow y keras para redes neuronales,
  • matplotlib para visualizar imágenes,
  • numpy para trabajar con arrays.

Después cargamos y preprocesamos los datos:

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train / 255.0
x_test = x_test / 255.0
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)

  • Descarga el dataset MNIST.
  • Normaliza los valores a rango [0,1].
  • Cambia la forma de (28, 28) a (28, 28, 1) para que el modelo lo interprete como imagen en escala de grises.

Definimos el modelo CNN:

def crear_modelo():
model = Sequential([
Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
MaxPooling2D((2, 2)),
Conv2D(64, (3, 3), activation='relu'),
MaxPooling2D((2, 2)),
Flatten(),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
model.compile(optimizer=Adam(),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model

  • Red convolucional simple con 2 capas Conv2D, max-pooling, y capas densas.
  • Compila el modelo con Adam y cross entropy para clasificación multiclase.

Entrenamos el modelo:

model = crear_modelo()
model.fit(x_train, y_train, epochs=5, batch_size=128, validation_split=0.1, verbose=1)

  • Se entrena el modelo durante 5 epochs con batches de 128 imágenes.
  • Se guarda un 10% de los datos para validación.

Generamos la imagen adversaria con FGSM:

def crear_ejemplo_adversario_fgsm(modelo, imagen, etiqueta, epsilon=0.1):

Esta función genera una imagen adversaria a partir de una imagen real.

  • Convierte la imagen en un tensor y activa el cálculo de gradientes con GradientTape.
  • Calcula la pérdida (loss) entre la predicción y la etiqueta real.
  • Calcula el gradiente del loss respecto a los píxeles.
  • Obtiene el signo del gradiente y lo multiplica por ε.
  • Crea la imagen adversaria sumando la perturbación.
  • Usa clip_by_value() para mantener los valores entre 0 y 1.

Devuelve una imagen con la misma forma original pero modificada para engañar al modelo.

Mostramos los resultados:

def visualizar_ejemplos(modelo, indice, epsilon):

  • Selecciona una imagen del conjunto de prueba.
  • Genera la imagen adversaria con crear_ejemplo_adversario_fgsm().
  • Hace predicciones con la imagen original y la modificada.
  • Muestra tres imágenes:
    1. Imagen original
    2. Imagen adversaria
    3. Perturbación aplicada (adversaria - original)

Esto te permite ver si el modelo se equivoca con la imagen modificada y qué perturbación se ha introducido.

Hacemos pruebas con varios ejemplos:

for idx in [10, 42, 56]:
for eps in [0.05, 0.1, 0.2]:
visualizar_ejemplos(model, idx, eps)

Genera y muestra resultados para varias imágenes y diferentes valores de ε.

Evaluamos cómo de robusto es el modelo:

def evaluar_bajo_ataque(modelo, epsilon=0.1, num_ejemplos=1000):

  • Elige aleatoriamente num_ejemplos del conjunto de prueba.
  • Genera versiones adversarias de todos ellos.
  • Evalúa el modelo con imágenes originales y adversarias.
  • Imprime la precisión original vs. la adversaria y la diferencia.

Y comprobamos que la precisión del modelo disminuye al aumentar la intensidad del ataque (ε).

for eps in [0.01, 0.05, 0.1, 0.2, 0.3]:
evaluar_bajo_ataque(model, epsilon=eps)

Aunque ninguno de los dos modelos es completamente inmune al ataque FGSM, la CNN muestra mayor robustez por defecto. ¿Por qué?

  • Porque aprende patrones espaciales locales, no solo asociaciones entre píxeles aislados.
  • Porque la arquitectura convolucional introduce cierta invariancia a pequeñas perturbaciones, especialmente si no afectan a las regiones clave de la imagen.

Sin embargo, ambas redes pueden ser engañadas por perturbaciones bien diseñadas si no se aplican defensas.