
Im Rahmen des jährlichen ZeroNights HackQuest 2018-Wettbewerbs wurden die Teilnehmer eingeladen, sich an einer Reihe nicht trivialer Aufgaben und Wettbewerbe zu versuchen. Ein Teil von ihnen bezog sich auf die Erzeugung eines kontroversen Beispiels für ein neuronales Netzwerk. In unseren Artikeln haben wir bereits auf Angriffsmethoden und die Verteidigung von Algorithmen für maschinelles Lernen geachtet. Im Rahmen dieser Veröffentlichung werden wir ein Beispiel analysieren, wie es möglich war, die Aufgabe mit ZeroNights Hackquest mithilfe der Foolbox-Bibliothek zu lösen.
In dieser Aufgabe sollte der Angreifer Zugriff auf den Server erhalten. Nachdem er erfolgreich war, sah er die folgende Dateistruktur in seinem Home-Verzeichnis:
| Home --| KerasModel.h5 --| Task.txt --| ZeroSource.bmp
Die folgenden Informationen befanden sich in der Datei Task.txt:
Now it is time for a final boss! http://51.15.100.188:36491/predict You have a mode and an image. To get a ticket, you need to change an image so that it is identified as "1". curl -X POST -F image=@ZeroSource.bmp 'http://51.15.100.188:36491/predict'. (don't forget about normalization (/255) ^_^)
Um das begehrte Ticket zu erhalten, wurde der Angreifer gebeten, ZeroSource.bmp zu konvertieren:

Nach dem Senden an den Server interpretiert das neuronale Netzwerk dieses Bild als 1. Wenn Sie versuchen, dieses Bild ohne Verarbeitung zu senden, ist das Ergebnis des neuronalen Netzwerks 0.
Der wichtigste Hinweis für diese Aufgabe ist natürlich die Modelldatei KerasModel.h5 (diese Datei hilft dem Angreifer, den Angriff auf die WhiteBox-Ebene zu übertragen, da ihm das neuronale Netzwerk und alle damit verbundenen Daten zugänglich sind). Der Dateiname enthält sofort einen Hinweis - den Namen des Frameworks, in dem das neuronale Netzwerk implementiert ist.
Mit diesen einleitenden Anmerkungen machte sich der Teilnehmer an die Lösung der Aufgabe:
- In Keras geschriebenes neuronales Netzwerkmodell.
- Die Möglichkeit, ein Bild mithilfe von Curl an den Server zu senden.
- Das Originalbild, das geändert werden musste.
Auf der Serverseite war die Überprüfung so einfach wie möglich:
- Das Bild sollte die richtige Größe haben - 28x28 Pixel.
- In diesem Bild sollte das Modell 1 zurückgeben.
- Der Unterschied zwischen dem ursprünglichen Image von ZeroSource.bmp und dem an den Server gesendeten Image sollte durch die MSE-Metrik kleiner als der Schwellenwert k sein (Standardfehler).
Also fangen wir an.
Zunächst musste der Teilnehmer Informationen darüber finden, wie das neuronale Netzwerk ausgetrickst werden kann. Nach kurzer Zeit bei Google erhielt er die Schlüsselwörter "Widersprüchliches Beispiel" und "Widersprüchlicher Angriff". Als nächstes musste er nach Werkzeugen suchen, um gegnerische Angriffe anzuwenden. Wenn Sie die Abfrage "Gegnerische Angriffe auf Keras Neural Net" in Google eingeben, wird der erste Link zum GitHub des FoolBox-Projekts erstellt - einer Python-Bibliothek zum Generieren von Beispielen für Gegner. Natürlich gibt es auch andere Bibliotheken (über einige davon haben wir in früheren Artikeln gesprochen ). Darüber hinaus könnten Angriffe, wie sie sagen, von Grund auf neu geschrieben werden. Wir konzentrieren uns jedoch immer noch auf die beliebteste Bibliothek, die eine Person, die zuvor noch nicht auf das Thema gegnerische Angriffe gestoßen ist, über den ersten Link bei Google finden kann.
Jetzt müssen Sie ein Python-Skript schreiben, das ein kontroverses Beispiel generiert.
Wir werden natürlich mit Importen beginnen.
import keras import numpy as np from PIL import Image import foolbox
Was sehen wir hier?
- Keras ist der Rahmen, auf dem das Neuronale Netz geschrieben ist, den wir täuschen werden.
- NumPy ist eine Bibliothek, mit der wir effizient mit Vektoren arbeiten können.
- PIL ist ein Werkzeug zum Arbeiten mit Bildern.
- FoolBox ist eine Bibliothek zum Generieren von gegnerischen Beispielen.
Das erste, was Sie tun müssen, ist natürlich, das neuronale Netzwerkmodell in den Speicher unseres Programms zu laden und die Modellinformationen anzuzeigen.
model = keras.models.load_model("KerasModel.h5")
Am Ausgang erhalten wir folgendes:
Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 26, 26, 32) 320 _________________________________________________________________ conv2d_2 (Conv2D) (None, 26, 26, 64) 18496 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 13, 13, 64) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 13, 13, 64) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 13, 13, 64) 36928 _________________________________________________________________ conv2d_4 (Conv2D) (None, 13, 13, 128) 73856 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 6, 6, 128) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 4608) 0 _________________________________________________________________ dense_1 (Dense) (None, 256) 1179904 _________________________________________________________________ dense_2 (Dense) (None, 10) 2570 ================================================================= Total params: 1,312,074 Trainable params: 1,312,074 Non-trainable params: 0 _________________________________________________________________ <tf.Tensor 'conv2d_1_input_1:0' shape=(?, 28, 28, 1) dtype=float32>
Welche Informationen kann ich hier rausholen?
- Das Eingabemodell (conv2d_1-Ebene) akzeptiert ein Objekt der Dimension? X28x28x1, wobei "?" - Anzahl der Objekte; Wenn das Bild eins ist, beträgt die Abmessung 1x28x28x1. Und das Bild ist ein dreidimensionales Array, wobei eine Dimension 1 ist. Das heißt, das Bild wird als Wertetabelle von 0 bis 255 bereitgestellt.
- Am Ausgang des Modells (dichte_2 Schicht) wird ein Vektor der Dimension 10 erhalten.
Wir laden das Bild und vergessen nicht, es in den Float-Typ umzuwandeln (außerdem arbeitet das neuronale Netzwerk mit reellen Zahlen) und normalisieren es (teilen Sie alle Werte durch 255). Hier sollte klargestellt werden, dass die Normalisierung einer der „obligatorischen“ Tricks bei der Arbeit mit neuronalen Netzen ist, der Angreifer dies jedoch möglicherweise nicht gewusst hat. Daher haben wir in der Beschreibung der Aufgabe einen kleinen Hinweis hinzugefügt.
img = Image.open("ZeroSource.bmp")
Jetzt können wir das Bild an das geladene Modell senden und sehen, welches Ergebnis es erzeugt:
model.predict(img.reshape(1,28,28,1))
Am Ausgang erhalten wir folgende Informationen
array([[1.0000000e+00, 4.2309660e-19, 3.1170484e-15, 6.2545637e-18, 1.4199094e-16, 6.3990816e-13, 6.9493417e-10, 2.8936278e-12, 8.9440377e-14, 1.6340098e-12]], dtype=float32)
Es lohnt sich zu erklären, was dieser Vektor ist: Tatsächlich handelt es sich um eine Wahrscheinlichkeitsverteilung, dh jede Zahl repräsentiert eine Wahrscheinlichkeit der Klasse 0,1,2 ..., 9. Die Summe aller Zahlen im Vektor ist 1. In diesem Fall ist ersichtlich, dass das Modell sicher ist, dass das Eingabebild die Klasse 0 mit einer Wahrscheinlichkeit von 100% darstellt.
Wenn wir dies in einem Histogramm darstellen, erhalten wir Folgendes:

Absolutes Vertrauen.
Wenn das Modell die Klasse nicht bestimmen könnte, würde der Wahrscheinlichkeitsvektor zu einer gleichmäßigen Verteilung tendieren, was wiederum bedeuten würde, dass das Modell das Objekt allen Klassen gleichzeitig mit derselben Wahrscheinlichkeit zuweist. Und das Histogramm würde so aussehen:

Es ist allgemein anerkannt, dass die Klasse eines Modells durch den Index der maximalen Anzahl in einem gegebenen Vektor bestimmt wird. Das heißt, das Modell könnte theoretisch eine Klasse mit einer Wahrscheinlichkeit von mehr als 10% auswählen. Diese Logik kann jedoch abhängig von der von den Entwicklern beschriebenen Entscheidungslogik variieren.
Kommen wir nun zum interessantesten Teil - den gegnerischen Angriffen.
Um mit einem Modell in der FoolBox-Bibliothek arbeiten zu können, müssen Sie das Modell zunächst in die Foolbox-Notation übersetzen. Sie können es so machen:
fmodel = foolbox.models.KerasModel(model,bounds=(0,1))
Danach können Sie verschiedene Angriffe testen. Beginnen wir mit dem beliebtesten - FGSM:
Fgsm
attack = foolbox.attacks.FGSM(fmodel)
Die Ausgabe des neuronalen Netzwerks wird wie folgt sein
[4.8592144e-01 2.5432981e-14 5.7048566e-13 1.6787202e-14 1.6875961e-11 1.2974949e-07 5.1407838e-01 3.9819957e-12 1.9827724e-09 5.7383300e-12] 6
Und das resultierende Bild:

So wurde nun mit einer Wahrscheinlichkeit von mehr als 50% 0 als 6 erkannt. Bereits gut. Wir wollen jedoch immer noch 1 und der Geräuschpegel ist nicht sehr beeindruckend. Das Bild sieht wirklich unplausibel aus. Dazu später mehr. Versuchen wir in der Zwischenzeit, die Angriffe einfach zu missachten. Plötzlich bekommen wir immer noch 1.
L-BFGS-Angriff
attack = foolbox.attacks.LBFGSAttack(fmodel) adversarial = attack(img.reshape(28,28,1),0) probs = model.predict(adversarial.reshape(1,28,28,1)) print(probs) print(np.argmax(probs))
Fazit:
[4.7782943e-01, 1.9682934e-10, 1.0285517e-06, 3.2558936e-10, 6.5797998e-05, 4.0495447e-06, 2.5545436e-04, 3.4730587e-02, 5.5223148e-07, 4.8711312e-01] 9
Bild:

Wieder von. Jetzt haben wir 0 als 9 mit einer Wahrscheinlichkeit von ~ 49% erkannt. Das Geräusch ist jedoch viel geringer.
Lassen Sie uns mit einem zufälligen Schlag enden. Ein Beispiel wurde so ausgewählt, dass es sehr schwierig wäre, das Ergebnis zufällig zu erhalten. Jetzt haben wir nirgendwo angegeben, dass wir 1 bekommen wollen. Dementsprechend haben wir einen nicht zielgerichteten Angriff durchgeführt und geglaubt, dass wir immer noch Klasse 1 bekommen würden, aber dies ist nicht geschehen. Daher lohnt es sich, gezielte Angriffe durchzuführen. Verwenden wir die Foolbox-Dokumentation und suchen dort das Kriterienmodul
In diesem Modul können Sie ein Kriterium für einen Angriff auswählen, sofern es diese unterstützt. Insbesondere interessieren uns zwei Kriterien:
- TargetClass - bewirkt, dass im Vektor der Wahrscheinlichkeitsverteilungen das Element mit der Zahl k die maximale Wahrscheinlichkeit hat.
- TargetClassProbability - macht es so, dass im Vektor der Wahrscheinlichkeitsverteilungen ein Element mit der Zahl k eine Wahrscheinlichkeit von mindestens p hat.
Versuchen wir beides:
L-BFGS + TargetClass
Die Hauptsache in den TargetClass-Kriterien ist, die Wahrscheinlichkeit der Klasse k zu erhalten, die höher ist als die Wahrscheinlichkeit einer anderen Klasse. Dann wird das Netzwerk, das die Entscheidung einfach durch Betrachten der maximalen Wahrscheinlichkeit trifft, falsch sein.
attack = foolbox.attacks.LBFGSAttack(fmodel,foolbox.criteria.TargetClass(1))
Fazit:
[3.2620126e-01 3.2813528e-01 8.5446298e-02 8.1292394e-04 1.1273423e-03 2.4886258e-02 3.3904776e-02 1.9947644e-01 8.2347924e-07 8.5878673e-06] 1
Bild:

Wie aus der Schlussfolgerung hervorgeht, behauptet unser neuronales Netzwerk nun, dass es 1 mit einer Wahrscheinlichkeit von 32,8% ist, während die Wahrscheinlichkeit von 0 so nahe wie möglich an diesem Wert liegt und 32,6% beträgt. Wir haben es geschafft! Im Prinzip reicht dies bereits aus, um die Aufgabe abzuschließen. Wir werden aber noch weiter gehen und versuchen, eine Wahrscheinlichkeit von 1 über 0,5 zu erhalten.
L-BFGS + TargetClassProbability
Jetzt verwenden wir das Kriterium TargetClassProbability, mit dem Sie die Wahrscheinlichkeit einer Klasse in einem Objekt ermitteln können, das nicht niedriger als p ist. Es hat nur zwei Parameter:
1) Die Klassennummer des Objekts.
2) Die Wahrscheinlichkeit dieser Klasse im gegnerischen Beispiel.
Wenn es außerdem unmöglich ist, eine solche Wahrscheinlichkeit zu erreichen, oder die Zeit, um ein solches Objekt zu finden, zu lange dauert, ist das gegnerische Objekt gleich Null. Sie können dies selbst überprüfen, indem Sie versuchen, die Wahrscheinlichkeit von beispielsweise 0,99 zu bestimmen. Dann kann die Methode durchaus nicht konvergieren.
attack = foolbox.attacks.LBFGSAttack(fmodel,foolbox.criteria.TargetClassProbability(1,0.5)) adversarial = attack(img.reshape(28,28,1),0) probs = model.predict(adversarial.reshape(1,28,28,1)) print(probs) print(np.argmax(probs))
Fazit:
[4.2620126e-01 5.0013528e-01 9.5413298e-02 8.1292394e-04 1.1273423e-03 2.4886258e-02 3.3904776e-02 1.9947644e-01 8.2347924e-07 8.5878673e-06]
Hurra! Es ist uns gelungen, ein gegnerisches Beispiel zu erhalten, bei dem die Wahrscheinlichkeit 1 für unser neuronales Netzwerk über 50% liegt! Großartig! Lassen Sie uns nun die Denormalisierung durchführen (das Bild auf das Format 0-255 zurücksetzen) und es speichern.
Das endgültige Skript lautet wie folgt:
import keras from PIL import Image import numpy as np import foolbox from foolbox.criteria import TargetClassProbability import scipy.misc model = keras.models.load_model("KerasModel.h5") img = Image.open("ZeroSource.bmp") img = np.array(img.getdata()) img = img.astype('float32') img = img /255. img = img.reshape(28,28,1) fmodel = foolbox.models.KerasModel(model,bounds=(0,1)) attack = foolbox.attacks.LBFGSAttack(fmodel,criterion=TargetClassProbability(1 ,p=.5)) adversarial = attack(img[:,:,::-1], 0) adversarial = adversarial * 255 adversarial = adversarial.astype('int') scipy.misc.toimage(adversarial.reshape(28,28)).save('AdversarialExampleZero.bmp')
Und das endgültige Bild ist wie folgt:
.
Schlussfolgerungen
Wie wir aus den obigen Beispielen gesehen haben, war es ziemlich einfach, ein neuronales Netzwerk zu täuschen. Es gibt auch eine große Anzahl von Methoden, die dazu in der Lage sind. Öffnen Sie einfach die Liste der verfügbaren Angriffe in der Narrenbox und versuchen Sie, sie anzuwenden. Wir empfehlen, dass Sie versuchen, dasselbe selbst anzukurbeln, wobei Sie dasselbe neuronale Netzwerk und dasselbe Bild als Grundlage verwenden, die als Referenz verfügbar sind. Sie können Ihre Fragen in den Kommentaren hinterlassen. Wir werden ihnen antworten!
Denken Sie immer daran, dass die Algorithmen und Modelle, egal wie nützlich sie sind, für kleine Verschiebungen, die zu schwerwiegenden Fehlern führen können, äußerst instabil sein können. Daher empfehlen wir Ihnen, Ihre Modelle zu testen, bei denen Python und Tools wie Foolbox hilfreich sein können.
Vielen Dank für Ihre Aufmerksamkeit!