[PYTHON] Implémentation Score-CAM avec keras. Comparaison avec Grad-CAM

Aperçu

compare.png

class6.jpg

Qu'est-ce que Score-CAM?

--Une des méthodes de visualisation de la base de jugement de CNN

environnement

Procédure Score-CAM

  1. Obtenez la carte d'activation et développez-la à la même taille que la première entrée avec complétion bilinéaire
  2. Normaliser chaque canal à l'intervalle [0,1]
  3. Pour l'image d'entrée, préparez autant d'images masquées que le nombre de canaux en multipliant chaque canal.
  4. Passez chaque image masquée via CNN pour obtenir le tableau après le calcul softmax.
  5. Définissez l'importance de chaque canal par le score de la classe cible après soft max.
  6. La carte de score finale est celle obtenue en ajoutant l'importance de chaque canal et en effectuant le calcul ReLU. (Je ne suis pas intéressé par les éléments négatifs via ReLU)

Mis en œuvre avec keras

import cv2
import numpy as np
from keras.models import Model

def ScoreCam(model, img_array, layer_name, max_N=-1):

    cls = np.argmax(model.predict(img_array))
    act_map_array = Model(inputs=model.input, outputs=model.get_layer(layer_name).output).predict(img_array)
    
    # extract effective maps
    if max_N != -1:
        act_map_std_list = [np.std(act_map_array[0,:,:,k]) for k in range(act_map_array.shape[3])]
        unsorted_max_indices = np.argpartition(-np.array(act_map_std_list), max_N)[:max_N]
        max_N_indices = unsorted_max_indices[np.argsort(-np.array(act_map_std_list)[unsorted_max_indices])]
        act_map_array = act_map_array[:,:,:,max_N_indices]

    input_shape = model.layers[0].output_shape[1:]  # get input shape
    # 1. upsampled to original input size
    act_map_resized_list = [cv2.resize(act_map_array[0,:,:,k], input_shape[:2], interpolation=cv2.INTER_LINEAR) for k in range(act_map_array.shape[3])]
    # 2. normalize the raw activation value in each activation map into [0, 1]
    act_map_normalized_list = []
    for act_map_resized in act_map_resized_list:
        if np.max(act_map_resized) - np.min(act_map_resized) != 0:
            act_map_normalized = act_map_resized / (np.max(act_map_resized) - np.min(act_map_resized))
        else:
            act_map_normalized = act_map_resized
        act_map_normalized_list.append(act_map_normalized)
    # 3. project highlighted area in the activation map to original input space by multiplying the normalized activation map
    masked_input_list = []
    for act_map_normalized in act_map_normalized_list:
        masked_input = np.copy(img_array)
        for k in range(3):
            masked_input[0,:,:,k] *= act_map_normalized
        masked_input_list.append(masked_input)
    masked_input_array = np.concatenate(masked_input_list, axis=0)
    # 4. feed masked inputs into CNN model and softmax
    pred_from_masked_input_array = softmax(model.predict(masked_input_array))
    # 5. define weight as the score of target class
    weights = pred_from_masked_input_array[:,cls]
    # 6. get final class discriminative localization map as linear weighted combination of all activation maps
    cam = np.dot(act_map_array[0,:,:,:], weights)
    cam = np.maximum(0, cam)  # Passing through ReLU
    cam /= np.max(cam)  # scale 0 to 1.0
    
    return cam

def softmax(x):
    f = np.exp(x)/np.sum(np.exp(x), axis = 1, keepdims = True)
    return f

Score-CAM avec VGG16

Appliquons-le à VGG16 qui a été formé avec ImageNet.

Chargement d'image

from keras.preprocessing.image import load_img
import matplotlib.pyplot as plt

orig_img = np.array(load_img('./image/hummingbird.jpg'),dtype=np.uint8)
plt.imshow(orig_img)
plt.show()

hummingbird.png

Carte obtenue par Score-CAM

from keras.applications.vgg16 import VGG16
from gradcamutils import read_and_preprocess_img
import matplotlib.pyplot as plt

model = VGG16(include_top=True, weights='imagenet')
layer_name = 'block5_conv3'
img_array = read_and_preprocess_img('./image/hummingbird.jpg', size=(224,224))

score_cam = ScoreCam(model,img_array,layer_name)

plt.imshow(score_cam)
plt.show()

heatmap.png

Explication de l'argument

--model: instance de modèle de keras --img_array: Données prétraitées de l'image pour laquelle vous souhaitez déterminer la zone de regard. Il doit être sous une forme qui peut exécuter predire immédiatement, comme model.predict (img_array). --layer_name: Le nom de la couche d'activation immédiatement après la couche de convolution finale. Si la couche d'activation est incluse dans la couche de convolution, le nom de la couche de convolution peut être utilisé. Vous pouvez vérifier le nom du calque avec model.summary (). --max_N: La valeur de réglage pour accélérer que j'ai implémentée sans autorisation. Si «-1», le score-CAM d'origine. La spécification d'un nombre naturel réduit le nombre d'inférences CNN à ce nombre. La valeur recommandée est d'environ 10. Une valeur élevée ne fera qu'augmenter le temps de traitement et n'aura pas beaucoup d'effet sur la carte thermique, mais si elle est trop petite, la carte thermique deviendra étrange.

Autres mises en garde

Comparer avec Grad-CAM, Grad-CAM ++

Affichons la carte thermique obtenue ci-dessus au-dessus de l'image d'origine.

Pour Grad-CAM et Grad-CAM ++, j'ai utilisé le code de gradcam ++ for keras.

Le code d'exécution peut être trouvé sur github.

sample_outputs.png

«L'image affichée comme« accentuée »montre la zone du regard plus clairement en ajoutant un traitement de seuil à la carte thermique. --Score-CAM semble capter la zone du regard de manière uniforme. --Guided Backpropagation est affiché pour le moment, mais comme indiqué dans here, ** ne reflète pas les informations du réseau neuronal. Il y a un doute **. ――Si vous souhaitez extraire les contours que vous regardez, il est toujours préférable d'afficher le dégradé sous forme d'image **, de sorte que l'image du bas est celle qui est superposée en calculant le dégradé sous forme d'image.

Seuls les résultats sont affichés pour les autres images.

dog.png

spoonbill.png

border_collie.png

Comparaison de la vitesse de traitement

Mesurez la vitesse de traitement avec Google cola boratory. Utilisez GPU.

print("Grad-CAM")
%timeit grad_cam = GradCam(model, img_array, layer_name)
print("Grad-CAM++")
%timeit grad_cam_plus_plus = GradCamPlusPlus(model, img_array, layer_name)
print("Score-Cam")
%timeit score_cam = ScoreCam(model, img_array, layer_name)
print("Faster-Score-Cam N=10")
%timeit faster_score_cam = ScoreCam(model, img_array, layer_name, max_N=10)
print("Faster-Score-Cam N=3")
%timeit faster_score_cam = ScoreCam(model, img_array, layer_name, max_N=3)
print("Guided-BP}")
%timeit saliency = GuidedBackPropagation(guided_model, img_array, layer_name)
Grad-CAM
1 loop, best of 3: 196 ms per loop
Grad-CAM++
1 loop, best of 3: 221 ms per loop
Score-Cam
1 loop, best of 3: 5.24 s per loop
Faster-Score-Cam N=10
1 loop, best of 3: 307 ms per loop
Faster-Score-Cam N=3
The slowest run took 4.45 times longer than the fastest. This could mean that an intermediate result is being cached.
1 loop, best of 3: 238 ms per loop
Guided-BP}
1 loop, best of 3: 415 ms per loop

Comme vous pouvez le voir, ** Score-CAM est très lourd **. ** Cela prend plus de 25 fois plus de temps que Grad-CAM **.

Amélioration de la vitesse de traitement (Faster-Score-CAM)

Quand j'ai expérimenté la sortie de la couche de convolution finale (512 canaux pour VGG16), j'ai pensé que plusieurs canaux étaient dominants dans la génération de la carte thermique finale. ** De chaque canal, la variable latente Faster-Score-CAM est celui avec le traitement de ** qui utilise préférentiellement celui avec une grande distribution de carte comme image de masque. (Je lui ai donné un nom sans autorisation. Si vous définissez max_N = -1, ce sera le Score-CAM d'origine)

L'effet est tel que décrit dans ** Comparaison de la vitesse de traitement **, et il est possible d'augmenter la vitesse de ** 10 fois ou plus **. Pourtant, Grad-CAM ++ est plus rapide.

Lorsque vous utilisez votre propre modèle

Pour confirmer son caractère pratique, nous appliquerons Score-CAM à notre propre modèle formé avec un ensemble de données ouvert.

Ensemble de données DAGM est utilisé comme ensemble de données, et ResNet (un peu profond avec environ 80 couches) est utilisé comme modèle.

Préparation du jeu de données DAGM

Veuillez réécrire le chemin dagm_path de l'emplacement où vous avez téléchargé et décompressé DAGM Dataset comme il convient.

from keras.utils import to_categorical
import numpy as np  
import glob
from sklearn.model_selection import train_test_split  
from gradcamutils import read_and_preprocess_img

num_classes = 2                               
img_size = (224,224)
dagm_path = "./DAGM"

def get_dagm_data(names):
    x = []
    y = []
    for i, name in enumerate(names):
        for path in glob.glob(f"{dagm_path}/{name}/*.png "):    
            img_array = read_and_preprocess_img(path, size=img_size)
            x.append(img_array)  
            y.append(i) 

    x = np.concatenate(x, axis=0)   
    y = np.array(y)  

    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=111)

    y_train = to_categorical(y_train, num_classes)
    y_test = to_categorical(y_test, num_classes)

    print(x_train.shape[0], 'train samples')
    print(x_test.shape[0], 'test samples')
    return x_train, x_test, y_train, y_test

x_train, x_test, y_train, y_test = get_dagm_data(["Class1","Class1_def"])

Préparation ResNet

Je le porte sur le côté et utilise celui coupé de ResNet dans les applications de keras. (Ce n'est pas une très bonne écriture, mais ça marche, donc c'est bien)

from keras.applications.resnet50 import ResNet50
from keras.models import Model
from keras.optimizers import Adam
from keras.layers import Dense, Input, Activation, GlobalAveragePooling2D
from keras.callbacks import EarlyStopping, ModelCheckpoint

def build_ResNet():
    model = ResNet50(include_top=True, input_tensor=Input(shape=(img_size[0],img_size[1],3)))

    x = model.layers[-98].output
    x = Activation('relu', name="act_last")(x)
    x = GlobalAveragePooling2D()(x)
    x = Dense(2, name="dense_out")(x)
    outputs = Activation('softmax')(x)

    model = Model(model.input, outputs)
    # model.summary()

    model.compile(loss='binary_crossentropy',
                  optimizer=Adam(amsgrad=True),
                  metrics=['accuracy'])
    return model

model = build_ResNet()

es_cb = EarlyStopping(monitor='val_loss', patience=5, verbose=1, mode='auto')
chkpt = './resnet_weight_DAGM.h5'
cp_cb = ModelCheckpoint(filepath = chkpt, monitor='val_loss', verbose=1, save_best_only=True, save_weights_only=True, mode='auto')

epochs = 15
batch_size = 32

history = model.fit(x_train, y_train,
                    batch_size=batch_size,
                    epochs=epochs,
                    validation_data=(x_test, y_test),
                    callbacks=[es_cb,cp_cb],
                    class_weight={0: 1., 1: 6.},
                    shuffle=True)

#Poids de charge
model.load_weights('./resnet_weight_DAGM.h5')

Appliquer Grad-CAM, ++, Score-CAM aux images rayées

import matplotlib.pyplot as plt
import cv2
import numpy as np
from gradcamutils import GradCam, GradCamPlusPlus, ScoreCam, GuidedBackPropagation, superimpose, read_and_preprocess_img

def build_ResNet_and_load():
    model = build_ResNet()
    model.load_weights('./resnet_weight_DAGM.h5')
    return model

img_path = f'{dagm_path}/Class1_def/12.png'
orig_img = np.array(load_img(img_path),dtype=np.uint8)
img_array = read_and_preprocess_img(img_path, size=(224,224))

layer_name = "act_last"

grad_cam=GradCam(model,img_array,layer_name)
grad_cam_superimposed = superimpose(img_path, grad_cam)
grad_cam_emphasized = superimpose(img_path, grad_cam, emphasize=True)

grad_cam_plus_plus=GradCamPlusPlus(model,img_array,layer_name)
grad_cam_plus_plus_superimposed = superimpose(img_path, grad_cam_plus_plus)
grad_cam_plus_plus_emphasized = superimpose(img_path, grad_cam_plus_plus, emphasize=True)

score_cam=ScoreCam(model,img_array,layer_name)
score_cam_superimposed = superimpose(img_path, score_cam)
score_cam_emphasized = superimpose(img_path, score_cam, emphasize=True)

faster_score_cam=ScoreCam(model,img_array,layer_name, max_N=10)
faster_score_cam_superimposed = superimpose(img_path, faster_score_cam)
faster_score_cam_emphasized = superimpose(img_path, faster_score_cam, emphasize=True)

guided_model = build_guided_model(build_ResNet_and_load)
saliency = GuidedBackPropagation(guided_model, img_array, layer_name)
saliency_resized = cv2.resize(saliency, (orig_img.shape[1], orig_img.shape[0]))

grad_cam_resized = cv2.resize(grad_cam, (orig_img.shape[1], orig_img.shape[0]))
guided_grad_cam = saliency_resized * grad_cam_resized[..., np.newaxis]

grad_cam_plus_plus_resized = cv2.resize(grad_cam_plus_plus, (orig_img.shape[1], orig_img.shape[0]))
guided_grad_cam_plus_plus = saliency_resized * grad_cam_plus_plus_resized[..., np.newaxis]

score_cam_resized = cv2.resize(score_cam, (orig_img.shape[1], orig_img.shape[0]))
guided_score_cam = saliency_resized * score_cam_resized[..., np.newaxis]

faster_score_cam_resized = cv2.resize(faster_score_cam, (orig_img.shape[1], orig_img.shape[0]))
guided_faster_score_cam = saliency_resized * faster_score_cam_resized[..., np.newaxis]

img_gray = cv2.imread(img_path, 0)
dx = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3)
dy = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3)
grad = np.sqrt(dx ** 2 + dy ** 2)  #Obtenir un dégradé d'image
grad = cv2.dilate(grad,kernel=np.ones((5,5)), iterations=1)  #Processus d'engraissement
grad -= np.min(grad)
grad /= np.max(grad)  # scale 0. to 1.

grad_times_grad_cam = grad * grad_cam_resized
grad_times_grad_cam_plus_plus = grad * grad_cam_plus_plus_resized
grad_times_score_cam = grad * score_cam_resized
grad_times_faster_score_cam = grad * faster_score_cam_resized

fig, ax = plt.subplots(nrows=4,ncols=5, figsize=(18, 16))
ax[0,0].imshow(orig_img)
ax[0,0].set_title("input image")
ax[0,1].imshow(grad_cam_superimposed)
ax[0,1].set_title("Grad-CAM")
ax[0,2].imshow(grad_cam_plus_plus_superimposed)
ax[0,2].set_title("Grad-CAM++")
ax[0,3].imshow(score_cam_superimposed)
ax[0,3].set_title("Score-CAM")
ax[0,4].imshow(faster_score_cam_superimposed)
ax[0,4].set_title("Faster-Score-CAM")
ax[1,0].imshow(orig_img)
ax[1,0].set_title("input image")
ax[1,1].imshow(grad_cam_emphasized)
ax[1,1].set_title("Grad-CAM emphasized")
ax[1,2].imshow(grad_cam_plus_plus_emphasized)
ax[1,2].set_title("Grad-CAM++ emphasized")
ax[1,3].imshow(score_cam_emphasized)
ax[1,3].set_title("Score-CAM emphasized")
ax[1,4].imshow(faster_score_cam_emphasized)
ax[1,4].set_title("Faster-Score-CAM emphasized")
ax[2,0].imshow(saliency_resized)
ax[2,0].set_title("Guided-BP")
ax[2,1].imshow(guided_grad_cam)
ax[2,1].set_title("Guided-Grad-CAM")
ax[2,2].imshow(guided_grad_cam_plus_plus)
ax[2,2].set_title("Guided-Grad-CAM++")
ax[2,3].imshow(guided_score_cam)
ax[2,3].set_title("Guided-Score-CAM")
ax[2,4].imshow(guided_faster_score_cam)
ax[2,4].set_title("Guided-Faster-Score-CAM")
ax[3,0].imshow(grad, 'gray')
ax[3,0].set_title("grad")
ax[3,1].imshow(grad_times_grad_cam, 'gray')
ax[3,1].set_title("grad * Grad-CAM")
ax[3,2].imshow(grad_times_grad_cam_plus_plus, 'gray')
ax[3,2].set_title("grad * Grad-CAM++")
ax[3,3].imshow(grad_times_score_cam, 'gray')
ax[3,3].set_title("grad * Score-CAM")
ax[3,4].imshow(grad_times_faster_score_cam, 'gray')
ax[3,4].set_title("grad * Faster-Score-CAM")
for i in range(4):
    for j in range(5):
        ax[i,j].axis('off')
plt.show()

class1.png

――Il semble que toutes les méthodes permettent de bien détecter la position du scratch. ――Il semble difficile d'afficher quelque chose qui ne souligne que les rayures. (Cela ne peut pas être aidé car les informations de la couche de conversion finale sont supprimées)

De la classe 2 à la classe 6

Seuls les résultats du traitement des seuils seront affichés pour 5 feuilles dans chaque classe.

Class2

Class2_result_0.png

Class3

Class3_result_0.png

Class4

Class4_result_0.png

Class5

Class5_result_0.png

Class6

Class6_result_0.png

Il semble que la position de la rayure puisse être représentée presque correctement.

Impressions

-Dans la ** détection d'anomalies **, il peut être utilisé pour une visualisation approximative des pièces anormales. --Il est très gênant pour Grad-CAM, ++ et Score-CAM de ** limiter les modèles qui peuvent être utilisés **.

référence

Recommended Posts

Implémentation Score-CAM avec keras. Comparaison avec Grad-CAM
Easy Grad-CAM avec pytorch-gradcam
Tutoriel CIFAR-10 avec Keras
LSTM multivarié avec Keras
J'ai essayé d'implémenter Grad-CAM avec keras et tensorflow
Implémentation CNN avec juste numpy
Installation de Keras (utilisée par Anaconda)
Notes AutoEncodder avec Keras
Implémentation de word2vec avec Theano + Keras
Génération de phrases avec GRU (keras)
Réglage des paramètres Keras avec Keras Tuner
Créez facilement des CNN avec Keras
Implémentation d'un GAN efficace avec keras
Reconnaissance d'image avec Keras + OpenCV
Implémentation Score-CAM avec keras. Comparaison avec Grad-CAM
Implémentation de Light CNN (Python Keras)
Implémentation simple de l'analyse de régression avec Keras
Mise en œuvre de la fermeture
installation de keras