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を呼ばないと実際にはデータが変わらないので、実用上このミスをおかしても問題ありませんが。 ↩