Regresión de caja delimitadora: una aventura en el fracaso

Jan 20 2021

He resuelto muchos problemas con las redes neuronales, pero rara vez trabajo con imágenes. Tengo alrededor de 18 horas para crear una red de regresión de cuadro delimitador y sigue fallando por completo. Con algunas funciones de pérdida, reclamará un 80% de precisión durante el entrenamiento y la validación (con una pérdida realmente masiva en ambos), pero al probar las predicciones se revela un cuadro delimitador que solo mueve uno o dos píxeles en cualquier dirección dada y parece ignorar totalmente los datos. Ahora he implementado una forma de pérdida de IoU, pero descubro que IoU está anclado en cero ... lo que obviamente es cierto según los resultados después del entrenamiento. :). Me gustaría que alguien revisara esto y me diera algunos consejos sobre cómo proceder a continuación.

Lo que tengo

Estoy generando 40000 ejemplos de imágenes de 200x100x3 con una sola letra colocada al azar en cada una. Simultáneamente, estoy generando los cuadros delimitadores de la verdad del terreno para cada muestra de entrenamiento. He validado a fondo que todo esto funciona y que los datos son correctos.

Lo que le hago

Luego estoy transformando las imágenes de 200x100x3 a escala de grises para producir una imagen de 200x100x1. Luego, las imágenes se normalizan y los cuadros delimitadores se escalan para que estén entre 0 y 1. En forma simplificada, esto sucede:

x_train_normalized = (x_data - 127.5) / 127.5
y_train_scaled = boxes[:TRAIN]/[WIDTH,HEIGHT,WIDTH,HEIGHT]

He revisado estos datos con cuidado, incluso reconstituyendo imágenes y recuadros delimitadores a partir de ellos. Definitivamente esto está funcionando.

Capacitación

Para entrenar, después de intentarlo msey muchos otros, todos los cuales fallan igualmente mal, he implementado una función de pérdida de pagaré personalizada simple. Realmente regresa -ln(IoU). Hice este cambio en base a un documento ya que la pérdida (¿curiosamente?) Se fijó en cero en varias épocas.

(Función de pérdida:)

import tensorflow.keras.backend as kb
def iou_loss(y_actual,y_pred):
    b1 = y_actual
    b2 = y_pred
#    tf.print(b1)
#    tf.print(b2)
    zero = tf.convert_to_tensor(0.0, b1.dtype)
    b1_ymin, b1_xmin, b1_ymax, b1_xmax = tf.unstack(b1, 4, axis=-1)
    b2_ymin, b2_xmin, b2_ymax, b2_xmax = tf.unstack(b2, 4, axis=-1)
    b1_width = tf.maximum(zero, b1_xmax - b1_xmin)
    b1_height = tf.maximum(zero, b1_ymax - b1_ymin)
    b2_width = tf.maximum(zero, b2_xmax - b2_xmin)
    b2_height = tf.maximum(zero, b2_ymax - b2_ymin)
    b1_area = b1_width * b1_height
    b2_area = b2_width * b2_height

    intersect_ymin = tf.maximum(b1_ymin, b2_ymin)
    intersect_xmin = tf.maximum(b1_xmin, b2_xmin)
    intersect_ymax = tf.minimum(b1_ymax, b2_ymax)
    intersect_xmax = tf.minimum(b1_xmax, b2_xmax)
    intersect_width = tf.maximum(zero, intersect_xmax - intersect_xmin)
    intersect_height = tf.maximum(zero, intersect_ymax - intersect_ymin)
    intersect_area = intersect_width * intersect_height

    union_area = b1_area + b2_area - intersect_area
    iou = -1 * tf.math.log(tf.math.divide_no_nan(intersect_area, union_area))
    return iou

La red

Esto ha pasado por muchas, muchas iteraciones. Como dije, he resuelto muchos otros problemas con las NN ... Este es el primero que me atasca por completo. En este punto, la red se reduce drásticamente, pero sigue sin capacitarse en absoluto:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, optimizers

tf.keras.backend.set_floatx('float32') # Use Float32s for everything

input_shape = x_train_normalized.shape[-3:]
model = keras.Sequential()
model.add(layers.Conv2D(4, 16, activation = tf.keras.layers.LeakyReLU(alpha=0.2), input_shape=input_shape))
model.add(layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(layers.Dropout(0.2))
model.add(layers.Flatten())
model.add(layers.Dense(200, activation = tf.keras.layers.LeakyReLU(alpha=0.2)))
model.add(layers.Dense(64, activation=tf.keras.layers.LeakyReLU(alpha=0.2)))
model.add(layers.Dense(4, activation="sigmoid"))

model.compile(loss = iou_loss, optimizer = "adadelta", metrics=['accuracy'])
history = model.fit(x_train_normalized, y_train_scaled, epochs=8, batch_size=100, validation_split=0.4)

¡Todos los consejos son bienvenidos! Mientras tanto, estoy implementando una función de pérdida de punto central para ver si eso ayuda en algo.

Respuestas

DavidHoelzer Jan 21 2021 at 07:35

Al final, este problema resultó ser en gran parte una cuestión de caída del gradiente en mínimos locales.

Para aquellos que leen para la posteridad, uno de los problemas en ML que es difícil de solucionar es que no podemos elegir intuitivamente valores iniciales razonables para los pesos, sesgos y núcleos (en la CNN). Como resultado, normalmente les permitimos que se inicialicen de forma aleatoria. Esto puede presentar algunos desafíos.

Uno de los mayores desafíos es que cuando comienzas desde un punto de partida aleatorio, es difícil decirle a alguien cómo replicar completamente tus experimentos. Esto no es muy importante al final, ya que puede proporcionarles los parámetros guardados de su modelo entrenado. Sin embargo, esto también puede llevar a redes que parecen ser "malas" y que, de hecho, están perfectamente bien.

En este caso, había pasado gran parte del tiempo inicializando CNN con un inicializador uniforme (no presente en el código anterior). A veces usaré una semilla aleatoria o alguna otra función para generar valores iniciales para poder mejorar mejor las redes a través de herramientas de búsqueda genética.

Parece que los inicializadores uniformes combinados con las diversas iteraciones de la red y estos datos en particular conducen a un rendimiento de entrenamiento absolutamente abismal y no convergencia.

Cuando ejecuté la red como se indicó anteriormente con inicializaciones aleatorias y uno o dos ajustes, convergió bien. Algunas iteraciones de entrenamiento fijarán uno de los lados del cuadro delimitador en el borde, algunos nunca convergerán, pero he logrado entrenar con éxito varios que están en el rango de precisión del 96-98% para los cuadros delimitadores en mi conjunto de prueba de 20000, ¡así que todo está bien!