Keras / Tensorflowでトレーニング可能な一般化されたバンプ関数レイヤーを実装する


8

コンポーネントごとに適用されるバンプ関数の次のバリアントをコーディングしようとしています:

一般化されたバンプ関数方程式

ここで、σはトレーニング可能です。しかし、機能していません(エラーは以下に報告されています)。


私の試み:

これが私がこれまでにコーディングしたものです(役立つ場合)。(たとえば)2つの関数があるとします。

  def f_True(x):
    # Compute Bump Function
    bump_value = 1-tf.math.pow(x,2)
    bump_value = -tf.math.pow(bump_value,-1)
    bump_value = tf.math.exp(bump_value)
    return(bump_value)

  def f_False(x):
    # Compute Bump Function
    x_out = 0*x
    return(x_out)

class trainable_bump_layer(tf.keras.layers.Layer):

    def __init__(self, *args, **kwargs):
        super(trainable_bump_layer, self).__init__(*args, **kwargs)

    def build(self, input_shape):
        self.threshold_level = self.add_weight(name='threshlevel',
                                    shape=[1],
                                    initializer='GlorotUniform',
                                    trainable=True)

    def call(self, input):
        # Determine Thresholding Logic
        The_Logic = tf.math.less(input,self.threshold_level)
        # Apply Logic
        output_step_3 = tf.cond(The_Logic, 
                                lambda: f_True(input),
                                lambda: f_False(input))
        return output_step_3

エラーレポート:

    Train on 100 samples
Epoch 1/10
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
 32/100 [========>.....................] - ETA: 3s

...

tensorflow:Gradients do not exist for variables 

さらに、(トレーニング不可能な問題を除いて)コンポーネントごとに適用されているようには見えません。何が問題でしょうか?


の次元は何inputですか?それはスカラーですか?
Vlad

1
こんにちは@ProbablyAHuman、シナリオに最小限の再現可能なコードを提供し、それがどのように機能していないかを正確に指定できますか?
TF_Support

1
@TF_Support目的の詳細とエラーレポートを追加しました...
FlabbyTheKatsu

シグマはトレーニング可能ですか?
DanielMöller

あなたが欲しいもののグラフを共有してもらえますか、そしてこのグラフの何が変わることができますか?
DanielMöller

回答:


2

与えられた警告の主な(そして唯一の)理由について誰も言及しなかったことに少し驚いています!どうやら、そのコードはバンプ関数の一般化されたバリアントを実装することになっています。ただし、再度実装された関数を見てください。

def f_True(x):
    # Compute Bump Function
    bump_value = 1-tf.math.pow(x,2)
    bump_value = -tf.math.pow(bump_value,-1)
    bump_value = tf.math.exp(bump_value)
    return(bump_value)

def f_False(x):
    # Compute Bump Function
    x_out = 0*x
    return(x_out)

エラーは明らかです。これらの関数では、レイヤーのトレーニング可能な重みを使用していません。したがって、そのためにグラデーションが存在しないというメッセージが表示されても驚くことはありません。まったく使用していないため、グラデーションを更新する必要はありません。むしろ、これはまさに元のバンプ関数です(つまり、トレーニング可能な重みはありません)。

しかし、「少なくとも、の条件でトレーニング可能なウェイトを使用したtf.condので、いくつかの勾配があるはずです!」と言うかもしれません。しかし、それはそうではなく、混乱を解消します。

  • まず、お気づきのとおり、要素ごとの条件付けに関心があります。したがって、代わりにtf.condを使用する必要がありますtf.where

  • 他の誤解はtf.less、が条件として使用されており、区別できないためです。つまり、入力に対して勾配がないためです(これは真です:ブール値の出力が実際の関数に対して定義された勾配はありません)大切な入力!)

    • それは単に間違っています!ここでの導関数は、トレーニング可能な重み付きのレイヤー出力から取得され、選択条件は出力に存在しません。むしろ、選択する出力ブランチを決定するのはブールテンソルにすぎません。それでおしまい!条件の導関数は取られず、必要になることはありません。だから、それは与えられた警告の理由ではありません。その理由は、私が前述したことだけです。層の出力におけるトレーニング可能な重みの寄与はありません。(注:条件に関するポイントが少し意外な場合は、簡単な例を考えてください。ReLU関数は、として定義されていrelu(x) = 0 if x < 0 else xます。条件の導関数の場合、つまりx < 0、考慮/必要あり、存在しない場合、モデルでReLUを使用して、勾配ベースの最適化方法を使用してそれらをトレーニングすることはできません!)

(注:ここから始めて、式のようにしきい値をsigmaとして参照して示します)。

大丈夫!実装のエラーの背後に理由が見つかりました。これを修正できますか?もちろん!更新された実際の実装は次のとおりです。

import tensorflow as tf
from tensorflow.keras.initializers import RandomUniform
from tensorflow.keras.constraints import NonNeg

class BumpLayer(tf.keras.layers.Layer):
    def __init__(self, *args, **kwargs):
        super(BumpLayer, self).__init__(*args, **kwargs)

    def build(self, input_shape):
        self.sigma = self.add_weight(
            name='sigma',
            shape=[1],
            initializer=RandomUniform(minval=0.0, maxval=0.1),
            trainable=True,
            constraint=tf.keras.constraints.NonNeg()
        )
        super().build(input_shape)

    def bump_function(self, x):
        return tf.math.exp(-self.sigma / (self.sigma - tf.math.pow(x, 2)))

    def call(self, inputs):
        greater = tf.math.greater(inputs, -self.sigma)
        less = tf.math.less(inputs, self.sigma)
        condition = tf.logical_and(greater, less)

        output = tf.where(
            condition, 
            self.bump_function(inputs),
            0.0
        )
        return output

この実装に関するいくつかのポイント:

  • 要素ごとの条件付けを行うために置き換えtf.condましtf.whereた。

  • さらに、ご覧のように、不等式の片側のみをチェックする実装とは異なり、を使用tf.math.lesstf.math.greaterています。tf.logical_and入力値未満の大きさを持っているかどうかを調べるためにsigma、代替的(、私たちは使用してこの操作を行うことができなかったtf.math.abstf.math.less、何の違いを!)。繰り返しますが、ブール出力関数をこの方法で使用しても問題は発生せず、導関数/勾配とは関係ありません。

  • また、層によって学習されたシグマ値に非負性制約を使用しています。どうして?0未満のシグマ値は意味をなさないため(つまり、(-sigma, sigma)シグマが負の場合、範囲は不明確です)。

  • また、前のポイントを考慮して、シグマ値を適切に(つまり、負でない小さな値に)初期化するように注意します。

  • また、次のようなことはしないでください0.0 * inputs。これは冗長(そして少し奇妙です)で、と同等0.0です。両方とも0.0(wrtinputs)のます。ゼロをテンソルで乗算しても、何も追加したり、既存の問題を解決したりすることはありません。少なくともこの場合はそうではありません!

次に、それをテストして、どのように機能するかを確認します。固定シグマ値に基づいてトレーニングデータを生成BumpLayerし、入力形状がのシングルを含むモデルを作成するためのヘルパー関数をいくつか作成します(1,)。トレーニングデータの生成に使用されるシグマ値を学習できるかどうかを見てみましょう。

import numpy as np

def generate_data(sigma, min_x=-1, max_x=1, shape=(100000,1)):
    assert sigma >= 0, 'Sigma should be non-negative!'
    x = np.random.uniform(min_x, max_x, size=shape)
    xp2 = np.power(x, 2)
    condition = np.logical_and(x < sigma, x > -sigma)
    y = np.where(condition, np.exp(-sigma / (sigma - xp2)), 0.0)
    dy = np.where(condition, xp2 * y / np.power((sigma - xp2), 2), 0)
    return x, y, dy

def make_model(input_shape=(1,)):
    model = tf.keras.Sequential()
    model.add(BumpLayer(input_shape=input_shape))

    model.compile(loss='mse', optimizer='adam')
    return model

# Generate training data using a fixed sigma value.
sigma = 0.5
x, y, _ = generate_data(sigma=sigma, min_x=-0.1, max_x=0.1)

model = make_model()

# Store initial value of sigma, so that it could be compared after training.
sigma_before = model.layers[0].get_weights()[0][0]

model.fit(x, y, epochs=5)

print('Sigma before training:', sigma_before)
print('Sigma after training:', model.layers[0].get_weights()[0][0])
print('Sigma used for generating data:', sigma)

# Sigma before training: 0.08271004
# Sigma after training: 0.5000002
# Sigma used for generating data: 0.5

はい、データの生成に使用されるシグマの価値を知ることができます!しかし、トレーニングデータのすべての異なる値とシグマの初期化で実際に機能することが保証されていますか?答えはノーだ!実際、上記のコードを実行して、nanトレーニング後のsigmaの値、またはinf損失値として取得することは可能です!だから問題は何ですか?なぜこれnanまたはinf値が生成されるのですか?以下で説明しましょう...


数値安定性の扱い

機械学習モデルを構築し、勾配ベースの最適化手法を使用してそれらをトレーニングする場合に考慮すべき重要なことの1つは、モデル内の操作と計算の数値的安定性です。演算またはその勾配によって非常に大きな値または小さな値が生成されると、ほぼ確実にトレーニングプロセスが中断されます(たとえば、これがCNNの画像ピクセル値を正規化してこの問題を防ぐ理由の1つです)。

それでは、この一般化されたバンプ関数を見てみましょう(そして今のところしきい値処理を破棄しましょう)。この関数には特異点(つまり、関数またはその勾配のいずれかが定義されていない点)があるx^2 = sigma(つまり、x = sqrt(sigma)またはx=-sqrt(sigma))ことは明らかです。以下のアニメーションの図は、シグマがゼロから始まり5に増加したときのバンプ関数(赤い実線)、その微分wrtシグマ(緑の点線)、x=sigmaおよびx=-sigmaライン(2つの垂直の青い破線)を示しています。

シグマがゼロから始まり、5に増加するときの一般化されたバンプ関数。

ご覧のとおり、特異点の領域の周りでは、関数とその導関数の両方がそれらの領域で非常に大きな値を取るという意味で、関数はシグマのすべての値に対して適切に動作していません。したがって、シグマの特定の値に対するこれらの領域での入力値が与えられると、爆発する出力値と勾配値が生成されるため、次の問題が発生します。inf損失値の発生します。

さらに、問題のある動作がtf.whereありnan、レイヤーのシグマ変数の値の問題が発生します。驚くべきことに、非アクティブなブランチで生成された値tf.whereが非常に大きい場合inf、またはバンプ関数を使用すると、非常に大きいinf値または勾配値になる場合、それからの勾配はですtf.wherenaninf非アクティブなブランチであり、選択されていないという事実にもかかわらずです(これについて正確に説明しているGithubの問題を参照してください)。

それで、この動作の回避策はありますtf.whereか?はい、実際にはこの回答で説明されているこの問題を何らかの方法で解決するトリックがあります。基本的にはtf.where、これらの領域に関数が適用されないようにするために、追加を使用できます。言い換えると、self.bump_function入力値に適用する代わりに、範囲(-self.sigma, self.sigma)(つまり、関数を適用する必要がある実際の範囲)にない値をフィルタリングし、代わりに関数にゼロ(常に安全な値を生成する、つまりと等しいexp(-1)):

     output = tf.where(
            condition, 
            self.bump_function(tf.where(condition, inputs, 0.0)),
            0.0
     )

この修正を適用するとnan、シグマの値の問題が完全に解決されます。さまざまなシグマ値で生成されたトレーニングデータ値で評価して、それがどのように機能するかを見てみましょう。

true_learned_sigma = []
for s in np.arange(0.1, 10.0, 0.1):
    model = make_model()
    x, y, dy = generate_data(sigma=s, shape=(100000,1))
    model.fit(x, y, epochs=3 if s < 1 else (5 if s < 5 else 10), verbose=False)
    sigma = model.layers[0].get_weights()[0][0]
    true_learned_sigma.append([s, sigma])
    print(s, sigma)

# Check if the learned values of sigma
# are actually close to true values of sigma, for all the experiments.
res = np.array(true_learned_sigma)
print(np.allclose(res[:,0], res[:,1], atol=1e-2))
# True

すべてのシグマ値を正しく学習できます!それはすばらしい。その回避策はうまくいきました!ただし、注意点が1つあります。これは、このレイヤーへの入力値が-1より大きく1未満の場合に適切に機能し、シグマ値を学習することが保証されています(つまり、これがgenerate_data関数のデフォルトのケースです)。それ以外のinf場合、入力値の大きさが1より大きい場合に発生する可能性のある損失値の問題が依然として存在します(下記のポイント#1および#2を参照)。


好奇心と関心のある心のために考えるための食べ物をいくつか紹介します。

  1. このレイヤーへの入力値が1より大きいか、または-1より小さい場合、問題が発生する可能性があると述べました。これが事実である理由を主張できますか?(ヒント:上記のアニメーション図を使用してケースを検討しsigma > 1、入力値の間であり、sqrt(sigma)且つsigma(又は間-sigma及び-sqrt(sigma))。

  2. ポイント1の問題の修正を提供できますか?つまり、レイヤーがすべての入力値で機能するようにできますか?(ヒント:の回避策と同様に、バンプ関数が適用される危険な値tf.whereさらに除外して、爆発する出力/勾配を生成する方法について考えてください。)

  3. ただし、この問題の修正に関心がなく、このレイヤーを現在のモデルで使用したい場合、このレイヤーへの入力値が常に-1から1の間であることをどのように保証しますか?(ヒント:1つの解決策として、正確にこの範囲の値を生成する一般的に使用されるアクティベーション関数があり、このレイヤーの前にあるレイヤーのアクティベーション関数として使用される可能性があります。)

  4. 最後のコードスニペットを見ると、使用したことがわかりますepochs=3 if s < 1 else (5 if s < 5 else 10)。何故ですか?大きな値のsigmaを学習するには、より多くのエポックが必要なのはなぜですか?(ヒント:再度、アニメーション図を使用して、シグマ値が増加するときの-1から1の間の入力値の関数の導関数を検討してください。それらの大きさは何ですか?)

  5. また、の生成されたトレーニングデータnaninfまたはの非常に大きな値をチェックして、yそれらを除外する必要がありますか?(ヒント:はい、sigma > 1そして値の範囲、つまりmin_xand max_xはの外にあり(-1, 1)ます;そうでなければ、それは必要ありません!なぜですか?演習として残してください!)


1
よくやった。@ProbablyAHumanこれは受け入れられる答えになるはずです。
rvinas

@今日。これは素晴らしいと思います。おそらく、私がこれまでスタックで見た中で最も詳細/正確/厳密な答えです。どうもありがとう!
FlabbyTheKatsu

4

残念ながら、x(-σ, σ)にあるかどうかをチェックする操作は微分可能ではないため、勾配降下法を介してσを学習することはできません。具体的には、は条件に関して微分できないself.threshold_levelため、に関する勾配を計算することtf.math.lessはできません。

要素ごとの条件については、代わりにtf.whereを使用して、条件のコンポーネントごとのブール値から、f_True(input)またはf_False(input)それに従って要素を選択できます。例えば:

output_step_3 = tf.where(The_Logic, f_True(input), f_False(input))

注:私は提供されたコードに基づいて、答えself.threshold_levelでは使用されませんf_Truef_Falseself.threshold_level提供された式のようにこれらの関数でが使用されている場合、関数はもちろんに関して微分可能self.threshold_levelです。

更新日:2020年4月19日:説明をありがとうございました


トレーニングを可能にするコーディングトリックが必要です...
FlabbyTheKatsu

数学が機能しない場合でも、魔法の実装のトリックによってトレーニングが可能になるとは
思い

「エラーメッセージはこれを正確に示しています。tf.math.lessはその入力に関して区別できないため、self.threshold_levelに関する勾配を計算することはできません。」->警告メッセージはtf.math.less、条件での使用と区別できません。この条件を機能させるために、微分可能である必要はありません。エラーは、トレーニング可能な重みがレイヤーの出力を生成するためにまったく使用されない(つまり、出力にその痕跡がない)ことにあります。これについて詳しくは、私の回答の最初の部分を参照してください。
今日

同意しました、それは警告メッセージが伝えていることではありません、そして私は私の言い回しを修正します。ただし、ポイントは同じです-変数が特定の範囲内にあるかどうかをチェックする操作を行うことはできず、限界変数に関して微分可能であると期待することはできません。とは言っても、この変数が出力の計算に使用されている場合(式で気が付かなかったので、認める必要があります)、もちろん勾配があります。
rvinas

3

バンプの代わりに正規分布を試すことをお勧めします。ここでの私のテストでは、このバンプ機能はうまく動作していません(バグを見つけることはできませんが破棄しませんが、グラフには2つの非常に鋭いバンプがあり、ネットワークには適していません)。

正規分布では、高さ、幅、中心を制御できる規則的で区別可能なバンプが得られます。

したがって、この関数を試すことができます:

y = a * exp ( - b * (x - c)²)

グラフで試してみて、動作を確認してください。

このため:

class trainable_bump_layer(tf.keras.layers.Layer):

    def __init__(self, *args, **kwargs):
        super(trainable_bump_layer, self).__init__(*args, **kwargs)

    def build(self, input_shape):

        #suggested shape (has a different kernel for each input feature/channel)
        shape = tuple(1 for _ in input_shape[:-1]) + input_shape[-1:]

        #for your desired shape of only 1:
        shape = tuple(1 for _ in input_shape) #all ones

        #height
        self.kernel_a = self.add_weight(name='kernel_a ',
                                    shape=shape
                                    initializer='ones',
                                    trainable=True)

        #inverse width
        self.kernel_b = self.add_weight(name='kernel_b',
                                    shape=shape
                                    initializer='ones',
                                    trainable=True)

        #center
        self.kernel_c = self.add_weight(name='kernel_c',
                                    shape=shape
                                    initializer='zeros',
                                    trainable=True)

    def call(self, input):
        exp_arg = - self.kernel_b * K.square(input - self.kernel_c)
        return self.kernel_a * K.exp(exp_arg)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.