Exemplo de adversário usando FGSM

Veja no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial cria um exemplo de adversário usando o ataque Fast Gradient Signed Method (FGSM) conforme descrito em Explicando e aproveitando exemplos de adversários de Goodfellow et al . Este foi um dos primeiros e mais populares ataques para enganar uma rede neural.

O que é um exemplo adversário?

Exemplos adversários são entradas especializadas criadas com o objetivo de confundir uma rede neural, resultando na classificação incorreta de uma determinada entrada. Essas notórias entradas são indistinguíveis ao olho humano, mas fazem com que a rede não consiga identificar o conteúdo da imagem. Existem vários tipos de ataques, no entanto, aqui o foco está no ataque do método de sinal de gradiente rápido, que é um ataque de caixa branca cujo objetivo é garantir erros de classificação. Um ataque de caixa branca é onde o invasor tem acesso completo ao modelo que está sendo atacado. Um dos exemplos mais famosos de uma imagem adversária mostrado abaixo é retirado do artigo mencionado acima.

Exemplo Adversário

Aqui, começando com a imagem de um panda, o invasor adiciona pequenas perturbações (distorções) à imagem original, o que resulta no modelo rotulando essa imagem como um gibão, com alta confiança. O processo de adição dessas perturbações é explicado abaixo.

Método de sinal de gradiente rápido

O método de sinal de gradiente rápido funciona usando os gradientes da rede neural para criar um exemplo contraditório. Para uma imagem de entrada, o método usa os gradientes da perda em relação à imagem de entrada para criar uma nova imagem que maximiza a perda. Essa nova imagem é chamada de imagem adversária. Isso pode ser resumido usando a seguinte expressão:

\[adv\_x = x + \epsilon*\text{sign}(\nabla_xJ(\theta, x, y))\]

Onde

  • adv_x : imagem do adversário.
  • x : Imagem de entrada original.
  • y : Etiqueta de entrada original.
  • \(\epsilon\) : Multiplicador para garantir que as perturbações sejam pequenas.
  • \(\theta\) : Parâmetros do modelo.
  • \(J\) : Perda.

Uma propriedade intrigante aqui é o fato de que os gradientes são obtidos em relação à imagem de entrada. Isso é feito porque o objetivo é criar uma imagem que maximize a perda. Um método para fazer isso é descobrir o quanto cada pixel na imagem contribui para o valor da perda e adicionar uma perturbação de acordo. Isso funciona muito rápido porque é fácil descobrir como cada pixel de entrada contribui para a perda usando a regra da cadeia e encontrando os gradientes necessários. Assim, os gradientes são obtidos em relação à imagem. Além disso, como o modelo não está mais sendo treinado (assim, o gradiente não é tomado em relação às variáveis ​​treináveis, ou seja, os parâmetros do modelo), e assim os parâmetros do modelo permanecem constantes. O único objetivo é enganar um modelo já treinado.

Então vamos tentar enganar um modelo pré-treinado. Neste tutorial, o modelo é o modelo MobileNetV2 , pré-treinado no ImageNet .

import tensorflow as tf
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['figure.figsize'] = (8, 8)
mpl.rcParams['axes.grid'] = False

Vamos carregar o modelo MobileNetV2 pré-treinado e os nomes das classes ImageNet.

pretrained_model = tf.keras.applications.MobileNetV2(include_top=True,
                                                     weights='imagenet')
pretrained_model.trainable = False

# ImageNet labels
decode_predictions = tf.keras.applications.mobilenet_v2.decode_predictions
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5
14540800/14536120 [==============================] - 0s 0us/step
14548992/14536120 [==============================] - 0s 0us/step
# Helper function to preprocess the image so that it can be inputted in MobileNetV2
def preprocess(image):
  image = tf.cast(image, tf.float32)
  image = tf.image.resize(image, (224, 224))
  image = tf.keras.applications.mobilenet_v2.preprocess_input(image)
  image = image[None, ...]
  return image

# Helper function to extract labels from probability vector
def get_imagenet_label(probs):
  return decode_predictions(probs, top=1)[0][0]

Imagem original

Vamos usar uma imagem de amostra de um Labrador Retriever por Mirko CC-BY-SA 3.0 do Wikimedia Common e criar exemplos adversários a partir dela. O primeiro passo é pré-processá-lo para que possa ser alimentado como entrada para o modelo MobileNetV2.

image_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg')
image_raw = tf.io.read_file(image_path)
image = tf.image.decode_image(image_raw)

image = preprocess(image)
image_probs = pretrained_model.predict(image)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg
90112/83281 [================================] - 0s 0us/step
98304/83281 [===================================] - 0s 0us/step

Vamos dar uma olhada na imagem.

plt.figure()
plt.imshow(image[0] * 0.5 + 0.5)  # To change [-1, 1] to [0,1]
_, image_class, class_confidence = get_imagenet_label(image_probs)
plt.title('{} : {:.2f}% Confidence'.format(image_class, class_confidence*100))
plt.show()
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json
40960/35363 [==================================] - 0s 0us/step
49152/35363 [=========================================] - 0s 0us/step

png

Crie a imagem do adversário

Implementando o método de sinal de gradiente rápido

O primeiro passo é criar perturbações que serão usadas para distorcer a imagem original resultando em uma imagem adversária. Como mencionado, para esta tarefa, os gradientes são obtidos em relação à imagem.

loss_object = tf.keras.losses.CategoricalCrossentropy()

def create_adversarial_pattern(input_image, input_label):
  with tf.GradientTape() as tape:
    tape.watch(input_image)
    prediction = pretrained_model(input_image)
    loss = loss_object(input_label, prediction)

  # Get the gradients of the loss w.r.t to the input image.
  gradient = tape.gradient(loss, input_image)
  # Get the sign of the gradients to create the perturbation
  signed_grad = tf.sign(gradient)
  return signed_grad

As perturbações resultantes também podem ser visualizadas.

# Get the input label of the image.
labrador_retriever_index = 208
label = tf.one_hot(labrador_retriever_index, image_probs.shape[-1])
label = tf.reshape(label, (1, image_probs.shape[-1]))

perturbations = create_adversarial_pattern(image, label)
plt.imshow(perturbations[0] * 0.5 + 0.5);  # To change [-1, 1] to [0,1]

png

Vamos tentar isso para diferentes valores de epsilon e observar a imagem resultante. Você notará que à medida que o valor de epsilon aumenta, fica mais fácil enganar a rede. No entanto, isso vem como um trade-off que resulta em perturbações tornando-se mais identificáveis.

def display_images(image, description):
  _, label, confidence = get_imagenet_label(pretrained_model.predict(image))
  plt.figure()
  plt.imshow(image[0]*0.5+0.5)
  plt.title('{} \n {} : {:.2f}% Confidence'.format(description,
                                                   label, confidence*100))
  plt.show()
epsilons = [0, 0.01, 0.1, 0.15]
descriptions = [('Epsilon = {:0.3f}'.format(eps) if eps else 'Input')
                for eps in epsilons]

for i, eps in enumerate(epsilons):
  adv_x = image + eps*perturbations
  adv_x = tf.clip_by_value(adv_x, -1, 1)
  display_images(adv_x, descriptions[i])

png

png

png

png

Próximos passos

Agora que você conhece os ataques adversários, experimente isso em diferentes conjuntos de dados e diferentes arquiteturas. Você também pode criar e treinar seu próprio modelo e tentar enganá-lo usando o mesmo método. Você também pode tentar ver como a confiança nas previsões varia conforme você altera o epsilon.

Embora poderoso, o ataque mostrado neste tutorial foi apenas o começo da pesquisa sobre ataques adversários, e vários artigos criaram ataques mais poderosos desde então. Além dos ataques adversários, a pesquisa também levou à criação de defesas, que visam criar modelos robustos de aprendizado de máquina. Você pode revisar este documento de pesquisa para obter uma lista abrangente de ataques e defesas de adversários.

Para muitas outras implementações de ataques e defesas de adversários, você pode querer ver a biblioteca de exemplos de adversários CleverHans .