はじめに:Grad-CAMとは?画像分類モデルの判断根拠を「見える化」する技術
Grad-CAM (Gradient-weighted Class Activation Mapping)は、画像分類モデルが「どの部分」を重視して判断しているかを視覚的に示す技術です。 特にCNN(畳み込みニューラルネットワーク)との相性が良く、深層学習の「説明可能性」を高める手法として注目されています。
実験の概要:使用ライブラリと目的
今回の可視化実験では、以下のような構成で実装を行います。
- 使用ライブラリ:Keras(TensorFlowバックエンド)
- 対象データセット:MNIST(手書き数字画像)
- 目的:CNNが画像を分類するときに注目している「領域」をGrad-CAMで可視化する
MNISTデータセットの前処理(リサイズ・正規化)
MNIST画像データを読み込み、機械学習モデルで扱いやすいように前処理を行います。
具体的には、画素値を0〜1に正規化し、チャンネル次元(グレースケール)を追加します。
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)
モデルの構築と学習
Grad-CAMを使用するには、Kerasの「Functional API」を使ってモデルを構築することが推奨されます。 Sequential APIでは後述の通りエラーが出る場合があります。
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(Gradient-weighted Class Activation Mapping)は、CNNモデルが画像を分類する際にどの部分に注目して判断しているかを可視化できる手法です。
このセクションでは、Grad-CAMを用いて、Kerasモデルからヒートマップ(注目領域のマップ)を生成する方法を詳しく解説します。
以下の手順で、対象画像に対してGrad-CAMを適用し、最終畳み込み層の活性マップと分類スコアの勾配からヒートマップを算出します。
ステップ1:対象画像の選択
まず、Grad-CAMを適用したい画像を選びます。ここでは、テストデータの1枚目を対象としています。
# 3. Grad-CAMの対象画像を選択
idx = 0
img = x_test[idx:idx+1]
true_label = y_test[idx]
ステップ2:Grad-CAMの計算関数
続いて、Grad-CAMのヒートマップを生成する関数 make_gradcam_heatmap()
を定義します。
この関数では、指定した畳み込み層の出力とモデルの予測を同時に取得し、分類スコアに対する勾配を取得して処理します。
# 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()
この関数を使用することで、どの画像クラスを対象とするか(pred_index
)を指定して、視覚的な判断根拠を取得することができます。
ステップ3:ヒートマップの生成
上記の関数を使って、実際に対象画像のGrad-CAMヒートマップを作成します。
# 5. ヒートマップ生成
img_tensor = tf.convert_to_tensor(img)
heatmap = make_gradcam_heatmap(img_tensor, model, last_conv_layer_name='conv2')
このようにして得られたヒートマップは、後のステップで入力画像に重ねることで「モデルが注目している領域」を直感的に理解するのに役立ちます。
画像にGrad-CAMヒートマップを重ねて表示する方法
モデルが画像のどこに注目して分類を行ったのかを視覚的に確認するには、Grad-CAMで得られたヒートマップを元画像に重ねて表示するのが有効です。
これにより、分類結果の「根拠」が直感的に把握でき、AIモデルの判断を説明可能にする第一歩となります。
ヒートマップのカラー変換と合成処理
ここでは、matplotlib
とTensorFlow
を用いて、以下の3ステップで画像を合成して表示します。
- ヒートマップをカラー(jetカラーマップ)に変換
- ヒートマップを元画像サイズにリサイズ
- 元画像とヒートマップを合成(alpha合成)
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)
なぜ画像に重ねて表示するのか?
- 分類根拠を可視化できる:モデルが「なぜそのクラスだと判断したのか」が見える化されます。
- 誤分類の分析に役立つ:注目領域がズレていれば、改善の余地を特定できます。
- 説明性の向上:医療・金融など説明責任が求められる分野で特に有効です。
このように、Grad-CAMヒートマップの重ね表示は、深層学習モデルの「透明性」と「信頼性」を高めるための重要な手法です。
分類モデルを使った実験やプロダクトに取り入れることで、より説得力ある成果を提供できます。
実行結果
下記は「7」と「2」の画像に対してGrad-CAMを適用した可視化結果です。
AIが注目している領域がどこか一目で分かるようになっています。
注意点:Sequentialモデルだとエラーになることがある
KerasのSequentialモデルは、構築後に `.inputs` や `.output` が自動で定義されないことがあります。
そのため、Grad-CAMのようにモデルの内部構造を参照する処理では、AttributeErrorが発生する場合があります。
# このコードではエラーになります
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.
改善したコード
Functional APIで構築したモデルでは、.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で深層学習モデルの「見える化」を体験しよう
Grad-CAMは、モデルの内部動作を視覚的に把握できる強力なツールです。
特に分類ミスの原因分析や、AI判断の「納得性」を高めたい場面において有用です。
KerasとMNISTを使った本記事のコードを参考に、ぜひ自分でも実装してみてください。
0 件のコメント:
コメントを投稿