――Ceci est le résultat de mon propre dossier d'étude sur l'apprentissage automatique et l'apprentissage profond.
--Cible: identique à la précédente. Pour plus de détails ** ici **. --Article de référence ** ○ Je vais expliquer en détail le code source de Grad-CAM et Guided Grad-CAM au Japon (implémentation Keras) **
--Acquis ** JDLA Deep Learning pour Engeneer 2019 # 2 ** en septembre 2019. ――Jusqu'à la fin mars 2020, vous occuperez le bureau d'une société d'intérêt public. À partir d'avril 2020, il a changé de carrière en ingénieur de données.
――Nous avons implémenté Grad-CAM et créé une carte thermique sur les caractéristiques qui sont à la base de la classification des photos de chiens Shiba.
** Procédure 1 Préparation ** ** Étape 2 Enregistrez les fonctions requises pour implémenter Grad-CAM et Guided Grad-CAM ** ** Étape 3 Mise en œuvre du traitement principal de Grad-CAM ** ** Étape 4 Mise en œuvre du traitement principal de Guided Grad-CAM **
Montez pour que les données puissent être lues dans Colab à partir du dossier contenant l'image du chien Shiba.
#Monture Google Drive
from google.colab import drive
drive.mount('/content/drive')
Importez avec le code suivant.
#Importer la bibliothèque
from __future__ import print_function
import keras
from keras.applications import VGG16
from keras.models import Sequential, load_model, model_from_json
from keras import models, optimizers, layers
from keras.optimizers import SGD
from keras.layers import Dense, Dropout, Activation, Flatten
from sklearn.model_selection import train_test_split
from PIL import Image
from keras.preprocessing import image as images
from keras.preprocessing.image import array_to_img, img_to_array, load_img
from keras import backend as K
import os
import numpy as np
import glob
import pandas as pd
import cv2
Vérifions la version des keras importés (au 19 janvier 2020 au moment de la rédaction de cet article) C'est cette version)
print(keras.__version__)
2.2.5
Il y a un point à noter ici. Le contenu qui m'intéressait était écrit en ** Comment ** joint à l'article original, mais la version de keras est ** 2.2. S'il n'est pas inférieur à .4 **, il semble qu'une erreur se produira lors de l'exécution de la couche source. Quand je l'ai essayé, j'ai reçu le message d'erreur suivant sur la dernière ligne de la série d'exécution du code source. Après tout, il semble que vous deviez abaisser la version de keras avant de l'exécuter.
AttributeError: module 'keras.backend' has no attribute 'image_dim_ordering'
Exécutez le code suivant.
#Version spécifique de keras(2.2.4)Changer pour
#Nécessite un redémarrage de l'exécution après l'exécution
!pip install keras==2.2.4
Une fois exécuté, l'écran suivant apparaîtra et la bibliothèque sera remplacée par la version spécifiée. Cependant, comme le texte brun vous avertit, vous devez redémarrer le runtime pour que cette modification prenne effet.
Pour redémarrer le runtime, cliquez sur "Restart Runtime" dans "Runtime" dans la barre de menus.
Après avoir redémarré le runtime, répétez toutes les étapes précédentes de l'étape 1 et vérifiez que la version keras est 2.2.4.
print(keras.__version__)
2.2.4
Exécutez tout le code ci-dessous.
def target_category_loss(x, category_index, nb_classes):
return tf.multiply(x, K.one_hot([category_index], nb_classes))
def target_category_loss_output_shape(input_shape):
return input_shape
def normalize(x):
# utility function to normalize a tensor by its L2 norm
return x / (K.sqrt(K.mean(K.square(x))) + 1e-5)
def load_image(path):
#img_path = sys.argv[1]
img_path = path
#Lire le fichier image spécifié par l'argument
#La taille est redimensionnée à la valeur par défaut de VGG16 de 224x224
img = image.load_img(img_path, target_size=(224, 224))
#Convertir l'image au format PIL lue en tableau
x = image.img_to_array(img)
#Tenseur 3D (lignes), cols, channels)À
#Tenseur 4D(samples, rows, cols, channels)Conversion en
#Puisqu'il n'y a qu'une seule image d'entrée, les échantillons=1 va bien
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
return x
def register_gradient():
#Si GuidedBackProp n'est pas enregistré, inscrivez-vous
if "GuidedBackProp" not in ops._gradient_registry._registry:
#Décorateur pour enregistrer un dégradé fait maison
#Cette fois_Fonction GuidedBackProp"GuidedBackProp"Inscrivez-vous en tant que
@ops.RegisterGradient("GuidedBackProp")
def _GuidedBackProp(op, grad):
'''Des gradients qui se sont propagés vers l'arrière, propagation vers l'avant/La rétropropagation est effectuée en définissant uniquement les cellules avec une valeur de rétro-propagation négative à 0'''
dtype = op.inputs[0].dtype
# grad :Gradient de rétropropagation
# tf.cast(grad > 0., dtype) :1 pour les cellules avec une gradation de 0 ou plus,Les cellules inférieures à 0 sont 0 matrices
# tf.cast(op.inputs[0] > 0., dtype) :1 pour les cellules avec 0 entrées ou plus,Les cellules inférieures à 0 sont 0 matrices
return grad * tf.cast(grad > 0., dtype) * \
tf.cast(op.inputs[0] > 0., dtype)
def compile_saliency_function(model, activation_layer='block5_conv3'):
'''Création d'une fonction qui calcule le gradient de l'entrée par rapport à la valeur maximale dans la direction du canal du calque spécifié'''
#Modèle d'entrée
input_img = model.input
#Conservez la couche après la couche d'entrée comme dictionnaire de noms de couches et d'instances
layer_dict = dict([(layer.name, layer) for layer in model.layers[1:]])
#Obtenez la sortie de l'instance avec le nom du calque spécifié par la forme d'argument=(?, 14, 14, 512)
layer_output = layer_dict[activation_layer].output
#Forme qui prend la valeur maximale dans la direction du canal=(?, 14, 14)
max_output = K.max(layer_output, axis=3)
#Une fonction qui calcule le gradient de l'entrée par rapport à la valeur maximale dans la direction du canal du calque spécifié.
saliency = K.gradients(K.sum(max_output), input_img)[0]
return K.function([input_img, K.learning_phase()], [saliency])
def modify_backprop(model, name):
'''Le gradient de la fonction ReLU"name"Remplacer par un dégradé'''
#ReLU avec"name"Remplacé par
g = tf.get_default_graph()
with g.gradient_override_map({'Relu': name}):
#▽▽▽▽▽ Question 4:Est-il nécessaire de remplacer le relu du modèle d'argument même si le nouveau modèle est renvoyé?? ▽▽▽▽▽
#Extraire et organiser uniquement les calques activés
# get layers that have an activation
layer_dict = [layer for layer in model.layers[1:]
if hasattr(layer, 'activation')]
#Remplacement de Keras RelU par tensorflow ReLU
# replace relu activation
for layer in layer_dict:
if layer.activation == keras.activations.relu:
layer.activation = tf.nn.relu
# △△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△
#Instancier un nouveau modèle
#Modifiez ici lorsque vous utilisez votre propre modèle
# re-instanciate a new model
new_model = VGG16(weights='imagenet')
return new_model
def deprocess_image(x):
'''
Same normalization as in:
https://github.com/fchollet/keras/blob/master/examples/conv_filter_visualization.py
'''
if np.ndim(x) > 3:
x = np.squeeze(x)
# normalize tensor: center on 0., ensure std is 0.1
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
# clip to [0, 1]
x += 0.5
x = np.clip(x, 0, 1)
# convert to RGB array
x *= 255
if K.image_dim_ordering() == 'th':
x = x.transpose((1, 2, 0))
x = np.clip(x, 0, 255).astype('uint8')
return x
def grad_cam(input_model, image, category_index, layer_name):
'''
Parameters
----------
input_model : model
Modèle Keras à évaluer
image :tuple etc.
Image d'entrée(Nombre de feuilles,Verticale,côté,Canal)
category_index : int
Classe de classification d'image d'entrée
layer_name : str
Nom de la couche d'activation après la dernière couche de conv..
Si l'activation est spécifiée dans la dernière couche de conv., Le nom de la couche de conv..
batch_Si l'activation n'est pas spécifiée dans la couche conv, comme lors de l'utilisation de la normalisation,
Nom de la couche d'activation après cela.
Returns
----------
cam : tuple
Grad-Image de la caméra
heatmap : tuple
Image de la carte thermique
'''
#Nombre de classes de classification
nb_classes = 1000
# ----- 1.Calculer la classe de prédiction de l'image d'entrée-----
#Catégorie d'entrée_index est la classe attendue
# ----- 2.Calculer la perte pour la classe de prédiction-----
#Données d'entrée x catégorie_Définition du traitement à mettre à 0 autre que l'index spécifié par index
target_layer = lambda x: target_category_loss(x, category_index, nb_classes)
#Entrée d'argument_cible après la couche de sortie du modèle_couche Ajouter une couche
#Si vous prédisez le modèle, les valeurs autres que la classe de prédiction seront 0.
x = input_model.layers[-1].output
x = Lambda(target_layer, output_shape=target_category_loss_output_shape)(x)
model = keras.models.Model(input_model.layers[0].input, x)
#Étant donné que les valeurs autres que la classe de prédiction sont 0, la somme est prise et seule la valeur de la classe de prédiction est extraite.
loss = K.sum(model.layers[-1].output)
#Couche d'argument_couche de nom(Dernière couche de conv.)Obtenez la sortie de
conv_output = [l for l in model.layers if l.name is layer_name][0].output
# ----- 3.Rétropropagation de la classe de prédiction Loss à la dernière couche de conv.(Pente)Calculer-----
#Définissez une fonction pour calculer le gradient de la valeur de la classe attendue à la dernière couche de conv.
#De la fonction définie
#contribution: [Image que vous voulez juger.shape=(1, 224, 224, 3)]、
#production: [Valeur de sortie de la dernière couche de conv..shape=(1, 14, 14, 512),Gradient entre la valeur de classe attendue et la dernière couche de conv..shape=(1, 14, 14, 512)]
grads = normalize(K.gradients(loss, conv_output)[0])
gradient_function = K.function([model.layers[0].input], [conv_output, grads])
#Calculer avec la fonction de calcul de gradient définie et formater les dimensions des données
#Après la mise en forme
# output.shape=(14, 14, 512), grad_val.shape=(14, 14, 512)
output, grads_val = gradient_function([image])
output, grads_val = output[0, :], grads_val[0, :, :, :]
# ----- 4.Calculez le gradient moyen pour chaque canal dans la dernière couche de conv et l'importance de chaque canal(poids)À-----
# weights.shape=(512, )
# cam.shape=(14, 14)
#* Question 1: L'initialisation de la caméra n'a-t-elle pas besoin d'être des zéros??
weights = np.mean(grads_val, axis = (0, 1))
cam = np.ones(output.shape[0 : 2], dtype = np.float32)
#cam = np.zeros(output.shape[0 : 2], dtype = np.float32) #Utilisez ceci dans mon propre modèle
# ----- 5.La sortie de propagation directe de la dernière couche conv est pondérée pour chaque canal, additionnée et transmise par ReLU.-----
#La sortie de propagation directe de la dernière couche conv est pondérée pour chaque canal et additionnée.
for i, w in enumerate(weights):
cam += w * output[:, :, i]
#Redimensionner à la taille de l'image d'entrée(14, 14) → (224, 224)
cam = cv2.resize(cam, (224, 224))
#Remplacez les valeurs négatives par 0. Le traitement est le même que ReLU.
cam = np.maximum(cam, 0)
#Valeur 0~Normalisé à 1.
#* Question 2: (cam - np.min(cam))/(np.max(cam) - np.min(cam))N'est-ce pas nécessaire?
heatmap = cam / np.max(cam)
#heatmap = (cam - np.min(cam))/(np.max(cam) - np.min(cam)) #Utilisez ceci dans mon propre modèle
# ----- 6.Multipliez l'image d'entrée et la carte thermique-----
#La valeur de l'image de l'image d'entrée est 0~Normalisé à 255. image.shape=(1, 224, 224, 3) → (224, 224, 3)
#Return to BGR [0..255] from the preprocessed image
image = image[0, :]
image -= np.min(image)
#* Question 3: np.uint8(image / np.max(image))N'a pas à être?
image = np.minimum(image, 255)
#Définissez la valeur de la carte de chaleur sur 0~Faites-en 255 et faites-en une carte de couleurs(3 canaux)
cam = cv2.applyColorMap(np.uint8(255*heatmap), cv2.COLORMAP_JET)
#Ajout de l'image d'entrée et de la carte thermique
cam = np.float32(cam) + np.float32(image)
#Valeur 0~Normalisé à 255
cam = 255 * cam / np.max(cam)
return np.uint8(cam), heatmap
Entrez le code ci-dessous. Spécifiez une image pour l'image. (Spécifiez mydog7.jpg dans cet exemple)
# cd '/content/drive/'My Drive/'Colab Notebooks'Déplacer vers le dossier de travail dans
%cd '/content/drive/'My Drive/Colab Notebooks/Self_Study/02_mydog_or_otherdogs/
#① Lire l'image d'entrée
#Changez ici pour convertir l'image d'entrée
# preprocessed_input = load_image(sys.argv[1])
preprocessed_input = load_image("./use_data/train/mydog/mydog07.jpg ")
Dans cet exemple, j'ai spécifié cette image.
--Chargez le modèle VGG16. Cette fois, nous utiliserons le modèle formé avec ImageNet tel quel. (Je voudrais mettre en œuvre les poids qui déterminent la différence entre mon enfant et les autres enfants à un autre moment.)
#② Chargez le modèle
#Changez ici si vous utilisez votre propre modèle
model = VGG16(weights='imagenet')
model.summary()
Le modèle devrait ressembler à ceci:
Model: "vgg16"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 224, 224, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
_________________________________________________________________
flatten (Flatten) (None, 25088) 0
_________________________________________________________________
fc1 (Dense) (None, 4096) 102764544
_________________________________________________________________
fc2 (Dense) (None, 4096) 16781312
_________________________________________________________________
predictions (Dense) (None, 1000) 4097000
=================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
_________________________________________________________________
Exécutez le code suivant.
#③ Probabilité de prédiction de l'image d'entrée(predictions)Et classe prédictive(predicted_class)Calculs de
#Top lors de l'utilisation de modèles autres que VGG16_1=~3 lignes de commentaire
predictions = model.predict(preprocessed_input)
top_1 = decode_predictions(predictions)[0][0]
print('Predicted class:')
print('%s (%s) with probability %.2f' % (top_1[1], top_1[0], top_1[2]))
predicted_class = np.argmax(predictions)
# ④ Grad-Calcul de came
#Dans le cas d'un modèle self-made, l'argument"block5_conv3"Modification du nom de la couche de conversion finale du modèle personnalisé.
cam, heatmap = grad_cam(model, preprocessed_input, predicted_class, "block5_conv3")
#⑤ Enregistrer l'image
cv2.imwrite("gradcam.jpg ", cam)
Une image de heatmap Grad-CAM sera générée dans le dossier ** 02_mydog_or_otherdogs **.
Exécutez tout le code suivant.
#① Implémentation du gradient pour la propagation arrière guidée
register_gradient()
#(2) Remplacez le calcul du gradient de ReLU par le calcul du gradient de la propagation arrière guidée
guided_model = modify_backprop(model, 'GuidedBackProp')
#③ Définition de la fonction pour le calcul GaidedBackPropagation
#Lorsque vous utilisez votre propre classe, spécifiez le nom de la dernière couche de convection en plus de cet argument
saliency_fn = compile_saliency_function(guided_model)
#④ Calcul de la propagation arrière gaidée
saliency = saliency_fn([preprocessed_input, 0])
# ⑤ Guided Grad-Calcul CAM
gradcam = saliency[0] * heatmap[..., np.newaxis]
#⑥ Enregistrer l'image
cv2.imwrite("guided_gradcam.jpg ", deprocess_image(gradcam))
Une image de carte thermique de Guided Grad-CAM sera générée dans le dossier ** 02_mydog_or_otherdogs **.
Ça sort comme ça. Certes, je pense qu'il capture les caractéristiques sous une forme facile à comprendre pour les humains, plutôt que de regarder la carte thermique.
Ci-dessous, je posterai certains traités par mydog et otherdogs. mydog otherdogs
Cette fois, nous avons créé une image qui capture les fonctionnalités de Grad-CAM et Guided Grad-CAM. Étant donné que l'expression de la quantité de caractéristiques dans chaque méthode qui apparaît dans l'image est très différente, lors de l'explication de la caractéristique capturée par le deeplearning, il est préférable d'utiliser autant que possible diverses perspectives et de rechercher une compréhension globale. Cela semble possible. Je voudrais continuer à publier sur diverses méthodes.
Recommended Posts