

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が保存されないのか、そうであるなら各層の勾配情報を取得するにはどのように記述したらよいかを教えていただけると幸いです。  
よろしくお願いいたします。


CC BY-SA 4.0
2018-04-10T05:23:49.180
43115
2018-09-26T08:20:56.820
2018-09-26T08:20:56.820
19769
28099
question
python chainer
chainerにおける勾配情報の取得方法について


すみません、間違えていました。質問者さんは「入力値」の勾配を取りたい、と。私の理解では、勾配は、値を変更する、学習するために計算をするものです。では、「入力値」は、学習するのでしょうか。当然しないので、勾配は計算されない、と思うのですが、いかがでしょうか。

では2つめ、任意の層の勾配ですが、すでにとれているのではないでしょうか。_hidden ポストフィックスの変数の grad に入っているはずです。  
なお、

[code]

     y = relu(conv2d(y))
     y = relu(conv2d(y))
     return y
    
[/code]

の様に、計算結果を残さない場合、最後の出力から creator.inputs を調べることで以前の結果に上っていけます。この場合、[Chainer v3
で勾配が計算されない件](https://groups.google.com/forum/#!topic/chainer-jp/2HDDM3yQskY)
で報告されているように、「Chainer V3以降」「Linear など（未詳）の関数を使う」の2つの条件を満たすと、勾配の計算結果が残りません。これは
[issue 4220](https://github.com/chainer/chainer/issues/4220)
に登録されています。回避策として、質問者さんのように、計算結果をどこかに参照させておきます。


CC BY-SA 4.0
2018-09-13T12:27:12.630
48364
2018-09-14T11:20:26.780
2018-09-14T11:20:26.780
29996
29996
43115
answer


短く言えば，[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]


CC BY-SA 4.0
2018-09-18T08:11:42.357
48501
2018-09-18T08:11:42.357
29620
43115
answer


For Q71.MSK氏  
別トピックを建てていただけると良かったのですが，それを待つのも面倒なので，ここでご回答いたします．

Example 4

[code]

    from chainer import Chain, Variable
    from chainer import functions as F
    from chainer import links as L
    
    import numpy as np
    
    
    class Model1(Chain):
        def __init__(self, input_chs, n_outputs):
            super().__init__()
            with self.init_scope():
                self.conv1 = L.Convolution2D(input_chs, 16, 5, stride=1, pad=0)
                self.conv2 = L.Convolution2D(16, 32, 5, stride=1, pad=0)
                self.conv3 = L.Convolution2D(32, 64, 5, stride=1, pad=0)
                self.l4 = L.Linear(1 * 1 * 64, 100)
                self.l5 = L.Linear(100, n_outputs, nobias=True)
    
        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
    
    
    model = Model1(3, 1)
    inp = np.arange(1 * 3 * 30 * 30, dtype=np.float32).reshape((1, 3, 30, 30))
    model_pred = model(inp)
    out = F.sum(model_pred)
    
    out.backward(retain_grad=True)
    print(type(model.pred.grad))
    print(type(model.I.grad))
    
[/code]

Output 4

[code]

    <class 'numpy.ndarray'>
    <class 'numpy.ndarray'>
    
[/code]

となっており，質問者のコードにおいてもbackward(retain_grad=True)は正しく動作しております．

貴方のissueで生じた動作は，同じくメモリの効率化によって生じたものです．

すごく短く言えば，backward(retain_grad=True)は「ライフタイムが切れた，誰からも参照されていないVariableについてはgradやinputを保存しない」という動作をします．

[追記]  
基本的にはVariableのライフタイムについて気にする必要は無いはずです．  
もしデバッグ用に，すでに実装されているFunctionの一時的なVariableにアクセスがしたい場合には，上記の方法でgradを取り出すことができます．


CC BY-SA 4.0
2018-09-20T03:26:04.800
48574
2018-09-26T02:26:37.460
2018-09-26T02:26:37.460
29620
29620
43115
answer