Python3でニューラルネットワークをだます方法
著者は、 Write forDOnationsプログラムの一環として寄付を受け取るためにDevColorを選択しました。
動物分類のためのニューラルネットワークはだまされる可能性がありますか? 動物分類器をだますことはほとんど影響を与えないかもしれません、しかし私たちの顔認証者がだまされることができたらどうでしょうか? または、自動運転車のプロトタイプのソフトウェアですか? 幸いなことに、多くのエンジニアと研究者が、プロトタイプのコンピュータービジョンモデルとモバイルデバイスまたは自動車の生産品質モデルの間に立っています。 それでも、これらのリスクには重大な影響があり、機械学習の実践者として考慮することが重要です。
このチュートリアルでは、動物分類子を「だます」またはだましてみます。 チュートリアルを進める際には、コンピュータービジョンライブラリである OpenCV と、ディープラーニングライブラリであるPyTorchを使用します。 敵対的機械学習の関連フィールドで次のトピックを取り上げます。
- ターゲットの敵対的な例を作成します。 たとえば、犬の画像を選択します。 target クラス、たとえば猫を選びます。 あなたの目標は、ニューラルネットワークをだまして、写真に写っている犬が猫であると信じ込ませることです。
- 敵対的防御を作成します。 つまり、トリックが何であるかを知らなくても、これらのトリッキーな画像からニューラルネットワークを保護します。
チュートリアルの終わりまでに、ニューラルネットワークをだますためのツールと、だましから身を守る方法を理解できるようになります。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- 1GB以上のRAMを搭載したPython3のローカル開発環境。 Python 3のローカルプログラミング環境をインストールおよびセットアップする方法に従って、必要なものをすべて構成できます。
- 感情ベースの犬用フィルターの作成を確認することをお勧めします。 このチュートリアルは明示的に使用されていませんが、分類の概念を紹介しています。
ステップ1—プロジェクトの作成と依存関係のインストール
このプロジェクトのワークスペースを作成し、必要な依存関係をインストールしましょう。 ワークスペースをAdversarialML
と呼びます。
mkdir ~/AdversarialML
AdversarialML
ディレクトリに移動します。
cd ~/AdversarialML
すべてのアセットを保持するディレクトリを作成します。
mkdir ~/AdversarialML/assets
次に、プロジェクトの新しい仮想環境を作成します。
python3 -m venv adversarialml
環境をアクティブ化します。
source adversarialml/bin/activate
次に、このチュートリアルで使用するPythonのディープラーニングフレームワークであるPyTorchをインストールします。
macOSで、次のコマンドを使用してPytorchをインストールします。
python -m pip install torch==1.2.0 torchvision==0.4.0
LinuxおよびWindowsでは、CPUのみのビルドに次のコマンドを使用します。
pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html pip install torchvision
次に、OpenCV
とnumpy
のパッケージ済みバイナリをインストールします。これらは、それぞれコンピュータビジョンと線形代数のライブラリです。 OpenCV
は画像の回転などのユーティリティを提供し、numpyは行列反転などの線形代数ユーティリティを提供します。
python -m pip install opencv-python==3.4.3.18 numpy==1.14.5
Linuxディストリビューションでは、libSM.so
をインストールする必要があります。
sudo apt-get install libsm6 libxext6 libxrender-dev
依存関係をインストールした状態で、次に説明するResNet18という動物分類子を実行してみましょう。
ステップ2—事前に訓練された動物分類器を実行する
PyTorchの公式コンピュータービジョンライブラリであるtorchvisionライブラリには、一般的に使用されるコンピュータービジョンニューラルネットワークの事前トレーニング済みバージョンが含まれています。 これらのニューラルネットワークはすべて、 ImageNet 2012 でトレーニングされています。これは、1000クラスの120万のトレーニング画像のデータセットです。 これらのクラスには、乗り物、場所、そして最も重要なことに動物が含まれます。 このステップでは、ResNet18と呼ばれるこれらの事前トレーニング済みニューラルネットワークの1つを実行します。 ImageNetでトレーニングされたResNet18を「動物分類子」と呼びます。
ResNet18とは何ですか? ResNet18は、 MSR (He et al。)によって開発された残差ニューラルネットワークと呼ばれるニューラルネットワークファミリーの中で最小のニューラルネットワークです。 要するに、彼はニューラルネットワーク(関数f
、入力x
、出力f(x)
)が「残差接続」x + f(x)
。 この残留接続は、今日でも最先端のニューラルネットワークで多用されています。 たとえば、 FBNetV2 、FBNetV3です。
次のコマンドを使用して、この犬の画像をダウンロードします。
wget -O assets/dog.jpg https://assets.digitalocean.com/articles/trick_neural_network/step2a.png
次に、JSONファイルをダウンロードして、ニューラルネットワークの出力を人間が読める形式のクラス名に変換します。
wget -O assets/imagenet_idx_to_label.json https://raw.githubusercontent.com/do-community/tricking-neural-networks/master/utils/imagenet_idx_to_label.json
次に、犬の画像で事前トレーニング済みのモデルを実行するスクリプトを作成します。 step_2_pretrained.py
という名前の新しいファイルを作成します。
nano step_2_pretrained.py
まず、必要なパッケージをインポートしてmain
関数を宣言することにより、Pythonボイラープレートを追加します。
step_2_pretrained.py
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()
次に、ニューラルネットワーク出力から人間が読めるクラス名へのマッピングをロードします。 インポートステートメントの直後で、main
関数の前にこれを追加します。
step_2_pretrained.py
. . . def get_idx_to_label(): with open("assets/imagenet_idx_to_label.json") as f: return json.load(f) . . .
入力画像のサイズが最初に正しく、次に正しく正規化されるようにする画像変換関数を作成します。 最後の直後に次の関数を追加します。
step_2_pretrained.py
. . . 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 . . .
get_image_transform
では、ニューラルネットワークに渡される画像に適用するさまざまな変換を定義します。
transforms.Resize(224)
:画像の小さい側のサイズを224に変更します。 たとえば、画像が448 x 672の場合、この操作は画像を224x336にダウンサンプリングします。transforms.CenterCrop(224)
:画像の中央からサイズ224x224の切り抜きを取ります。transforms.ToTensor()
:画像をPyTorchテンソルに変換します。 すべてのPyTorchモデルでは、入力としてPyTorchテンソルが必要です。transforms.Normalize(mean=..., std=...)
:平均を減算し、標準偏差で除算することにより、入力を標準化します。 これについては、torchvisionのドキュメントで詳しく説明されています。
画像を指定して、動物のクラスを予測するユーティリティを追加します。 この方法では、以前の両方のユーティリティを使用して動物の分類を実行します。
step_2_pretrained.py
. . . 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 . . .
ここで、predict
関数は、事前にトレーニングされたニューラルネットワークを使用して提供された画像を分類します。
models.resnet18(pretrained=True)
:ResNet18と呼ばれる事前トレーニング済みニューラルネットワークをロードします。model.eval()
:「評価」モードで実行するようにモデルをインプレースで変更します。 他の唯一のモードは「トレーニング」モードですが、このチュートリアルではモデルをトレーニングしていない(つまり、モデルのパラメーターを更新していない)ため、トレーニングモードは必要ありません。out = model(image)
:提供された変換された画像でニューラルネットワークを実行します。_, pred = torch.max(out, 1)
:ニューラルネットワークは、可能なクラスごとに1つの確率を出力します。 このステップでは、最も高い確率でクラスのインデックスを計算します。 たとえば、out = [0.4, 0.1, 0.2]
の場合、pred = 0
です。idx_to_label = get_idx_to_label()
:クラスインデックスから人間が読めるクラス名へのマッピングを取得します。 たとえば、マッピングは{0: cat, 1: dog, 2: fish}
のようになります。cls = idx_to_label[str(int(pred))]
:予測されたクラスインデックスをクラス名に変換します。 最後の2つの箇条書きで示した例では、cls = idx_to_label[0] = 'cat'
が生成されます。
次に、最後の関数に続いて、画像をロードするユーティリティを追加します。
step_2_pretrained.py
. . . 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 . . .
これにより、スクリプトの最初の引数で指定されたパスから画像が読み込まれます。 transform(image)[None]
は、前の行で定義された一連の画像変換を適用します。
最後に、main
関数に次の情報を入力して、画像を読み込み、画像内の動物を分類します。
step_2_pretrained.py
def main(): x = load_image() print(f'Prediction: {predict(x)}')
ファイルがGitHubのstep_2_pretrained.pyにある最後のステップ2スクリプトと一致することを再確認してください。 スクリプトを保存して終了し、動物分類子を実行します。
python step_2_pretrained.py assets/dog.jpg
これにより、次の出力が生成され、動物分類子が期待どおりに機能することが示されます。
OutputPrediction: Pembroke, Pembroke Welsh corgi
これで、事前にトレーニングされたモデルで推論を実行することはできます。 次に、画像にわずかな違いがあるニューラルネットワークをだまして、敵対的な例が実際に動作しているのを確認します。
ステップ3—敵対的な例を試す
次に、敵対的な例を合成し、その例でニューラルネットワークをテストします。 このチュートリアルでは、x + r
の形式の敵対的な例を作成します。ここで、x
は元の画像であり、r
はいくつかの「摂動」です。 最終的には摂動r
をダウンロードします。 摂動r
をダウンロードすることから始めます。
wget -O assets/adversarial_r.npy https://github.com/do-community/tricking-neural-networks/blob/master/outputs/adversarial_r.npy?raw=true
次に、画像を摂動と合成します。 step_3_adversarial.py
という名前の新しいファイルを作成します。
nano step_3_adversarial.py
このファイルでは、次の3つのステップのプロセスを実行して、敵対的な例を作成します。
- 画像を変換する
- 摂動を適用します
r
- 摂動画像を逆変換します
ステップ3の終わりに、敵対的なイメージがあります。 まず、必要なパッケージをインポートし、main
関数を宣言します。
step_3_adversarial.py
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()
次に、以前の画像変換を反転する「画像変換」を作成します。 インポート後、main
関数の前にこれを配置します。
step_3_adversarial.py
. . . 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]) . . .
前と同じように、transforms.Normalize
操作は平均を減算し、標準偏差で除算します(つまり、元の画像x
の場合、y = transforms.Normalize(mean=u, std=o) = (x - u) / o
)。 いくつかの代数を実行し、この正規化関数(transforms.Normalize(mean=-u/o, std=1/o) = (y - -u/o) / 1/o = (y + u/o) o = yo + u = x
)を逆にする新しい操作を定義します。
逆変換の一部として、PyTorchテンソルをPIL画像に変換するメソッドを追加します。 最後の関数の後にこれを追加します。
step_3_adversarial.py
. . . 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()
は、PyTorchテンソルをNumPy配列に変換します。.transpose(1, 2, 0)
は、(channels, width, height)
を(height, width, channels)
に再配置します。 このNumPyアレイは、おおよそ[X47X]の範囲にあります。 最後に、255を掛けて、画像が(0, 255)
の範囲にあることを確認します。np.clip
は、画像内のすべての値が(0, 255)
の間にあることを保証します。x.astype(np.uint8)
は、すべての画像値が整数であることを保証します。 最後に、Image.fromarray(...)
はNumPy配列からPIL画像オブジェクトを作成します。
次に、これらのユーティリティを使用して、次のような敵対的な例を作成します。
step_3_adversarial.py
. . . def get_adversarial_example(x, r): y = x + r y = get_inverse_transform()(y[0]) image = tensor_to_image(y) return image . . .
この関数は、セクションの冒頭で説明したように、敵対的な例を生成します。
y = x + r
。 摂動r
を取り、元の画像x
に追加します。get_inverse_transform
:数行前に定義した逆画像変換を取得して適用します。tensor_to_image
:最後に、PyTorchテンソルを画像オブジェクトに変換し直します。
最後に、main
関数を変更して、画像をロードし、敵対的摂動r
をロードし、摂動を適用し、敵対的例をディスクに保存し、敵対的例で予測を実行します。
step_3_adversarial.py
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)}')
完成したファイルは、GitHubのstep_3_adversarial.pyと一致する必要があります。 ファイルを保存し、エディターを終了して、次のコマンドでスクリプトを起動します。
python step_3_adversarial.py assets/dog.jpg
次の出力が表示されます。
OutputOld prediction: Pembroke, Pembroke Welsh corgi New prediction: goldfish, Carassius auratus
これで、敵対的な例を作成しました。ニューラルネットワークをだまして、コーギーを金魚だと思い込ませます。 次のステップでは、ここで使用した摂動r
を実際に作成します。
ステップ4—敵対的な例を理解する
分類の入門書については、「感情ベースの犬用フィルターの作成方法」を参照してください。
一歩下がって、分類モデルが各クラスの確率を出力することを思い出してください。 推論中に、モデルは最も高い確率でクラスを予測します。 トレーニング中に、モデルパラメータt
を更新して、データx
が与えられた場合に、正しいクラスy
の確率を最大化します。
argmax_y P(y|x,t)
ただし、敵対的な例を生成するために、ここで目標を変更します。 クラスを見つける代わりに、あなたの目標は新しい画像x
を見つけることです。 正しいクラス以外のクラスを受講してください。 この新しいクラスをw
と呼びましょう。 あなたの新しい目的は、間違ったクラスの確率を最大化することです。
argmax_x P(w|x)
ニューラルネットワークの重みt
が上記の式から欠落していることに注意してください。 これは、あなたが敵の役割を引き受けるためです。他の誰かがモデルをトレーニングして展開しました。 敵対的な入力を作成することのみが許可されており、デプロイされたモデルを変更することは許可されていません。 敵対的な例x
を生成するには、「トレーニング」を実行できます。ただし、ニューラルネットワークの重みを更新する代わりに、入力画像を新しい目的で更新します。
このチュートリアルでは、敵対的な例がx
のアフィン変換であると想定しています。 言い換えれば、あなたの敵対的な例は、いくつかのr
に対してx + r
の形式を取ります。 次のステップでは、このr
を生成するスクリプトを記述します。
ステップ5—敵対的な例を作成する
このステップでは、摂動r
を学習します。これにより、コーギーは金魚として誤って分類されます。 step_5_perturb.py
という名前の新しいファイルを作成します。
nano step_5_perturb.py
必要なパッケージをインポートし、main
関数を宣言します。
step_5_perturb.py
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()
インポートの直後で、main
関数の前に、次の2つの定数を定義します。
step_5_perturb.py
. . . TARGET_LABEL = 1 EPSILON = 10 / 255. . . .
最初の定数TARGET_LABEL
は、コーギーを誤って分類するクラスです。 この場合、インデックス1
は「金魚」に対応します。 2番目の定数EPSILON
は、各画像値に許可される摂動の最大量です。 この制限は、画像がいつの間にか変更されるように導入されています。
2つの定数に続いて、ニューラルネットワークと摂動パラメーターr
を定義するヘルパー関数を追加します。
step_5_perturb.py
. . . 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 . . .
model.resnet18(pretrained=True)
は、以前と同様に、ResNet18と呼ばれる事前トレーニング済みのニューラルネットワークをロードします。 また、前と同じように、.eval
を使用してモデルを評価モードに設定します。nn.Parameter(...)
は、入力画像のサイズである新しい摂動r
を定義します。 入力画像も(1, 3, 224, 224)
のサイズです。requires_grad=True
キーワード引数により、このファイルの後の行でこの摂動r
を更新できるようになります。
次に、main
関数の変更を開始します。 モデルnet
をロードし、入力x
をロードし、ラベルlabel
を定義することから始めます。
step_5_perturb.py
. . . 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() . . .
次に、main
関数で基準とオプティマイザーの両方を定義します。 前者は、PyTorchに目的が何であるか、つまり、どのような損失を最小限に抑えるかを指示します。 後者は、パラメータr
をトレーニングする方法をPyTorchに指示します。
step_5_perturb.py
. . . criterion = nn.CrossEntropyLoss() optimizer = optim.SGD([r], lr=0.1, momentum=0.1) . . .
直後に、パラメーターr
のメイントレーニングループを追加します。
step_5_perturb.py
. . . 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))]}') . . .
このトレーニングループの各反復で、次のことを行います。
r.data.clamp_(...)
:パラメーターr
が小さく、EPSILON
が0の範囲内であることを確認してください。optimizer.zero_grad()
:前の反復で計算したすべての勾配をクリアします。model(x + r)
:変更された画像x + r
で推論を実行します。loss
を計算します。- 勾配
loss.backward
を計算します。 - 最急降下法
optimizer.step
を実行します。 - 予測
pred
を計算します。 - 最後に、損失と予測クラス
print(...)
を報告します。
次に、最終的な摂動を保存しますr
:
step_5_perturb.py
def main(): . . . for i in range(30): . . . . . . np.save('outputs/adversarial_r.npy', r.data.numpy())
直後に、main
関数で、摂動された画像を保存します。
step_5_perturb.py
. . . os.makedirs('outputs', exist_ok=True) adversarial = get_adversarial_example(x, r)
最後に、元の画像と敵対的な例の両方で予測を実行します。
step_5_perturb.py
print(f'Old prediction: {predict(x)}') print(f'New prediction: {predict(x + r)}')
スクリプトがGitHubのstep_5_perturb.pyと一致することを再確認してください。 スクリプトを保存して終了し、実行します。
python step_5_perturb.py assets/dog.jpg
スクリプトは次のように出力します。
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
最後の2行は、敵対的な例の作成を最初から完了したことを示しています。 これで、ニューラルネットワークは完全に合理的なコーギー画像を金魚として分類します。
これで、ニューラルネットワークが簡単にだまされる可能性があることを示しました。さらに、敵対的な例に対する堅牢性の欠如は重大な結果をもたらします。 当然の次の質問はこれです:どのように敵対的な例と戦うことができますか? OpenAI を含むさまざまな組織によって、かなりの量の研究が行われてきました。 次のセクションでは、この敵対的な例を阻止するための防御を実行します。
ステップ6—敵対的な例に対する防御
このステップでは、敵対的な例に対する防御を実装します。 アイデアは次のとおりです。あなたは現在、生産に配備されている動物分類器の所有者です。 どのような敵対的な例が生成されるかはわかりませんが、攻撃から保護するために画像またはモデルを変更することができます。
防御する前に、画像操作がどれほど知覚できないかを自分で確認する必要があります。 次の両方の画像を開きます。
assets/dog.jpg
outputs/adversarial.png
ここでは、両方を並べて表示します。 元の画像のアスペクト比は異なります。 どちらが敵対的な例かわかりますか?
新しい画像が元の画像と同じに見えることに注意してください。 結局のところ、左の画像は敵対的な画像です。 確かに、イメージをダウンロードして、評価スクリプトを実行します。
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
これにより、金魚のクラスが出力され、その敵対的な性質が証明されます。
OutputPrediction: goldfish, Carassius auratus
かなり単純ですが、効果的な防御を実行します。不可逆JPEG形式で書き込むことにより、画像を圧縮します。 Pythonインタラクティブプロンプトを開きます。
python
次に、敵対する画像をPNGとしてロードし、JPEGとして保存し直します。
from PIL import Image image = Image.open('assets/adversarial.png') image.save('outputs/adversarial.jpg')
CTRL + D
と入力して、Pythonインタラクティブプロンプトを終了します。 次に、圧縮された敵対的な例でモデルを使用して推論を実行します。
python step_2_pretrained.py outputs/adversarial.jpg
これでコーギークラスが出力され、素朴な防御の効果が証明されます。
OutputPrediction: Pembroke, Pembroke Welsh corgi
これで、最初の敵対的防御が完了しました。 この防御では、敵対的な例が生成された方法を知る必要がないことに注意してください。 これが効果的な防御になります。 他にも多くの防御形態があり、その多くはニューラルネットワークの再トレーニングを伴います。 ただし、これらの再トレーニング手順は独自のトピックであり、このチュートリアルの範囲を超えています。 これで、敵対的な機械学習へのガイドは終わりです。
結論
このチュートリアルでの作業の意味を理解するには、元の例と敵対的な例の2つの画像を並べて再検討してください。
両方の画像が人間の目と同じように見えるという事実にもかかわらず、最初の画像はモデルをだますために操作されています。 どちらの画像も明らかにコーギーを特徴としていますが、モデルは2番目のモデルに金魚が含まれていることを完全に確信しています。 これはあなたに関係するはずであり、このチュートリアルを締めくくるときは、モデルの脆弱性に留意してください。 単純な変換を適用するだけで、それをだますことができます。 これらは、最先端の研究でさえも回避する現実的でもっともらしい危険です。 機械学習のセキュリティを超えた研究は、これらの欠陥の影響を受けやすく、実践者として、機械学習を安全に適用するのはあなた次第です。 詳細については、次のリンクを確認してください。
- NeurIPS2018ConferenceのAdversarialMachineLearningチュートリアル。
- 敵対的な例による機械学習への攻撃、目に見えない敵対者に対するロバスト性のテスト、およびロバストな敵対的入力に関するOpenAIの関連ブログ投稿。
機械学習のコンテンツとチュートリアルの詳細については、機械学習トピックページにアクセスしてください。