私の備忘録がないわね...私の...

画像処理とかプログラミングのお話。

FGSMでロジスティック回帰を騙す

Adversarial Examplesを少し調べたことのある人なら親の顔より見たことがあるであろうこの画像

f:id:kamakuraviel:20200503162559p:plain
親の顔より見た画像

このAdversarial Examples生成手法はFGSM(Fast Gradient Sign Method)といい、最も基本的なAttackの一つです。

細かい説明は他記事に譲るとして今回はこの手法とMNISTを用いてロジスティック回帰(Logistic regression)を騙してみます。

元論文の方でもロジスティック回帰に対してFGSMを行なっているのですが、3と7でしかやっていません。今回は全クラスでやってます。つまり多クラスロジスティック回帰です。

実装

以下のコードは全てpytorchです。

importとデータセットの読み込み

シード値も一応固定しておきます。

import os
import torch
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt

torch.manual_seed(0)

mnist_data = torchvision.datasets.MNIST(root="/home_dir", transform=transforms.ToTensor())

訓練

batch_size = 100
data_loader = DataLoader(mnist_data, batch_size=100, shuffle=True)

class LR(torch.nn.Module):
    
    def __init__(self):
        super().__init__()
        self.l1 = torch.nn.Linear(28*28, 10)

    def forward(self, x):
        h1 = self.l1(x)
        # PyTorchではロス関数であるF.CrossEntropyの中でSoftmax関数を適用しているのでこの層はいらないのですが、確率を見たいので入れておきます。
        h2 = F.softmax(h1, dim=1)
        return h2
    
model = LR().cuda()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

epochs = 10
for epoch in range(epochs):
    for inputs, labels in data_loader:
        optimizer.zero_grad()
        inputs = inputs.view(batch_size, 28*28).cuda()
        labels = labels.cuda()
        outputs = model(inputs)
        loss = F.cross_entropy(outputs, labels)
        loss.backward()
        optimizer.step()
    print(epoch)

 

この時点での正解率

FGSMなしでの正解率を見てみます。  

def test_accuracy():
    batch_size = 1
    data_loader = DataLoader(mnist_data, batch_size=batch_size, shuffle=False)
    correct = 0
    count = 0
    for inputs, labels in data_loader:
        inputs = inputs.view(batch_size, 28*28).cuda()
        labels = labels.cuda()
        output = model(inputs)
        pred = output.max(1)[1]
        if labels.item() == pred.item():
            correct += 1
        count += 1
    print(correct/count*100)

test_accuracy()

実行してみると80.15%, まずまずです。

FGSM

pytorchのtutorialそのままです。

def fgsm_attack(image, epsilon, data_grad):
    sign_data_grad = data_grad.sign()
    perturbed_image = image + epsilon*sign_data_grad
    perturbed_image = torch.clamp(perturbed_image, 0, 1)
    return perturbed_image

実際に動かしてみる

FGSMとその画像表示

def ae_test_with_img(images, labels, epsilon):
    fig = plt.figure(figsize=(25, 10))
    for i in range(5):
        # predict
        data = images[i].view(1, 28*28).cuda()
        data.requires_grad = True # for fgsm
        output = model(data)
        predict_n = output.max(1)[1]
        # fgsm
        label = labels[i].view(1).cuda()
        loss = F.cross_entropy(output, label)
        model.zero_grad()
        loss.backward()
        perturbed_data = fgsm_attack(data, epsilon, data.grad.data)
        output = model(perturbed_data)
        final_pred = output.max(1)[1]
        # show img
        plt.subplot(2, 5, i+1)
        plt.title("true: {},  pred: {}".format(label.item(), predict_n.item()), fontsize=25)
        plt_data = images[i].view(28, 28).numpy()
        plt.imshow(plt_data, cmap="gray")
        plt.axis("off")
        plt.subplot(2, 5, i+6)
        plt.title("pred: {}".format(final_pred.item()), fontsize=25)
        perturbed_img = perturbed_data.cpu()[0].cpu().detach().numpy().reshape(28, 28)
        plt.imshow(perturbed_img, cmap="gray")
        plt.axis("off")
    fig.suptitle("epsilon: {}".format(epsilon), fontsize=28)
    plt.show()

data_loader = iter(DataLoader(mnist_data, batch_size=5, shuffle=False))
images, labels = next(data_loader)
epsilon_list = [0.03, 0.3, 3]
for epsilon in epsilon_list:
    ae_test_with_img(images, labels, epsilon)

以下が結果です。

f:id:kamakuraviel:20200503184117p:plain
影響なし

f:id:kamakuraviel:20200503184125p:plain
一番いい感じ

f:id:kamakuraviel:20200503184213p:plain
人間もわからない

 \epsilon=0.3が「人間は理解できるけど、機械はできない」一番いい感じです。ちなみに元論文の方では \epsilon=0.25を使っています。(合わせておけよ)

条件付き正解率

FGSMを行う前に正解していた画像の中で、FGSMを行なった後も正解している条件付きの正解率を調べてみます。

def ae_test_accuracy(epsilon):
    batch_size = 1
    data_loader = DataLoader(mnist_data, batch_size=batch_size, shuffle=False)
    correct = 0
    count = 0
    for inputs, labels in data_loader:
        model.zero_grad()
        inputs = inputs.view(batch_size, 28*28).cuda()
        inputs.requires_grad = True
        labels = labels.cuda()
        output = model(inputs)
        pred = output.max(1)[1]
        loss = F.cross_entropy(output, labels)
        loss.backward()
        perturbed_data = fgsm_attack(inputs, epsilon, inputs.grad.data)
        pred_after_ae= model(perturbed_data).max(1)[1]
        if labels.item() == pred.item():
            if pred.item() == pred_after_ae.item():
                correct += 1
            count += 1
    print(correct/count*100)

epsilon_list = [0, 0.03, 0.3, 3]
for epsilon in epsilon_list:
    ae_test_accuracy(epsilon)

以下のような結果になりました。

 \epsilon 条件付きの正解率
0 100%
0.03 92.7%
0.3 0.274%
3 0%

 0.03\to0.3でガクッと落ちていますね。

参考文献