如何在Tensorflow中只使用Python来自定义激活函数?

假设你需要做一个不可能使用的预定义的tensorflow构build块的激活函数,你能做什么?

所以在Tensorflow中,可以创build自己的激活function。 但是这是相当复杂的,你必须用C ++编写它并重新编译整个tensorflow [1] [2] 。

有一个更简单的方法吗?

就在这里!

信用:很难find这些信息并使其工作,但这里是一个从这里和这里find的原理和代码复制的例子。

要求:在开始之前,有两个要求能够成功。 首先,你需要能够在numpy数组上编写你的激活函数。 其次,您必须能够将该函数的派生函数作为Tensorflow中的函数(更简单)来编写,或者在最坏的情况下作为numpy数组的函数来编写。

写作激活function:

所以我们来举个例子,我们想要使用一个激活函数:

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) 

现在我们应该写出它的衍生物。

激活梯度:在我们的情况下,很容易,如果x mod 1 <0.5,则为1,否则为0。 所以:

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

现在要做一个TensorFlowfunction的困难部分。

对张量stream函数做一个numpy fct:我们首先将np_d_spiky变成tensorflow函数。 在张量tf.py_func(func, inp, Tout, stateful=stateful, name=name)有一个函数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 ops.op_scope([x], name, "d_spiky") 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选项是告诉tensorflow函数是否总是给同一个input(stateful = False)输出相同的输出,在这种情况下,张量stream可以简单地描述tensorflow图,这是我们的情况,在大多数情况下可能是这种情况。 有一点需要注意的是,numpy使用的是float64但是tensorflow使用的是float32所以你需要将你的函数转换为使用float32然后才能将其转换为tensorflow函数,否则tensorflow会抱怨。 这就是为什么我们需要首先创buildnp_d_spiky_32

什么梯度? 这样做的问题是,即使我们现在有tf_d_spiky这是np_d_spiky的tensorflow版本,如果我们想要,我们不能使用它作为激活函数,因为tensorflow不知道如何计算梯度function。

黑客获取渐变:正如上面提到的来源所解释的那样,使用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函数的grad函数需要采取特殊的forms。 在操作之前需要进行操作,以及之前的梯度,并在操作之后向后传递梯度。

渐变函数:所以我们的尖刻激活函数是这样做的:

 def spikygrad(op, grad): x = op.inputs[0] n_gr = tf_d_spiky(x) return grad * n_gr 

激活函数只有一个input,这就是为什么x = op.inputs[0] 。 如果操作有很多input,我们需要返回一个元组,每个input一个渐变。 例如,如果操作是ab关于ab的梯度为+1而关于b-1那么我们将return +1*grad,-1*grad 。 请注意,我们需要返回input的tensorflow函数,这就是为什么需要tf_d_spikynp_d_spiky不能工作,因为它不能作用于张量stream张量。 或者我们可以使用张量函数写出导数:

 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 ops.op_scope([x], name, "spiky") 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] 

现在我们完成了。 我们可以testing它。

testing:

 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.20000005 0.] [1.0.1.0]

成功!

为什么不简单地使用tensorflow中已有的函数来构build你的新函数?

对于答案中的spikyfunction,这可能如下所示

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

我会认为这非常容易(甚至不需要计算任何梯度),除非你想做非常奇特的事情,我几乎不能想象tensorflow不提供构build高度复杂的激活函数的基石。

Interesting Posts