如何在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_spiky
, np_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你的新函数?
对于答案中的spiky
function,这可能如下所示
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高度复杂的激活函数的基石。