[PYTHON] Score-CAM-Implementierung mit Keras. Vergleich mit Grad-CAM

Überblick

compare.png

class6.jpg

Was ist Score-CAM?

Umgebung

Score-CAM-Verfahren

  1. Holen Sie sich die Aktivierungskarte und erweitern Sie sie auf die gleiche Größe wie die erste Eingabe mit bilinearer Vervollständigung
  2. Normalisieren Sie jeden Kanal auf das Intervall [0,1].
  3. Bereiten Sie für das Eingabebild so viele maskierte Bilder wie die Anzahl der Kanäle vor, indem Sie jeden Kanal multiplizieren.
  4. Führen Sie jedes maskierte Bild durch CNN, um das Array nach der Softmax-Berechnung zu erhalten.
  5. Definieren Sie die Wichtigkeit jedes Kanals durch die Punktzahl der Zielklasse nach soft max.
  6. Die endgültige Score-Map wird erhalten, indem die Wichtigkeit jedes Kanals addiert und die ReLU-Berechnung durchgeführt wird. (Ich bin nicht an den negativen Elementen durch ReLU interessiert)

Implementiert mit 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 mit VGG16

Wenden wir es auf VGG16 an, das mit ImageNet trainiert wurde.

Bild wird geladen

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

Karte von Score-CAM erhalten

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

Argument Erklärung

--model: Modellinstanz von Keras --img_array: Vorverarbeitete Daten des Bildes, für das Sie den Blickbereich bestimmen möchten. Es sollte in einer Form vorliegen, die "Vorhersagen" sofort ausführen kann, z. B. "model.predict (img_array)". --layer_name: Der Name der Aktivierungsschicht unmittelbar nach der letzten Faltungsschicht. Wenn die Aktivierungsschicht in der Faltungsschicht enthalten ist, kann der Name der Faltungsschicht verwendet werden. Sie können den Layernamen mit model.summary () überprüfen. --max_N: Der Einstellungswert für die Beschleunigung, den ich ohne Erlaubnis implementiert habe. Wenn -1, die ursprüngliche Score-CAM. Durch die Angabe einer natürlichen Zahl wird die Anzahl der CNN-Inferenzen auf diese Zahl reduziert. Der empfohlene Wert beträgt ca. 10. Ein großer Wert erhöht nur die Verarbeitungszeit und wirkt sich nicht so stark auf die Wärmekarte aus. Wenn er jedoch zu klein ist, ist die Wärmekarte seltsam.

Andere Vorsichtsmaßnahmen

Vergleiche mit Grad-CAM, Grad-CAM ++

Lassen Sie uns die oben erhaltene Wärmekarte über dem Originalbild anzeigen.

Für Grad-CAM und Grad-CAM ++ habe ich den Code von [gradcam ++ für Keras] verwendet (https://github.com/totti0223/gradcamplusplus).

Der Ausführungscode finden Sie unter github.

sample_outputs.png

――Das als "hervorgehoben" angezeigte Bild zeigt den Blickbereich deutlicher, indem der Wärmekarte eine Schwellenwertverarbeitung hinzugefügt wird. --Score-CAM scheint den Blickbereich gleichmäßig aufzunehmen. --Guided Backpropagation wird vorerst veröffentlicht, aber wie in hier ausgeführt, spiegelt ** nicht die Informationen des neuronalen Netzes wider. Es gibt Zweifel **.

Für andere Bilder werden nur die Ergebnisse angezeigt.

dog.png

spoonbill.png

border_collie.png

Vergleich der Verarbeitungsgeschwindigkeit

Messen Sie die Verarbeitungsgeschwindigkeit mit Google Cola Boratory. Verwenden Sie eine 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

Wie Sie sehen können, ist ** Score-CAM sehr schwer **. ** Es dauert mehr als 25 Mal länger als Grad-CAM **.

Verbesserung der Verarbeitungsgeschwindigkeit (Faster-Score-CAM)

Als ich mit der Ausgabe der endgültigen Faltungsschicht (512 Kanäle für VGG16) experimentierte, dachte ich, dass mehrere Kanäle bei der Erzeugung der endgültigen Wärmekarte dominieren. ** Von jedem Kanal die latente Variable Faster-Score-CAM ist das mit der Verarbeitung von **, das vorzugsweise das mit einer großen Kartendispersion als Maskenbild verwendet. (Ich habe ihm ohne Erlaubnis einen Namen gegeben. Wenn Sie "max_N = -1" setzen, ist es das ursprüngliche Score-CAM.)

Der Effekt ist wie in ** Vergleich der Verarbeitungsgeschwindigkeit ** beschrieben und es ist möglich, die Geschwindigkeit um das 10-fache oder mehr zu erhöhen. Trotzdem ist Grad-CAM ++ schneller.

Wenn Sie Ihr eigenes Modell verwenden

Um seine Praktikabilität zu bestätigen, werden wir Score-CAM auf unser eigenes Modell anwenden, das mit einem offenen Datensatz trainiert wurde.

Als Datensatz wird DAGM-Datensatz und als Modell ResNet (flacher Datensatz mit ca. 80 Ebenen) verwendet.

Vorbereitung des DAGM-Datensatzes

Bitte schreiben Sie den Pfad "dagm_path" des Speicherorts, an den Sie [DAGM Dataset] heruntergeladen und entpackt haben (https://resources.mpi-inf.mpg.de/conference/dagm/2007/prizes.html), entsprechend.

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

ResNet-Vorbereitung

Ich trage es seitwärts und verwende den von ResNet geschnittenen für die Anwendung von Keras. (Es ist nicht sehr gut zu schreiben, aber es funktioniert, also ist es gut)

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)

#Lastgewicht
model.load_weights('./resnet_weight_DAGM.h5')

Wenden Sie Grad-CAM, ++, Score-CAM auf zerkratzte Bilder an

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)  #Bildverlauf abrufen
grad = cv2.dilate(grad,kernel=np.ones((5,5)), iterations=1)  #Mastprozess
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

――Es scheint, dass alle Methoden die Position des Kratzers gut erkennen können. ――Es scheint schwierig zu sein, etwas anzuzeigen, das nur Kratzer hervorhebt. (Es kann nicht geholfen werden, da die Informationen der letzten Conv-Schicht entfernt werden.)

Von Klasse 2 bis Klasse 6

Nur die Ergebnisse der Schwellenwertverarbeitung werden für 5 Blätter in jeder Klasse veröffentlicht.

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

Es scheint, dass die Position des Kratzers fast korrekt dargestellt werden kann.

Impressionen

-In ** Anomalieerkennung ** kann es zur groben Visualisierung abnormaler Teile verwendet werden.

Referenz

Recommended Posts

Score-CAM-Implementierung mit Keras. Vergleich mit Grad-CAM
Easy Grad-CAM mit Pytorch-Gradcam
CIFAR-10-Tutorial mit Keras
Multivariates LSTM mit Keras
Ich habe versucht, Grad-CAM mit Keras und Tensorflow zu implementieren
CNN-Implementierung mit nur Numpy
Keras-Installation (von Anaconda verwendet)
AutoEncodder-Notizen mit Keras
Word2vec mit Theano + Keras implementiert
Satzerzeugung mit GRU (Keras)
Optimieren von Keras-Parametern mit Keras Tuner
Erstellen Sie einfach CNNs mit Keras
Effizientes GAN mit Keras implementiert
Bilderkennung mit Keras + OpenCV
Score-CAM-Implementierung mit Keras. Vergleich mit Grad-CAM
Implementierung von Light CNN (Python Keras)
Einfache Implementierung einer Regressionsanalyse mit Keras
Abschlussimplementierung
Installation von Keras