Astuce de réseau de neurones pour les débutants

image


Dans le cadre du concours annuel ZeroNights HackQuest 2018, les participants ont été invités à s'essayer à un certain nombre de tâches et de compétitions non triviales. L' une d'entre elles était liée à la génération d'un exemple contradictoire pour un réseau neuronal. Dans nos articles, nous avons déjà prêté attention aux méthodes d' attaque et à la défense des algorithmes d'apprentissage automatique. Dans le cadre de cette publication, nous analyserons un exemple de la façon dont il a été possible de résoudre la tâche avec ZeroNights Hackquest en utilisant la bibliothèque foolbox.


Dans cette tâche, l' attaquant était censé accéder au serveur. Après avoir réussi, il a vu la structure de fichiers suivante dans son répertoire personnel:


| Home --| KerasModel.h5 --| Task.txt --| ZeroSource.bmp 

Les informations suivantes se trouvaient dans le fichier 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) ^_^) 

Pour obtenir le ticket convoité, l'attaquant a été invité à convertir ZeroSource.bmp:


image


de sorte qu'après l'envoi au serveur, le réseau neuronal interprète cette image comme 1. Si vous essayez d'envoyer cette image sans traitement, le résultat du réseau neuronal sera 0.


Et, bien sûr, le principal indice de cette tâche est le fichier modèle KerasModel.h5 (ce fichier aide l'attaquant à transférer l'attaque vers le plan WhiteBox, car le réseau neuronal et toutes les données qui lui sont associées lui sont accessibles). Le nom du fichier contient immédiatement un indice - le nom du cadre sur lequel le réseau neuronal est implémenté.


C'est avec ces notes introductives que le participant a entrepris de résoudre la tâche:


  • Modèle de réseau neuronal écrit en Keras.
  • La possibilité d'envoyer une image au serveur en utilisant curl.
  • L'image d'origine qui devait être modifiée.

Côté serveur, la vérification a été aussi simple que possible:


  1. L'image doit être de la bonne taille - 28x28 pixels.
  2. Dans cette image, le modèle doit renvoyer 1.
  3. La différence entre l'image initiale de ZeroSource.bmp et celle envoyée au serveur doit être inférieure au seuil k par la métrique MSE (erreur standard).

Commençons donc.


Tout d'abord, le participant devait trouver des informations sur la façon de tromper le réseau neuronal. Après un court instant sur Google, il a obtenu les mots clés "exemple contradictoire" et "attaque contradictoire". Ensuite, il devait chercher des outils pour appliquer des attaques contradictoires. Si vous lancez dans Google la requête "Attaques contradictoires sur Keras Neural Net", le premier lien sera vers le GitHub du projet FoolBox - une bibliothèque python pour générer des exemples contradictoires. Bien sûr, il existe d'autres bibliothèques (nous en avons parlé dans les articles précédents ). De plus, les attaques pourraient être écrites, comme on dit, à partir de zéro. Mais nous nous concentrons toujours sur la bibliothèque la plus populaire, qu'une personne qui n'a pas déjà rencontré le sujet des attaques contradictoires peut trouver sur le tout premier lien sur Google.


Vous devez maintenant écrire un script Python qui générera un exemple contradictoire.
Nous allons bien sûr commencer par les importations.


 import keras import numpy as np from PIL import Image import foolbox 

Que voyons-nous ici?


  1. Keras est le cadre sur lequel le réseau neuronal est écrit, que nous allons tromper.
  2. NumPy est une bibliothèque qui nous permettra de travailler efficacement avec des vecteurs.
  3. PIL est un outil pour travailler avec des images.
  4. FoolBox est une bibliothèque pour générer des exemples contradictoires.

La première chose à faire est bien sûr de charger le modèle de réseau neuronal dans la mémoire de notre programme et de voir les informations du modèle.


 model = keras.models.load_model("KerasModel.h5") #   model.summary() #     model.input #    ,        

En sortie, nous obtenons ce qui suit:


 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> 

Quelles informations puis-je obtenir d'ici?


  1. Le modèle d'entrée (couche conv2d_1) accepte un objet de dimension? X28x28x1, où "?" - nombre d'objets; si l'image est une, alors la dimension sera 1x28x28x1. Et l'image est un tableau tridimensionnel, où une dimension est 1. Autrement dit, l'image est servie sous forme de tableau de valeurs de 0 à 255.
  2. En sortie du modèle (couche dense_2), un vecteur de dimension 10 est obtenu.

Nous chargeons l'image et n'oubliez pas de la convertir en type flottant (en outre, le réseau de neurones fonctionnera avec des nombres réels) et de la normaliser (divisez toutes les valeurs par 255). Ici, il convient de préciser que la normalisation est l'une des astuces «obligatoires» lorsque vous travaillez avec des réseaux de neurones, mais l'attaquant peut ne pas le savoir, nous avons donc spécialement ajouté un petit indice dans la description de la tâche):


 img = Image.open("ZeroSource.bmp") #   img = np.array(img) #     numpy.array img = img.astype('float32') #      float img /= 255 #  

Maintenant, nous pouvons envoyer l'image au modèle chargé et voir quel résultat elle produit:


 model.predict(img.reshape(1,28,28,1)) #   predict            

En sortie, nous obtenons les informations suivantes


 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) 

Il vaut la peine d'expliquer ce qu'est ce vecteur: en fait, il s'agit d'une distribution de probabilité, c'est-à-dire que chaque nombre représente une probabilité de la classe 0,1,2 ..., 9. La somme de tous les nombres dans le vecteur est 1. Dans ce cas, on peut voir que le modèle est sûr que l'image d'entrée représente la classe 0 avec une probabilité de 100%.


Si nous représentons cela sur un histogramme, nous obtenons ce qui suit:


image


Confiance absolue.


Si le modèle ne pouvait pas déterminer la classe, le vecteur de probabilité tendrait vers une distribution uniforme, ce qui, à son tour, signifierait que le modèle attribue l'objet à toutes les classes simultanément avec la même probabilité. Et l'histogramme ressemblerait à ceci:


image


Il est généralement admis que la classe d'un modèle est déterminée par l'indice du nombre maximum dans ce vecteur. Autrement dit, le modèle pourrait théoriquement choisir une classe avec une probabilité de plus de 10%. Mais cette logique peut varier en fonction de la logique de décision décrite par les développeurs.


Passons maintenant à la partie la plus intéressante - les attaques contradictoires.


Tout d'abord, pour travailler avec un modèle dans la bibliothèque FoolBox, vous devez traduire le modèle en notation Foolbox. Vous pouvez le faire de cette façon:


 fmodel = foolbox.models.KerasModel(model,bounds=(0,1)) #  bounds ,       ,        255,        0-1. 

Après cela, vous pouvez tester différentes attaques. Commençons par le plus populaire - FGSM:


Fgsm

 attack = foolbox.attacks.FGSM(fmodel) #    FGSM     adversarial = attack(img.reshape(28,28,1),0) #  ,  adversarial  probs = model.predict(adversarial.reshape(1,28,28,1)) #     print(probs) #    print(np.argmax(probs)) #      

La sortie du réseau neuronal sera la suivante


 [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 

Et l'image résultante:


image


Donc, maintenant avec une probabilité de plus de 50% 0 a été reconnu comme 6. Déjà bon. Cependant, nous voulons toujours obtenir 1, et le niveau de bruit n'est pas très impressionnant. L'image semble vraiment peu plausible. Plus d'informations à ce sujet plus tard. En attendant, essayons de bafouer les attaques. Soudain, nous obtenons toujours 1.


Attaque L-BFGS

 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)) 

Conclusion:


 [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 

Image:


image


Encore une fois par. Nous avons maintenant 0 reconnu comme 9 avec une probabilité de ~ 49%. Cependant, le bruit est beaucoup moins.


Finissons avec un rythme aléatoire. Un exemple a été choisi de telle manière qu'il serait très difficile d'obtenir le résultat au hasard. Maintenant, nous n'avons indiqué nulle part que nous voulions obtenir 1. En conséquence, nous avons mené une attaque non ciblée et nous pensions que nous obtiendrions toujours la classe 1, mais cela ne s'est pas produit. Par conséquent, cela vaut la peine de passer à des attaques ciblées. Utilisons la documentation de la foolbox et y trouvons le module de critères


Dans ce module, vous pouvez sélectionner un critère pour une attaque, s'il le prend en charge. Plus précisément, nous nous intéressons à deux critères:


  1. TargetClass - fait en sorte que dans le vecteur de distributions de probabilité, l'élément avec le nombre k ait la probabilité maximale.
  2. TargetClassProbability - fait en sorte que dans le vecteur des distributions de probabilité, un élément avec le nombre k ait une probabilité d'au moins p.

Essayons les deux:


L-BFGS + TargetClass

L'essentiel des critères TargetClass est d'obtenir une probabilité de classe k supérieure à la probabilité de toute autre classe. Ensuite, le réseau qui prend la décision simplement en regardant la probabilité maximale se trompe.


 attack = foolbox.attacks.LBFGSAttack(fmodel,foolbox.criteria.TargetClass(1))#    ,     ,    TargetClass,  ,      ,      adversarial = attack(img.reshape(28,28,1),0) probs = model.predict(adversarial.reshape(1,28,28,1)) print(probs) print(np.argmax(probs)) 

Conclusion:


 [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 

Image:


image


Comme le montre la conclusion, notre réseau de neurones prétend maintenant qu'il est 1 avec une probabilité de 32,8%, tandis que la probabilité de 0 est aussi proche de cette valeur que possible et est de 32,6%. Nous l'avons fait! En principe, cela suffit déjà pour terminer la tâche. Mais nous irons plus loin et essayer d'obtenir une probabilité de 1 supérieure à 0,5.


L-BFGS + TargetClassProbability

Nous utilisons maintenant le critère TargetClassProbability, qui vous permet d'obtenir la probabilité d'une classe dans un objet non inférieur à p. Il n'a que deux paramètres:
1) Le numéro de classe de l'objet.
2) La probabilité de cette classe dans l'exemple contradictoire.
De plus, s'il est impossible d'atteindre une telle probabilité, ou si le temps pour trouver un tel objet prend trop de temps, alors l'objet contradictoire sera égal à aucun. Vous pouvez le vérifier vous-même en essayant de rendre la probabilité de, disons, 0,99. La méthode risque alors de ne pas converger.


 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)) 

Conclusion:


 [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] 

Hourra! Nous avons réussi à obtenir un exemple contradictoire, dans lequel la probabilité 1 pour notre réseau de neurones est supérieure à 50%! Super! Maintenant, faisons la dénormalisation (remettons l'image au format 0-255) et sauvegardons-la.


Le script final est le suivant:


 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') 

Et l'image finale est la suivante:


image .


Conclusions

Ainsi, comme nous l'avons vu dans les exemples ci-dessus, tromper un réseau de neurones était assez simple. Il existe également un grand nombre de méthodes capables de le faire. Ouvrez simplement la liste des attaques disponibles dans foolbox et essayez de les appliquer. Nous vous suggérons d'essayer de faire tourner le même vous-même, en prenant comme base le même réseau neuronal et la même image, disponibles par référence . Vous pouvez laisser vos questions dans les commentaires. Nous leur répondrons!


Rappelez-vous toujours que, quelle que soit l'utilité des algorithmes et des modèles, ils peuvent être extrêmement instables pour de petits décalages pouvant entraîner de graves erreurs. Par conséquent, nous vous recommandons de tester vos modèles, dans lesquels python et des outils comme foolbox peuvent vous aider.


Merci de votre attention!

Source: https://habr.com/ru/post/fr443164/


All Articles