Как сделать пользовательскую функцию активации только с Python в Tensorflow?

Предположим, вам нужно создать функцию активации, которая невозможна с использованием только предварительно определенных строительных блоков тензорного потока, что вы можете сделать?

Таким образом, в Tensorflow можно создать свою собственную функцию активации. Но это довольно сложно, вам нужно написать его на C ++ и перекомпилировать весь тензорный поток [1] [2].

Есть способ попроще?


person patapouf_ai    schedule 07.10.2016    source источник
comment
comment
Трудно иметь абсолютную свободу с любым программным обеспечением, но если вы дадите нам представление о том, какую функцию активации (семейство функций) вы пытаетесь создать, кто-то может вам помочь.   -  person user1700890    schedule 01.12.2017


Ответы (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 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
comment
@lahwran, это не та функция активации, которую вы бы хотели использовать в реальной жизни. Это просто пример того, как реализовать настраиваемую функцию активации, если вам это нужно. - person patapouf_ai; 03.07.2017
comment
полностью, поэтому мне было любопытно, удалось ли вам заставить его работать: p - person lahwran; 03.07.2017
comment
да, это работает :) но я не пробовал использовать сеть в реальной проблеме обучения, мне нужно было сделать гораздо более сложную функцию активации, чем та, для моей цели и той, которую выучили, но для сообщения здесь я только положил игрушку функция активации, с которой я не пытался учиться. - person patapouf_ai; 04.07.2017
comment
о да. Мне было в основном любопытно, потому что циклы в функции активации кажутся довольно ужасными, и мне было любопытно, удалось ли вам получить что-то с циклами для изучения. - person lahwran; 04.07.2017
comment
классно ! Примечание для людей, которые в настоящее время хотят использовать ваш метод, вам следует заменить op.scope на tf.name_scope, поскольку первый устарел. op.scope принимает аргумент следующим образом: op.scope (значения, имя, default_name), тогда как порядок аргументов tf.name_scope - tf.name_scope (name, default_name, values), поэтому вместо ops.op_scope ([x], name, spiky ) следует использовать tf.name_scope (name, spiky, [x]) - person nsaura; 11.06.2018
comment
@patapouf_ai использует ли TensorFlow ускорение графического процессора для пользовательских функций? То есть будет ли эта активация применяться параллельно ко многим тензорным элементам в ядрах CUDA? - person Rohan Saxena; 07.08.2018
comment
@RohanSaxena, да. - person patapouf_ai; 08.08.2018
comment
@patapouf_ai Самое ясное объяснение создания пользовательской функции тензорного потока, которое я видел до сих пор - спасибо! - person jtlz2; 14.04.2020

Почему бы просто не использовать функции, которые уже доступны в тензорном потоке, для создания вашей новой функции?

Для функции spiky в вашем ответе это может выглядеть следующим образом

def spiky(x):
    r = tf.floormod(x, tf.constant(1))
    cond = tf.less_equal(r, tf.constant(0.5))
    return tf.where(cond, r, tf.constant(0))

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

person Mr Tsjolder    schedule 22.07.2017
comment
Да, действительно, spiky можно сделать с помощью tf-примитивов, но spiky - это всего лишь простой пример, чтобы не слишком запутаться из-за сложности функции, которую я действительно хотел реализовать. К сожалению, функция, которую я действительно хотел реализовать, не могла быть реализована с помощью примитивов tf. - person patapouf_ai; 24.07.2017
comment
Весь смысл вопроса в следующем: что делать, если вы не можете сформулировать функцию активации с помощью tf-примитивов. - person patapouf_ai; 24.07.2017
comment
@patapouf_ai Я этого уже ожидал, но из вашего вопроса это не ясно. Из-за популярности этого вопроса я подумал, что было бы неплохо указать и на это решение (для людей с небольшим опытом работы с тензорным потоком, пытающихся создать свои собственные функции активации). - person Mr Tsjolder; 24.07.2017
comment
Очень полезный ответ, за исключением того, что вы можете использовать форму тензора x следующим образом: def spiky (x): r = tf.floormod (x, tf.constant (1, shape = x.shape)) cond = tf.less_equal ( r, tf.constant (0.5, shape = x.shape)) return tf.where (cond, r, tf.constant (0, shape = x.shape)), иначе вы можете получить такую ​​ошибку: ValueError: Shape must быть рангом xx, но иметь ранг xx для 'cond_xx / Switch' (op: 'Switch') - person adrienbourgeois; 22.07.2018