EN

Double-backward

2019年11月6日 水曜日

API , チュートリアル

Posted by Kazuki Yoshiyama

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が設定されています.この引数は,gradsx.gradのメモリ領域をバインドしています.APIに関しての詳細は,Grad APIを参照してください.

Backward on both a backward and forward graph

2つ目の例で,nn.gradの戻値であるgradsnn.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にはいくつかの形式があります.

  1. One-centered gradient penalty, WGAN-GP
  2. Zero-centered gradient penalties, R1 and R2

一つ目は,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になるように制約をかけます.