Я работаю над проектом, в котором мы пытаемся восстановить 2D-изображение из примитивов геометрии c. С этой целью я разработал пользовательский слой Keras, который выводит изображение конуса с учетом его геометрии c характеристик.
Его вход представляет собой тензор формы batch_size * 5, где пять чисел представляют собой xy координаты вершины конуса, координаты xy единичного вектора, описывающего ось конуса, и угол на вершине конуса.
Цель состоит в том, чтобы использовать этот слой в качестве не обучаемого декодер в архитектуре кодер-декодер. Затем мы будем кормить нейронную сеть конусными изображениями. Ожидаемое поведение состоит в том, что нейронная сеть должна затем выучить скрытое представление, подобное описанному выше.
Когда я включаю этот уровень в большую сеть и пытаюсь оптимизировать его, неизменно некоторые веса в конечном итоге обновляются до NaN. Это происходит даже в такой простой сети, как скрытый слой с двумя нейронами без функций активации.
Я тщательно проверил свой слой. Его вывод соответствует тому, что я ожидаю. Я не могу найти ни одной тривиальной ошибки в реализации (но вы должны быть предупреждены, я все еще довольно новичок в tenorflow и keras). Я сузил вопрос до автоматической c дифференциации слоя.
Градиент, по-видимому, равен либо 0,0, либо NaN. Насколько я понимаю, некоторая численная нестабильность приводит к тому, что градиент расходится.
Вопрос состоит из двух частей:
Ниже приведен минимальный рабочий пример, показывающий, как градиент увеличивается до 0,0 или NaN для указанных значений c.
import numpy as np
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Layer
import tensorflow as tf
import numpy.random as rnd
class Cones(Layer):
def __init__(self, output_dim, **kwargs):
super(Cones, self).__init__(**kwargs)
self.output_dim = output_dim
coordinates = np.zeros((self.output_dim, self.output_dim, 2))
for i in range(self.output_dim):
for j in range(self.output_dim):
coordinates[i,j,:] = np.array([i,j])
coordinates = K.constant(coordinates)
self.coordinates = tf.Variable(initial_value=coordinates, trainable=False)
self.smooth_sign_width = tf.Variable(initial_value=output_dim, dtype=tf.float32, trainable=False)
self.grid_width = tf.Variable(initial_value=output_dim, dtype=tf.float32, trainable=False)
def build(self, input_shape):
super(Cones, self).build(input_shape)
def call(self, x):
center = self.grid_width*x[:,:2]
center = K.expand_dims(center, axis=1)
center = K.expand_dims(center, axis=1)
direction = x[:,2:4]
direction = K.expand_dims(direction,1)
direction = K.expand_dims(direction,1)
direction = K.l2_normalize(direction, axis=-1)
aperture = np.pi*x[:,4:]
aperture = K.expand_dims(aperture)
u = self.coordinates - center
u = K.l2_normalize(u, axis=-1)
angle = K.sum(u*direction, axis=-1)
angle = K.minimum(angle, K.ones_like(angle))
angle = K.maximum(angle, -K.ones_like(angle))
angle = tf.math.acos(angle)
output = self.smooth_sign(aperture-angle)
output = K.expand_dims(output, -1)
return output
def smooth_sign(self, x):
return tf.math.sigmoid(self.smooth_sign_width*x)
def compute_output_shape(self, input_shape):
return (input_shape[0], self.output_dim, self.output_dim, 1)
geom = K.constant([[0.34015268, 0.31530404, -0.6827047, 0.7306944, 0.8521315]])
image = Cones(Nx)(geom)
x0 = geom
y0 = image
with tf.GradientTape() as t:
t.watch(x0)
cone = Cones(Nx)(x0)
error = cone-y0
error_squared = error*error
mse = tf.math.reduce_mean(error_squared)
print(t.gradient(mse, x0))
geom = K.constant([[0.742021, 0.25431857, 0.90899783, 0.4168009, 0.58542883]])
image = Cones(Nx)(geom)
x0 = geom
y0 = image
with tf.GradientTape() as t:
t.watch(x0)
cone = Cones(Nx)(x0)
error = cone-y0
error_squared = error*error
mse = tf.math.reduce_mean(error_squared)
print(t.gradient(mse, x0))