Regresja w polu granicznym - przygoda w niepowodzeniu

Jan 19 2021

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 msei 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

DavidHoelzer Jan 21 2021 at 07:35

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!