TL;DR
-
enable_backprop
はbackward()
実行時ではなくforwardグラフ構築時に適用される。- ただし、動きがあまり直感的でないため、グラフ中に何度も
enable_backprop
を変更するような操作はしないほうが良い。
- ただし、動きがあまり直感的でないため、グラフ中に何度も
-
chainer.configuration
においてtrain
はenable_backprop
を内包しないので気をつける。- 標準ライブラリの
chainer.extensions.Evaluator
はこれらを適切に設定してくれるので安心して大丈夫。 - 自前の
chainer.extensions.Evaluator
みたいなものを書くときは両方のconfigurations
を変更すること
- 標準ライブラリの
目的
とうとう重い腰をあげてChainerとオレオレヘルパーライブラリをv2にあげようとしたところ、enable_backprop
の挙動がきになったのでもメモ。
特に、下記の質問について考えてみる。
- グラフの途中で
enable_backprop
が変わったらどうなるのだろうか。.unchain_backward()
みたいなことはできるのだろうか? -
train
をFalseにしたら、enable_backprop
も自動的にFalseになるのだろうか? - 標準ライブラリの
chainer.extensions.Evaluator
は信用して使って大丈夫なのだろうか?
ちなみにこの投稿はv2.0.0時点のものです。
調査
グラフの途中でenable_backprop
が変わったらどうなる?
contextmanagerを使った実装だと、グラフの途中でenable_backprop
が変わったらどうなるのかがとても気になります。
もし、no_backprop_mode
がbackward()
実行時に効果を持ち、forward計算時にはno_backprop_mode
を使っていたのにbackward計算をno_backprop_mode
で囲い忘れたがために異常に良い精度がでてしまった...では困ります1。
この答えは、function_node.pyにあります。ここでの記載のとおり、no_backprop_mode
の効果は計算グラフ構築時に親を登録しないところにあるので、forward計算時に指定した範囲で効果を持ちます。
通常の実行
a = chainer.Variable(np.array([0.1], dtype=np.float32))
with chainer.configuration.using_config('enable_backprop', True):
chainer.config.show()
b = a * 2.0
b.backward()
print a.grad
cudnn_deterministic False
debug False
enable_backprop True
keep_graph_on_report False
train True
type_check True
use_cudnn auto
[ 2.]
backwardが範囲外にあってもちゃんと効果があることがわかります。
a = chainer.Variable(np.array([0.1], dtype=np.float32))
with chainer.configuration.using_config('enable_backprop', False):
chainer.config.show()
b = a * 2.0
b.backward()
print a.grad
cudnn_deterministic False
debug False
enable_backprop False
keep_graph_on_report False
train True
type_check True
use_cudnn auto
None
また、上記で述べたように、enable_backprop
は親との接続を断ち切ります。したがってcontextmanager内で新しく作られた変数ではなく、その親の勾配が0になります。
a = chainer.Variable(np.array([0.1], dtype=np.float32))
with chainer.configuration.using_config('enable_backprop', False):
b = a * 2.0
c = b + 0.5
c.backward()
print a.grad # None
print b.grad # [ 1.]
しかも、backwardが呼ばれた時のconfigurationに完全に依存しないかというと、そうではありません。なので、1つの計算グラフの中で何度もenable_backprop
を使うような実装はしないで、素直にunchain_backward()
を使ったほうがよさそうです。
train
をFalseにしたら、enable_backprop
も自動的にFalseになる?
なりません。
a = chainer.Variable(np.array([0.1], dtype=np.float32))
with chainer.configuration.using_config('train', False):
chainer.config.show()
b = a * 2.0
b.backward()
print a.grad
cudnn_deterministic False
debug False
enable_backprop True
keep_graph_on_report False
train False
type_check True
use_cudnn auto
[ 2.]
なので、自分でextensions.Evaluator
みたいなコードを書く場合はenable_backprop
とtrain
の両方をFalse
にする必要があります。
標準ライブラリの chainer.extensions.Evaluator
は信用して使って大丈夫?
大丈夫そうです。 enable_backprop
とtrain
の両方をFalse
にしてくれています。
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import training
from chainer.training import extensions
# Network definition
class MLP(chainer.Chain):
def __init__(self, n_out):
super(MLP, self).__init__()
with self.init_scope():
self.l1 = L.Linear(None, n_out)
def __call__(self, x):
chainer.config.show()
print ""
return self.l1(x)
model = L.Classifier(MLP(10))
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
# Load the MNIST dataset
train, test = chainer.datasets.get_mnist()
test = chainer.datasets.split_dataset(test, 1)[0]
train_iter = chainer.iterators.SerialIterator(train, 32)
test_iter = chainer.iterators.SerialIterator(test, 1,
repeat=False, shuffle=False)
# Set up a trainer
updater = training.StandardUpdater(train_iter, optimizer)
trainer = training.Trainer(updater, (1, 'iteration'))
trainer.extend(extensions.Evaluator(test_iter, model), trigger=(1, 'iteration'))
# Run the training
trainer.run()
cudnn_deterministic False
debug False
enable_backprop True
keep_graph_on_report False
train True
type_check True
use_cudnn auto
cudnn_deterministic False
debug False
enable_backprop False
keep_graph_on_report False
train False
type_check True
use_cudnn auto
ご覧のとおりenable_backprop
とtrain
の両方がFalse
になっています。コードでいうとこのあたりが該当しています。
-
もちろん、optimizerを呼ばないと実際にはデータが変わらないので、実用上このミスをおかしても問題ありませんが。 ↩