Пользовательский расчет градиента слоя Keras / tenorflow, который требует цикла, и если - PullRequest
0 голосов
/ 25 ноября 2018

У меня вопрос по поводу пользовательского расчета градиента.У меня есть пользовательский слой и я не могу понять, как правильно запрограммировать его градиент.Этот градиент соответствует передаточной функции, которая похожа на жесткий ограничитель (или сигмоид с очень крутым переходом): выходное значение равно y=x, если абсолютное значение |x| < th меньше порогового значения th или sign(x) в противном случае,Таким образом, градиент g равен g=0, если |x| >= th или g=1 в противном случае.После поиска в Интернете я попробовал функцию _MySignGrad, описанную ниже.Код выполняется, но я сталкиваюсь с двумя проблемами: 1) я пытался «зарегистрировать» вычисление grad, но по какой-то причине он не вызывается, и 2) всякий раз, когда он вызывается, я уверен, что получит ошибку времени выполнения, поскольку код потенциально неверен.Есть намеки?

import numpy as np
import tensorflow as tf
from keras.layers import Input, Dense, Flatten
from keras.models import Model
import keras.backend as K
from keras.engine.topology import Layer
from tensorflow.python.framework import ops

# training parameters
epochs = 10
batch_size = 3
dim_x = 2
dim_y = 4
N = 100 #half training examples

#define some training data and labels
train_class_0 = np.random.normal(size=(N,dim_x,dim_y)) + 3
train_class_1 = np.random.normal(size=(N,dim_x,dim_y)) - 3
train_data = np.concatenate((train_class_0,train_class_1),axis=0)
output_labels = np.concatenate((0*np.ones(N,),1*np.ones(N,)))

# Define custom function which takes also a grad op as argument:
def ak_py_func(func, inp, Tout, stateful=True, name=None, grad=None):
    # Need to generate a unique name to avoid duplicates:
    rnd_name = 'AkPyFuncGrad' + str(np.random.randint(0, 1E+7))
    tf.RegisterGradient(rnd_name)(grad)
    g = tf.get_default_graph()
    #tf.py_func allows to use a python code as an op
    with g.gradient_override_map({"PyFunc": rnd_name, "PyFuncStateless": rnd_name}):
        return tf.py_func(func, inp, Tout, stateful=stateful, name=name)

# My custom gradient calculation that is wrong
def _MySignGrad(op, grad):
    threshold = 0.01
    newgrad = grad #ok, I guess I cannot do this
    for i in range(batch_size):
        mask = K.ones((op.inputs[i].shape)) #initialize with 1's
        mask[K.abs(op.inputs[i]) > threshold] = 0 #mas is zero when input is above threshold
        newgrad[i] = grad[i] * mask #zero selected gradient entries
    return newgrad

# function that binds forward and backward pass of tensorflow ops
def myfunc(x, name=None):
    with ops.name_scope(name, "Myfunc", [x]) as name:
        output_x = ak_py_func(np.sign, #try also square and exp
                            [x],
                            [tf.float32],
                            name=name,
                            grad=_MySignGrad)  # <-- here's the call to the gradient
        return output_x[0]

#Custom layer
class LimiterLayer(Layer):
    def __init__(self, **kwargs):
        super(LimiterLayer, self).__init__(**kwargs)
    def call(self, x):
        return myfunc(x)
    def get_output_shape_for(self, input_shape):
        return tuple(input_shape)

# Define all layers
l1_input = Input(shape=(dim_x,dim_y),name='l1_input')
l2_flatten = Flatten(name='l2_flatten')(l1_input)
l3_limiter = LimiterLayer(name='l3_limiter')(l2_flatten)
l4_dense = Dense(10, activation='tanh', name='l4_dense')(l3_limiter)
l5_output = Dense(1, activation='sigmoid', name='l5_output') (l4_dense)
# put it all together, creating model from layers
model = Model(l1_input, l5_output)
print(model.summary())
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model.fit(train_data, output_labels, epochs=epochs, batch_size=batch_size, validation_split=0.3)
...