LoginSignup
33
28

More than 5 years have passed since last update.

Chainer v2 でSmoothGradを実装

Last updated at Posted at 2017-06-24

はじめに

画像認識において、識別器がどこを注目して認識したかを可視化することは重要です。
CNNでは、損失関数の勾配をバックプロパゲーションで入力画像まで伝搬し、その絶対値の強度を可視化する方法が提案されていますが、ノイズが多いという問題がありました。
SmoothGradでは、入力画像にガウシアンノイズを与え、複数の勾配を平均するだけで、きれいな可視化でできるという、とても簡単な方法です。

cheetah.jpg

平均化される様子
cheetah.gif

Smooth Grad
MN099.jpg

Vanilla Grad(従来手法)
VanillaMM.jpg

TensorFlowのコードや論文は下記からダウンロードできるようです。
https://tensorflow.github.io/saliency/

Chainer v2の勉強を兼ねて、SmoothGradを実装してみました。
モデルには、学習済みのVGG16モデルを使っています。
環境は、
Windows7 64bit
Python 3.5.2 |Anaconda 4.2.0 (64-bit)
chainerのversionは'2.0.0'です。
GPUには対応していません。

実装

import

smoothgrad.py
import chainer
import chainer.functions as F
from chainer.variable import Variable
from chainer.links import VGG16Layers
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

config

テストモードで行いますが、バックプロパゲーションは行う必要があるので、
chainer.configを以下のように設定します。

smoothgrad.py
chainer.config.train=False
chainer.config.enable_backprop=True

VGG16モデルのロード

VGG16モデルをロードします。
モデルは500MB程度あるので、事前にダウンロードされていない場合は、それなりに時間がかかります。

smoothgrad.py
model = VGG16Layers()

画像の読み込みと前処理

VGGは画像サイズが224x224なのでリサイズしておきます。

smoothgrad.py
image = Image.open("cheetah.png")
image = image.resize((224,224))

パラメータ

サンプリング数とノイズレベルを設定します。
サンプリング数は100、ノイズレベルは20%としています。

smoothgrad.py
sampleSize = 100
noiseLevel = 0.2 # 20%
sigma = noiseLevel*255.0

勾配計算

使用メモリの関係上、今回は一枚ずつ行うことにします。
まず、VGG16ではチャネルの並びがBGRなので変換し、平均値を引きます。
次に画像のノイズを加えながら、順伝搬、ロスを計算し、逆伝搬することで勾配を計算します。
求めた勾配は、リストに追加しておきます。

smoothgrad.py
gradList = []
for _ in range(sampleSize):
    x = np.asarray(image, dtype=np.float32)
    # RGB to BGR
    x = x[:,:,::-1]
    # 平均を引く
    x -= np.array([103.939, 116.779, 123.68], dtype=np.float32)
    x = x.transpose((2, 0, 1))
    x = x[np.newaxis]
    # ノイズを追加
    x += sigma*np.random.randn(x.shape[0],x.shape[1],x.shape[2],x.shape[3])    
    x = Variable(np.asarray(x))
    # FPして最終層を取り出す
    y = model(x, layers=['prob'])['prob']
    # 予測が最大のラベルでBP
    t = np.zeros((x.data.shape[0]),dtype=np.int32)
    t[:] = np.argmax(y.data)
    t = Variable(np.asarray(t))
    loss = F.softmax_cross_entropy(y,t)
    loss.backward()
    # 勾配をリストに追加
    grad = np.copy(x.grad)
    gradList.append(grad)
    # 勾配をクリア
    model.cleargrads()

可視化

勾配の各チャネルの絶対値の最大値を取り、画像に対して平均を取ります。

smoothgrad.py
G = np.array(gradList)
M = np.mean(np.max(np.abs(G),axis=2),axis=0)
M = np.squeeze(M)
plt.imshow(M,"gray")
plt.show()

M049.png

結果

平均する枚数を増やしながら、元画像とマップを画素ごとにかけて可視化してみました。

1サンプル
MN000.jpg

2サンプル
MN001.jpg

3サンプル
MN003.jpg

10サンプル
MN009.jpg

20サンプル
MN019.jpg

30サンプル
MN029.jpg

50サンプル
MN049.jpg

75サンプル
MN074.jpg

100サンプル
MN099.jpg

ノイズを与えずに、一枚のサンプルから可視化すると以下のようにノイズが多く見られます。
VanillaMM.jpg

33
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
28