O autor selecionou a Dev Color para receber uma doação como parte do programa Write for DOnations.
Será que uma rede neural para classificação de animais pode ser enganada? Enganar um classificador de animais pode gerar poucas consequências, mas e se nosso autenticador facial pudesse ser enganado? Ou o software do nosso protótipo de carro autônomo? Felizmente,existem legiões de engenheiros e pesquisas entre um modelo visual computacional protótipo e modelos de qualidade de produção em nossos dispositivos móveis ou carros. Ainda assim, esses riscos têm implicações significativas e é importante que sejam considerados pelos profissionais de machine learning.
Neste tutorial, você irá tentar “iludir” ou enganar um classificador de animais. Ao longo do tutorial, você irá usar o OpenCV
, uma biblioteca de visão computacional e o PyTorch
, uma biblioteca de deep learning. Os seguintes tópicos serão abordados no campo associado do adversarial machine learning (machine learning contraditório):
Ao final do tutorial, você terá uma ferramenta para enganar redes neurais e um entendimento sobre como se defender contra os truques.
Para concluir este tutorial, você precisará do seguinte:
Vamos criar um espaço de trabalho para este projeto e instalar as dependências que você irá precisar. Você irá chamar seu espaço de trabalho AdversarialML
:
- mkdir ~/AdversarialML
Navegue até o diretório AdversarialML
:
- cd ~/AdversarialML
Crie um diretório para manter todos os seus recursos:
- mkdir ~/AdversarialML/assets
A seguir, crie um novo ambiente virtual para o projeto:
- python3 -m venv adversarialml
Ative seu ambiente:
- source adversarialml/bin/activate
Em seguida, instale o PyTorch, um framework de deep learning para Python que você usará neste tutorial.
No macOS, instale o Pytorch com o seguinte comando:
- python -m pip install torch==1.2.0 torchvision==0.4.0
No Linux e Windows, utilize os seguintes comandos para uma compilação CPU-only:
- pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
- pip install torchvision
Agora, instale binários pré-empacotados para o OpenCV
e numpy
, que são bibliotecas para visão computacional e álgebra linear, respectivamente. O OpenCV
oferece utilitários como rotações de imagem, e o numpy
oferece utilitários de álgebra linear, como inversão de matriz:
- python -m pip install opencv-python==3.4.3.18 numpy==1.14.5
Em distribuições Linux, você precisará instalar a libSM.so
:
- sudo apt-get install libsm6 libxext6 libxrender-dev
Com as dependências instaladas, vamos executar um classificador de animais chamado ResNet18, que descrevemos a seguir.
A biblioteca torchvision
, que é a biblioteca oficial de visão computacional para o PyTorch, contém versões pré-treinadas de redes neurais de visão computacional comumente usadas. Essas redes neurais são todas treinadas no ImageNet 2012, um conjunto de dados que consiste em 1,2 milhões de imagens de treinamento com 1000 classes. Essas classes incluem veículos, lugares e, acima de tudo, animais. Neste passo, você irá executar uma dessas redes neurais pré-treinadas chamada ResNet18. Chamaremos a rede neural ResNet18 treinada no ImageNet de “classificador de animais”.
O que é o ResNet18? O ResNet18 é a menor rede neural em uma família de redes neurais chamada redes neurais residuais, desenvolvida pela MSR (He et al.). Em resumo, ele descobriu que uma rede neural (denotada como uma função f
, com entrada x
, e saída f(x
)) teria melhor desempenho com uma “conexão residual” x + f(x)
. Essa conexão residual é usada prolificamente em redes neurais no estado da arte, mesmo hoje. Por exemplo, FBNetV2, FBNetV3.
Baixe esta imagem de um cachorro com o seguinte comando:
- wget -O assets/dog.jpg https://assets.digitalocean.com/articles/trick_neural_network/step2a.png
Então, baixe um arquivo JSON para converter o resultado da rede neural em um nome de classe humanamente legível:
- wget -O assets/imagenet_idx_to_label.json https://raw.githubusercontent.com/do-community/tricking-neural-networks/master/utils/imagenet_idx_to_label.json
Em seguida, crie um script para executar seu modelo pré-treinado na imagem do cão. Crie um novo arquivo chamado step_2_pretrained.py
:
- nano step_2_pretrained.py
Primeiro, adicione o código padrão Python importando os pacotes necessários e declarando uma função main
(principal):
from PIL import Image
import json
import torchvision.models as models
import torchvision.transforms as transforms
import torch
import sys
def main():
pass
if __name__ == '__main__':
main()
Em seguida, carregue o mapeamento a partir do resultado da rede neural para nomes de classe humanamente legíveis. Adicione isto diretamente após suas declarações de importação e antes de sua função main
:
. . .
def get_idx_to_label():
with open("assets/imagenet_idx_to_label.json") as f:
return json.load(f)
. . .
Crie uma função de transformação de imagem que irá garantir que sua imagem de entrada tenha as dimensões corretas e que seja normalizada corretamente. Adicione a seguinte função diretamente após a última:
. . .
def get_image_transform():
transform = transforms.Compose([
transforms.Resize(224),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
return transform
. . .
Em get_image_transform
, você define um número de transformações diferentes para aplicar às imagens que são passadas para sua rede neural:
transforms.Resize(224)
: redimensiona o lado menor da imagem para 224. Por exemplo, se a imagem tiver 448 x 672, esta operação reduziria a resolução dela para 224 x 336.transforms.CenterCrop(224)
: faz um recorte do centro da imagem, de tamanho 224 x 224.transforms.ToTensor()
: Converte a imagem em um tensor do PyTorch. Todos os modelos PyTorch exigem os tensores do PyTorch como entrada.transforms.Normalize(mean=..., std=...)
: Normaliza sua entrada primeiro subtraindo a média, então dividindo pelo desvio padrão. Isso é descrito mais precisamente na documentação do torchvision
.Adicione um utilitário para prever a classe animal, dada a imagem. Este método usa ambos os utilitários anteriores para realizar a classificação de animais:
. . .
def predict(image):
model = models.resnet18(pretrained=True)
model.eval()
out = model(image)
_, pred = torch.max(out, 1)
idx_to_label = get_idx_to_label()
cls = idx_to_label[str(int(pred))]
return cls
. . .
Aqui a função de predict
(prever) classifica a imagem fornecida usando uma rede neural pré-treinada:
models.resnet18(pretrained=True):
Carrega uma rede neural pré-treinada chamada ResNet18.model.eval()
: modifica o modelo em vigor para ser executar no modo ‘avaliação’. O único outro modo é o modo ‘treinamento’, mas o modo de treinamento não é necessário, pois você não está treinando o modelo (ou seja, atualizando os parâmetros do modelo) neste tutorial.out = model(image)
: Executa a rede neural na imagem transformada fornecida._, pred = torch.max(out, 1)
: A rede neural gera uma probabilidade para cada classe possível. Esse passo computa o índice da classe com a maior probabilidade. Por exemplo, se out = [0.4, 0.1, 0.2]
, então pred = 0
.idx_to_label = get_idx_to_label()
: Obtém um mapeamento do índice de classes para nomes de classe humanamente legíveis. Por exemplo, o mapeamento poderia ser {0: cat, 1: dog, 2: fish}
.cls = idx_to_label[str(int(pred))]
: Converte o índice de classe previsto em um nome de classe. Os exemplos fornecidos nos dois últimos tópicos iriam gerar cls = idx_to_label[0] = 'cat'
.Em seguida, adicione um utilitário para carregar imagens após a última função:
. . .
def load_image():
assert len(sys.argv) > 1, 'Need to pass path to image'
image = Image.open(sys.argv[1])
transform = get_image_transform()
image = transform(image)[None]
return image
. . .
Isso irá carregar uma imagem a partir do caminho fornecido no primeiro argumento para o script. transform(image)[None]
aplica a sequência de transformações de imagem definida nas linhas anteriores.
Por fim, preencha sua função main
da seguinte forma, para carregar sua imagem e classificar o animal na imagem:
def main():
x = load_image()
print(f'Prediction: {predict(x)}')
Verifique se seu arquivo corresponde ao nosso script do final do passo 2 em step_2_pretrained.py
no GitHub. Salve e saia do seu script. Em seguida, execute o classificador de animais:
- python step_2_pretrained.py assets/dog.jpg
Isso irá produzir o seguinte resultado, mostrando que seu classificador de animais funciona como esperado:
OutputPrediction: Pembroke, Pembroke Welsh corgi
Isso conclui que há uma inferência em execução com seu modelo pré-treinado. Em seguida, você verá um exemplo contraditório em ação enganando uma rede neural com diferenças imperceptíveis na imagem.
Agora, você irá sintetizar um exemplo contraditório e testar a rede neural nesse exemplo. Para este tutorial, você irá compilar exemplos contraditórios da forma x + r
, onde x
é a imagem original e r
é alguma “perturbação”. Eventualmente, você irá criar a perturbação r
por conta própria, mas, neste passo, irá baixar uma que já criamos para você. Comece baixando a perturbação r
:
- wget -O assets/adversarial_r.npy https://github.com/do-community/tricking-neural-networks/blob/master/outputs/adversarial_r.npy?raw=true
Agora, crie uma composição da figura com a perturbação. Crie um novo arquivo chamado step_3_adversarial.py
:
- nano step_3_adversarial.py
Neste arquivo, você irá realizar o seguinte processo de três etapas, para produzir um exemplo contraditório:
r
No final do passo 3, você terá uma imagem contraditória. Primeiro, importe os pacotes necessários e declare uma função main
:
from PIL import Image
import torchvision.transforms as transforms
import torch
import numpy as np
import os
import sys
from step_2_pretrained import get_idx_to_label, get_image_transform, predict, load_image
def main():
pass
if __name__ == '__main__':
main()
Em seguida, crie uma “transformação de imagem” que inverte a transformação de imagem anterior. Coloque isto após suas importações, antes da função main
:
. . .
def get_inverse_transform():
return transforms.Normalize(
mean=[-0.485/0.229, -0.456/0.224, -0.406/0.255], # INVERSE normalize images, according to https://pytorch.org/docs/stable/torchvision/models.html
std=[1/0.229, 1/0.224, 1/0.255])
. . .
Assim como antes, a operação transforms.Normalize
subtrai a média e divide o valor pelo desvio padrão (ou seja, para a imagem original x
, y = transforms.Normalize(mean=u, std=o) = (x - u) / o
). Você aplica um pouco de álgebra e define uma nova operação que reverte essa função normalizadora (transforms.Normalize(mean=-u/o, std=1/o) = (y - -u/o) / 1/o = (y + u/o) o = yo + u = x
).
Como parte da transformação inversa, adicione um método que transforma um tensor do PyTorch de volta em uma imagem PIL. Adicione isto após a última função:
. . .
def tensor_to_image(tensor):
x = tensor.data.numpy().transpose(1, 2, 0) * 255.
x = np.clip(x, 0, 255)
return Image.fromarray(x.astype(np.uint8))
. . .
tensor.data.numpy
() converte o tensor do PyTorch em uma matriz do NumPy. .transpose(1, 2, 0)
reorganiza (channels, width, height)
em (height, width, channels)
. Essa matriz do NumPy está aproximadamente no intervalo (0, 1)
. Por fim, multiplique isso por 255 para garantir que a imagem esteja agora na faixa (0, 255)
.np.clip
garante que todos os valores na imagem estejam entre (0, 255)
.x.astype(np.uint8)
garante que todos os valores de imagem sejam inteiros. Por fim, o Image.fromarray(...)
cria um objeto de imagem PIL a partir da matriz do NumPy.Em seguida, use esses utilitários para criar o exemplo contraditório da seguinte forma:
. . .
def get_adversarial_example(x, r):
y = x + r
y = get_inverse_transform()(y[0])
image = tensor_to_image(y)
return image
. . .
Essa função gera o exemplo contraditório como descrito no início da seção:
y = x + r
. Pega sua perturbação r
e a adiciona à imagem original x
.get_inverse_transform
: Obtém e aplica a transformação reversa de imagem que você definiu várias linhas atrás.tensor_to_image
: Por fim, converte o tensor do PyTorch de volta para um objeto de imagem.Em último lugar, modifique sua função main
para carregar a imagem, carregar a perturbação contraditória r
, aplicar a perturbação, salvar o exemplo contraditório no disco e executar uma previsão no exemplo contraditório:
def main():
x = load_image()
r = torch.Tensor(np.load('assets/adversarial_r.npy'))
# save perturbed image
os.makedirs('outputs', exist_ok=True)
adversarial = get_adversarial_example(x, r)
adversarial.save('outputs/adversarial.png')
# check prediction is new class
print(f'Old prediction: {predict(x)}')
print(f'New prediction: {predict(x + r)}')
Seu arquivo finalizado deve corresponder ao step_3_adversarial.py
no GitHub. Salve o arquivo, saia do editor e inicie seu script com:
- python step_3_adversarial.py assets/dog.jpg
Você verá este resultado:
OutputOld prediction: Pembroke, Pembroke Welsh corgi
New prediction: goldfish, Carassius auratus
Agora, você criou um exemplo contraditório: enganou a rede neural para pensar que um corgi é um peixe dourado. No próximo passo, você irá efetivamente criar a perturbação r
que você usou aqui.
Para uma cartilha sobre classificação, consulte “How to Build an Emotion-Based Dog Filter”.
Dando um passo para trás, lembre-se que seu modelo de classificação gera uma probabilidade para cada classe. Durante a inferência, o modelo prevê a classe que tenha a maior probabilidade. Durante o treinamento, você atualiza os parâmetros t
do modelo para maximizar a probabilidade da classe correta y
, dado os seus dados x
.
argmax_y P(y|x,t)
No entanto, para gerar exemplos contraditórios, agora seu objetivo é outro. Em vez de encontrar uma classe, seu objetivo agora é encontrar uma nova imagem, x
. Escolha qualquer classe que não seja a correta. Chamemos essa nova classe de w
. Seu novo objetivo é maximizar a probabilidade da classe errada.
argmax_x P(w|x)
Observe que os pesos t
da rede neural foram deixados de fora da expressão acima. Isso ocorre porque agora assumem o papel da contradição: outra pessoa treinou e implantou um modelo. Você só pode criar entradas contraditórias e não é permitido modificar o modelo implantado. Para gerar o exemplo contraditório x
, é possível executar um “treinamento”, exceto que, em vez de atualizar os pesos da rede neural, você atualiza a imagem de entrada com o novo objetivo.
Como um lembrete, para este tutorial, você supõe que o exemplo contraditório é uma transformação afim de x
. Em outras palavras, seu exemplo contraditório assume a forma x + r
para alguns r
. No próximo passo, você irá escrever um script para gerar este r
.
Neste passo, você irá aprender uma perturbação r
, para que seu corgi seja classificado erroneamente como um peixe dourado. Crie um novo arquivo chamado step_5_perturb.py
:
- nano step_5_perturb.py
Importe os pacotes necessários e declare uma função main
:
from torch.autograd import Variable
import torchvision.models as models
import torch.nn as nn
import torch.optim as optim
import numpy as np
import torch
import os
from step_2_pretrained import get_idx_to_label, get_image_transform, predict, load_image
from step_3_adversarial import get_adversarial_example
def main():
pass
if __name__ == '__main__':
main()
Logo após suas importações e antes da função main
, defina duas constantes:
. . .
TARGET_LABEL = 1
EPSILON = 10 / 255.
. . .
A primeira constante, TARGET_LABEL
, é a classe que será utilizada para classificar erroneamente o corgi. Neste caso, o índice 1
corresponde a “goldfish”(peixe dourado). A segunda constante do EPSILON
é a quantidade máxima de perturbação permitida para cada valor da imagem. Este limite é introduzido para que a imagem seja alterada de maneira imperceptível.
Após suas duas constantes, adicione uma função auxiliar para definir uma rede neural e o parâmetro de perturbação r
:
. . .
def get_model():
net = models.resnet18(pretrained=True).eval()
r = nn.Parameter(data=torch.zeros(1, 3, 224, 224), requires_grad=True)
return net, r
. . .
O model.resnet18(pré-trained=True)
carrega uma rede neural pré-treinada chamada ResNet18, como antes. Também como antes, você define o modelo para o modo de avaliação usando .eval
.nn.Parameter(...)
define uma nova perturbação r
, com o tamanho da imagem de entrada. A imagem de entrada também tem o tamanho (1, 3, 224, 224)
. O argumento de palavra-chave requires_grad=True
garante que você possa atualizar essa perturbação r
em linhas posteriores neste arquivo.Em seguida, comece a modificar sua função main
. Comece carregando o modelo net
, carregando as entradas x
e definindo a etiqueta label
:
. . .
def main():
print(f'Target class: {get_idx_to_label()[str(TARGET_LABEL)]}')
net, r = get_model()
x = load_image()
labels = Variable(torch.Tensor([TARGET_LABEL])).long()
. . .
Em seguida, defina tanto o critério quanto o otimizador em sua função main
. O primeiro diz ao PyTorch qual é o objetivo – ou seja, qual perda deve ser minimizada. O último diz ao PyTorch como treinar seu parâmetro r
:
. . .
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD([r], lr=0.1, momentum=0.1)
. . .
Diretamente a seguir, adicione o loop de treinamento principal para seu parâmetro r
:
. . .
for i in range(30):
r.data.clamp_(-EPSILON, EPSILON)
optimizer.zero_grad()
outputs = net(x + r)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
_, pred = torch.max(outputs, 1)
if i % 5 == 0:
print(f'Loss: {loss.item():.2f} / Class: {get_idx_to_label()[str(int(pred))]}')
. . .
Em cada iteração deste loop de treinamento, você:
r.data.clamp_(...)
: certifica-se que o parâmetro r
é pequeno, dentro do EPSILON
de 0.optimizer.zero_grad()
: limpa quaisquer gradientes que você computou na iteração anterior.model(x + r)
: executa uma inferência na imagem modificada x + r
.loss
(perda).loss.backward
.optimizer.step
.pred
.print(...)
.Em seguida, salve a perturbação final r
:
def main():
. . .
for i in range(30):
. . .
. . .
np.save('outputs/adversarial_r.npy', r.data.numpy())
Logo a seguir, ainda na função main
, salve a imagem perturbada:
. . .
os.makedirs('outputs', exist_ok=True)
adversarial = get_adversarial_example(x, r)
Por fim, execute uma previsão tanto na imagem original quanto no exemplo contraditório:
print(f'Old prediction: {predict(x)}')
print(f'New prediction: {predict(x + r)}')
Verifique novamente se seu script corresponde ao step_5_perturb.py
no GitHub. Salve, saia e execute o script:
- python step_5_perturb.py assets/dog.jpg
Seu script irá gerar o seguinte resultado.
OutputTarget class: goldfish, Carassius auratus
Loss: 17.03 / Class: Pembroke, Pembroke Welsh corgi
Loss: 8.19 / Class: Pembroke, Pembroke Welsh corgi
Loss: 5.56 / Class: Pembroke, Pembroke Welsh corgi
Loss: 3.53 / Class: Pembroke, Pembroke Welsh corgi
Loss: 1.99 / Class: Pembroke, Pembroke Welsh corgi
Loss: 1.00 / Class: goldfish, Carassius auratus
Old prediction: Pembroke, Pembroke Welsh corgi
New prediction: goldfish, Carassius auratus
As duas últimas linhas indicam que agora você completou a construção de um exemplo contraditório do zero. Sua rede neural agora classifica uma imagem perfeitamente razoável de um corgi como um peixe dourado.
Agora, você mostrou que as redes neurais podem ser enganadas com facilidade—mais que isso, a falta de robustez nos exemplos contraditórios tem consequências significativas. Uma pergunta que surge naturalmente é: Como se combate exemplos contraditórios? Muitas pesquisas foram realizadas por várias organizações, incluindo a OpenAI. Na próxima seção, você irá executar uma defesa para impedir este exemplo contraditório.
Neste passo, você irá implementar uma defesa contra exemplos contraditórios. A ideia é a seguinte: agora você é o proprietário do classificador de animais que está sendo implantado para a produção. Você não sabe quais exemplos contraditórios podem ser gerados, mas pode modificar a imagem ou o modelo para proteger-se contra os ataques.
Antes de se defender, você deve ver por si mesmo como a manipulação de imagem é imperceptível. Abra as duas imagens a seguir:
assets/dog.jpg
outputs/adversarial.png
Aqui estão as duas lado a lado. Sua imagem original terá uma taxa de proporção diferente. Consegue dizer qual delas é o exemplo contraditório?
Observe que a nova imagem parece ser idêntica à original. Na realidade, a imagem da esquerda é sua imagem contraditória. Para ter certeza disso, faça o download da imagem e execute seu script de avaliação:
- wget -O assets/adversarial.png https://github.com/alvinwan/fooling-neural-network/blob/master/outputs/adversarial.png?raw=true
- python step_2_pretrained.py assets/adversarial.png
Isso irá mostrar como resultado a classe de peixe dourado, para provar sua natureza contraditória:
OutputPrediction: goldfish, Carassius auratus
Você irá por em prática uma defesa bastante ingênua, mas eficaz: comprima a imagem gravando-a em um formato JPEG com perdas. Abra o prompt interativo do Python:
- python
Em seguida, carregue a imagem contraditória como PNG e a salve de volta como JPEG.
- from PIL import Image
- image = Image.open('assets/adversarial.png')
- image.save('outputs/adversarial.jpg')
Digite CTRL + D
para sair do prompt interativo do Python. Depois disso, realize uma inferência com seu modelo no exemplo contraditório comprimido:
- python step_2_pretrained.py outputs/adversarial.jpg
Isso gera uma classe corgi, mostrando a eficácia de sua defesa ingênua.
OutputPrediction: Pembroke, Pembroke Welsh corgi
Agora, você completou sua primeira defesa contraditória. Observe que essa defesa não requer saber como o exemplo contraditório foi gerado. Isso é o que torna uma defesa eficaz. Há ainda muitas outras formas de defesa, muitas das quais envolvem um maior treinamento da rede neural. No entanto, esses procedimentos de treinamento são um tema próprio e estão fora do âmbito deste tutorial. Com isso, isso conclui seu guia sobre o machine learning contraditório.
Para compreender as implicações do seu trabalho neste tutorial, revisite as duas imagens lado a lado – o exemplo original e o contraditório.
Apesar do fato de ambas as imagens serem idênticas ao olho humano, a primeira foi manipulada para enganar seu modelo. As duas imagens exibem claramente um corgi, mas o modelo está totalmente convencido de que o segundo modelo contém um peixe dourado. Isso deve gerar-lhe preocupação. Enquanto finaliza este tutorial, tenha em mente a fragilidade do seu modelo. Apenas aplicando uma simples transformação, você foi capaz de enganá-lo. Esses são perigos reais e plausíveis que escapam aos olhos, até mesmo de uma pesquisa de ponta. Pesquisas que vão além da segurança em machine learning são igualmente suscetíveis a essas falhas e, como um profissional, cabe a você aplicar o machine learning com segurança. Para mais leituras, confira os seguintes links:
Para mais conteúdo e tutoriais de machine learning, visite nossa página do tópico de Machine Learning.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!