web-dev-qa-db-ja.com

累積された勾配でモデルパラメーターを更新するには?

TensorFlowを使用してディープラーニングモデルを構築しています。 TensorFlowの新機能。

何らかの理由により、モデルのバッチサイズが制限されているため、この制限されたバッチサイズにより、モデルの分散が大きくなります。

だから、私はいくつかのトリックを使用してバッチサイズを大きくしたいと思います。私のアイデアは、各ミニバッチの勾配(たとえば64ミニバッチ)を格納し、勾配を合計し、この64ミニバッチのトレーニングデータの平均勾配を使用してモデルのパラメーターを更新することです。

つまり、最初の63個のミニバッチでは、パラメーターを更新せず、64個のミニバッチの後は、モデルのパラメーターを1回だけ更新します。

しかし、TensorFlowはグラフベースであるため、この必要な機能を実装する方法を誰かが知っていますか?

どうもありがとう。

17
weixsong

私はここに解決策を見つけました: https://github.com/tensorflow/tensorflow/issues/3994#event-766328647

opt = tf.train.AdamOptimizer()
tvs = tf.trainable_variables()
accum_vars = [tf.Variable(tf.zeros_like(tv.initialized_value()), trainable=False) for tv in tvs]                                        
zero_ops = [tv.assign(tf.zeros_like(tv)) for tv in accum_vars]
gvs = opt.compute_gradients(rmse, tvs)
accum_ops = [accum_vars[i].assign_add(gv[0]) for i, gv in enumerate(gvs)]
train_step = opt.apply_gradients([(accum_vars[i], gv[1]) for i, gv in enumerate(gvs)])

トレーニングループでは:

while True:
    sess.run(zero_ops)
    for i in xrange(n_minibatches):
        sess.run(accum_ops, feed_dict=dict(X: Xs[i], y: ys[i]))
    sess.run(train_step)

しかし、このコードはあまりクリーンできれいではないようです、誰でもこれらのコードを最適化する方法を知っていますか?

12
weixsong

私は同じ問題を抱えていて、それを理解しました。

最初にシンボリックグラデーションを取得してから、累積されたグラデーションをtf.Variablesとして定義します。 (tf.global_variables_initializer()は_grads_accum_を定義する前に実行する必要があるようです。それ以外の場合はエラーが発生しました。理由は不明です。)

_tvars = tf.trainable_variables()
optimizer = tf.train.GradientDescentOptimizer(lr)
grads = tf.gradients(cost, tvars)

# initialize
tf.local_variables_initializer().run()
tf.global_variables_initializer().run()

grads_accum = [tf.Variable(tf.zeros_like(v)) for v in grads] 
update_op = optimizer.apply_gradients(Zip(grads_accum, tvars)) 
_

トレーニングでは、バッチごとに(_gradients_accum_に保存された)勾配を累積し、64番目のバッチの実行後にモデルを更新できます。

_feed_dict = dict()
for i, _grads in enumerate(gradients_accum):
    feed_dict[grads_accum[i]] = _grads
sess.run(fetches=[update_op], feed_dict=feed_dict) 
_

使用例については tensorflow/tensorflow/python/training/optimizer_test.py を参照してください。特に、この関数はtestGradientsAsVariables()です。

それが役に立てば幸い。

2
Irene W.

以前のソリューションは、累積された勾配の平均を計算しないため、トレーニングが不安定になる可能性があります。この問題を解決するはずの上記のコードを変更しました。

# Fetch a list of our network's trainable parameters.
trainable_vars = tf.trainable_variables()

# Create variables to store accumulated gradients
accumulators = [
    tf.Variable(
        tf.zeros_like(tv.initialized_value()),
        trainable=False
    ) for tv in trainable_vars
]

# Create a variable for counting the number of accumulations
accumulation_counter = tf.Variable(0.0, trainable=False)

# Compute gradients; grad_pairs contains (gradient, variable) pairs
grad_pairs = optimizer.compute_gradients(loss, trainable_vars)

# Create operations which add a variable's gradient to its accumulator.
accumulate_ops = [
    accumulator.assign_add(
        grad
    ) for (accumulator, (grad, var)) in Zip(accumulators, grad_pairs)
]

# The final accumulation operation is to increment the counter
accumulate_ops.append(accumulation_counter.assign_add(1.0))

# Update trainable variables by applying the accumulated gradients
# divided by the counter. Note: apply_gradients takes in a list of 
# (grad, var) pairs
train_step = optimizer.apply_gradients(
    [(accumulator / accumulation_counter, var) \
        for (accumulator, (grad, var)) in Zip(accumulators, grad_pairs)]
)

# Accumulators must be zeroed once the accumulated gradient is applied.
zero_ops = [
    accumulator.assign(
        tf.zeros_like(tv)
    ) for (accumulator, tv) in Zip(accumulators, trainable_vars)
]

# Add one last op for zeroing the counter
zero_ops.append(accumulation_counter.assign(0.0))

このコードは、@ weixsongが提供するものと同じ方法で使用されます。

1
Ben Price

Sess.run(train_step)でfeed_dictを再度指定しないと、投稿したメソッドが失敗したようです。なぜfeed_dictが必要なのかはわかりませんが、最後の例を繰り返してすべてのアキュムレータを追加して再度実行することは可能です。これは私の場合に私がしなければならなかったことです:

            self.session.run(zero_ops)
            for i in range(0, mini_batch):

                self.session.run(accum_ops, feed_dict={self.ph_X: imgs_feed[np.newaxis, i, :, :, :], self.ph_Y: flow_labels[np.newaxis, i, :, :, :], self.keep_prob: self.dropout})

            self.session.run(norm_acums, feed_dict={self.ph_X: imgs_feed[np.newaxis, i, :, :, :], self.ph_Y: flow_labels[np.newaxis, i, :, :, :], self.keep_prob: self.dropout})
            self.session.run(train_op, feed_dict={self.ph_X: imgs_feed[np.newaxis, i, :, :, :], self.ph_Y: flow_labels[np.newaxis, i, :, :, :], self.keep_prob: self.dropout})

そして、勾配を正規化するために、私はそれが累積サイズをバッチサイズで除算するだけであることを理解しているので、新しい操作のみを追加します

norm_accums = [accum_op/float(batchsize) for accum_op in accum_ops]

誰かに同じ問題のfeed_dictがありましたか?

* UPDATE間違っていると推測したため、バッチ内の最後の例ですべてのグラフを再度実行します。この小さなコードはそれをテストします

import numpy as np
import tensorflow as tf
ph = tf.placeholder(dtype=tf.float32, shape=[])
var_accum = tf.get_variable("acum", shape=[], 
initializer=tf.zeros_initializer())
acum = tf.assign_add(var_accum, ph)
divide = acum/5.0
init = tf.global_variables_initializer()
    with tf.Session() as sess:
    sess.run(init)
    for i in range(5):
         sess.run(acum, feed_dict={ph: 2.0})

c = sess.run([divide], feed_dict={ph: 2.0})
#10/5 = 2
print(c)
#but it gives 2.4, that is 12/5, so sums one more time

これを解決する方法を見つけました。したがって、テンソルフローには条件付き演算があります。累積を1つのブランチに配置し、最後の累積を正規化して別のブランチに更新します。私のコードはめちゃくちゃですが、すばやく確認するために、使用例の小さなコードを示します。

import numpy as np
import tensorflow as tf

ph = tf.placeholder(dtype=tf.float32, shape=[])
#placeholder for conditional braching in the graph
condph = tf.placeholder(dtype=tf.bool, shape=[])

var_accum = tf.get_variable("acum", shape=[], initializer=tf.zeros_initializer())

accum_op = tf.assign_add(var_accum, ph)

#function when condition of condph is True
def truefn():
   return accum_op
#function when condtion of condph is False
def falsefn():
   div = accum_op/5.0
   return div

#return the conditional operation
cond = tf.cond(condph, truefn, falsefn)

init = tf.global_variables_initializer()

with tf.Session() as sess:
   sess.run(init)
   for i in range(4):
       #run only accumulation
       sess.run(cond, feed_dict={ph: 2.0, condph: True})
   #run acumulation and divition
   c = sess.run(cond, feed_dict={ph: 2.0, condph: False})

print(c)
#now gives 2

*重要な注意:すべてが機能しなかったことを忘れてください。オプティマイザーは障害をドロップします。

1
imart302

Tensorflow 2.0互換の回答:上記のweixsongの回答と Tensorflow Website で提供される説明に沿って、 Tensorflowバージョン2.0でグラデーションを累積するためのコード:

def train(epochs):
  for Epoch in range(epochs):
    for (batch, (images, labels)) in enumerate(dataset):
       with tf.GradientTape() as tape:
        logits = mnist_model(images, training=True)
        tvs = mnist_model.trainable_variables
        accum_vars = [tf.Variable(tf.zeros_like(tv.initialized_value()), trainable=False) for tv in tvs]
        zero_ops = [tv.assign(tf.zeros_like(tv)) for tv in accum_vars]
        loss_value = loss_object(labels, logits)

       loss_history.append(loss_value.numpy().mean())
       grads = tape.gradient(loss_value, tvs)
       #print(grads[0].shape)
       #print(accum_vars[0].shape)
       accum_ops = [accum_vars[i].assign_add(grad) for i, grad in enumerate(grads)]



    optimizer.apply_gradients(Zip(grads, mnist_model.trainable_variables))
    print ('Epoch {} finished'.format(Epoch))

# call the above function    
train(epochs = 3)

完全なコードは、この Github Gist にあります。

0

Tensorflowの代わりにPytorchを使用できます。トレーニング中に勾配を蓄積できるためです。

0