前回のPart1の記事では、機械学習モデルの解釈性の重要性、そしてGrad-CAMを中心とする、いくつかの予測根拠の可視化ツールを紹介しました。
前回:https://gri.jp/media/entry/2224
今回は、早速Grad-CAMを実装してみましょう。
環境・設定:
- Google Colaboratory 上でJupyter Notebook を走らせる
- ディープラーニングのライブラリとしてKerasを使用
- ResNet50を転移学習に使用
- 入力画像:図1
Grad-CAMを実装するコード
コードSection1:モジュールなどをimport
まずimport の部分では、Kerasから必要なモジュールを持ってきます。そして今回はResNet50を使用します。
Section2: クラスの初期化・入力データの準備
GradCam というクラスを作成します。このクラスの中で、まずモデルの初期化を行い、続けてGrad-CAMの実質的な内容を定義する関数を構築します。
初期化 def init の部分では、最後の畳み込み層の名前を確認するために、各レイヤーの名称の情報のprintをさせています。
その下の def gradcam_func(…) はGrad-CAMの実際の機能を実装する関数です。まず入力画像のピクセル数値のデータ型を調整し、正規化を行います。
このmainの部分で行う処理は概ね以下となります。
- 入力画像の読み込み・前処理
- 画像認識用のモデルの読み込み
- 入力画像のクラス(確率)を予測する
- Grad-Camの計算(勾配を計算し、それをRelu関数に通した値をヒートマップの値にする)
- 出力画像の保存
Section3: 入力画像に対する予測
この部分では、畳み込み層の最後のレイヤーを受け取り、モデルに渡します。
勾配を記録するためのメソッドとして tf.GradientTapeがあります。勾配を計算するために、numpy配列をtfの型に変換します。
続いて、予測したクラス及びそのクラスの特徴量を取得します。
次に、上記で得られた情報を使って勾配を計算します。
Section4: 勾配の計算・Global Average Pooling・ヒートマップ出力
勾配を記録するために tf.GradientTape() の中のgradient() 関数を用いて勾配を計算したのちに、Global Average Pooling (GAP)を実装して平均を取ります。最後に、ヒートマップの値を計算するために勾配値をRelu 関数に通す必要があります。ReLU関数からの出力された値がヒートマップの値となります。
また、以前入力データの前処理で正規化をしていたので、ここで正規化前の値に戻します。クラスの最後の部分はcv2の関数を使ってカラーのマップを作ります。
以上で、メインの関数の定義が終わりました。ここからは、クラスGradCamをメインコードで呼び出して、実際に画像を入力して、予測結果の解釈を行ってみましょう。
Section5: メイン操作(クラスGradCamを呼び出しモデルの解釈結果を出力)
まずmodelを呼び出します。重みはimagenetで訓練したものです。画像をgoogle drive から読み込むときにちょっと一癖があります。本記事の一番最後の画像をマウントするための一例を載せますので、ご参考にしてください。グーグルドライブ上で「マイドライブ」として見えているものは、ディレクトリー構造でいうと、/content/dive/My Drive/ のものであることが多いです。出力の場所も同様に設定します。
読み込んだ画像をリサイズし、gradcam関数を呼び出します。コードの実行が終われば、out.jpgという解釈結果のヒートマップがマイドライブに出来上がっているはずです。これをみてみましょう。
ここまでのコードを実装します。
初期化で指定したように、ニューラルネットワークの各レイヤーの情報をprintさせており、最終の畳み込み層も確認できます(ここからの勾配を使うので)。
得られたアウトプット画像は以下のようです。ご覧のように色分けで画像分類の際の注目度を表示しています。
参考:Gドライブへ画像をマウントする方法
複数のやり方があります。ここでは単なる一例です。
いかがでしたか?転移学習やKerasの便利な機能を使えるため、コード自体は難しくなりません。ぜひご自身が試したい画像で画像認識解釈のヒートマップを出力してみてください。
今回も読んでいただき、ありがとうございました。次回もお会いしましょう。
記事担当:ヤン ジャクリン(分析官・講師)