Можно ли реализовать градиентный спуск, как оптимизаторы, только с примером кода из обработки градиентов в TensorFlow?

Я смотрел пример кода для обработки градиентов, который есть в TensorFlow:

# 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)

однако я заметил, что функция apply_gradients была получена из GradientDescentOptimizer. Означает ли это, что, используя приведенный выше пример кода, можно реализовать только градиент, например правила спуска (обратите внимание, что мы можем изменить opt = GradientDescentOptimizer или Adam или любой из других оптимизаторов)? В частности, что делает apply_gradients? Я окончательно проверяю код на странице tf github но это была связка Python, не имевшая ничего общего с математическими выражениями, поэтому было трудно сказать, что она делает и как она изменилась от оптимизатора к оптимизатору.

Например, если бы я хотел реализовать свой собственный оптимизатор, который мог бы использовать градиенты (или не мог, например, просто изменять веса напрямую с помощью некоторого правила, может быть, более биологически правдоподобного правила), это невозможно с приведенным выше примером кода?


В частности, я хотел реализовать версию градиентного спуска, которая искусственно ограничена в компактной области. В частности, я хотел реализовать следующее уравнение:

w := (w - mu*grad + eps) mod B

в TensorFlow. Я понял, что верно следующее:

w := w mod B - mu*grad mod B + eps mod B

поэтому я подумал, что могу просто реализовать это, выполнив:

def Process_grads(g,mu_noise,stddev_noise,B):
    return (g+tf.random_normal(tf.shape(g),mean=mu_noise,stddev=stddev_noise) ) % B

а затем просто имея:

processed_grads_and_vars = [(Process_grads(gv[0]), gv[1]) for gv in grads_and_vars]
# Ask the optimizer to apply the processed gradients.
opt.apply_gradients(processed_grads_and_vars)

однако я понял, что этого недостаточно, потому что на самом деле у меня нет доступа к w, поэтому я не могу реализовать:

w mod B

по крайней мере, не так, как я пытался. Есть ли способ сделать это? т.е. непосредственно изменить правило обновления? По крайней мере так, как я пробовал?

Я знаю, что это своего рода хитрое правило обновления, но я хочу больше изменить уравнение обновления, чем на самом деле уделять много внимания этому правилу обновления (так что не зацикливайтесь на нем, если это немного странно).


Я придумал супер-хакерское решение:

def manual_update_GDL(arg,learning_rate,g,mu_noise,stddev_noise):
    with tf.variable_scope(arg.mdl_scope_name,reuse=True):
        W_var = tf.get_variable(name='W')
        eps = tf.random_normal(tf.shape(g),mean=mu_noise,stddev=stddev_noise)
        #
        W_new = tf.mod( W_var - learning_rate*g + eps , 20)
        sess.run( W_var.assign(W_new) )

def manual_GDL(arg,loss,learning_rate,mu_noise,stddev_noise,compact,B):
    # Compute the gradients for a list of variables.
    grads_and_vars = opt.compute_gradients(loss)
    # process gradients
    processed_grads_and_vars = [(manual_update_GDL(arg,learning_rate,g,mu_noise,stddev_noise), v) for g,v in grads_and_vars]

не уверен, что это работает, но что-то вроде этого должно работать в целом. Идея состоит в том, чтобы просто записать уравнение, которое вы хотите использовать (в TensorFlow) для скорости обучения, а затем обновить веса вручную с помощью сеанса.

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


Я понял, что для этой очень простой проблемы можно просто выполнить обычное правило обновления оптимизатора, а затем просто взять модификацию весов и переназначить им их значение:

sess.run(fetches=train_step)
if arg.compact:
    # apply w := ( w - mu*g + eps ) mod B
    W_val = W_var.eval()
    W_new = tf.mod(W_var,arg.B).eval()
    W_var.assign(W_new).eval()

но в данном случае это совпадение, что такое простое решение существует (к сожалению, обходит весь смысл моего вопроса).

На самом деле эти решения сильно замедляют код. На данный момент это лучшее, что у меня есть.


В качестве справки я видел этот вопрос: Как создать оптимизатор в Tensorflow, но не нашел прямого ответа на мой вопрос.


person Charlie Parker    schedule 18.03.2017    source источник
comment
Что, если вы измените все градиенты на grad = w - (w mod B - mu*grad mod B + eps mod B) и получите скорость обучения 1.0 с tf.train.GradientDescentOptimizer? Это должно применить градиенты как w -= grad, то есть w = w mod B - mu*grad mod B + eps mod B.   -  person Olivier Moindrot    schedule 22.03.2017


Ответы (2)


Ваше решение замедляет код, потому что вы используете код sess.run и .eval() во время создания "train_step". Вместо этого вы должны создать график train_step, используя только внутренние функции тензорного потока (без использования sess.run и .eval()). После этого вы оцениваете только train_step в цикле.

Если вы не хотите использовать какой-либо стандартный оптимизатор, вы можете написать свой собственный график «применения градиента». Вот одно из возможных решений для этого:

learning_rate = tf.Variable(tf.constant(0.1))
mu_noise = 0.
stddev_noise = 0.01

#add all your W variables here when you have more than one:
train_w_vars_list = [W]
grad = tf.gradients(some_loss, train_w_vars_list)

assign_list = []
for g, v in zip(grad, train_w_vars_list):
  eps = tf.random_normal(tf.shape(g), mean=mu_noise, stddev=stddev_noise)
  assign_list.append(v.assign(tf.mod(v - learning_rate*g + eps, 20)))

#also update the learning rate here if you want to:
assign_list.append(learning_rate.assign(learning_rate - 0.001))

train_step = tf.group(*assign_list)

Вы также можете использовать один из стандартных оптимизаторов для создания списка grads_and_vars (тогда используйте его вместо zip (grad, train_w_vars_list)).

Вот простой пример для MNIST с вашей потерей:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

# Import data
mnist = input_data.read_data_sets('PATH TO MNIST_data', one_hot=True)

# Create the model
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
y = tf.matmul(x, W)


# Define loss and optimizer
y_ = tf.placeholder(tf.float32, [None, 10])

cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))

learning_rate = tf.Variable(tf.constant(0.1))
mu_noise = 0.
stddev_noise = 0.01

#add all your W variables here when you have more than one:
train_w_vars_list = [W]
grad = tf.gradients(cross_entropy, train_w_vars_list)

assign_list = []
for g, v in zip(grad, train_w_vars_list):
  eps = tf.random_normal(tf.shape(g), mean=mu_noise, stddev=stddev_noise)
  assign_list.append(v.assign(tf.mod(v - learning_rate*g + eps, 20)))

#also update the learning rate here if you want to:
assign_list.append(learning_rate.assign(learning_rate - 0.001))

train_step = tf.group(*assign_list)


sess = tf.InteractiveSession()
tf.global_variables_initializer().run()


# Train
for _ in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})


# Test trained model
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(sess.run(accuracy, feed_dict={x: mnist.test.images,
                                    y_: mnist.test.labels}))
person BlueSun    schedule 27.03.2017

На самом деле вы несколько ограничены и ничего не можете сделать. Однако то, что вы хотите сделать, можно легко сделать, создав ваш дочерний класс из класса tensorflow Optimizer.

Все, что вам нужно сделать, это написать _apply_dense метод для вашего класса. Метод _apply_dense принимает в качестве аргументов grad и w, поэтому все, что вы хотите сделать с ними, вы можете делать с переменными.

Посмотрите, например, здесь: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/training/adam.py Это реализация Адама в тензорном потоке, все, что вам нужно сделать, это изменить _apply_dense в строке 131, а также _prepare и _finish методы.

Так например:

def _apply_dense(self, grad, var):
    B = math_ops.cast(self.B, var.dtype.base_dtype)
    eps = math_ops.cast(self.eps, var.dtype.base_dtype)
    mu = math_ops.cast(self.mu, var.dtype.base_dtype)


    var_update = state_ops.assign(var, tf.floormod(var - mu*grad + eps,B),
                           use_locking=self._use_locking)

    return var_update
person patapouf_ai    schedule 26.03.2017