事前定義されたテンソルフローのビルディングブロックだけでは使用できないアクティベーション関数を作成する必要があると仮定すると、あなたは何ができますか?
そのため、Tensorflowでは、独自のアクティベーション関数を作成できます。しかし、それは非常に複雑です。C++で記述し、テンソルフロー全体を再コンパイルする必要があります [1][2] 。
もっと簡単な方法はありますか?
はいあり!
クレジット:情報を見つけて機能させるのは困難でしたが、ここに見つかった原則とコードからコピーする例があります here =および ここ 。
要件:始める前に、これが成功するための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)
_
次に、その派生物を書く必要があります。
活性化の勾配:このケースでは簡単です。xmod 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関数を作成するのが難しい部分です。
テンソルフローfctへのnumpy 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
_はテンソルのリストに作用し(そしてテンソルのリストを返します)、それが_[x]
_を持っている理由です(そして_y[0]
_を返します)。 stateful
オプションは、関数が常に同じ入力(stateful = False)に対して同じ出力を与えるかどうかをtensorflowに伝えることです。この場合、tensorflowは単純にtensorflowグラフになります。ほとんどの状況。この時点で注意すべきことの1つは、numpyが_float64
_を使用したが、tensorflowは_float32
_を使用するため、関数を変換してから_float32
_を使用する必要があります。テンソルフローは文句を言います。これが、最初に_np_d_spiky_32
_を作成する必要がある理由です。
勾配についてはどうですか?上記のことだけを行う場合の問題は、現在、_tf_d_spiky
_があり、これが_np_d_spiky
_、テンソルフローはその関数の勾配を計算する方法を知らないため、必要に応じてアクティベーション関数として使用できませんでした。
勾配を取得するためのハック:上記のソースで説明したように、_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つの勾配のTupleを返す必要があります。たとえば、操作が_a-b
_であった場合、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.]
成功!
テンソルフローで既に利用可能な関数を単に使用して、新しい関数を作成してみませんか?
回答 の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))
私はこれをかなり簡単に考えます(勾配を計算する必要もありません)、本当にエキゾチックなことをしたくない限り、私はテンソルフローが非常に複雑なアクティベーション関数を構築するためのビルディングブロックを提供しないとほとんど想像できません。