Как сделать пользовательскую функцию активации только с Python в Tensorflow?
Предположим, вам нужно сделать функцию активации, которая невозможна, используя только предварительно определенные строительные блоки tensorflow, что вы можете сделать?
таким образом, в Tensorflow можно сделать свою собственную функцию активации. Но это довольно сложно, вы должны написать его на C++ и перекомпилировать весь tensorflow [1] [2].
есть ли более простой способ?
2 ответов
Да есть!
кредит: Было трудно найти информацию и заставить ее работать, но вот пример копирования из найденных принципов и кода здесь и здесь.
требования: Прежде чем мы начнем, есть два требования для этого, чтобы иметь возможность добиться успеха. Сначала вам нужно иметь возможность писать свою активацию как функцию на массивах numpy. Во-вторых, вы должны быть в состоянии напишите производную этой функции либо как функцию в Tensorflow (проще), либо в худшем случае как функцию на массивах numpy.
функция активации записи:
Итак, давайте возьмем для примера эту функцию, которую мы хотели бы использовать функцию активации:
def spiky(x):
r = x % 1
if r <= 0.5:
return r
else:
return 0
, которые выглядят следующим образом:
первый шаг делает его в функцию numpy, это легко:
import numpy as np
np_spiky = np.vectorize(spiky)
теперь мы должны оставить его производные.
градиент активации: В нашем случае это легко, это 1, Если X мод 1
def d_spiky(x):
r = x % 1
if r <= 0.5:
return 1
else:
return 0
np_d_spiky = np.vectorize(d_spiky)
теперь самая трудная часть делает функцию TensorFlow из него.
создание numpy fct для tensorflow fct:
Мы начнем с создания np_d_spiky в функцию tensorflow. Существует функция в tensorflow tf.py_func(func, inp, Tout, stateful=stateful, name=name)
[doc] который преобразует любую функцию numpy в функцию tensorflow, поэтому мы можем ее использовать:
import tensorflow as tf
from tensorflow.python.framework import ops
np_d_spiky_32 = lambda x: np_d_spiky(x).astype(np.float32)
def tf_d_spiky(x,name=None):
with tf.name_scope(name, "d_spiky", [x]) as name:
y = tf.py_func(np_d_spiky_32,
[x],
[tf.float32],
name=name,
stateful=False)
return y[0]
tf.py_func
действует на списки тензоров (и возвращает список тензоров), поэтому у нас есть [x]
(и return y[0]
). The stateful
опция должна сказать tensorflow, всегда ли функция дает один и тот же выход для одного и того же входа (stateful = False), и в этом случае tensorflow может просто график tensorflow, это наш случай и, вероятно, будет в большинстве случаев положения. Одна вещь, чтобы быть осторожным в этот момент, что numpy используется float64
но tensorflow использует float32
поэтому вам нужно преобразовать свою функцию в use float32
прежде чем вы сможете преобразовать его в функцию tensorflow, иначе tensorflow будет жаловаться. Вот почему нам нужно сделать np_d_spiky_32
первый.
как насчет градиентов? проблема только в том, что мы делаем это, хотя теперь у нас есть tf_d_spiky
который является версией tensorflow np_d_spiky
, мы не удалось бы использовать его в качестве функции активации, если бы мы хотели, потому что tensorflow не знает, как вычислить градиенты этой функции.
Hack, чтобы получить градиенты: как объясняется в источниках, упомянутых выше, существует хак для определения градиентов функции с помощью tf.RegisterGradient
[doc] и tf.Graph.gradient_override_map
[doc]. Копировать код harpone мы можем изменить tf.py_func
функция, чтобы определить градиент на то же время:
def py_func(func, inp, Tout, stateful=True, name=None, grad=None):
# Need to generate a unique name to avoid duplicates:
rnd_name = 'PyFuncGrad' + str(np.random.randint(0, 1E+8))
tf.RegisterGradient(rnd_name)(grad) # see _MySquareGrad for grad example
g = tf.get_default_graph()
with g.gradient_override_map({"PyFunc": rnd_name}):
return tf.py_func(func, inp, Tout, stateful=stateful, name=name)
теперь мы почти закончили, единственное, что функция grad, которую нам нужно передать вышеуказанной функции py_func, должна принять специальную форму. Он должен принимать операцию и предыдущие градиенты до операции и распространять градиенты назад после операции.
Градиент Функции: Итак, для нашей функции активации spiky, вот как мы это сделаем:
def spikygrad(op, grad):
x = op.inputs[0]
n_gr = tf_d_spiky(x)
return grad * n_gr
функции активации только один вход, вот почему x = op.inputs[0]
. Если бы операция имела много входов, нам нужно было бы вернуть Кортеж, один градиент для каждого входа. Например, если операция была a-b
градиент по отношению к a
и +1
и в отношении b
is -1
так мы бы return +1*grad,-1*grad
. Обратите внимание, что нам нужно вернуть тензорные функции ввода, поэтому нужно tf_d_spiky
, np_d_spiky
не сработал бы, потому что он не может действовать на tensorflow тензоров. В качестве альтернативы мы могли бы написано производной, используя tensorflow функции:
def spikygrad2(op, grad):
x = op.inputs[0]
r = tf.mod(x,1)
n_gr = tf.to_float(tf.less_equal(r, 0.5))
return grad * n_gr
объединение всех вместе: теперь, когда у нас есть все части, мы можем объединить их все вместе:
np_spiky_32 = lambda x: np_spiky(x).astype(np.float32)
def tf_spiky(x, name=None):
with tf.name_scope(name, "spiky", [x]) as name:
y = py_func(np_spiky_32,
[x],
[tf.float32],
name=name,
grad=spikygrad) # <-- here's the call to the gradient
return y[0]
и теперь мы сделали. И мы можем это проверить.
:
with tf.Session() as sess:
x = tf.constant([0.2,0.7,1.2,1.7])
y = tf_spiky(x)
tf.initialize_all_variables().run()
print(x.eval(), y.eval(), tf.gradients(y, [x])[0].eval())
[ 0.2 0.69999999 1.20000005 1.70000005] [ 0.2 0. 0.20000005 0.] [ 1. 0. 1. 0.]
успехов!
почему бы просто не использовать функции, которые уже доступны в tensorflow для создания новой функции?
на