Regressão da caixa delimitadora - uma aventura em fracasso

Jan 19 2021

Resolvi muitos problemas com redes neurais, mas raramente trabalho com imagens. Tenho cerca de 18 horas para criar uma rede de regressão de caixa delimitadora e ela continua a falhar totalmente. Com algumas funções de perda, ele reivindicará 80% de precisão durante o treinamento e a validação (com uma perda realmente enorme em ambos), mas o teste das previsões revela uma caixa delimitadora que se move apenas um ou dois pixels em qualquer direção e parece ignorar totalmente os dados. Agora implementei uma forma de perda de IoU, mas descobri que IoU está fixado em zero ... o que é obviamente verdadeiro com base nas saídas após o treinamento. :). Gostaria que alguém examinasse isso e me desse alguns conselhos sobre como proceder a seguir.

O que eu tenho

Estou gerando 40.000 exemplos de imagens 200x100x3 com uma única letra colocada aleatoriamente em cada uma. Simultaneamente, estou gerando as caixas delimitadoras da verdade fundamental para cada amostra de treinamento. Eu validei completamente que tudo isso funciona e os dados estão corretos.

O que eu faço para isso

Em seguida, estou transformando as imagens 200x100x3 em tons de cinza para produzir uma imagem 200x100x1. As imagens são então normalizadas e as caixas delimitadoras são dimensionadas para ficarem entre 0 e 1. De forma simplificada, isso acontece:

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

Examinei esses dados com cuidado, até mesmo reconstituindo imagens e delimitando caixas a partir deles. Isso definitivamente está funcionando.

Treinamento

Para treinar, depois de tentar msee muitos outros, todos os quais falham igualmente mal, implementei uma função de perda de IOU personalizada simples. Ele realmente retorna -ln(IoU). Fiz essa alteração com base em um artigo, já que a perda foi (estranhamente?) Fixada em zero em várias épocas.

(Função de perda :)

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

A rede

Isso passou por muitas, muitas iterações. Como eu disse, já resolvi muitos outros problemas com NNs ... Esse é o primeiro que me deixa completamente paralisado. Neste ponto, a rede é drasticamente reduzida, mas continua a falhar no treinamento:

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)

Todas as dicas são bem-vindas! Nesse ínterim, estou implementando uma função de perda do ponto central para ver se isso ajuda em alguma coisa.

Respostas

DavidHoelzer Jan 21 2021 at 07:35

No final, esse problema acabou sendo em grande parte uma questão de a descida do gradiente cair em mínimos locais.

Para aqueles que estão lendo para a posteridade, uma das questões no ML que é difícil de contornar é que não podemos escolher intuitivamente valores iniciais razoáveis ​​para os pesos, vieses e kernels (na CNN). Como resultado, normalmente permitimos que inicializem aleatoriamente. Isso pode apresentar alguns desafios.

Um dos maiores desafios é que, quando você começa de um ponto de partida aleatório, é difícil dizer a alguém como replicar completamente seus experimentos. Isso não é muito importante no final, pois você pode fornecer a eles os parâmetros salvos de seu modelo treinado. No entanto, isso também pode levar a redes que parecem "ruins" e que, na verdade, são perfeitamente boas.

Nesse caso, gastei muito tempo inicializando a CNN com um inicializador uniforme (não presente no código acima). Às vezes, vou usar uma semente aleatória ou alguma outra função para gerar valores iniciais para que eu possa melhorar as redes por meio de ferramentas de pesquisa genética.

Parece que os inicializadores uniformes combinados com as várias iterações de rede e esses dados em particular levam a um desempenho de treinamento absolutamente abismal e não-convergência.

Quando executei a rede como acima com inicializações aleatórias e um ou dois ajustes, ela convergiu bem. Algumas iterações de treinamento fixarão um dos lados da caixa delimitadora na borda, algumas nunca convergirão, mas consegui treinar com sucesso várias que estão na faixa de precisão de 96-98% para as caixas delimitadoras em meu conjunto de teste de 20.000, então está tudo bem!