Градиенты Tensorflow не существуют для смещения в пользовательском слое - PullRequest
0 голосов
/ 01 декабря 2019

Я построил входную выпуклую нейронную сеть в Tensorflow, следуя этой статье ArXiv , которая является скалярной моделью прямого вывода. Первый скрытый слой является плотным, а последующие слои являются пользовательскими, которые принимают два входа: вывод предыдущего слоя (ядро) и ввод модели (проход). Отдельные веса применяются к каждому. Это позволяет применять положительный регуляризатор весов к весам ядра, но не к проходу. Я вычисляю регуляризатор и добавляю его, используя self.add_loss в методе call пользовательского слоя. Я также использую пользовательские функции активации, которые представляют собой квадраты неплотного ReLU и неплотного ReLU.

Когда я тренируюсь в этой сети, я могу рассчитать градиент для смещения в первом плотном слое, но получаю предупреждение, чтоградиент не существует для смещения в пользовательском слое. Когда я добавляю @tf.function к своим функциям активации, предупреждение исчезает, но градиент равен 0. Кроме того, loss.numpy() выдает ошибку, когда я использую @tf.function и запускаюсь в локальной записной книжке Jupyter (но не в Colab).

Есть идеи, почему существует градиент смещения для плотного, но не пользовательского слоя и как рассчитать градиент смещения для всех слоев? В этом ноутбуке Colab представлен минимальный рабочий пример. Очень признателен!

Ниже мой пользовательский слой. Это очень похоже на стандартный плотный слой.

class DensePartiallyConstrained(Layer):
    '''
    A custom layer inheriting from `tf.keras.layers.Layers` class.
    This class is a fully-connected layer with two inputs. This allows
    for different constraints on the weights of each input. This enables
    a passthrough of the inputs to each hidden layer to have no
    weight constraints while the input from the previous layer can have
    a positive constraint. It also allows for different initializations
    of the weight values for each input.

    Most of this code and documentation was borrowed from the
    `tf.keras.layers.Dense` documentation on Github (thanks!).
    '''
    def __init__(self,
                 units,
                 activation = None,
                 use_bias = True,
                 kernel_initializer = 'glorot_uniform',
                 passthrough_initializer = 'glorot_uniform',
                 bias_initializer = 'zeros',
                 kernel_constraint = None,
                 passthrough_constraint = None,
                 bias_constraint = None,
                 activity_regularizer = None,
                 regularizer_constant = 1.0,
                 **kwargs):

        if 'input_shape' not in kwargs and 'input_dim' in kwargs:
            kwargs['input_shape'] = (kwargs.pop('input_dim'),)

        super(DensePartiallyConstrained, self).__init__(
                activity_regularizer = regularizers.get(activity_regularizer), **kwargs)

        self.units = int(units)
        self.activation = activations.get(activation)
        self.use_bias = use_bias
        self.kernel_initializer = initializers.get(kernel_initializer)
        self.passthrough_initializer = initializers.get(passthrough_initializer)
        self.bias_initializer = initializers.get(bias_initializer)
        self.kernel_constraint = constraints.get(kernel_constraint)
        self.passthrough_constraint = constraints.get(passthrough_constraint)
        self.bias_constraint = constraints.get(bias_constraint)

        # This is for add_loss in call() method
        self.regularizer_constant = regularizer_constant

        # What does this do?
        self.supports_masking = True

        self.kernel_input_spec = InputSpec(min_ndim=2)
        self.passthrough_input_spec = InputSpec(min_ndim=2)


    def build(self, input_shape):
        # Input shapes provided as list [kernel, passthrough]
        kernel_input_shape, passthrough_input_shape = input_shape

        # Check for proper datatype
        dtype = dtypes.as_dtype(self.dtype or K.floatx())
        if not (dtype.is_floating or dtype.is_complex):
          raise TypeError('Unable to build `DensePartiallyConstrained` layer with non-floating point '
                          'dtype %s' % (dtype,))

        # Check kernel input dimensions
        kernel_input_shape = tensor_shape.TensorShape(kernel_input_shape)
        if tensor_shape.dimension_value(kernel_input_shape[-1]) is None:
          raise ValueError('The last dimension of the inputs to `DensePartiallyConstrained` '
                           'should be defined. Found `None`.')
        kernel_last_dim = tensor_shape.dimension_value(kernel_input_shape[-1])
        self.kernel_input_spec = InputSpec(min_ndim=2,
                                    axes={-1: kernel_last_dim})

        # Check passthrough input dimensions
        passthrough_input_shape = tensor_shape.TensorShape(passthrough_input_shape)
        if tensor_shape.dimension_value(passthrough_input_shape[-1]) is None:
          raise ValueError('The last dimension of the inputs to `DensePartiallyConstrained` '
                           'should be defined. Found `None`.')
        passthrough_last_dim = tensor_shape.dimension_value(passthrough_input_shape[-1])
        self.passthrough_input_spec = InputSpec(min_ndim=2,
                                    axes={-1: passthrough_last_dim})

        # Add weights to kernel (between layer connections)
        self.kernel = self.add_weight(name = 'kernel',
                                      shape = [kernel_last_dim, self.units],
                                      initializer = self.kernel_initializer,
                                      constraint = self.kernel_constraint,
                                      dtype = self.dtype,
                                      trainable = True)
        # Add weight to input passthrough
        self.passthrough = self.add_weight(name = 'passthrough',
                                      shape = [passthrough_last_dim, self.units],
                                      initializer = self.passthrough_initializer,
                                      constraint = self.passthrough_constraint,
                                      dtype = self.dtype,
                                      trainable = True)
        # Add weights to bias
        if self.use_bias:
            self.bias = self.add_weight(name = 'bias',
                                        shape = [self.units,],
                                        initializer = self.bias_initializer,
                                        constraint = self.bias_constraint,
                                        dtype = self.dtype,
                                        trainable = True)
        else:
            self.bias = None

        self.built = True

        super(DensePartiallyConstrained, self).build(input_shape)


    def call(self, inputs):
        # Inputs provided as list [kernel, passthrough]
        kernel_input, passthrough_input = inputs

        # Calculate weights regularizer
        self.add_loss(self.regularizer_constant * tf.reduce_sum(tf.square(tf.math.maximum(tf.negative(self.kernel), 0.0))))

        # Calculate layer output
        outputs = tf.add(tf.matmul(kernel_input, self.kernel), tf.matmul(passthrough_input, self.passthrough))

        if self.use_bias:
            outputs = tf.add(outputs, self.bias)

        if self.activation is not None:
            return self.activation(outputs)
        return outputs

И мои функции активации:

#@tf.function
def squared_leaky_ReLU(x, alpha = 0.2):
    return tf.square(tf.maximum(x, alpha * x))
#@tf.function
def leaky_ReLU(x, alpha = 0.2):
    return tf.maximum(x, alpha * x)

Редактировать: С обновлением тензорного потока я теперь могу получить доступ loss.numpy() при использовании @tf.function с моими функциями активации. Это возвращает 0 градиентов для смещения во всех моих пользовательских слоях.

Я начинаю думать, что отсутствие градиента для смещений в пользовательском слое может иметь какое-то отношение к моей функции потерь: минимаксная потеря , где регуляризатор - регуляризация только для весов в ядре пользовательского уровня. Потери для g(x) основаны на градиенте относительно входных данных, поэтому они не содержат никакой информации о смещении (смещение в f(x) обновляется обычно). Тем не менее, если это так, я не понимаю, почему смещение в первом скрытом плотном слое g(y) обновляется? Сети идентичны, кроме того, что f(x) имеет положительное ограничение на вес ядра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...