Regressão da caixa delimitadora - uma aventura em fracasso
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 mse
e 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
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!