web-dev-qa-db-fra.com

Comment créer une fonction d'activation personnalisée avec seulement Python dans Tensorflow?

Supposons que vous deviez créer une fonction d'activation qui n'est pas possible en utilisant uniquement des blocs de construction tensorflow prédéfinis, que pouvez-vous faire?

Donc, dans Tensorflow, il est possible de créer votre propre fonction d'activation. Mais c’est assez compliqué, vous devez l’écrire en C++ et recompiler l’ensemble du tensorflow [1][2] .

Y a-t-il un moyen plus simple?

47
patapouf_ai

Oui, il y a!

Crédit: Il était difficile de trouver l'information et de la faire fonctionner, mais voici un exemple de copie des principes et du code trouvés ici = et ici .

Conditions requises: Avant de commencer, vous devez disposer de deux conditions préalables pour réussir. Vous devez d’abord pouvoir écrire votre activation en tant que fonction sur les tableaux numpy. Deuxièmement, vous devez être capable d'écrire la dérivée de cette fonction en tant que fonction dans Tensorflow (plus facile) ou, dans le pire des cas, en tant que fonction sur des tableaux numpy.

Fonction d'activation de l'écriture:

Prenons par exemple cette fonction pour laquelle nous voudrions utiliser une fonction d'activation:

_def spiky(x):
    r = x % 1
    if r <= 0.5:
        return r
    else:
        return 0
_

Qui regarde comme suit: Spiky Activation

La première étape consiste à en faire une fonction numpy, ceci est facile:

_import numpy as np
np_spiky = np.vectorize(spiky)
_

Maintenant, nous devrions écrire sa dérivée.

Gradient d'activation: Dans notre cas, c'est facile, c'est 1 si x mod 1 <0.5 et 0 sinon. Alors:

_def d_spiky(x):
    r = x % 1
    if r <= 0.5:
        return 1
    else:
        return 0
np_d_spiky = np.vectorize(d_spiky)
_

Passons maintenant à la difficulté de créer une fonction TensorFlow.

Création d'une fonction numpy en une fonction tenseur: Nous allons commencer par transformer np_d_spiky en une fonction tensorflow. Tensorflow tf.py_func(func, inp, Tout, stateful=stateful, name=name)[doc] transforme n'importe quelle fonction numpy en une fonction tensorflow, nous pouvons donc l'utiliser:

_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_ agit sur des listes de tenseurs (et retourne une liste de tenseurs), c’est pourquoi nous avons _[x]_ (et nous retournons _y[0]_). L'option stateful indique au tensorflow si la fonction donne toujours la même sortie pour la même entrée (stateful = False), auquel cas tensorflow peut simplement servir de graphe tensorflow, c'est notre cas et ce sera probablement le cas dans la plupart des situations. Une chose à faire à ce stade est que numpy utilise _float64_ mais tensorflow utilise _float32_ et vous devez donc convertir votre fonction pour qu'elle utilise _float32_ avant de pouvoir la convertir en une fonction tensorflow sinon. Tensorflow va se plaindre. C'est pourquoi nous devons commencer par _np_d_spiky_32_.

Qu'en est-il des gradients? Le problème avec ce que je viens de faire est que même si nous avons maintenant _tf_d_spiky_ qui est la version tensorflow de _np_d_spiky_, nous ne pourrions pas l’utiliser comme fonction d’activation si nous le voulions, car tensorflow ne sait pas calculer les gradients de cette fonction.

Hack pour obtenir des dégradés: Comme expliqué dans les sources mentionnées ci-dessus, il existe un hack permettant de définir les dégradés d'une fonction à l'aide de _tf.RegisterGradient_ [doc] et _tf.Graph.gradient_override_map_ [doc] . En copiant le code de harpone , nous pouvons modifier la fonction _tf.py_func_ pour lui faire définir le dégradé en même temps:

_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)
_

Maintenant que nous avons presque terminé, la seule chose à faire est que la fonction grad que nous devons passer à la fonction ci-dessus py_func doit prendre une forme spéciale. Il doit prendre en compte une opération et les gradients précédents avant l'opération et propager les gradients en arrière après l'opération.

Fonction de gradient: Donc, pour notre fonction d'activation épineuse, voici comment nous le ferions:

_def spikygrad(op, grad):
    x = op.inputs[0]

    n_gr = tf_d_spiky(x)
    return grad * n_gr  
_

La fonction d'activation n'a qu'une seule entrée, c'est pourquoi _x = op.inputs[0]_. Si l'opération comportait de nombreuses entrées, il faudrait renvoyer un tuple, un dégradé pour chaque entrée. Par exemple, si l'opération était _a-b_, le dégradé par rapport à a est _+1_ et par rapport à b est _-1_ et nous aurions donc _return +1*grad,-1*grad_. Notez que nous devons renvoyer les fonctions tensorflow de l'entrée. C'est pourquoi need _tf_d_spiky_, _np_d_spiky_ n'aurait pas fonctionné car il ne peut pas agir sur les tenseurs tenseur. Sinon, nous aurions pu écrire la dérivée à l'aide de fonctions 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  
_

Combiner le tout ensemble: Maintenant que nous avons toutes les pièces, nous pouvons les combiner tous ensemble:

_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]
_

Et maintenant nous avons fini. Et nous pouvons le tester.

Test:

_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.]

Succès!

74
patapouf_ai

Pourquoi ne pas simplement utiliser les fonctions déjà disponibles dans tensorflow pour construire votre nouvelle fonction?

Pour la fonction spiky dans votre réponse , ceci pourrait ressembler à ceci

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))

Je considérerais cela beaucoup plus facile (même pas besoin de calculer de gradients) et à moins que vous ne vouliez faire des choses vraiment exotiques, je peux à peine imaginer que tensorflow ne fournit pas les éléments de base pour la création de fonctions d’activation très complexes.

12
Mr Tsjolder