事前定義された tensorflow ビルディング ブロックだけでは不可能なアクティベーション関数を作成する必要があるとします。何ができるでしょうか?
したがって、Tensorflow では、独自のアクティベーション関数を作成することができます。しかし、これは非常に複雑です。C++ で記述し、テンソルフロー全体を再コンパイルする必要があります[1] [2]。
もっと簡単な方法はありますか?
はいあります!
クレジット: 情報を見つけて機能させるのは困難でしたが、こことここにある原則とコードをコピーした例を次に示します。
要件: 始める前に、これを成功させるには 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)
次に、その導関数を書きます。
活性化の勾配: この場合は簡単です。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)
ここから、TensorFlow 関数を作成する難しい部分について説明します。
numpy fct を tensorflow fct にする:
np_d_spiky を tensorflow 関数にすることから始めます。テンソルフロー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
は tensor のリストに作用します (そして tensor のリストを返します[x]
) y[0]
。オプションは、関数が常に同じ入力 (ステートフル = False) に対して同じ出力を与えるかどうかを tensorflow に伝えることです。stateful
その場合、tensorflow は単純に tensorflow グラフを作成できます。この時点で注意すべきことの 1 つは、numpy が使用されているfloat64
が tensorflow が使用しているため、関数を tensorflow 関数に変換する前float32
に使用するように変換する必要があることfloat32
です。そうしないと、tensorflow が文句を言います。np_d_spiky_32
これが、最初に作成する必要がある理由です。
グラデーションはどうですか?上記のみを実行することの問題tf_d_spiky
は、 の tensorflow バージョンであるnp_d_spiky
を取得したにもかかわらず、必要に応じてそれをアクティベーション関数として使用できないことです。これは、tensorflow がその関数の勾配を計算する方法を知らないためです。
勾配を取得するためのハック:上記のソースで説明されているように、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 関数が特別な形式を取る必要があることです。操作と、操作前の以前の勾配を取り込み、操作後に勾配を後方に伝播する必要があります。
勾配関数:とがった活性化関数については、次のようにします。
def spikygrad(op, grad):
x = op.inputs[0]
n_gr = tf_d_spiky(x)
return grad * n_gr
アクティベーション関数には入力が 1 つしかないため、x = op.inputs[0]
. 操作に多くの入力がある場合、入力ごとに 1 つの勾配のタプルを返す必要があります。たとえば、演算がisa-b
に関する勾配であり、isに関する勾配であった場合、となります。入力のテンソルフロー関数を返す必要があることに注意してください。これが、テンソルフロー テンソルに作用できないため、 need が機能しなかった理由です。別の方法として、テンソルフロー関数を使用して導関数を記述することもできます。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.]
成功!
tensorflow で既に利用可能な関数を単純に使用して、新しい関数を作成してみませんか?
answerの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))
これはかなり簡単だと思います(勾配を計算する必要さえありません)。本当にエキゾチックなことをしたいのでない限り、テンソルフローが非常に複雑なアクティベーション関数を構築するためのビルディングブロックを提供しないとはほとんど想像できません。