#image-processing #keras #classification #conv-neural-network
#обработка изображений #keras #классификация #conv-нейронная сеть
Вопрос:
Я хочу сгенерировать карту активации классов (CAM) для задачи двоичной классификации. Имеющиеся у меня данные не имеют каких-либо ограничивающих рамок или содержат какие-либо примечания, и это простая проблема двоичной классификации. Пример ввода и вывода данных генерируется в X и y в приведенном ниже коде.
Я использую следующий ссылочный код, реализованный с использованием подхода Grad CAM (https://github.com/gorogoroyasu/mnist-Grad-CAM ) в наборе данных классификации MNIST. Grad CAM, используемый в коде, упоминается в документе (https://arxiv.org/pdf/1610.02391.pdf).
import numpy as np
np.random.seed(37)
import pandas as pd
tf.set_random_seed(89)
import random as rn
rn.seed(1254)
import keras
import tensorflow as tf
from keras import backend as k
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1,
inter_op_parallelism_threads=1)
sess = tf.Session(graph = tf.get_default_graph(), config = session_conf)
k.set_session(sess)
import matplotlib.pyplot as plt
from keras.layers import *
from keras.layers import Activation
from keras.layers.core import Dense, Flatten
from keras.optimizers import Adam
from keras.metrics import categorical_crossentropy
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import *
import itertools
import math
from keras.models import Sequential, Model
from keras.layers import Input, Flatten, Dense, Dropout, Convolution2D, Conv2D, MaxPooling2D, Lambda, GlobalMaxPooling2D, GlobalAveragePooling2D, BatchNormalization, Activation, AveragePooling2D, Concatenate
from keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau
from keras.utils import np_utils
from keras.callbacks import CSVLogger
#%matplotlib inline
keras.backend.set_image_data_format('channels_last')
from keras import initializers
X = np.random.random_integers(0, 1, size=(2237, 95, 95, 1))
y = np.random.random_integers(0, 1, size=(2237, 1))
print("X.shape : ", X.shape) ## (2237, 95, 95, 1)
print("y.shape : ", y.shape) ## (2237, 1)
model_21 = Sequential()
model_21.add(Conv2D(20, kernel_size = (3, 3), strides=(1, 1), activation = 'relu', padding = 'valid', input_shape = X.shape[1:],kernel_initializer=initializers.glorot_uniform(seed=90), bias_initializer='zeros', name = 'conv_lyr_1'))
model_21.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='valid'))
model_21.add(Conv2D(20, kernel_size = (3, 3), strides=(1, 1), activation = 'relu', padding= 'valid', kernel_initializer=initializers.glorot_uniform(seed=90), bias_initializer='zeros', name = 'conv_lyr_2'))
model_21.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='valid'))
model_21.add(GlobalAveragePooling2D())
model_21.add(Dense(1, activation = 'sigmoid'))
model_21.compile(optimizer=Adam(lr = 0.0001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False), loss = 'binary_crossentropy', metrics = ['accuracy'],)
early_stop = EarlyStopping(monitor='loss', patience=5, verbose=1)
hist = model_21.fit(X[:2000], y[:2000], batch_size = 64, epochs = 10, verbose = 2, shuffle=True, validation_split=0.05, callbacks=[early_stop])
Обучение модели не дало нам очень хорошего результата с глобальным средним объединением (хотя упомянутый код не использует глобальное среднее объединение, но я прочитал другие статьи, в которых используется глобальное среднее объединение (GAP) для отображения активации классов, напримерhttps://github.com/metalbubble/CAM). Эта реализация не использовала GAP при построении модели, но использовала его в следующих строках кода: (показано в следующем разделе кода)
# global average pooling
weights = np.mean(conv_grad, axis = (0, 1))
cam = np.zeros(conv_output.shape[0 : 2], dtype = np.float32).
## RESULT OF ABOVE TRAINING
Train on 1900 samples, validate on 100 samples
Epoch 1/10
- 20s - loss: 0.6940 - acc: 0.4947 - val_loss: 0.6932 - val_acc: 0.5100
Epoch 2/10
- 20s - loss: 0.6939 - acc: 0.4711 - val_loss: 0.6934 - val_acc: 0.4500
Epoch 3/10
- 19s - loss: 0.6932 - acc: 0.5026 - val_loss: 0.6937 - val_acc: 0.4900
Epoch 4/10
- 19s - loss: 0.6933 - acc: 0.5011 - val_loss: 0.6935 - val_acc: 0.4900
Epoch 5/10
- 21s - loss: 0.6933 - acc: 0.5026 - val_loss: 0.6935 - val_acc: 0.4900
Epoch 6/10
- 21s - loss: 0.6933 - acc: 0.4905 - val_loss: 0.6936 - val_acc: 0.4900
Epoch 7/10
- 19s - loss: 0.6933 - acc: 0.5021 - val_loss: 0.6939 - val_acc: 0.4900
Epoch 8/10
- 19s - loss: 0.6934 - acc: 0.4911 - val_loss: 0.6933 - val_acc: 0.4800
Epoch 00008: early stopping
Примечание: GAP работает не очень хорошо, и этот пост с меньшим количеством эпох ( https://stats.stackexchange.com/questions/330119/why-global-average-pooling-is-able-to-work-correctly ) и ( https://datascience.stackexchange.com/questions/28120/globalaveragepooling2d-in-inception-v3-example ) может объяснить причину этого.
Поскольку обучение было проведено для первых 2000 точек данных, теперь я буду тестировать изображения для остальных 237 точек данных с 2001 по 2237 точки данных, и мне интересно просмотреть их CAM. Однако из приведенного ниже кода я не понимаю, как я могу использовать conv_grad, input_grad и grad_RGB.
Поскольку мы можем получить веса глобального среднего уровня объединения, мы можем опустить существующий код и напрямую использовать веса слоев с пробелами.
Следующий код находится здесь:
import sys, cv2
from tensorflow.keras.datasets import mnist
from mnist_model import Model as MM
from pathlib import Path
from tensorflow.keras.models import Model
img_rows, img_cols = 300, 400
num_classes = 2
#model=Model(inputs=[m.labels, m.inputs], outputs=[m.predictions, m.g, m.a, m.gb_grad])
for target_y_train_num in range(2000, 2237):
result = model_21.predict(X[target_y_train_num].reshape((-1, 95, 95, 1)))
print('answer: ', K.eval(K.argmax(y[target_y_train_num])))
print('prediction: ', K.eval(K.argmax(result[0])))
print(result) ## [[0.50195]]
conv_grad = result[1] ## What should I use here?? -->> QUESTION
conv_grad = conv_grad.reshape(conv_grad.shape[1:]) ## What should I use here?? -->> QUESTION
conv_output = result[2] ## What should I use here?? -->> QUESTION
conv_output = conv_output.reshape(conv_output.shape[1:]) ## What should I use here??
input_grad = result[3] ## What should I use here?? -->> QUESTION
input_grad = input_grad.reshape(input_grad.shape[1:]) ## What should I use here??
gradRGB = gb_viz = input_grad ## What should I use here as ours is a single channel input but I guess the heat map should always be in RGB
from skimage.transform import resize
#import cv2
# global average pooling -->> QUESTION
## How to recover the 20 weights obtained by GAP layer??
weights = np.mean(conv_grad, axis = (0, 1))
cam = np.zeros(conv_output.shape[0 : 2], dtype = np.float32)
for i, w in enumerate(weights):
cam = w * conv_output[:, :, i]
cam = np.maximum(cam, 0)
cam = cam / np.max(cam)
cam = resize(cam, (95,95), preserve_range=True)
img = x_test[target_y_train_num].astype(float)
img -= np.min(img)
img /= img.max()
cam_heatmap = cv2.applyColorMap(np.uint8(255*cam), cv2.COLORMAP_JET)
cam_heatmap = cv2.cvtColor(cam_heatmap, cv2.COLOR_BGR2RGB)
cam = np.float32(cam.reshape((95, 95, 1))) * np.float32(img)
cam = 255 * cam / np.max(cam)
cam = np.uint8(cam)
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
plt.figure()
img_int = (img * 255.).astype(int).reshape(img.shape[:2])
plt.gray()
plt.imshow(img_int)
plt.savefig('original_{}.png'.format(target_y_train_num))
plt.close()
plt.figure()
plt.imshow(cam_heatmap)
plt.savefig('heatmap_{}.png'.format(target_y_train_num))
plt.close()
plt.figure()
plt.imshow(img_int)
plt.imshow(cam_heatmap, alpha=0.5)
plt.savefig('heatmap_overlaied_{}.png'.format(target_y_train_num))
plt.close()
gb_viz -= np.min(gb_viz)
gb_viz /= gb_viz.max()
img_int = (gb_viz * 255.).astype(int).reshape(img.shape[:2])
imgplot = plt.imshow(img_int)
plt.savefig('grad-cam-backpropagation_{}.png'.format(target_y_train_num))
plt.close()
gd_gb = gb_viz * cam
img_int = (gd_gb * 255.).astype(int).reshape(img.shape[:2])
imgplot = plt.imshow(img_int)
plt.savefig('guided-grad-cam_{}.png'.format(target_y_train_num))
plt.close()
Я снова прочитал статью и ее концепции, но я не мог понять, как следует вычислять значения conv_grad, conv_output, input_grad и cam. Я попытался поместить и извлечь 20 весов слоя GAP для вычисления cam, но безуспешно, и я не понимаю процесс вычисления. Извините за длинный пост и заранее спасибо.