はじめに:Grad-CAMとは?
Grad-CAM (Gradient-weighted Class Activation Mapping)は、画像分類モデルが「どの部分」を重視して判断しているかを視覚的に示す技術です。 特にCNN(畳み込みニューラルネットワーク)との相性が良く、深層学習の「説明可能性」を高める手法として注目されています。
実験概要
- 使用ライブラリ:Keras / TensorFlow
- データセット:手書き数字の画像(MNIST)
- 目的:画像分類の「根拠」を可視化
データの準備
MNISTデータセットを読み込み、正規化と形状の調整を行います。
import tensorflow as tf
from tensorflow import keras
import numpy as np
# 1. データ準備
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)
モデルの構築と学習
Functional API を使用してCNNモデルを構築し、訓練します。
(SequentialモデルではGrad-CAM利用時にエラーが出る場合があります。詳細は後述)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# 2. モデル構築
def create_model():
inputs = keras.Input(shape=(28, 28, 1))
x = layers.Conv2D(32, 3, activation='relu', name='conv1')(inputs)
x = layers.MaxPooling2D()(x)
x = layers.Conv2D(64, 3, activation='relu', name='conv2')(x)
x = layers.MaxPooling2D()(x)
x = layers.Flatten()(x)
x = layers.Dense(64, activation='relu')(x)
outputs = layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs, outputs)
return model
model = create_model()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=3, validation_split=0.1)
Grad-CAMヒートマップの作成
Grad-CAMを計算する関数を定義し、ヒートマップを作成します。
# 3. Grad-CAMの対象画像を選択
idx = 0
img = x_test[idx:idx+1]
true_label = y_test[idx]
# 4. Grad-CAM計算関数
def make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):
grad_model = tf.keras.models.Model(
[model.inputs],
[model.get_layer(last_conv_layer_name).output, model.output]
)
with tf.GradientTape() as tape:
conv_outputs, predictions = grad_model(img_array)
if pred_index is None:
pred_index = tf.argmax(predictions[0])
class_channel = predictions[:, pred_index]
# 勾配計算
grads = tape.gradient(class_channel, conv_outputs)
pooled_grads = tf.reduce_mean(grads, axis=(1, 2))
conv_outputs = conv_outputs[0]
heatmap = conv_outputs @ pooled_grads[..., tf.newaxis]
heatmap = tf.squeeze(heatmap)
heatmap = tf.maximum(heatmap, 0) / tf.reduce_max(heatmap)
return heatmap.numpy()
# 5. ヒートマップ生成
img_tensor = tf.convert_to_tensor(img)
heatmap = make_gradcam_heatmap(img_tensor, model, last_conv_layer_name='conv2')
画像にヒートマップを重ねて表示
作成したヒートマップを、元画像に重ねて視覚化します。
import matplotlib.pyplot as plt
import matplotlib as mpl
# 6. 合成表示
def save_and_display_gradcam(img, heatmap, alpha=0.4):
# ヒートマップをカラーに変換
heatmap = np.uint8(255 * heatmap)
jet = mpl.colormaps.get_cmap("jet")
jet_colors = jet(np.arange(256))[:, :3]
jet_heatmap = jet_colors[heatmap]
# リサイズして元画像に重ねる
jet_heatmap = tf.image.resize(jet_heatmap[np.newaxis, ...], size=(28, 28)).numpy()[0]
img_rgb = np.repeat(img[0], 3, axis=-1)
superimposed_img = jet_heatmap * alpha + img_rgb
superimposed_img = np.clip(superimposed_img, 0, 1)
# 表示
plt.figure(figsize=(6, 3))
plt.subplot(1, 2, 1)
plt.title(f"Original (Label: {true_label})")
plt.imshow(img[0].squeeze(), cmap="gray")
plt.axis('off')
plt.subplot(1, 2, 2)
plt.title("Grad-CAM")
plt.imshow(superimposed_img)
plt.axis('off')
plt.tight_layout()
plt.show()
# 実行
save_and_display_gradcam(img, heatmap)
実行結果
下記の画像は「7」と「2」のヒートマップ結果です。それぞれAIが注目している部分がわかります。
注意:Sequentialモデル使用時のエラー
KerasのSequentialモデルは、model.fit() 後でも .inputs や .output が未定義な場合があり、Grad-CAMでエラーになることがあります。
# このコードではエラーになります
def create_model():
model = keras.Sequential([
keras.layers.Input(shape=(28, 28, 1)),
keras.layers.Conv2D(32, 3, activation='relu', name='conv1'),
keras.layers.MaxPooling2D(),
keras.layers.Conv2D(64, 3, activation='relu', name='conv2'),
keras.layers.MaxPooling2D(),
keras.layers.Flatten(),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
return model
エラーサンプル
ヒートマップ作成時のエラーサンプル
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipython-input-6-2117604201.py in <cell line: 0>()
31 # 5. ヒートマップ生成
32 img_tensor = tf.convert_to_tensor(img)
---> 33 heatmap = make_gradcam_heatmap(img_tensor, model, last_conv_layer_name='conv2')
2 frames
/tmp/ipython-input-6-2117604201.py in make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index)
9 grad_model = tf.keras.models.Model(
10 [model.inputs],
---> 11 [model.get_layer(last_conv_layer_name).output, model.output]
12 )
13
/usr/local/lib/python3.11/dist-packages/keras/src/ops/operation.py in output(self)
264 Output tensor or list of output tensors.
265 """
--> 266 return self._get_node_attribute_at_index(0, "output_tensors", "output")
267
268 def _get_node_attribute_at_index(self, node_index, attr, attr_name):
/usr/local/lib/python3.11/dist-packages/keras/src/ops/operation.py in _get_node_attribute_at_index(self, node_index, attr, attr_name)
283 """
284 if not self._inbound_nodes:
--> 285 raise AttributeError(
286 f"The layer {self.name} has never been called "
287 f"and thus has no defined {attr_name}."
AttributeError: The layer sequential_1 has never been called and thus has no defined output.
改善したコード
このように keras.Model(inputs, outputs) を使えば、モデルを構築した時点で .inputs や .output が確定し、Grad-CAM に問題なく使用できます。
def create_model():
inputs = keras.Input(shape=(28, 28, 1))
x = layers.Conv2D(32, 3, activation='relu', name='conv1')(inputs)
x = layers.MaxPooling2D()(x)
x = layers.Conv2D(64, 3, activation='relu', name='conv2')(x)
x = layers.MaxPooling2D()(x)
x = layers.Flatten()(x)
x = layers.Dense(64, activation='relu')(x)
outputs = layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs, outputs)
return model
まとめ
Grad-CAMを活用することで、AIがどこに注目して分類しているかを直感的に把握できます。
誤分類時の原因分析や、モデルの信頼性向上にも役立つ技術です。
0 件のコメント:
コメントを投稿