はじめに
まだまだeagerモードについて私自身理解できていません。
とりあえず実行結果から使い方を少しだけ考えてみます。
ここでは以下の記事
s0sem0y.hatenablog.com
と被る内容は触れないので、基本事項は確認しておいてください。
今回触れるのはネットワークの書き方とドロップアウトに関する内容です。
本題
ネットワークの書き方
書き方としては以下のような形になります。
class Net(tfe.Network):
def __init__(self):
super(Net, self).__init__()
self.conv1 = self.track_layer(tf.layers.Conv2D(10, 5))
self.conv2 = self.track_layer(tf.layers.Conv2D(20, 5))
self.fc1 = self.track_layer(tf.layers.Dense(50))
self.fc2 = self.track_layer(tf.layers.Dense(10))
def call(self, x):
x = tf.nn.relu(
tf.nn.max_pool(
self.conv1(x),
[1, 2, 2, 1],
[1, 1, 1, 1],
'VALID'))
x = self.conv2(x)
x = tf.nn.relu(
tf.nn.max_pool(
x,
[1, 2, 2, 1],
[1, 1, 1, 1],
'VALID'))
x = tf.layers.flatten(x)
x = tf.nn.relu(self.fc1(x))
x = self.fc2(x)
return x
__init__で層を定義する際に、self.track_layer()というおまじないでtf.layersを囲ってやる必要性を除けば、基本的にPyTorchなどと書き方は極めて似た感じになります。tf.layersで定義される層には活性化関数を付随させることができますが、今回はtf.nn.reluをcallに書いてやることにしました。とりあえずlayersやnnが混在することは構わないようです(これは普通のTensorFlowでもそうだった)。
学習に備えてドロップアウトの実行を制御(失敗例
次にドロップアウトを学習時には実行し、検証・テスト時には実行しないという制御を加えるために以下のように書きます。self.trainingの切り替えはPyTorchの方式をとりあえず模倣していますが、どういう形式でも構わないでしょう。
class Net(tfe.Network):
def __init__(self):
super(Net, self).__init__()
self.conv1 = self.track_layer(tf.layers.Conv2D(10, 5))
self.conv2 = self.track_layer(tf.layers.Conv2D(20, 5))
self.conv2_drop = self.track_layer(tf.layers.Dropout(0.5))
self.fc1 = self.track_layer(tf.layers.Dense(50))
self.fc1_drop = self.track_layer(tf.layers.Dropout(0.))
self.fc2 = self.track_layer(tf.layers.Dense(10))
self.training = True
def train(self):
self.training = True
def eval(self):
self.training = False
def call(self, x):
x = tf.nn.relu(
tf.nn.max_pool(
self.conv1(x),
[1, 2, 2, 1],
[1, 1, 1, 1],
'VALID'))
x = self.conv2(x)
if self.training:
x = self.conv2_drop(x)
x = tf.nn.relu(
tf.nn.max_pool(
x,
[1, 2, 2, 1],
[1, 1, 1, 1],
'VALID'))
x = tf.layers.flatten(x)
x = tf.nn.relu(self.fc1(x))
if self.training:
x = self.fc1_drop(x)
x = self.fc2(x)
return x
model = Net()
X = np.random.randn(100, 28, 28, 1)
X = tf.convert_to_tensor(X, tf.float32)
model.eval()
print(model.training)
Y1 = model(X)
model.train()
print(model.training)
Y2 = model(X)
model.eval()
print(model.training)
Y3 = model(X)
print(Y1.numpy() == Y2.numpy())
print(Y1.numpy() == Y3.numpy())
このプログラムに期待される出力をコメントともに載せています。
しかし期待される出力は得られませんでした。私がTensorFlowの使い方を根本的に間違えている可能性もありますが、tf.layers.Dropoutのメソッドをcallで呼び出しても、ドロップアウトは実行されていない様子でした。
(成功例?
上記の失敗例のネットワークを以下に書き換えます。
変わっているのは、__init__でtf.layers.Dropoutクラスの層を定義するのではなく、callの方でtf.layers.dropout関数を用いている点です。
class Net(tfe.Network):
def __init__(self):
super(Net, self).__init__()
self.conv1 = self.track_layer(tf.layers.Conv2D(10, 5))
self.conv2 = self.track_layer(tf.layers.Conv2D(20, 5))
self.fc1 = self.track_layer(tf.layers.Dense(50))
self.fc2 = self.track_layer(tf.layers.Dense(10))
self.training = True
def train(self):
self.training = True
def eval(self):
self.training = False
def call(self, x):
x = tf.nn.relu(
tf.nn.max_pool(
self.conv1(x),
[1, 2, 2, 1],
[1, 1, 1, 1],
'VALID'))
x = self.conv2(x)
x = tf.layers.dropout(x, 0.5, training=self.training)
x = tf.nn.relu(
tf.nn.max_pool(
x,
[1, 2, 2, 1],
[1, 1, 1, 1],
'VALID'))
x = tf.layers.flatten(x)
x = tf.nn.relu(self.fc1(x))
x = tf.layers.dropout(x, 0.5, training=self.training)
x = self.fc2(x)
return x
model = Net()
X = np.random.randn(100, 28, 28, 1)
X = tf.convert_to_tensor(X, tf.float32)
model.eval()
print(model.training)
Y1 = model(X)
model.train()
print(model.training)
Y2 = model(X)
model.eval()
print(model.training)
Y3 = model(X)
print(Y1.numpy() == Y2.numpy())
print(Y1.numpy() == Y3.numpy())
こちらは期待通りの挙動を示しました。
ちょっと理由はよくわからないです。tf.layers.Dropoutの方が常にドロップアウトをしない状況になってしまうということは、例えばtfe.Networkクラスに学習か検証かを統一的に管理している変数があるのかもしれません(それがデフォルトでFalseになっているとか)。あるいは根本的にtf.layers.Dropoutの使い方が間違っているのか…(TensorFlow久々でよく分からん)。
いずれにしても、少しTensorFlowと格闘する必要がありそうです。あと、Google Rearch Blogを見た感じ、勾配の計算が若干癖がありそうな気もしました。ここらへんは追々ということで。