Python
可視化
ニューラルネットワーク
Chainer

Layer-wise Relevance Propagation を Chainer で実装する

1. Layer-wise Relevance Propagation(LRP)

元論文は これ で、詳しいことは以下参照。

Qiita:ディープラーニングの判断根拠を理解する手法

私はLRPを

一度データを順伝搬させて、出力層から各層の出力と重みを元に貢献度を求めていく手法

だと理解しています。

 

2. Chainer

国産のニューラルネットワーク用のフレームワークです。

一度、順伝搬させるとデータが数珠つなぎ的に残るので、個人的には使いやすくて好きです。

Chainer: A flexible framework for neural networks

 

3. 実装 (chainer v2.0.0)

Chainerは一度、順伝搬させるとデータと生成された層の種類を参照できるので、それを利用して実装しますバグなどがあるかもしれません。

下記のコードは最低限、線形結合と畳込み、プーリングのみに対応しています。
また、入力zのshapeは(データ数, 出力ニューロン数)を想定しています。

import chainer
import numpy as np

def LRP(z, epsilon=0):
    creator = z.creator
    var = z
    # relevance value
    r = np.zeros(z.data.shape)
    for i, d in enumerate(z.data):
        r[i, d.argmax()] = d.max()

    while(creator is not None):
        x = creator.inputs[0].data
        y = creator.outputs[0]().data

        if len(creator.inputs) > 1:
            w = creator.inputs[1].data
        if creator.label == "LinearFunction":
            _y = y + epsilon*np.sign(y)
            r = x.reshape(r.shape[0], -1) * (np.dot(r/_y, w))
        elif creator.label == "Convolution2DFunction":
            _y = y + epsilon*np.sign(y)
            r = x * chainer.functions.deconvolution_2d(r.reshape(y.shape)/_y,
                                                       w).data
        elif creator.label == "MaxPooling2D":
            r = chainer.functions.unpooling_2d(
                r.reshape(y.shape),
                ksize=creator.kh,
                stride=creator.sy,
                outsize=x.shape[2:]).data

        var = creator.inputs[0]
        creator = var.creator
    return r

一応、出力層のデータが消えないように以下のhookを順伝搬の際に追加しました。

from chainer.function import FunctionHook

class RetainOutputHook(FunctionHook):
    def forward_postprocess(self, function, in_data):
        function.retain_outputs([0])

''' Example
with RetainOutputHook():
    z = model.predict(x)
'''

4.出力例

下記のネットワーク(n_units=100)にMNISTを学習させて、入力に対するLRPを可視化しました。

class CNN(chainer.Chain):
    def __init__(self, n_units):
        super(CNN, self).__init__()
        with self.init_scope():
            self.conv1 = chainer.links.Convolution2D(in_channels=1, out_channels=n_units//2, ksize=3, stride=1)
            self.conv2 = chainer.links.Convolution2D(in_channels=None, out_channels=n_units, ksize=3, stride=1)
            self.l = chainer.links.Linear(None, 10)

    def __call__(self, x):
        x = chainer.functions.relu(self.conv1(x))
        x = chainer.functions.max_pooling_2d(x, ksize=2, stride=2)
        x = chainer.functions.relu(self.conv2(x))
        x = chainer.functions.max_pooling_2d(x, ksize=2, stride=2)
        return self.l(x)

左:入力画像 右:LRP
lrp.png