Як застосувати відсікання градієнта в TensorFlow?


96

Розглядаючи приклад коду .

Я хотів би знати, як застосувати відсікання градієнта в цій мережі на RNN, де існує можливість вибуху градієнтів.

tf.clip_by_value(t, clip_value_min, clip_value_max, name=None)

Це приклад, який можна використати, але де я можу це ввести? У def RNN

    lstm_cell = rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0)
    # Split data because rnn cell needs a list of inputs for the RNN inner loop
    _X = tf.split(0, n_steps, _X) # n_steps
tf.clip_by_value(_X, -1, 1, name=None)

Але це не має сенсу, оскільки тензор _X є вхідним, а не градусом, що слід відсікти?

Чи потрібно для цього визначати власний оптимізатор, чи є простіший варіант?

Відповіді:


143

Градієнтне відсікання має відбуватися після обчислення градієнтів, але перед застосуванням їх для оновлення параметрів моделі. У вашому прикладі обидві ці речі обробляються AdamOptimizer.minimize()методом.

Для того, щоб відсікати ваші градієнти, вам потрібно буде явно обчислити, відрізати та застосувати їх, як описано в цьому розділі документації API TensorFlow . Зокрема, вам потрібно буде замінити виклик minimize()методу приблизно наступним:

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
gvs = optimizer.compute_gradients(cost)
capped_gvs = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gvs]
train_op = optimizer.apply_gradients(capped_gvs)

4
Стирк, дякую за допис. Чи знаєте ви, якими наступними кроками є насправді запуск ітерації оптимізатора? Як правило, оптимізатор створюється як, optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) а потім ітерація оптимізатора робиться як, optimizer.run()але використання optimizer.run(), здається, не працює в цьому випадку?
applecider

6
Гаразд, зрозуміло, optimizer.apply_gradients(capped_gvs)що потрібно щось призначити, x = optimizer.apply_gradients(capped_gvs)то протягом сеансу ви можете тренуватися якx.run(...)
applecider

3
Крикніть @ remi-cuingnet за приємну пропозицію щодо редагування . (Що, на жаль, було відхилено поспішними рецензентами)
Styrke

Це дає мені UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.Отже, так чи інакше мої рідкісні градієнти перетворюються на щільні. Будь-яка ідея, як подолати цю проблему?
Pekka

8
Насправді правильний спосіб відсікання градієнтів (згідно з документами tensorflow, інформатиками та логікою) - це tf.clip_by_global_norm, як запропонував @danijar
gdelab

116

Незважаючи на те, що здається популярним, ви, мабуть, хочете обрізати весь градієнт за його загальносвітовою нормою:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimize = optimizer.apply_gradients(zip(gradients, variables))

Відсікання кожної матриці градієнтів окремо змінює їх відносний масштаб, але це також можливо:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients = [
    None if gradient is None else tf.clip_by_norm(gradient, 5.0)
    for gradient in gradients]
optimize = optimizer.apply_gradients(zip(gradients, variables))

У TensorFlow 2 стрічка обчислює градієнти, оптимізатори надходять від Keras, і нам не потрібно зберігати операційне оновлення, оскільки воно запускається автоматично, не передаючи його сеансу:

optimizer = tf.keras.optimizers.Adam(1e-3)
# ...
with tf.GradientTape() as tape:
  loss = ...
variables = ...
gradients = tape.gradient(loss, variables)
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimizer.apply_gradients(zip(gradients, variables))

10
Хороший приклад з clip_by_global_norm()! Це також описано як the correct way to perform gradient clippingу документах tensorflow
MZHm

9
@Escachator Це емпірично і залежатиме від вашої моделі та можливо завдання. Що я роблю, це візуалізувати норму градієнта, tf.global_norm(gradients)щоб побачити її звичайний діапазон, а потім відрізати трохи вище, щоб запобігти несподіванкам, які псують навчання.
danijar

1
Ви все-таки зателефонували б opt.minimize()після цього, чи не зателефонували б щось інше, як opt.run()пропонується в деяких коментарях до інших відповідей?
reese0106

3
@ reese0106 Ні, optimizer.minimize(loss)це просто скорочення для обчислення та застосування градієнтів. Ви можете запустити приклад у моїй відповіді з sess.run(optimize).
danijar

1
Отже, якби я використовував tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)функцію експерименту, то Ви optimizeзамінили б мою train_opправильну? Зараз моя, train_op = optimizer.minimize(loss, global_step=global_step))тому я намагаюсь переконатись, що я відповідним чином налаштуюсь ...
reese0106

10

Це насправді належним чином пояснено в документації. :

Виклик minimize () дбає як про обчислення градієнтів, так і про їх застосування до змінних. Якщо ви хочете обробити градієнти перед їх застосуванням, ви можете замість цього скористатися оптимізатором у три кроки:

  • Обчисліть градієнти за допомогою compute_gradients ().
  • Обробляйте градієнти як завгодно.
  • Застосуйте оброблені градієнти за допомогою apply_gradients ().

І в наведеному прикладі вони використовують ці 3 кроки:

# Create an optimizer.
opt = GradientDescentOptimizer(learning_rate=0.1)

# Compute the gradients for a list of variables.
grads_and_vars = opt.compute_gradients(loss, <list of variables>)

# grads_and_vars is a list of tuples (gradient, variable).  Do whatever you
# need to the 'gradient' part, for example cap them, etc.
capped_grads_and_vars = [(MyCapper(gv[0]), gv[1]) for gv in grads_and_vars]

# Ask the optimizer to apply the capped gradients.
opt.apply_gradients(capped_grads_and_vars)

Ось MyCapperбудь-яка функція, яка обмежує ваш градієнт. Список корисних функцій (крім tf.clip_by_value()) тут .


Ви все-таки зателефонували б opt.minimize()після цього, чи не зателефонували б щось інше, як opt.run()пропонується в деяких коментарях до інших відповідей?
reese0106

@ reese0106 Ні, вам потрібно призначити opt.apply_gradients(...)змінну, як train_stepнаприклад (так само, як і для opt.minimize(). А у вашому основному циклі ви називаєте це, як зазвичай, тренуватисяsess.run([train_step, ...], feed_dict)
dsalaj,

Майте на увазі, що градієнт визначається як вектор похідних втрат wrt за всіма параметрами в моделі. TensorFlow представляє його як список Python, який містить кортеж для кожної змінної та її градієнт. Це означає, що для відсікання норми градієнта не можна обрізати кожен тензор окремо, потрібно розглянути список відразу (наприклад, використовуючи tf.clip_by_global_norm(list_of_tensors)).
danijar

8

Для тих, хто хотів би зрозуміти ідею градієнтного відсікання (за нормою):

Всякий раз, коли норма градієнта перевищує певний поріг, ми відсікаємо норму градієнта, щоб вона залишалася в межах порогу. Іноді цей поріг встановлюють 5.

Нехай градієнт буде g, а max_norm_threshold - j .

Тепер, якщо || g || > j , робимо:

g = ( j * g ) / || g ||

Це реалізація зроблена в tf.clip_by_norm


якщо мені потрібно вибрати поріг вручну, чи є загальноприйнятий спосіб зробити це?
ningyuwhut

Це свого роду чорна магія, яку пропонують деякі статті. В іншому випадку вам доведеться провести багато експериментів і з’ясувати, який з них працює краще.
kmario23

4

IMO найкращим рішенням є обгортання вашого оптимізатора декоратором оцінювача TF tf.contrib.estimator.clip_gradients_by_norm:

original_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
optimizer = tf.contrib.estimator.clip_gradients_by_norm(original_optimizer, clip_norm=5.0)
train_op = optimizer.minimize(loss)

Таким чином, вам потрібно лише один раз визначити це, а не запускати його після кожного розрахунку градієнтів.

Документація: https://www.tensorflow.org/api_docs/python/tf/contrib/estimator/clip_gradients_by_norm


2

Відсікання градієнта в основному допомагає у випадку вибуху або зникнення градієнтів. Скажімо, ваші втрати занадто великі, що призведе до експоненціальних градієнтів, щоб протікати через мережу, що може призвести до значень Nan. Щоб подолати це, ми відсікаємо градієнти в межах певного діапазону (від -1 до 1 або будь-якого діапазону відповідно до умови).

clipped_value=tf.clip_by_value(grad, -range, +range), var) for grad, var in grads_and_vars

де grads _and_vars - пари градієнтів (які ви обчислюєте за допомогою tf.compute_gradients) та їх змінні, до яких вони будуть застосовані.

Після відсікання ми просто застосовуємо його значення за допомогою оптимізатора. optimizer.apply_gradients(clipped_value)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.