Grad-CAMでAIの判断根拠を可視化してみた!【CNN × 可視化 × Keras】

2025年6月20日金曜日

X f B! P L
アイキャッチ画像 Grad-CAMでAIの判断根拠を可視化してみた!【CNN × 可視化 × Keras】

はじめに: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が注目している部分がわかります。

Grad-CAMの例1
Grad-CAMの例2

注意: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がどこに注目して分類しているかを直感的に把握できます。
誤分類時の原因分析や、モデルの信頼性向上にも役立つ技術です。

参考リンク

このブログを検索

自己紹介

はじめまして、機械学習を独学中のSHOU TAKEと申します。本ブログでは、Python・Keras・Google Colabを活用した画像分類やニューラルネットワークの実験記事を中心に発信しています。初学者の方にも分かりやすく、学んだことをそのまま実験形式でまとめるスタイルです。これまで取り組んだテーマには、学習率やOptimizerの比較、Batch Sizeの検証、事前学習の活用などがあります。ご質問やご感想は、お問い合わせフォームからお気軽にどうぞ。

お問い合わせフォーム

名前

メール *

メッセージ *

プライバシーポリシー

QooQ