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
まず、必要なパッケージをインポートして宣言することにより、Pythonボイラープレートを追加します。 main
関数:
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
関数:
. . .
def get_idx_to_label():
with open("assets/imagenet_idx_to_label.json") as f:
return json.load(f)
. . .
入力画像のサイズが最初に正しく、次に正しく正規化されるようにする画像変換関数を作成します。 最後の直後に次の関数を追加します。
. . .
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の場合、この操作は画像を224×336にダウンサンプリングします。transforms.CenterCrop(224)
:画像の中央からサイズ224×224の切り抜きを取ります。transforms.ToTensor()
:画像をPyTorchテンソルに変換します。 すべてのPyTorchモデルでは、入力としてPyTorchテンソルが必要です。transforms.Normalize(mean=..., std=...)
:平均を減算し、標準偏差で除算することにより、入力を標準化します。 これについては、torchvisionのドキュメントで詳しく説明されています。
画像を指定して、動物のクラスを予測するユーティリティを追加します。 この方法では、以前の両方のユーティリティを使用して動物の分類を実行します。
. . .
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'
.
次に、最後の関数に続いて、画像をロードするユーティリティを追加します。
. . .
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
次の機能を使用して、画像を読み込み、画像内の動物を分類します。
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
関数:
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
関数:
. . .
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画像に変換するメソッドを追加します。 最後の関数の後にこれを追加します。
. . .
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アレイは、ほぼ範囲内にあります(0, 1)
. 最後に、255を掛けて、画像が範囲内にあることを確認します(0, 255)
.np.clip
画像内のすべての値が(0, 255)
.x.astype(np.uint8)
すべての画像値が整数であることを確認します。 ついに、Image.fromarray(...)
NumPy配列からPIL画像オブジェクトを作成します。
次に、これらのユーティリティを使用して、次のような敵対的な例を作成します。
. . .
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
、摂動を適用し、敵対的な例をディスクに保存し、敵対的な例で予測を実行します。
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
正しいクラスの確率を最大化する y
、あなたのデータを考えると x
.
argmax_y P(y|x,t)
ただし、敵対的な例を生成するために、ここで目標を変更します。 クラスを見つける代わりに、あなたの目標は新しい画像を見つけることです。 x
. 正しいクラス以外のクラスを受講してください。 この新しいクラスと呼びましょう w
. あなたの新しい目的は、間違ったクラスの確率を最大化することです。
argmax_x P(w|x)
ニューラルネットワークの重みに注意してください t
上記の式から欠落しています。 これは、あなたが敵の役割を引き受けるためです。他の誰かがモデルをトレーニングして展開しました。 敵対的な入力を作成することのみが許可されており、デプロイされたモデルを変更することは許可されていません。 敵対的な例を生成するには x
、「トレーニング」を実行できます。ただし、ニューラルネットワークの重みを更新する代わりに、入力画像を新しい目的で更新します。
念のため、このチュートリアルでは、敵対的な例は次のアフィン変換であると想定しています。 x
. 言い換えれば、あなたの敵対的な例は次の形をとります x + r
いくつかのための r
. 次のステップでは、これを生成するためのスクリプトを作成します r
.
ステップ5—敵対的な例を作成する
このステップでは、摂動を学びます r
、コーギーが金魚として誤って分類されるようにします。 と呼ばれる新しいファイルを作成します step_5_perturb.py
:
- nano step_5_perturb.py
必要なパッケージをインポートし、宣言します 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()
インポートの直後で、 main
関数、2つの定数を定義します。
. . .
TARGET_LABEL = 1
EPSILON = 10 / 255.
. . .
最初の定数 TARGET_LABEL
コーギーを誤分類するクラスです。 この場合、インデックス 1
「金魚」に相当します。 2番目の定数 EPSILON
は、各画像値に許可される摂動の最大量です。 この制限は、画像がいつの間にか変更されるように導入されています。
2つの定数に続いて、ニューラルネットワークと摂動パラメーターを定義するヘルパー関数を追加します 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
. . .
model.resnet18(pretrained=True)
以前と同様に、ResNet18と呼ばれる事前トレーニング済みのニューラルネットワークをロードします。 また、以前と同様に、を使用してモデルを評価モードに設定します.eval
.nn.Parameter(...)
新しい摂動を定義しますr
、入力画像のサイズ。 入力画像もサイズが異なります(1, 3, 224, 224)
. Therequires_grad=True
キーワード引数は、この摂動を更新できることを保証しますr
後の行で、このファイルで。
次に、変更を開始します main
関数。 モデルをロードすることから始めます net
、入力の読み込み x
、およびラベルの定義 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()
. . .
次に、基準とオプティマイザーの両方を定義します main
関数。 前者は、PyTorchに目的が何であるか、つまり、どのような損失を最小限に抑えるかを指示します。 後者は、PyTorchにパラメーターのトレーニング方法を指示します r
:
. . .
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD([r], lr=0.1, momentum=0.1)
. . .
直後に、パラメータのメイントレーニングループを追加します 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))]}')
. . .
このトレーニングループの各反復で、次のことを行います。
r.data.clamp_(...)
:パラメータを確認してくださいr
小さいですEPSILON
0の。optimizer.zero_grad()
:前の反復で計算したすべての勾配をクリアします。model(x + r)
:変更された画像に対して推論を実行しますx + r
.- を計算する
loss
. - 勾配を計算する
loss.backward
. - 最急降下法を実行します
optimizer.step
. - 予測を計算する
pred
. - 最後に、損失と予測されるクラスを報告します
print(...)
.
次に、最終的な摂動を保存します r
:
def main():
. . .
for i in range(30):
. . .
. . .
np.save('outputs/adversarial_r.npy', r.data.numpy())
直接続く、まだ main
関数、摂動画像を保存します:
. . .
os.makedirs('outputs', exist_ok=True)
adversarial = get_adversarial_example(x, r)
最後に、元の画像と敵対的な例の両方で予測を実行します。
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の関連ブログ投稿。
機械学習のコンテンツとチュートリアルの詳細については、機械学習トピックページにアクセスしてください。