Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
28
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

Chainer v2 でSmoothGradを実装

はじめに

画像認識において、識別器がどこを注目して認識したかを可視化することは重要です。
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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
28
Help us understand the problem. What are the problem?