Regresja w polu granicznym - przygoda w niepowodzeniu
Rozwiązałem wiele problemów z sieciami neuronowymi, ale rzadko pracuję z obrazami. Mam około 18 godzin na stworzenie sieci regresji z polami granicznymi i nadal całkowicie kończy się ona niepowodzeniem. Z niektórymi funkcjami utraty będzie on zapewniał 80% dokładność podczas uczenia i walidacji (z naprawdę ogromną stratą na obu), ale testowanie przewidywań ujawnia obwiednię, która przesuwa tylko jeden lub dwa piksele w dowolnym kierunku i wydaje się całkowicie ignorować dane. Zaimplementowałem teraz formę utraty IoU, ale stwierdziłem, że IoU jest przypięte do zera ... co jest oczywiście prawdą na podstawie wyników po treningu. :). Chciałbym, żeby ktoś to przejrzał i udzielił mi kilku porad, jak dalej postępować.
Co ja mam
Generuję 40000 przykładów obrazów 200x100x3 z losowo umieszczoną pojedynczą literą. Jednocześnie generuję pola ograniczające prawdę o podłożu dla każdej próbki treningowej. Dokładnie potwierdziłem, że wszystko działa, a dane są poprawne.
Co z tym robię
Następnie przekształcam obrazy 200x100x3 w dół do skali szarości, aby uzyskać obraz 200x100x1. Obrazy są następnie normalizowane, a ramki ograniczające są skalowane tak, aby mieściły się w zakresie od 0 do 1. W uproszczonej formie dzieje się tak:
x_train_normalized = (x_data - 127.5) / 127.5
y_train_scaled = boxes[:TRAIN]/[WIDTH,HEIGHT,WIDTH,HEIGHT]
Dokładnie przejrzałem te dane, nawet odtwarzając z nich obrazy i ramki ograniczające. To zdecydowanie działa.
Trening
Aby trenować, po wypróbowaniu mse
i wielu innych, z których wszystkie zawodzą równie źle, zaimplementowałem prostą niestandardową funkcję utraty IOU. W rzeczywistości powraca -ln(IoU)
. Dokonałem tej zmiany na podstawie artykułu, ponieważ strata była (co dziwne?) Przypięta do zera w wielu epokach.
(Funkcja straty :)
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
Sieć
To było przez wiele, wiele iteracji. Jak powiedziałem, rozwiązałem wiele innych problemów z NN-ami ... To jest pierwszy, który całkowicie mnie utknął. W tym momencie sieć jest dramatycznie rozebrana, ale nadal w ogóle nie trenuje:
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)
Wszystkie wskazówki są mile widziane! W międzyczasie wdrażam funkcję utraty punktu środkowego, aby zobaczyć, czy to w ogóle pomaga.
Odpowiedzi
Ostatecznie ten problem okazał się w dużej mierze spowodowany spadkiem gradientu do lokalnych minimów.
Dla tych, którzy czytają dla potomności, jednym z problemów w ML, które są trudne do obejścia, jest to, że nie możemy intuicyjnie wybrać rozsądnych wartości początkowych dla wag, odchyleń i jąder (w CNN). W rezultacie zazwyczaj pozwalamy im na losową inicjalizację. Może to stanowić pewne wyzwanie.
Jednym z największych wyzwań jest to, że kiedy zaczynasz od przypadkowego punktu wyjścia, trudno jest komuś powiedzieć, jak całkowicie powtórzyć swoje eksperymenty. Ostatecznie nie jest to szczególnie ważne, ponieważ możesz zapewnić im zapisane parametry z wytrenowanego modelu. Jednak może to również prowadzić do sieci, które wydają się być „złe”, które w rzeczywistości są całkowicie w porządku.
W tym przypadku spędziłem dużo czasu na inicjalizacji CNN za pomocą jednolitego inicjatora (nieobecnego w powyższym kodzie). Czasami używam losowego ziarna lub innej funkcji do generowania wartości początkowych, aby móc lepiej ulepszyć sieci za pomocą narzędzi wyszukiwania genetycznego.
Wydaje się, że jednolite inicjatory w połączeniu z różnymi iteracjami sieci i te konkretne dane prowadzą do absolutnie fatalnej wydajności treningu i braku konwergencji.
Kiedy uruchomiłem sieć, jak powyżej, z przypadkowymi inicjalizacjami i jedną lub dwiema poprawkami, zbiegło się dobrze. Niektóre iteracje treningowe przypinają jeden z boków obwiedni do krawędzi, inne nigdy się nie zbiegną, ale udało mi się z powodzeniem wytrenować kilka z dokładnością w zakresie 96-98% dla obwiedni w moim zestawie testowym 20000, więc wszystko jest w porządku!