Как сделать пользовательскую функцию активации только с 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

, которые выглядят следующим образом: Spiky Activation

первый шаг делает его в функцию 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 для создания новой функции?

на