Double Backwards
NNabla 1.1.0からdouble backwardsの機能がサポートされました.
このブログでは,簡単にdouble backwardsの機能とユースケースを紹介します.
Feature of Double Backwards
Double backwardsの機能は主に2つあります.
– フォワードグラフの展開
– 展開されたグラフでのバックプロパゲーション
それぞれについてみていきます.
Forward Graph Expansion
NNablaのグラフエンジンでは,フォワードグラフを逆にたどるように,バックプロパゲーションが実装されています.バックワードグラフがフォワードグラフから展開されているわけではありません.これはバックプロパゲーションをどのように実装するのかの一つの選択で,初期の実装デザインではNNablaは前者を選んでいました.
次のコードスニペットは,異なる方法で同様の結果になる,バックプロパゲーションを計算しています.
1. Backpropagation on forward graph
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import numpy as np
rng = np.random.RandomState(313)
x = nn.Variable.from_numpy_array(rng.randn(2, 3)).apply(need_grad=True)
x.grad.zero()
y = F.sigmoid(x)
y.forward()
y.backward(clear_buffer=True)
print(x.g)
2. Backpropagation by forward graph expansion
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import numpy as np
rng = np.random.RandomState(313)
x = nn.Variable.from_numpy_array(rng.randn(2, 3)).apply(need_grad=True)
x.grad.zero()
y = F.sigmoid(x)
grads = nn.grad([y], [x], bind_grad_output=True)
nn.forward_all(grads)
print(x.g)
初めの例では,フォワードグラフを逆にたどるようにバックプロパゲーションの計算をしています.一方で,次の例では,フォワードグラフからバックワードグラフを展開して,バックプロパゲーションの計算をしています.
これらの例では,一次の勾配のみを計算しているので,nn.grad APIのbind_grad_output=Trueが設定されています.この引数は,gradsとx.gradのメモリ領域をバインドしています.APIに関しての詳細は,Grad APIを参照してください.
Backward on both a backward and forward graph
2つ目の例で,nn.gradの戻値であるgradsもnn.Variableのリストなので,1つ目の例の様に通常の計算が行えます.
次の例では,nn.gradの戻値であるgradsを通常の計算に使っています.
3. Backpropagation on both a backward and forward graph
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import numpy as np
rng = np.random.RandomState(313)
b, c, h, w = 16, 3, 32, 32
x = nn.Variable.from_numpy_array(rng.randn(b, c, h, w)).apply(need_grad=True)
x.grad.zero()
y = F.sigmoid(x)
grads = nn.grad([y], [x])
norms = [F.sum(g ** 2.0, [1, 2, 3]) ** 0.5 for g in grads]
gp = sum([F.mean((norm - 1.0) ** 2.0) for norm in norms])
gp.forward()
gp.backward()
print(x.g)
この例では単純にxの勾配をプリントしていますが,実際のアプリケーションではモデルパラメータに関する勾配に意味がる場合がほとんどなので,x.gの値が実際に欲しい場合を除き,x.grad.zero()を明示的に実行する必要はありません.
Usecase
Double Backwardsは,一次の勾配を目的関数に入れたい場合によく使われます.よくある例はGANs (Generative Adversarial Networks)の学習を安定させるGradient penaltyです.Gradient penaltyにはいくつかの形式があります.
一つ目は,NNabla exampleで見つかるので,二つ目の例のR1 zero-centered gradient penaltyのコードスニペットを紹介します.
4. R1 Zero-centerd Gradient Penalty
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import numpy as np
# Generator
x_fake = <generator(...)>
p_fake = <discriminator(x_fake)>
# Discriminator Loss
x_real = <nn.Variable(...)>.apply(need_grad=True)
p_real = <discriminator(x_real)>
loss_dis = <gan_loss>(p_real, p_fake)
# R1 Zero-centerd Gradient Penalty
grads = nn.grad([p_real], [x_real])
norms = [F.sum(g ** 2.0, [1, 2, 3]) ** 0.5 for g in grads]
r1_zc_gp = sum([F.mean(norm ** 2.0) for norm in norms])
# Total loss for the discriminator
loss_dis += 0.5 * <gamma> * r1_zc_gp
R1 zero-centered gradient penaltyは,真の分布からのデータとDiscriminatorの出力を使って,勾配のL2-Normを計算し,それが1ではなくて0になるように制約をかけます.