Estoy tratando de codificar la siguiente variante de la función Bump , aplicada por componentes:
donde σ es entrenable; pero no funciona (los errores se informan a continuación).
Mi intento:
Esto es lo que he codificado hasta ahora (si ayuda). Supongamos que tengo dos funciones (por ejemplo):
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
Reporte de error:
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
Además, no parece aplicarse por componentes (además del problema no entrenable). ¿Cual podría ser el problema?
python
tensorflow
machine-learning
keras
tf.keras
FlabbyTheKatsu
fuente
fuente
input
? ¿Es un escalar?Respuestas:
¡Estoy un poco sorprendido de que nadie haya mencionado la razón principal (y única) de la advertencia dada! Como parece, se supone que ese código implementa la variante generalizada de la función Bump; sin embargo, solo eche un vistazo a las funciones implementadas nuevamente:
El error es evidente: ¡ no se puede utilizar el peso entrenable de la capa en estas funciones! Por lo tanto, no sorprende que reciba el mensaje que dice que no existe un gradiente para eso: no lo está utilizando en absoluto, ¡así que no hay gradiente para actualizarlo! Más bien, esta es exactamente la función Bump original (es decir, sin peso entrenable).
Pero, podría decir que: "al menos, usé el peso entrenable en la condición de
tf.cond
, ¿entonces debe haber algunos gradientes?"; Sin embargo, no es así y déjame aclarar la confusión:En primer lugar, como también ha notado, estamos interesados en el acondicionamiento basado en elementos. Así que en lugar de
tf.cond
que necesita utilizartf.where
.El otro concepto erróneo es afirmar que ya que
tf.less
se usa como condición, y dado que no es diferenciable, es decir, no tiene gradiente con respecto a sus entradas (lo cual es cierto: no hay gradiente definido para una función con salida booleana wrt su real- entradas valiosas!), entonces eso da como resultado la advertencia dada!relu(x) = 0 if x < 0 else x
. Si la derivada de la condición, es decirx < 0
, se considera / necesita, lo que no existe, ¡entonces no podríamos usar ReLU en nuestros modelos y entrenarlos utilizando métodos de optimización basados en gradientes!)(Nota: a partir de aquí, me referiría y denotaría el valor umbral como sigma , como en la ecuación).
¡Todo bien! Encontramos la razón detrás del error en la implementación. ¿Podríamos arreglar esto? ¡Por supuesto! Aquí está la implementación de trabajo actualizada:
Algunos puntos con respecto a esta implementación:
Hemos reemplazado
tf.cond
con eltf.where
fin de hacer un condicionamiento basado en elementos.Además, como puede ver, a diferencia de su implementación, que solo verificó un lado de la desigualdad, estamos usando
tf.math.less
,tf.math.greater
y tambiéntf.logical_and
para averiguar si los valores de entrada tienen magnitudes menores quesigma
(alternativamente, podríamos hacer esto usando solotf.math.abs
ytf.math.less
; no hay diferencia !). Y repitámoslo: el uso de funciones de salida booleana de esta manera no causa ningún problema y no tiene nada que ver con derivados / gradientes.También estamos utilizando una restricción de no negatividad en el valor sigma aprendido por capa. ¿Por qué? Porque los valores sigma inferiores a cero no tienen sentido (es decir, el rango
(-sigma, sigma)
está mal definido cuando sigma es negativo).Y teniendo en cuenta el punto anterior, nos encargamos de inicializar el valor sigma correctamente (es decir, a un pequeño valor no negativo).
Y también, ¡por favor no hagas cosas como
0.0 * inputs
! Es redundante (y un poco raro) y es equivalente a0.0
; y ambos tienen un gradiente de0.0
(wrtinputs
). Multiplicar cero con un tensor no agrega nada ni resuelve ningún problema existente, ¡al menos no en este caso!Ahora, probémoslo para ver cómo funciona. Escribimos algunas funciones auxiliares para generar datos de entrenamiento basados en un valor sigma fijo, y también para crear un modelo que contenga un único
BumpLayer
con forma de entrada de(1,)
. Veamos si podría aprender el valor sigma que se utiliza para generar datos de entrenamiento:¡Sí, podría aprender el valor de sigma utilizado para generar datos! Pero, ¿está garantizado que realmente funcione para todos los valores diferentes de datos de entrenamiento e inicialización de sigma? ¡La respuesta es no! En realidad, es posible que ejecute el código anterior y obtenga
nan
el valor de sigma después del entrenamiento, ¡oinf
el valor de pérdida! ¿Entonces, cuál es el problema? ¿Por qué estonan
oinf
valores pueden ser producidos? Discutamos a continuación ...Tratando con la estabilidad numérica
Una de las cosas importantes a tener en cuenta, cuando se construye un modelo de aprendizaje automático y se utilizan métodos de optimización basados en gradientes para entrenarlos, es la estabilidad numérica de las operaciones y los cálculos en un modelo. Cuando una operación o su gradiente generan valores extremadamente grandes o pequeños, es casi seguro que interrumpiría el proceso de entrenamiento (por ejemplo, esa es una de las razones detrás de la normalización de los valores de píxeles de imagen en CNN para evitar este problema).
Entonces, echemos un vistazo a esta función de relieve generalizada (y descartemos el límite por ahora). Es obvio que esta función tiene singularidades (es decir, puntos donde la función o su gradiente no están definidos) en
x^2 = sigma
(es decir, cuándox = sqrt(sigma)
ox=-sqrt(sigma)
). El siguiente diagrama animado muestra la función de relieve (la línea roja continua), su derivada wrt sigma (la línea verde punteada)x=sigma
y lasx=-sigma
líneas (dos líneas azules discontinuas verticales), cuando sigma comienza desde cero y se incrementa a 5:Como puede ver, alrededor de la región de singularidades, la función no se comporta bien para todos los valores de sigma, en el sentido de que tanto la función como su derivada toman valores extremadamente grandes en esas regiones. Entonces, dado un valor de entrada en esas regiones para un valor particular de sigma, se generarían valores de salida y gradiente explosivos, de ahí la cuestión del
inf
valor de pérdida.Aún más, hay un comportamiento problemático
tf.where
que causa la emisión denan
valores para la variable sigma en la capa: sorprendentemente, si el valor producido en la rama inactiva detf.where
es extremadamente grande oinf
, lo que con la función de relieve da como resultadoinf
valores extremadamente grandes o de gradiente , entonces el gradiente detf.where
seríanan
, a pesar del hecho de queinf
está en la rama inactiva y ni siquiera está seleccionado (¡vea este tema de Github que trata exactamente esto!)Entonces, ¿hay alguna solución para este comportamiento
tf.where
? Sí, en realidad hay un truco para resolver este problema de alguna manera que se explica en esta respuesta : básicamente podemos usar un adicionaltf.where
para evitar que la función se aplique en estas regiones. En otras palabras, en lugar de aplicarself.bump_function
cualquier valor de entrada, filtramos aquellos valores que NO están en el rango(-self.sigma, self.sigma)
(es decir, el rango real al que se debe aplicar la función) y en su lugar alimentamos la función con cero (que siempre produce valores seguros, es decir es igual aexp(-1)
):La aplicación de esta solución resolvería por completo el problema de los
nan
valores para sigma. Evaluémoslo en los valores de datos de entrenamiento generados con diferentes valores sigma y veamos cómo funcionaría:¡Podría aprender todos los valores sigma correctamente! Eso es bueno. Esa solución funcionó! Sin embargo, hay una advertencia: se garantiza que funcionará correctamente y aprenderá cualquier valor sigma si los valores de entrada a esta capa son mayores que -1 y menores que 1 (es decir, este es el caso predeterminado de nuestra
generate_data
función); de lo contrario, todavía existe el problema delinf
valor de pérdida que podría suceder si los valores de entrada tienen una magnitud mayor que 1 (consulte los puntos 1 y 2 a continuación).Aquí hay algunos alimentos para pensar para los curiosos y la mente interesada:
Se acaba de mencionar que si los valores de entrada a esta capa son mayores que 1 o menores que -1, puede causar problemas. ¿Puedes discutir por qué este es el caso? (Sugerencia: use el diagrama animado anterior y considere los casos en que
sigma > 1
el valor de entrada esté entresqrt(sigma)
ysigma
(o entre-sigma
y-sqrt(sigma)
).¿Puede proporcionar una solución para el problema en el punto n. ° 1, es decir, que la capa podría funcionar para todos los valores de entrada? (Sugerencia: al igual que la solución alternativa
tf.where
, piense en cómo puede filtrar aún más los valores inseguros en los que se podría aplicar la función de relieve y producir una salida / gradiente explosivo).Sin embargo, si no está interesado en solucionar este problema y desea utilizar esta capa en un modelo como está ahora, ¿cómo garantizaría que los valores de entrada para esta capa siempre estén entre -1 y 1? (Sugerencia: como una solución, hay una función de activación de uso común que produce valores exactamente en este rango y podría usarse potencialmente como la función de activación de la capa que está antes de esta capa).
Si echa un vistazo al último fragmento de código, verá que lo hemos utilizado
epochs=3 if s < 1 else (5 if s < 5 else 10)
. ¿Porqué es eso? ¿Por qué los grandes valores de sigma necesitan más épocas para aprender? (Sugerencia: nuevamente, use el diagrama animado y considere la derivada de la función para valores de entrada entre -1 y 1 a medida que aumenta el valor sigma. ¿Cuál es su magnitud?)Es lo que también necesitamos comprobar los datos de entrenamiento generados por cualquier
nan
,inf
o los valores extremadamente grandesy
y filtrarlos a cabo? (Sugerencia: sí, sisigma > 1
y el rango de valores, es decir,min_x
ymax_x
, quedan fuera de(-1, 1)
; de lo contrario, ¡no, eso no es necesario! ¿Por qué es eso? ¡Dejado como ejercicio!)fuente
Desafortunadamente, ninguna operación para verificar si
x
está dentro(-σ, σ)
será diferenciable y, por lo tanto, σ no se puede aprender a través de ningún método de descenso de gradiente. Específicamente, no es posible calcular los gradientes con respecto aself.threshold_level
porquetf.math.less
no es diferenciable con respecto a la condición.Con respecto al condicional en cuanto a elementos, puede usar tf.where para seleccionar elementos de
f_True(input)
of_False(input)
según los valores booleanos de la condición según los componentes. Por ejemplo:NOTA: respondí en función del código proporcionado, donde
self.threshold_level
no se usaf_True
ni enf_False
. Siself.threshold_level
se usa en esas funciones como en la fórmula provista, la función, por supuesto, será diferenciable con respecto aself.threshold_level
.Actualizado el 19/04/2020: Gracias @today por la aclaración .
fuente
tf.math.less
en la condición y el hecho de que no es diferenciable. La condición no necesita ser diferenciable para que esto funcione. El error radica en el hecho de que el peso entrenable no se utiliza en absoluto para producir la salida de la capa (es decir, no hay rastro de ella en la salida). Consulte la primera parte de mi respuesta para obtener más información al respecto.Le sugiero que pruebe una distribución normal en lugar de una protuberancia. En mis pruebas aquí, esta función de relieve no se comporta bien (no puedo encontrar un error pero no lo descarto, pero mi gráfico muestra dos golpes muy agudos, lo que no es bueno para las redes)
Con una distribución normal, obtendría una protuberancia regular y diferenciable cuya altura, ancho y centro puede controlar.
Entonces, puedes probar esta función:
Pruébelo en algún gráfico y vea cómo se comporta.
Para esto:
fuente