4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AdverTorchで騙されないモデルを作る

Last updated at Posted at 2019-09-08

はじめに

PyTorch Ecosystemの一つであるAdverTorchを使って,Adversarial Examplesに対してRobustな画像分類機を作ってみようと思います.
installに始まり,FGSMを使ったAdversarial Trainingを行ない,学習方法と制度の関係についての実験まで,順を追って解説していこうと思います.
実装した全コードのリンクは以下です.

目次

  • AdverTorchとは
  • Adversarial Examplesとは
  • Install
  • Tutorialのコードをベースとしてモデル構築
  • 実験
  • まとめ

AdverTorchとは

AdverTorchとは,PyTorchベースで作られているwrapperライブラリ,PyTorchのEcosystemの一つで,Adversarial Examplesに特化したライブラリです.
他にもAdversarial Examplesのライブラリだと,他にはfoolboxなどが有名かと思います.

Adversarial Examplesとは

知らない方のために,Adversarial Examplesについて,最初に少しだけ説明しておきます.(知っている方は,読み飛ばしていただいて構いません)

Deep Neural Networks (DNNs)はCVやNLPの様々なタスクで,高い精度を達成してきました.一方で,DNNsは特定のノイズがのった入力に対しては,高確率で誤った出力をしてしまうという脆弱性があることが明らかになってきました.これらの特定のノイズがのった入力がAdversarial Examplesと呼ばれています.このAdversarial Examplesの作成方法をAdversarial Attack Method,様々なAdversarial Examplesに対して頑健なモデルを作る方法をAdversarial Defencesと呼んだりします.

イメージを掴んでもらうために,以下の代表的な画像を使って説明しようと思います.
image
これは,左の「パンダ」の画像に,微小なノイズを加えて,右の画像を作り出しているという意味です.人間の我々からすると,左も右もどちらも「パンダ」の画像に見えます.**しかし,モデルは右の画像を「テナガザル(gibbon)」として分類しています.**これが,「Adversarial Examples」と呼ばれるもので,モデルが誤った出力をしてしまうような入力ということになります.

そこで,気になるのが,ノイズとはなんなんだということだと思います.ここで,右の画像に加わっているノイズというのは,**Loss関数を最大化するような微小な摂動(ノイズのようなもの)**になっています.つまり,モデルが誤分類するように,デザインされた特殊なノイズです.

この特殊なノイズを作り,Adversarial Examplesを作りだすことをAttack,作り出されたAdversarial Examplesに対してモデルが誤分類しないように学習をモデルのアーキテクチャや学習方法等を工夫することをDefenceと呼んだりします.

そして,今回行うAdversarial Trainingというのは,Defence手法の一つでAdversarial Examplesに対して頑健なモデルを作るための学習方法です.仕組みは単純で,単に訓練データにAdversarial Examplesを混ぜるだけです.これによって,通常の画像もノイズが加わった画像も同じように分類することができるという方法です.
学習中に訓練データからのサンプルに対して,ノイズを計算してAdversarial Examplesを作成するという手順があるため,それを行うためのAttack方法を定義しておく必要はあります.

Adversarial Examplesは人間にはわからない形でモデルを騙せることから,顔による本人認証や,スマートスピーカー,自動運転での標識識別などの社会実装の際に問題となるため,解決すべき課題として,近年盛んに研究が行われています.
また,Adversarial Examplesの特性とDNNsの特性は密接に関係していることから,DNNsの解釈という観点からも,多くの研究がなされています.

詳しく知りたい方は,NeurIPS Tutorialとかを読むことをお勧めします.

長々と説明してしまいましたが,ここから本題に入っていこうと思います.

Install

Environment

READMEに以下のように記載されていたため,python3.6を使用し,pytorchのversionは1.0.0 or 0.4にしておきましょう.
可能であれば,仮想環境で行うことをお勧めします.

We developed AdverTorch under Python 3.6 and PyTorch 1.0.0 & 0.4.1.

私は,pipenvを使ってinstallしてきますが,ここにはpipでinstallする場合のコードを記します

Install dependencies

以下の3つのライブラリをinstallしていきます

  • torch==1.0.0
  • torchvision==0.2.2
  • numpy
pip install torch==1.0.0 torchvision==0.2.2 numpy

Install advertorch

READMEに書いてある通りの手順でinstallしていきます.

pip install advertorch

終わり.簡単ですね.

Tutorialのコードをベースとしてモデル構築

advertorchのREADMEでも紹介されているtutorial_train_mnist.pyを実験しやすいように改変して,jupyterで動かしてみようと思います.
完成形のnotebook or codeへのリンクは,本ページの上部に記載しております.

import libraries

まず,必要なライブラリ郡をimportします

import os
import argparse
import random

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

from advertorch.attacks import GradientSignAttack
from advertorch.context import ctx_noparamgrad_and_eval
from advertorch.test_utils import LeNet5
from advertorch_examples.utils import get_mnist_train_loader
from advertorch_examples.utils import get_mnist_test_loader
from advertorch_examples.utils import TRAINED_MODEL_PATH

Define variables

まず,実行の際のオプションとなる変数を定義していきます.

seed = 0
mode = 'adv' # 'cln' or 'half' or 'adv'
train_bs = 50 # train batch size
test_bs = 1000 # test batch size
log_interval = 200

次に,device (cuda or cpu)と,modeに合わせて各変数を定義していきます.

torch.manual_seed(seed)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

if mode == 'cln':
    nb_epoch = 10
    model_filename = 'mnist_lenet5_clntrained.pth'
elif mode == 'half':
    nb_epoch = 50
    model_filename = "mnist_lenet5_halftrained.pth"
elif mode == 'adv':
    nb_epoch = 50
    model_filename = "mnist_lenet5_advtrained.pth"
else:
    raise RuntimeError('mode must be "cls" or "adv"')

dataloaderとmodelの定義
このTutorialではdatasetとしてMNISTを使用して,modelはLeNetを使用します.
実行すると,dataのdownloadが始まって,~/.advertorch/data/data/mnist以下にdatasetが保存されると思います.

train_loader = get_mnist_train_loader(
    batch_size=train_bs, shuffle=True)
test_loader = get_mnist_test_loader(
    batch_size=test_bs, shuffle=False)

model = LeNet5()
model.to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-4)

ここまでは,通常のPyTorchでのコードと大差ないです.
最も大きな違いは,次のattack方法を定義するところです.

Define Attack Method

元のTutorialのcodeでは$L_\infty$ノルムのPGDを使用していますが,今回はFGSMを使用します.
ここでModelとLossとParameterを渡し,学習中にこれらの情報を利用してAdversarial Examplesを計算していく.

adversary = GradientSignAttack(
    model, loss_fn=nn.CrossEntropyLoss(reduction="sum"),
    eps=0.3, clip_min=0.0, clip_max=1.0, targeted=False)

Model training

Attack方法を定義したところで,実際にRobustなModelを学習してみましょう

Note: 以下の水平線で囲まれた領域は,インデントはそのままにして,同じセルに貼り付けてください(一つのfor文の中で動作させなければならないコードです)


Simple epoch iteration

for epoch in range(nb_epoch):
    model.train()

Model training iteration

    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        ori = data
        if mode == 'adv' or (mode == 'half' and random.random() > 0.5):
            # when performing attack, the model needs to be in eval mode
            # also the parameters should be accumulating gradients
            with ctx_noparamgrad_and_eval(model):
                data = adversary.perturb(data, target)

        optimizer.zero_grad()
        output = model(data)
        loss = F.cross_entropy(
            output, target, reduction='elementwise_mean')
        loss.backward()
        optimizer.step()
        if batch_idx % log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx *
                len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))

これも基本的には,通常のPyTorchの学習と同じだが,一点だけ異なる点がある.それがdata = adversary.perturb(data, target)の行.ここで,入力画像に対してAttackをしてAdversarial Examplesを作成している.これによって入力されるdataという変数がattackされたものに入れ替わる.

Model evaluation for original testset


    model.eval()
    test_clnloss = 0
    clncorrect = 0

    test_advloss = 0
    advcorrect = 0

    for clndata, target in test_loader:
        clndata, target = clndata.to(device), target.to(device)
        with torch.no_grad():
            output = model(clndata)
        test_clnloss += F.cross_entropy(
            output, target, reduction='sum').item()
        pred = output.max(1, keepdim=True)[1]
        clncorrect += pred.eq(target.view_as(pred)).sum().item()

ここは通常のTestsetを利用したModelの評価と同じ.

Model evaluation for adversarial testset

        advdata = adversary.perturb(clndata, target)
        with torch.no_grad():
            output = model(advdata)
        test_advloss += F.cross_entropy(
            output, target, reduction='sum').item()
        pred = output.max(1, keepdim=True)[1]
        advcorrect += pred.eq(target.view_as(pred)).sum().item()

Adversarial Examplesに対してのRobustさを測るために,テストセットに対してのAdversarial Attackを行い,それによって作成されたAdversarial Examplesに対しての精度を検証している.
学習時と同様に,テストセットの入力であるclndataを変換したadvdataを利用してmodelの性能を評価している.

Report loss and accuracy

    test_clnloss /= len(test_loader.dataset)
    print('\nTest set: avg cln loss: {:.4f},'
          ' cln acc: {}/{} ({:.0f}%)\n'.format(
              test_clnloss, clncorrect, len(test_loader.dataset),
              100. * clncorrect / len(test_loader.dataset)))
    
    test_advloss /= len(test_loader.dataset)
    print('Test set: avg adv loss: {:.4f},'
          ' adv acc: {}/{} ({:.0f}%)\n'.format(
              test_advloss, advcorrect, len(test_loader.dataset),
              100. * advcorrect / len(test_loader.dataset)))

通常のテストセットに対しての結果と,Adversarial Attackされたテストセットに対してのLossと精度を出力


ここから別セルで大丈夫です.

Saving the trained model (optional)

torch.save(
    model.state_dict(),
    os.path.join(TRAINED_MODEL_PATH, model_filename))

最後に,作成したモデルを保存して終了.

実験

Adversarial Trainingを利用することで,モデルがどれほどAdversarial Examplesに対してRobustになるのかを検証する.

評価指標

  • cln loss と cln acc
    • 通常のテストセットに対してのLossと精度
  • adv loss と adv acc
    Adversarial Examplesに変換されたテストセットに対してのLossと精度

学習方法

以下の3つの学習方法(mode)を試す.

  • mode = 'cln'
    • 通常の学習
  • mode = 'half'
    • 訓練データの半分をAdversarial Examplesに変えた学習
  • mode = 'adv'
    • 訓練データの全てをAdversarial Examplesに変えた学習

結果

mode cln loss cln acc adv loss adv acc
cln 0.0325 99% 8.7803% 3%
half 0.0234 99% 0.0181 99%
adv 3.4272 68% 0.0094 100%

考察

  • cln: 通常の学習手法
    • 通常のテストセットに対しては,99%の精度を達成している.
    • 一方で,Adversarial Attackがされたテストセットに対しては,わずか3%しか正しく分類できていない.
    • MNISTは10クラス分類問題であるため,ランダムよりも精度が著しく低い.
  • half: 半分をAdversarial Examplesに変換して学習
    • 通常のテストセットと,Adversarial Examplesのみのテストセットの両方に対して,99%の精度で分類できている.
  • adv: 全てをAdversarial Examplesに変換して学習
    • Adversarial Examplesのみのテストセットに対しては,高い確率で分類できている.
    • 一方で,逆に通常のテストセットでは,分類性能が低下している.

以上から,通常の画像も正しく分類することができ,Adversarial Examplesに対してもRobustであるModelを構築するためには,0.5の確率で入力をAdversarial Examplesに変換して学習すると良い.(これが最適な解法ではない)

まとめ

AdverTorchを使って,簡単にAdversarial Attackを実装して,RobustなModelを作ることができた.
一方で,新しい論文を実装する際などに,AdverTorchにどの程度の拡張性があるのかなどは,今後使っていって検証していきたい.

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?