画像認識AIの説明性 Grad-CAM サンプルコード解説(tensorflow keras)初心者向け

データサイエンス

 AIアルゴリズムは、ブラックボックスであるため、なぜその答えを導き出したのかがわからず、問題になることがあります。

 画像認識AIがなぜ、その答えを導き出したのかとい説明に役立つGrad-CAMというアルゴリズムが提案されており、様々な分野で活用されています。

 Grad-CAMのサンプルコードを初心者の方向けにわかりやすく解説します。

はじめに

 今回は、tensorflow kerasのサンプルコードです。参考にしたのは、こちらになります。説明がい少ないので、本ページでは詳しく書いておきます。

Keras documentation: Grad-CAM class activation visualization
Keras documentation

 非常にシンプルなコードですので、ぜひ身に着けてください。colaboのリンクもあるので便利ですよ。

サンプルコード解説

Set up

import numpy as np
import tensorflow as tf
from tensorflow import keras

# Display
from IPython.display import Image, display
import matplotlib.pyplot as plt
import matplotlib.cm as cm

各種ライブラリのインポートです

  • 1~3行目:numpyとtensorflowとkerasをインポート
  • 6行目:画像表示関係のライブラリをインポート
  • 7行目:matplotのpyplot グラフや画像描画ライブラリをインポート
  • 8行目:matplotのカラーマップをインポート

Configurable parameters(パラメータ設定)

model_builder = keras.applications.xception.Xception
img_size = (299, 299)
preprocess_input = keras.applications.xception.preprocess_input
decode_predictions = keras.applications.xception.decode_predictions

last_conv_layer_name = "block14_sepconv2_act"

# The local path to our target image
img_path = keras.utils.get_file(
    "african_elephant.jpg", "https://i.imgur.com/Bvro0YD.png"
)

display(Image(img_path))

1行目:学習済みモデルXceptionを使います

2行目:モデルに入力する画像サイズは、幅299、高さ299pixel

3行目:画像の前処理の定義です

4行目:画像の出力層の定義です

6行目:最後の畳み込み層の名前です。この層を取り出します。

8~11行目:試してみる画像です。アフリカゾウですね

13行目:アフリカゾウの画像をみてみます。

The Grad-CAM algorithm

def get_img_array(img_path, size):
    # `img` is a PIL image of size 299x299
    img = keras.utils.load_img(img_path, target_size=size)
    # `array` is a float32 Numpy array of shape (299, 299, 3)
    array = keras.utils.img_to_array(img)
    # We add a dimension to transform our array into a "batch"
    # of size (1, 299, 299, 3)
    array = np.expand_dims(array, axis=0)
    return array


def make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):
    # First, we create a model that maps the input image to the activations
    # of the last conv layer as well as the output predictions
    grad_model = tf.keras.models.Model(
        [model.inputs], [model.get_layer(last_conv_layer_name).output, model.output]
    )

    # Then, we compute the gradient of the top predicted class for our input image
    # with respect to the activations of the last conv layer
    with tf.GradientTape() as tape:
        last_conv_layer_output, preds = grad_model(img_array)
        if pred_index is None:
            pred_index = tf.argmax(preds[0])
        class_channel = preds[:, pred_index]

    # This is the gradient of the output neuron (top predicted or chosen)
    # with regard to the output feature map of the last conv layer
    grads = tape.gradient(class_channel, last_conv_layer_output)

    # This is a vector where each entry is the mean intensity of the gradient
    # over a specific feature map channel
    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))

    # We multiply each channel in the feature map array
    # by "how important this channel is" with regard to the top predicted class
    # then sum all the channels to obtain the heatmap class activation
    last_conv_layer_output = last_conv_layer_output[0]
    heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]
    heatmap = tf.squeeze(heatmap)

    # For visualization purpose, we will also normalize the heatmap between 0 & 1
    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)
    return heatmap.numpy()

Grad-CAMのアルゴリズム部分です

  • 1行目:画像を取得する関数の定義です
  • 3行目:画像データを読み込みます
  • 5行目:画像データをnumpy形式に変換します。こっちの方が計算しやすいので
  • 8行目:バッチ処理するため、軸を1個追加します。データの個数の次元ですね。axis=0なので、先頭に追加です。
  • 9行目:numpyのarrayで返します
  • 12行目:本題のGrad-CAMでヒートマップを作成する関数です。
    引数は、img_array:画像データをnumpy形式に変換したもの、model:画像認識モデル、last_conv_layer_name:最後の畳み込み層の名前、pred_index:???
  • 15~17行目:Grad-CAMのモデル定義。model.inputs画像認識モデルの入力層が入り口で、出力の一つ目が、model.get_layer(last_conv_layer_name).output、画像認識モデルの最後の畳み込み層。出力の二つ目が、model.output、画像認識モデルのフツーの出力層。

ここからは、画像認識の予測ラベルでtopとなったときの、モデルの勾配を計算します

  • 21行目:tf.GradientTapeは勾配の自動計算です。openで読み込みます
  • 22行目:モデルに画像を入れて、画像認識の予測結果と、最後の畳み込み層を出力します
  • 23~24行目:pred_indexを指定しないなら。画像認識の予測結果とします。
  • 25行目:class_channelという変数に、画像認識の予測ラベルとpred_indexを保存しておきます
  • 29行目:class_channelの時の、最後の畳み込み層までの勾配を計算します。
  • 33行目:勾配データの平均を計算します。平均する方向は、0,1,2です。
  • 38行目:最後の畳み込み層の出力を取り出します
  • 39行目:最後の畳み込み層に勾配計算を実行します。tf.newaxisは、次元を1個増やしてつじつま合わせです。
  • 40行目:次元を1個削ってもどします。
  • 43行目:標準化しておきます。最大値で割っているだけです。
  • 44行目:heatmapをnumpy形式で返します

Let’s test-drive it (実際にやってみよう!)

# Prepare image
img_array = preprocess_input(get_img_array(img_path, size=img_size))

# Make model
model = model_builder(weights="imagenet")

# Remove last layer's softmax
model.layers[-1].activation = None

# Print what the top predicted class is
preds = model.predict(img_array)
print("Predicted:", decode_predictions(preds, top=1)[0])

# Generate class activation heatmap
heatmap = make_gradcam_heatmap(img_array, model, last_conv_layer_name)

# Display heatmap
plt.matshow(heatmap)
plt.show()

実際に画像を入力してためしてみます。

  • 2行目:入力する画像を用意します。指定したサイズにリサイズされます。
  • 5行目:モデルオブジェクトを作成。重みはimagenetです。
  • 8行目:モデルの最後のsoftmax層を削ります
  • 11行目:モデルに画像を入力して、出力させます。soft_maxの前までですよね
  • 12行目:decode_predictionsで、top1の結果を出して、表示します。確認用です。
  • 15行目:heat_mapを作成します。
  • 18行目:heatmapを描画します
  • 19行目:描画を表示します。見えるようにします。

結果はこんな感じです。10×10データの数値ですよ。

Create a superimposed visualization(スーパーインポーズ画像を作ってみよう)

def save_and_display_gradcam(img_path, heatmap, cam_path="cam.jpg", alpha=0.4):
    # Load the original image
    img = keras.utils.load_img(img_path)
    img = keras.utils.img_to_array(img)

    # Rescale heatmap to a range 0-255
    heatmap = np.uint8(255 * heatmap)

    # Use jet colormap to colorize heatmap
    jet = cm.get_cmap("jet")

    # Use RGB values of the colormap
    jet_colors = jet(np.arange(256))[:, :3]
    jet_heatmap = jet_colors[heatmap]

    # Create an image with RGB colorized heatmap
    jet_heatmap = keras.utils.array_to_img(jet_heatmap)
    jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))
    jet_heatmap = keras.utils.img_to_array(jet_heatmap)

    # Superimpose the heatmap on original image
    superimposed_img = jet_heatmap * alpha + img
    superimposed_img = keras.utils.array_to_img(superimposed_img)

    # Save the superimposed image
    superimposed_img.save(cam_path)

    # Display Grad CAM
    display(Image(cam_path))


save_and_display_gradcam(img_path, heatmap)

heatmapだけだとわかりずらいので、元の画像データに重ねて、スーパーインポーズ画像を作成します。画像処理の技としてもよく使いますので、覚えておきましょう。

  • 1行目:関数ですよ。alpahはスケスケ具合です
  • 3行目:画像データを読み込んで
  • 4行目:計算しやすいようにnumpy形式に変換
  • 7行目:heatmapが0~1の数値なので、画像データのように0~255の8bit整数データに変換
  • 10行目:カラーマップを設定します。安定のjetです。
  • 13行目:jetのカラーマップを作って
  • 14行目:heatmapをカラーマップ変換します
  • 17行目:heatmapを画像データに変換します
  • 18行目:画像データをリシェイプします。(数値のマトリックスデータは、行→列の順ですが、画像データは幅→高さの順なので、反転させる必要があるのです。。。)
  • 19行目:再び、numpy 形式に変換。
  • 22行目:ヒートマップ画像に、元画像×0.4にしたものを足します。うっすら、元画像を重ねます
  • 23行目:スーパーインポーズデータをnumpy形式から画像データへ変換
  • 26行目:保存
  • 29行目:表示
  • 32行目:関数を呼び出して実行します!

Let’s try another image(他の画像で試してみよう)

img_path = keras.utils.get_file(
    "cat_and_dog.jpg",
    "https://storage.googleapis.com/petbacker/images/blog/2017/dog-and-cat-cover.jpg",
)

display(Image(img_path))

# Prepare image
img_array = preprocess_input(get_img_array(img_path, size=img_size))

# Print what the two top predicted classes are
preds = model.predict(img_array)
print("Predicted:", decode_predictions(preds, top=2)[0])
heatmap = make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=260)

save_and_display_gradcam(img_path, heatmap)
heatmap = make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=285)

save_and_display_gradcam(img_path, heatmap)

別画像でためしています。。。こちらは試してみてください。

まとめ

画像認識の説明用アルゴリズムGrad-CAMのサンプルコードを説明しました。

シンプルなコードですのでぜひ身に着けてください。

スーパーインポーズ画像の作成は画像処理としても便利な技ですので、こちらも覚えるとスキルUPになりますよ。

タイトルとURLをコピーしました