kerasでGrad-CAM 自分で作ったモデルで

### kerasでGrad-CAM 自分で作ったモデルで

```
ここでは、Python3.6.4 で行なっています。また、主に以下のパッケージを利用しています。
Keras (2.1.5)
```
### はじめに
***
kerasでGrad-CAMを行ってみました。自分で作成したモデルで試しています。
モデルは、kaggleの dog vs cat のデータについてResnet50で転移学習をおこない
作成しました。

犬か猫かを判別するモデルについて、どこの影響が大きいのかをみてみます。
なお、画像サイズは200x200でモデルを作成したので、そのサイズにしています。

まずは関数の部分。

```py

# coding:utf-8

import pandas as pd
import numpy as np
import cv2
from keras import backend as K
from keras.preprocessing.image import array_to_img, img_to_array, load_img
from keras.models import load_model

K.set_learning_phase(1) #set learning phase



def Grad_Cam(input_model, x, layer_name):
    '''
    Args:
       input_model: モデルオブジェクト
       x: 画像(array)
       layer_name: 畳み込み層の名前
       
    Returns:
       jetcam: 影響の大きい箇所を色付けした画像(array)
    
    '''
    
    # 前処理
    X = np.expand_dims(x, axis=0)
    
    X = X.astype('float32')
    preprocessed_input = X / 255.0
    
    
    # 予測クラスの算出
    
    predictions = model.predict(preprocessed_input)
    class_idx = np.argmax(predictions[0])
    class_output = model.output[:, class_idx]
    
    
    #  勾配を取得
    
    conv_output = model.get_layer(layer_name).output   # layer_nameのレイヤーのアウトプット
    grads = K.gradients(class_output, conv_output)[0]  # gradients(loss, variables) で、variablesのlossに関しての勾配を返す
    gradient_function = K.function([model.input], [conv_output, grads])  # model.inputを入力すると、conv_outputとgradsを出力する関数
    
    output, grads_val = gradient_function([preprocessed_input])
    output, grads_val = output[0], grads_val[0]
    
    # 重みを平均化して、レイヤーのアウトプットに乗じる
    weights = np.mean(grads_val, axis=(0, 1))
    cam = np.dot(output, weights)
    
    
    # 画像化してヒートマップにして合成
    
    cam = cv2.resize(cam, (200, 200), cv2.INTER_LINEAR) # 画像サイズは200で処理したので
    cam = np.maximum(cam, 0) 
    cam = cam / cam.max()
    
    jetcam = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)  # モノクロ画像に疑似的に色をつける
    jetcam = cv2.cvtColor(jetcam, cv2.COLOR_BGR2RGB)  # 色をRGBに変換
    jetcam = (np.float32(jetcam) + x / 2)   # もとの画像に合成
    
    return jetcam

```

### やってること
***
理論の詳細な説明は他に譲りますが、ここでは画像の予測クラスを取得したのち、
予測への影響が大きい画像箇所を特定し、色付けをしています。
予測への影響については、その箇所に変化を加えたときに、予測確率にどの程度の変化が生じるか、
ということで特定しています。

コードでは、「勾配を取得」のあたりで計算をしています。ここでは、レイヤーの出力が必要になりますので、
K.functionなどを利用しています。

そのあと、レイヤーに重みを乗じて画像化してヒートマップにしています。
ヒートマップにする部分はopencvを利用していますが、pythonで画像を読んだ時とRGBの順番が
違うので、同じにして元の画像と合成できるようにしています。
(画像部分では型変換をしきりに行っていますが、arrayを画像にしたり連結させる上で必要な処理とのこと)


### 実行
***

実際に実行してみますが、レイヤーの名称を特定する必要があります。
model.summary()でレイヤーの構造と名称は把握できると思います。
Grad-CAMではConvolution最終層の勾配を取るらしいので、その名称を入力しています。
だいたい、(各種処理・・・ → pooling → flatten )　と進むと思うので、pooling直前にある、
各種処理の最後の層名称を入れてます。
activationになっちゃってるのはresnetだからかな・・・

私自身の理解が怪しい部分もあるので、間違っている部分などあれば
ご指摘いただけるとありがたいです。


```py

model = load_model("./resnet_cat_dog.h5")
x = img_to_array(load_img('./input/cat10007.jpg', target_size=(200,200)))
array_to_img(x)
```
![Unknown.jpg](https://qiita-image-store.s3.amazonaws.com/0/50110/2a7556fb-4bce-7cad-8465-be74bf0586c4.jpeg)

```py
image = Grad_Cam(model, x, 'activation_49') 
array_to_img(image)
```


![Unknown.jpg](https://qiita-image-store.s3.amazonaws.com/0/50110/da97b315-2ed7-a724-cd1b-258fa0e560a2.jpeg)

```py
x = img_to_array(load_img('./input/dog7947.jpg', target_size=(200,200)))
array_to_img(x)
```
![Unknown.jpg](https://qiita-image-store.s3.amazonaws.com/0/50110/f57b63e5-966b-8902-893a-6fee5509a4c9.jpeg)

```py
image = Grad_Cam(model, x, 'activation_49') 
array_to_img(image)
```
![Unknown.jpg](https://qiita-image-store.s3.amazonaws.com/0/50110/d99e4413-e1db-5375-0863-fd4a8842b6c7.jpeg)


### 終わりに
***
こういった処理をすることで、他の人に納得感を持って分析を伝えられたり、
また学習がうまくいっていない場合のヒントを得ることができるのかなと。

にしても、改めて凄さを感じたと言うか。ちゃんと見てるんだなぁと思いました。