tutorial - ¿Cómo hacer una función de activación personalizada con solo Python en Tensorflow?
tensorflow tutorial español pdf (2)
Supongamos que necesita realizar una función de activación que no es posible utilizando solo bloques de construcción de tensorflow predefinidos, ¿qué puede hacer?
Entonces, en Tensorflow es posible hacer su propia función de activación. Pero es bastante complicado, tienes que escribirlo en C ++ y recompilar todo el flujo de tensor [1] [2] .
¿Hay alguna forma más simple?
¿Por qué no simplemente usar las funciones que ya están disponibles en tensorflow para construir su nueva función?
Para la función de
spiky
en
su respuesta
, esto podría verse de la siguiente manera
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))
Consideraría esto mucho más fácil (ni siquiera es necesario calcular gradientes) y, a menos que desee hacer cosas realmente exóticas, apenas puedo imaginar que el flujo de tensor no proporcione los bloques de construcción para construir funciones de activación altamente complejas.
¡Sí hay!
Crédito: fue difícil encontrar la información y hacer que funcionara, pero aquí hay un ejemplo que copia de los principios y el código que se encuentran here y here .
Requisitos: antes de comenzar, hay dos requisitos para que esto pueda tener éxito. Primero debe poder escribir su activación como una función en matrices numpy. En segundo lugar, debe poder escribir la derivada de esa función como una función en Tensorflow (más fácil) o, en el peor de los casos, como una función en matrices numpy.
Función de activación de escritura:
Así que tomemos por ejemplo esta función que nos gustaría usar una función de activación:
def spiky(x):
r = x % 1
if r <= 0.5:
return r
else:
return 0
Que se ve de la siguiente manera:
El primer paso es convertirlo en una función numpy, esto es fácil:
import numpy as np
np_spiky = np.vectorize(spiky)
Ahora deberíamos escribir su derivada.
Gradiente de activación: en nuestro caso es fácil, es 1 si x mod 1 <0.5 y 0 en caso contrario. Entonces:
def d_spiky(x):
r = x % 1
if r <= 0.5:
return 1
else:
return 0
np_d_spiky = np.vectorize(d_spiky)
Ahora, para la parte difícil de hacer que TensorFlow funcione fuera de él.
Hacer un fct numpy a un fct tensorflow:
Comenzaremos haciendo np_d_spiky en una función tensorflow.
Hay una función en tensorflow
tf.py_func(func, inp, Tout, stateful=stateful, name=name)
[doc]
que transforma cualquier función numpy en una función de tensorflow, por lo que podemos usarla:
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
actúa en listas de tensores (y devuelve una lista de tensores), es por eso que tenemos
[x]
(y devolvemos
y[0]
).
La opción con
stateful
es decirle al tensorflow si la función siempre da la misma salida para la misma entrada (stateful = False) en cuyo caso el tensorflow puede simplemente el gráfico de tensorflow, este es nuestro caso y probablemente será el caso en la mayoría de las situaciones.
Una cosa a tener en cuenta en este punto es que numpy usó
float64
pero tensorflow usa
float32
por lo que debe convertir su función para usar
float32
antes de poder convertirla en una función de tensorflow, de lo contrario, tensorflow se quejará.
Es por eso que necesitamos hacer
np_d_spiky_32
primero.
¿Qué pasa con los gradientes?
El problema con solo hacer lo anterior es que a pesar de que ahora tenemos
tf_d_spiky
que es la versión de
np_d_spiky
de
np_d_spiky
, no podríamos usarlo como una función de activación si quisiéramos porque el tensorflow no sabe cómo calcular los gradientes de ese función.
Hack para obtener degradados:
como se explica en las fuentes mencionadas anteriormente, hay un hack para definir gradientes de una función usando
tf.RegisterGradient
[doc]
y
tf.Graph.gradient_override_map
[doc]
.
Al copiar el código de
here
podemos modificar la función
tf.py_func
para que defina el gradiente al mismo tiempo:
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)
Ahora casi hemos terminado, lo único es que la función grad que necesitamos pasar a la función py_func anterior necesita tomar una forma especial. Necesita tomar una operación y los gradientes anteriores antes de la operación y propagar los gradientes hacia atrás después de la operación.
Función de gradiente: Entonces, para nuestra función de activación puntiaguda, así es como lo haríamos:
def spikygrad(op, grad):
x = op.inputs[0]
n_gr = tf_d_spiky(x)
return grad * n_gr
La función de activación tiene una sola entrada, por eso
x = op.inputs[0]
.
Si la operación tuviera muchas entradas, tendríamos que devolver una tupla, un gradiente para cada entrada.
Por ejemplo, si la operación fuera
ab
el gradiente con respecto a
a
es
+1
y con respecto a
b
es
-1
por lo que tendríamos
return +1*grad,-1*grad
.
Tenga en cuenta que necesitamos devolver las funciones de tensorflow de la entrada, es por eso que necesitar
tf_d_spiky
,
np_d_spiky
no habría funcionado porque no puede actuar sobre los tensores de tensorflow.
Alternativamente, podríamos haber escrito la derivada usando las funciones de 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
Combinándolo todo: ahora que tenemos todas las piezas, podemos combinarlas todas juntas:
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]
Y ahora hemos terminado. Y podemos probarlo.
Prueba:
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.]
¡Éxito!