Как применить градиентную обрезку в TensorFlow?

96

Учитывая пример кода .

Я хотел бы знать, как применить градиентное отсечение в этой сети в RNN, где есть возможность взрывных градиентов.

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

Это пример, который можно использовать, но где мне его представить? В деф РНН

    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(...)
понятно,
3
Благодарю @ remi-cuingnet за прекрасное предложение по редактированию . (Что, к сожалению, было отвергнуто поспешными рецензентами)
Стырке 01
Это дает мне UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.Итак, мои разреженные градиенты каким-то образом превращаются в плотные. Есть идеи, как решить эту проблему?
Пекка
8
На самом деле правильный способ обрезать градиенты (согласно документации по тензорному потоку, компьютерным специалистам и логике) - это с 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в документации по тензорному
потоку
9
@Escachator Это эмпирический анализ, который будет зависеть от вашей модели и, возможно, задачи. Что я делаю, так это визуализирую норму градиента, tf.global_norm(gradients)чтобы увидеть ее обычный диапазон, а затем вырезать немного выше этого, чтобы выбросы не испортили обучение.
danijar
1
Вы бы все равно позвонили opt.minimize()после этого или вы бы назвали что-то другое, opt.run()как это предлагается в некоторых комментариях к другим ответам?
reese0106 05
3
@ reese0106 Нет, optimizer.minimize(loss)это просто сокращение для вычисления и применения градиентов. Вы можете запустить пример в моем ответе с помощью sess.run(optimize).
danijar 05
1
Итак, если бы я использовал tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)функцию эксперимента, то вы optimizeбы заменили мою train_opправильную? Прямо сейчас мой, train_op = optimizer.minimize(loss, global_step=global_step))так что я пытаюсь убедиться, что я соответствующим образом
настроился
10

На самом деле это правильно объяснено в документации. :

Вызов метода minim () заботится как о вычислении градиентов, так и о применении их к переменным. Если вы хотите обработать градиенты перед их применением, вы можете вместо этого использовать оптимизатор в три этапа:

  • Вычислите градиенты с 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
Имейте в виду, что градиент определяется как вектор производных потерь по всем параметрам модели. TensorFlow представляет его как список Python, содержащий кортеж для каждой переменной и ее градиента. Это означает обрезать норму градиента, вы не можете обрезать каждый тензор по отдельности, вам нужно учитывать список сразу (например, используя tf.clip_by_global_norm(list_of_tensors)).
Данижар
8

Для тех, кто хотел бы понять идею градиентного отсечения (по норме):

Каждый раз, когда норма градиента превышает определенный порог, мы обрезаем норму градиента, чтобы она оставалась в пределах порога. Иногда этот порог устанавливается равным 5.

Пусть градиент равен g, а max_norm_threshold равен j .

Теперь, если || г || > j , делаем:

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

Это реализация, сделанная в tf.clip_by_norm

kmario23
источник
если мне нужно выбрать порог вручную, есть ли какой-нибудь общий способ сделать это?
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)

Радж
источник