Я построил входную выпуклую нейронную сеть в 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)
имеет положительное ограничение на вес ядра.