Да, есть!
Кредит: Было сложно найти информацию и заставить ее работать, но вот пример копирования принципов и кода, найденных здесь и здесь.
Требования: Прежде чем мы начнем, есть два требования для достижения успеха. Сначала вам нужно написать свою активацию как функцию для массивов 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 mod 1 ‹0,5, и 0 в противном случае. Так:
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 в функцию тензорного потока: Мы начнем с преобразования np_d_spiky в функцию тензорного потока. В тензорном потоке tf.py_func(func, inp, Tout, stateful=stateful, name=name)
[doc] есть функция, которая преобразует любую функцию numpy в функцию тензорного потока, поэтому мы можем ее использовать:
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]
(и возвращаем y[0]
). Параметр stateful
указывает тензорному потоку, всегда ли функция дает один и тот же вывод для одного и того же входа (stateful = False), и в этом случае тензорный поток может быть просто графиком тензорного потока, это наш случай и, вероятно, будет иметь место в большинстве ситуаций. На этом этапе нужно быть осторожным, чтобы numpy использовал float64
, но тензорный поток использует float32
, поэтому вам нужно преобразовать свою функцию для использования float32
, прежде чем вы сможете преобразовать ее в функцию тензорного потока, иначе тензорный поток будет жаловаться. Вот почему нам нужно сначала сделать np_d_spiky_32
.
А как насчет градиентов? Проблема с выполнением только вышеуказанного заключается в том, что, хотя теперь у нас есть tf_d_spiky
, который является версией np_d_spiky
тензорного потока, мы не могли бы использовать его в качестве функции активации, если бы захотели, потому что tenorflow не знает, как вычислить градиенты этой функции.
Как получить градиенты: Как объяснялось в источниках, упомянутых выше, существует способ определения градиентов функции с помощью 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)
Теперь мы почти закончили, единственное, что нам нужно передать функции градиента в вышеупомянутую функцию py_func, должна иметь особую форму. Он должен принять операцию и предыдущие градиенты перед операцией и распространить градиенты назад после операции.
Функция градиента. Для нашей функции активации с шипами мы бы сделали это следующим образом:
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
равен -1
, поэтому у нас будет return +1*grad,-1*grad
. Обратите внимание, что нам нужно возвращать функции тензорного потока ввода, поэтому потребность tf_d_spiky
, np_d_spiky
не сработала бы, потому что она не может воздействовать на тензоры тензорного потока. В качестве альтернативы мы могли бы записать производную, используя функции тензорного потока:
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.]
Успешно!
person
patapouf_ai
schedule
07.10.2016