chainerにおける勾配情報の取得方法について


chainerに関する質問です。

学習済みのNNモデルをロードし、ある画像を入力した際の各層の勾配情報を取得したいのですが、実装できずに悩んでいます。

ネットワークは以下のように定義しています。

[code]

    class Model1(chainer.Chain):
    
        def __init__(self, input_chs, n_outputs):
    
            super(Model1Activation, self).__init__()
    
            with self.init_scope():
    
                self.I = []
                self.conv1 = L.Convolution2D(input_chs, 16, 5, stride=1, pad=0)
                self.conv1_hidden = []
                self.relu1_hidden = []
                self.maxpool1_hidden = []
                self.conv2 = L.Convolution2D(16, 32, 5, stride=1, pad=0)
                self.conv2_hidden = []
                self.relu2_hidden = []
                self.maxpool2_hidden = []
                self.conv3 = L.Convolution2D(32, 64, 5, stride=1, pad=0)
                self.conv3_hidden = []
                self.relu3_hidden = []
                self.l4 = L.Linear(1*1*64, 100)
                self.l4_hidden = []
                self.l5 = L.Linear(100, n_outputs, nobias=True)
                self.pred = []
    
        def __call__(self, X):
    
            # -> Input
            self.I = Variable(X)
    
            # -> 14*14*16
            self.conv1_hidden = self.conv1(self.I)
            self.relu1_hidden = F.relu(self.conv1_hidden)
            self.maxpool1_hidden = F.max_pooling_2d(self.relu1_hidden, 2)
    
            # -> 5*5*32
            self.conv2_hidden = self.conv2(self.maxpool1_hidden)
            self.relu2_hidden = F.relu(self.conv2_hidden)
            self.maxpool2_hidden = F.max_pooling_2d(self.relu2_hidden, 2)
    
            # -> 1*1*64
            self.conv3_hidden = self.conv3(self.maxpool2_hidden)
            self.relu3_hidden = F.relu(self.conv3_hidden)
    
            # -> 100
            self.l4_hidden = self.l4(self.relu3_hidden)
            self.relu4_hidden = F.relu(self.l4_hidden)
    
            # -> n_outputs
            self.pred = self.l5(self.relu4_hidden)
    
            return self.pred
    
[/code]

mainの中でモデルを読み込み、以下のようにして勾配情報を取り出そうとするとNoneと表示されてしまいます。

[code]

    main():
    
        #中略
        #(modelという変数にModel1モデルを読み込み、画像を入力した際のロスをlossに格納)
    
        loss.backward(retain_grad=True)
        print(model.I.grad)
    
[/code]

クラスとして定義したモデルにはgradが保存されないのか、そうであるなら各層の勾配情報を取得するにはどのように記述したらよいかを教えていただけると幸いです。  
よろしくお願いいたします。




短く言えば，[chainer.grad](https://docs.chainer.org/en/stable/reference/generated/chainer.grad.html)を利用してください．(使用例はExample
3を参照ください．)  
以下は詳細です．

Q71.MSK氏の回答は誤っています．まず，通常，Variable.backward()後に，入力の勾配を取得することはできます．

Example 1

[code]

    import chainer
    from chainer import Variable
    from chainer import functions as F
    
    import numpy as np
    
    
    inp = Variable(np.array([1., 2., 3.]))
    intermediate = inp * 3
    out = F.sum(intermediate)
    
    out.backward()
    
    print(inp.grad)
    
[/code]

Output 1

[code]

    [3. 3. 3.]
    
[/code]

このように，入力値の勾配を取ることはできます．  
しかし，質問の通り，中間特徴の勾配を取ることはできません．

Example 2

[code]

    import chainer
    from chainer import Variable
    from chainer import functions as F
    
    import numpy as np
    
    
    inp = Variable(np.array([1., 2., 3.]))
    intermediate = inp * 3
    out = F.sum(intermediate)
    
    out.backward()
    
    print(intermediate.grad)
    
[/code]

Output 2

[code]

    None
    
[/code]

これは，計算グラフの効率化と呼ばれる仕様で，GPUメモリの省略のために，Variable.backward()は葉ノード以外の全てのgradを記憶しません．  
そこで，正しいコードは以下のようになります．

Example 3

[code]

    import chainer
    from chainer import Variable
    from chainer import functions as F
    
    import numpy as np
    
    
    inp = Variable(np.array([1., 2., 3.]))
    intermediate = inp * 3
    out = F.sum(intermediate)
    
    intermediate_grad = chainer.grad((out,), (intermediate,))[0]
    print(intermediate_grad)
    
[/code]

Output 3

[code]

    variable([1. 1. 1.])
    
[/code]

