La capa intermedia hace que el optimizador de tensorflow deje de funcionar

Este gráfico entrena un codificador de identidad de señal simple, y de hecho muestra que el optimizador está desarrollando los pesos:

import tensorflow as tf import numpy as np initia = tf.random_normal_initializer(0, 1e-3) DEPTH_1 = 16 OUT_DEPTH = 1 I = tf.placeholder(tf.float32, shape=[None,1], name='I') # input W = tf.get_variable('W', shape=[1,DEPTH_1], initializer=initia, dtype=tf.float32, trainable=True) # weights b = tf.get_variable('b', shape=[DEPTH_1], initializer=initia, dtype=tf.float32, trainable=True) # biases O = tf.nn.relu(tf.matmul(I, W) + b, name='O') # activation / output #W1 = tf.get_variable('W1', shape=[DEPTH_1,DEPTH_1], initializer=initia, dtype=tf.float32) # weights #b1 = tf.get_variable('b1', shape=[DEPTH_1], initializer=initia, dtype=tf.float32) # biases #O1 = tf.nn.relu(tf.matmul(O, W1) + b1, name='O1') W2 = tf.get_variable('W2', shape=[DEPTH_1,OUT_DEPTH], initializer=initia, dtype=tf.float32) # weights b2 = tf.get_variable('b2', shape=[OUT_DEPTH], initializer=initia, dtype=tf.float32) # biases O2 = tf.matmul(O, W2) + b2 O2_0 = tf.gather_nd(O2, [[0,0]]) estimate0 = 2.0*O2_0 eval_inp = tf.gather_nd(I,[[0,0]]) k = 1e-5 L = 5.0 distance = tf.reduce_sum( tf.square( eval_inp - estimate0 ) ) opt = tf.train.GradientDescentOptimizer(1e-3) grads_and_vars = opt.compute_gradients(distance, [W, b, #W1, b1, W2, b2]) clipped_grads_and_vars = [(tf.clip_by_value(g, -4.5, 4.5), v) for g, v in grads_and_vars] train_op = opt.apply_gradients(clipped_grads_and_vars) saver = tf.train.Saver() init_op = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init_op) for i in range(10000): print sess.run([train_op, I, W, distance], feed_dict={ I: 2.0*np.random.rand(1,1) - 1.0}) for i in range(10): print sess.run([eval_inp, W, estimate0], feed_dict={ I: 2.0*np.random.rand(1,1) - 1.0}) 

Sin embargo, cuando descomento la capa oculta intermedia y entreno la red resultante, veo que los pesos ya no están evolucionando:

 import tensorflow as tf import numpy as np initia = tf.random_normal_initializer(0, 1e-3) DEPTH_1 = 16 OUT_DEPTH = 1 I = tf.placeholder(tf.float32, shape=[None,1], name='I') # input W = tf.get_variable('W', shape=[1,DEPTH_1], initializer=initia, dtype=tf.float32, trainable=True) # weights b = tf.get_variable('b', shape=[DEPTH_1], initializer=initia, dtype=tf.float32, trainable=True) # biases O = tf.nn.relu(tf.matmul(I, W) + b, name='O') # activation / output W1 = tf.get_variable('W1', shape=[DEPTH_1,DEPTH_1], initializer=initia, dtype=tf.float32) # weights b1 = tf.get_variable('b1', shape=[DEPTH_1], initializer=initia, dtype=tf.float32) # biases O1 = tf.nn.relu(tf.matmul(O, W1) + b1, name='O1') W2 = tf.get_variable('W2', shape=[DEPTH_1,OUT_DEPTH], initializer=initia, dtype=tf.float32) # weights b2 = tf.get_variable('b2', shape=[OUT_DEPTH], initializer=initia, dtype=tf.float32) # biases O2 = tf.matmul(O1, W2) + b2 O2_0 = tf.gather_nd(O2, [[0,0]]) estimate0 = 2.0*O2_0 eval_inp = tf.gather_nd(I,[[0,0]]) distance = tf.reduce_sum( tf.square( eval_inp - estimate0 ) ) opt = tf.train.GradientDescentOptimizer(1e-3) grads_and_vars = opt.compute_gradients(distance, [W, b, W1, b1, W2, b2]) clipped_grads_and_vars = [(tf.clip_by_value(g, -4.5, 4.5), v) for g, v in grads_and_vars] train_op = opt.apply_gradients(clipped_grads_and_vars) saver = tf.train.Saver() init_op = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init_op) for i in range(10000): print sess.run([train_op, I, W, distance], feed_dict={ I: 2.0*np.random.rand(1,1) - 1.0}) for i in range(10): print sess.run([eval_inp, W, estimate0], feed_dict={ I: 2.0*np.random.rand(1,1) - 1.0}) 

La evaluación de la estimate0 converge rápidamente en algún valor fijo que se vuelve independiente de la señal de entrada. No tengo idea de por qué sucede esto.

Pregunta:

¿Alguna idea de lo que podría estar mal con el segundo ejemplo?

    TL; DR: cuanto más profunda se vuelve la neural network, más debes prestar atención al flujo de gradiente (consulta esta discusión de “gradientes de fuga”). Un caso particular es la inicialización de variables .


    Análisis del problema

    He agregado resúmenes de tensorboard para las variables y gradientes en ambos scripts y obtuve lo siguiente:

    Red de 2 capas

    2 capas

    Red de 3 capas

    Red de 3 capas

    Los gráficos muestran las distribuciones de la variable W:0 (la primera capa) y cómo se cambian de 0 época a 1000 (se puede hacer clic). De hecho, podemos ver que la tasa de cambio es mucho mayor en una red de 2 capas. Pero me gustaría prestar atención a la distribución del gradiente, que está mucho más cerca de 0 en una red de 3 capas (la primera varianza es de alrededor de 0.005 , la segunda es de alrededor de 0.000002 , es decir, 1000 veces más pequeña). Este es el problema de la degradación de la desaparición .

    Aquí está el código de ayuda si está interesado:

     for g, v in grads_and_vars: tf.summary.histogram(v.name, v) tf.summary.histogram(v.name + '_grad', g) merged = tf.summary.merge_all() writer = tf.summary.FileWriter('train_log_layer2', tf.get_default_graph()) ... _, summary = sess.run([train_op, merged], feed_dict={I: 2*np.random.rand(1, 1)-1}) if i % 10 == 0: writer.add_summary(summary, global_step=i) 

    Solución

    Todas las redes profundas sufren de esto hasta cierto punto y no existe una solución universal que pueda reparar automáticamente cualquier red. Pero hay algunas técnicas que pueden empujarlo en la dirección correcta. La inicialización es una de ellas.

    Reemplazé su inicialización normal con:

     W_init = tf.contrib.layers.xavier_initializer() b_init = tf.constant_initializer(0.1) 

    Hay muchos tutoriales en Xavier init, puedes echarle un vistazo a este , por ejemplo. Tenga en cuenta que establezco que el inicio del sesgo es ligeramente positivo para asegurarme de que las salidas ReLu sean positivas para la mayoría de las neuronas, al menos al principio.

    Esto cambió la imagen de inmediato:

    3 capas mejoradas

    Los pesos aún no se mueven tan rápido como antes, pero se están moviendo (tenga en cuenta la escala de los valores de W:0 ) y la distribución de gradientes se volvió mucho menos alta en 0, por lo tanto, mucho mejor.

    Por supuesto, no es el final. Para mejorar aún más, debe implementar el autocodificador completo, ya que actualmente la pérdida se ve afectada por la reconstrucción del elemento [0,0] , por lo que la mayoría de las salidas no se utilizan en la optimización. También puedes jugar con diferentes optimizadores (Adam sería mi elección) y las tasas de aprendizaje.