Pregunta

Estoy tratando de implementar autoencoders convolucionales en TensorFlow, en el conjunto de datos MNIST.

El problema es que el autoencoder no parece aprender correctamente: siempre aprenderá a reproducir la forma 0, pero no hay otras formas, de hecho, generalmente obtengo una pérdida promedio de aproximadamente 0.09, que es 1/10 de las clases que tiene debería aprender.

Estoy usando núcleos 2x2 con Stride 2 para las convoluciones de entrada y salida, pero los filtros parecen aprender correctamente. Cuando visualizo los datos, la imagen de entrada se pasa a través de 16 (1er Conv) y 32 filtros (2º Conv), y por inspección de imágenes parece funcionar bien (es decir, aparentemente se detectan características como curvas, cruces, etc.).

El problema parece surgir en la parte completamente conectada de la red: no importa cuál sea la imagen de entrada, su codificación será siempre lo mismo.

Mi primer pensamiento es "Probablemente solo lo estoy alimentando con ceros mientras entrenaba", pero no creo que haya cometido este error (ver el código a continuación).

Editar Me di cuenta de que el conjunto de datos no estaba barajado, lo que introdujo un sesgo y podría ser la causa del problema. Después de introducirlo, la pérdida promedio es menor (0.06 en lugar de 0.09), y de hecho la imagen de salida parece un borde de 8, pero las conclusiones son las mismas: la entrada codificada será la misma sin importar cuál sea la imagen de entrada.

Aquí una entrada de muestra con la salida relativa

A sample input with the relative output

Aquí están la activación de la imagen de arriba, con las dos capas completamente conectadas en la parte inferior (la codificación es el bottommost).

activations

Finalmente, aquí están la activación para las capas completamente conectadas para diferentes entradas. Cada imagen de entrada corresponde a una línea en las imágenes de activación.

Como puede ver, siempre producen la misma salida. Si uso pesos transponidos en lugar de inicializar diferentes, la primera capa FC (imagen en el medio) se ve un poco más aleatorizada, pero el patrón subyacente sigue siendo evidente. En la capa de codificación (imagen en la parte inferior), la salida siempre será la misma sin importar cuál sea la entrada (por supuesto, el patrón varía de un entrenamiento y el siguiente).

FC layers activation

Aquí está el código relevante

# A placeholder for the input data
x = tf.placeholder('float', shape=(None, mnist.data.shape[1]))

# conv2d_transpose cannot use -1 in output size so we read the value
# directly in the graph
batch_size = tf.shape(x)[0]

# Variables for weights and biases
with tf.variable_scope('encoding'):
    # After converting the input to a square image, we apply the first convolution, using 2x2 kernels
    with tf.variable_scope('conv1'):
        wec1 = tf.get_variable('w', shape=(2, 2, 1, m_c1), initializer=tf.truncated_normal_initializer())
        bec1 = tf.get_variable('b', shape=(m_c1,), initializer=tf.constant_initializer(0))
    # Second convolution
    with tf.variable_scope('conv2'):
        wec2 = tf.get_variable('w', shape=(2, 2, m_c1, m_c2), initializer=tf.truncated_normal_initializer())
        bec2 = tf.get_variable('b', shape=(m_c2,), initializer=tf.constant_initializer(0))
    # First fully connected layer
    with tf.variable_scope('fc1'):
        wef1 = tf.get_variable('w', shape=(7*7*m_c2, n_h1), initializer=tf.contrib.layers.xavier_initializer())
        bef1 = tf.get_variable('b', shape=(n_h1,), initializer=tf.constant_initializer(0))
    # Second fully connected layer
    with tf.variable_scope('fc2'):
        wef2 = tf.get_variable('w', shape=(n_h1, n_h2), initializer=tf.contrib.layers.xavier_initializer())
        bef2 = tf.get_variable('b', shape=(n_h2,), initializer=tf.constant_initializer(0))

reshaped_x = tf.reshape(x, (-1, 28, 28, 1))
y1 = tf.nn.conv2d(reshaped_x, wec1, strides=(1, 2, 2, 1), padding='VALID')
y2 = tf.nn.sigmoid(y1 + bec1)
y3 = tf.nn.conv2d(y2, wec2, strides=(1, 2, 2, 1), padding='VALID')
y4 = tf.nn.sigmoid(y3 + bec2)
y5 = tf.reshape(y4, (-1, 7*7*m_c2))
y6 = tf.nn.sigmoid(tf.matmul(y5, wef1) + bef1)
encode = tf.nn.sigmoid(tf.matmul(y6, wef2) + bef2)

with tf.variable_scope('decoding'):
    # for the transposed convolutions, we use the same weights defined above
    with tf.variable_scope('fc1'):
        #wdf1 = tf.transpose(wef2)
        wdf1 = tf.get_variable('w', shape=(n_h2, n_h1), initializer=tf.contrib.layers.xavier_initializer())
        bdf1 = tf.get_variable('b', shape=(n_h1,), initializer=tf.constant_initializer(0))
    with tf.variable_scope('fc2'):
        #wdf2 = tf.transpose(wef1)
        wdf2 = tf.get_variable('w', shape=(n_h1, 7*7*m_c2), initializer=tf.contrib.layers.xavier_initializer())
        bdf2 = tf.get_variable('b', shape=(7*7*m_c2,), initializer=tf.constant_initializer(0))
    with tf.variable_scope('deconv1'):
        wdd1 = tf.get_variable('w', shape=(2, 2, m_c1, m_c2), initializer=tf.contrib.layers.xavier_initializer())
        bdd1 = tf.get_variable('b', shape=(m_c1,), initializer=tf.constant_initializer(0))
    with tf.variable_scope('deconv2'):
        wdd2 = tf.get_variable('w', shape=(2, 2, 1, m_c1), initializer=tf.contrib.layers.xavier_initializer())
        bdd2 = tf.get_variable('b', shape=(1,), initializer=tf.constant_initializer(0))

u1 = tf.nn.sigmoid(tf.matmul(encode, wdf1) + bdf1)
u2 = tf.nn.sigmoid(tf.matmul(u1, wdf2) + bdf2)
u3 = tf.reshape(u2, (-1, 7, 7, m_c2))
u4 = tf.nn.conv2d_transpose(u3, wdd1, output_shape=(batch_size, 14, 14, m_c1), strides=(1, 2, 2, 1), padding='VALID')
u5 = tf.nn.sigmoid(u4 + bdd1)
u6 = tf.nn.conv2d_transpose(u5, wdd2, output_shape=(batch_size, 28, 28, 1), strides=(1, 2, 2, 1), padding='VALID')
u7 = tf.nn.sigmoid(u6 + bdd2)
decode = tf.reshape(u7, (-1, 784))

loss = tf.reduce_mean(tf.square(x - decode))
opt = tf.train.AdamOptimizer(0.0001).minimize(loss)

try:
    tf.global_variables_initializer().run()
except AttributeError:
    tf.initialize_all_variables().run() # Deprecated after r0.11

print('Starting training...')
bs = 1000 # Batch size
for i in range(501): # Reasonable results around this epoch 
    # Apply permutation of data at each epoch, should improve convergence time
    train_data = np.random.permutation(mnist.data)
    if i % 100 == 0:
        print('Iteration:', i, 'Loss:', loss.eval(feed_dict={x: train_data}))
    for j in range(0, train_data.shape[0], bs):
        batch = train_data[j*bs:(j+1)*bs]
        sess.run(opt, feed_dict={x: batch})
        # TODO introduce noise
print('Training done')
¿Fue útil?

Solución

Bueno, el problema estaba relacionado principalmente con el tamaño del núcleo. El uso de la convolución 2x2 con paso de (2,2) se volvió como una mala idea. El uso de tamaños 5x5 y 3x3 arrojaron resultados decentes.

Licenciado bajo: CC-BY-SA con atribución
scroll top