Я пытаюсь реализовать схему обучения/финализации, когда в каждой итерации backpropagation определенный набор параметров остается фиксированным. Я хочу иметь возможность изменять набор обновлений или фиксированных параметров с итерации на итерацию. Метод tf.stop_gradient
, который, по-видимому, заставляет градиенты некоторых параметров оставаться нулевыми, очень полезен для этой цели и отлично работает с разными оптимизаторами, если набор обновлений или фиксированных параметров не изменяется от итераций к итерациям. Он также может обрабатывать различные установки обновления или фиксированные параметры, если он используется при стохастическом градиентном спуске. Моя проблема в том, что tf.stop_gradient
не может обрабатывать такие случаи при использовании с оптимизатором Адама. Более конкретно, он сохраняет градиенты фиксированных параметров в ноль на выходе tf.compute_gradients
, но при применении градиентов (tf.apply_gradients
) значение фиксированных параметров изменяется. Я полагаю, это связано с тем, что шаг оптимизации в оптимизаторе Ада не равен нулю, даже если градиент равен нулю (на основе алгоритма 1 в бумаге Кингма и Ба). Существует ли дешевый способ замораживания переменной набора параметров в каждой итерации Адама без явного сохранения предыдущих значений итерации фиксированных параметров?
Предположим, что у меня есть одноуровневая сеть с весовой матричной переменной W
и матрицей-заполнителем бинарной маски MW
которая определяет, какие элементы W
должны обновляться на каждой итерации (значение 1 в). Вместо того, чтобы использовать W
для записи отношения ввода/вывода этого слоя, я изменяю его как ниже
masked_W = MW*W + tf.stop_gradient(tf.abs(1-MW)*W)
для маскировки некоторых элементов W
из ненулевых градиентов. Затем я использую masked_W
для формирования вывода слоя, и, следовательно, потеря сети зависит от этой маскированной переменной. Дело в том, что MW
меняется на каждой итерации. Пусть W
- вектор из 4 элементов, инициализированных всем нулевым вектором. Вот что происходит:
opt=tf.AdamOptimizer(1e-5)
sess.run(tf.global_variables_initializer())
grads_vars=opt.compute_gradients(loss, W)
# initial value of W=[0,0,0,0]
# first iteration:
MW_val = [0,1,1,0]
feed_dict={MW:MW_val, x: batch_of_data, y_:batch_of_labels}
sess.run(opt.apply_gradients(grads_vars), feed_dict=feed_dict))
# gradient of W=[0,xx,xx,0]
# new value of W=[0,a,b,0]
где xx
- некоторые ненулевые значения градиента, а a
и b
- новые значения обновляющих элементов W
На второй итерации мы изменим значение, присвоенное матричной матричной маске MW
на [1,0,0,1], поэтому мы ожидаем иметь фиксированные значения для W[1]
и W[2]
и обновить значения для W[0]
и W[3]
. Но вот что происходит:
# second iteration
MW_val = [1,0,0,1]
feed_dict={MW:MW_val, x: batch_of_data, y_:batch_of_labels}
sess.run(opt.apply_gradients(grads_vars), feed_dict=feed_dict))
# gradient of W=[xx,0,0,xx]
# new value of W=[c,aa,bb,d]
То есть, хотя градиенты W[1]
и W[2]
равны нулю, они получают новые значения (aa != a
bb != b
и bb != b
). При изменении оптимизатора от Адама до SGD значения фиксированных параметров остаются такими же, как ожидалось.
Я нашел решение своего вопроса и поделился им здесь, если другие найдут это полезным. После первой итерации моменты тех параметров, которые были обновлены на первой итерации, уже отличны от нуля. Поэтому, даже если на второй итерации положить их градиенты на ноль, они будут обновляться из-за их ненулевых тензоров импульса. Чтобы предотвратить обновление, tf.stop_gradient
использовать только tf.stop_gradient
, мы также должны удалить их импульс. В случае оптимизатора Адама это можно сделать с помощью метода get_slot
оптимизатора: opt.get_slot(par, 'm')
и opt.get_slot(par,'v')
, где первые и последние предоставляют доступ к первому и вторых импульсов импульса параметра par
, соответственно. В примере вопроса мы должны добавить следующие строки, чтобы заморозить W[1]
и W[2]
на второй итерации:
# momentums of W in the first iteration
m_vals = opt.get_slot(W, 'm')
v_vals = opt.get_slot(W, 'v')
# mask them before running the second iteration
masked_m_vals[[1,2]]=0
masked_v_vals[[1,2]]=0
sess.run(opt.get_slot(W, 'm').assign(masked_m_vals))
sess.run(opt.get_slot(W, 'v').assign(masked_v_vals))
Лучше сохранить замаскированные импульсы в примере выше m_vals[[1,2]]
и v_vals[[1,2]]
, так что если на третьей итерации мы ослаблим фиксирующее ограничение W[1]
и W[2]
, мы можем восстановить их импульсы до их первоначальных значений на первой итерации.
В качестве альтернативы вы можете передавать разные подмножества переменных apply_gradients, когда вы хотите обновлять разные подмножества переменных.
opt.compute_gradients
вы должны либо включать, либо исключать целую переменную, например, вы не можете написатьopt.compute_gradients(loss, W[1:3])
потому что TF жалуется на неподдерживаемый тип ( который, я думаю, происходит от подачи полосатого ломтика). Есть ли какой-нибудь дешевый метод, который может разложить огромные тензоры параметров на определенные части (что-то вроде индексации тензора с произвольными наборами индексов) и иметьtf.apply_gradients
для работы только с этими частями?