Warstwa normalizacji wsadowej Keras jest uszkodzona w PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

Warstwa normalizacji wsadowej w Kerasie jest uszkodzona

AKTUALIZACJA: Niestety moje żądanie ściągnięcia do Keras, które zmieniło zachowanie warstwy normalizacji wsadowej, nie zostało zaakceptowane. Możesz przeczytać szczegóły tutaj. Dla tych z Was, którzy są na tyle odważni, by bawić się niestandardowymi implementacjami, kod może znaleźć w moja gałąź. Mogę go utrzymywać i łączyć z najnowszą stabilną wersją Keras (2.1.6, 2.2.2 i 2.2.4) tak długo, jak go używam, ale bez obietnic.

Większość osób, które pracują w technologii Deep Learning, korzystała lub słyszała o Keras. Dla tych z Was, którzy tego nie zrobili, jest to świetna biblioteka, która wyodrębnia podstawowe platformy Deep Learning, takie jak TensorFlow, Theano i CNTK, i zapewnia API wysokiego poziomu do szkolenia SSN. Jest łatwy w użyciu, umożliwia szybkie prototypowanie i ma przyjazną, aktywną społeczność. Używam go intensywnie i od jakiegoś czasu regularnie współtworzę projekt i zdecydowanie polecam każdemu, kto chce pracować nad Deep Learning.

Mimo że Keras ułatwił mi życie, wiele razy byłem ukąszony dziwnym zachowaniem warstwy normalizacji wsadowej. Jego domyślne zachowanie zmieniło się z biegiem czasu, niemniej jednak nadal powoduje problemy dla wielu użytkowników, w wyniku czego istnieje kilka powiązanych otwarte kwestie na Github. W tym wpisie na blogu spróbuję wyjaśnić, dlaczego warstwa BatchNormalization Keras nie działa dobrze z Transfer Learning, podam kod, który rozwiązuje problem i podam przykłady z wynikami łata.

W poniższych podrozdziałach przedstawiam wprowadzenie na temat tego, jak Transfer Learning jest używany w Deep Learning, czym jest warstwa normalizacji wsadowej, jak działa learnining_phase i jak Keras zmieniał zachowanie BN w czasie. Jeśli już je znasz, możesz bezpiecznie przejść bezpośrednio do sekcji 2.

1.1 Korzystanie z uczenia transferowego ma kluczowe znaczenie dla uczenia głębokiego

Jednym z powodów, dla których Deep Learning był krytykowany w przeszłości, jest to, że wymaga zbyt dużej ilości danych. Nie zawsze jest to prawda; istnieje kilka technik radzenia sobie z tym ograniczeniem, z których jedną jest transfer uczenia się.

Załóżmy, że pracujesz nad aplikacją Computer Vision i chcesz zbudować klasyfikator odróżniający koty od psów. W rzeczywistości nie potrzebujesz milionów zdjęć kotów / psów, aby wytrenować model. Zamiast tego możesz użyć wstępnie wytrenowanego klasyfikatora i dostroić najlepsze zwoje przy użyciu mniejszej ilości danych. Pomysł polega na tym, że ponieważ wstępnie wytrenowany model został dopasowany do obrazów, dolne zwoje mogą rozpoznawać cechy, takie jak linie, krawędzie i inne przydatne wzorce, co oznacza, że ​​możesz użyć jego wag jako dobrych wartości inicjalizacyjnych lub częściowo ponownie przeszkolić sieć za pomocą danych .
Warstwa normalizacji wsadowej Keras jest uszkodzona w PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.
Keras zawiera kilka wstępnie wyszkolonych modeli i łatwych w użyciu przykładów, jak dostroić modele. Możesz przeczytać więcej na dokumentacja.

1.2 Co to jest warstwa normalizacji wsadowej?

Warstwa Normalizacja wsadowa została wprowadzona w 2014 roku przez Ioffe i Szegedy. Rozwiązuje problem znikającego gradientu poprzez standaryzację danych wyjściowych poprzedniej warstwy, przyspiesza szkolenie poprzez zmniejszenie liczby wymaganych iteracji i umożliwia uczenie głębszych sieci neuronowych. Dokładne wyjaśnienie, jak to działa, wykracza poza zakres tego postu, ale gorąco zachęcam do przeczytania oryginalny papier. Zbyt uproszczonym wyjaśnieniem jest to, że przeskalowuje dane wejściowe, odejmując ich średnią i dzieląc przez odchylenie standardowe; w razie potrzeby może również nauczyć się cofać transformację.
Warstwa normalizacji wsadowej Keras jest uszkodzona w PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

1.3 Jaka jest faza learning_phase w Keras?

Niektóre warstwy działają inaczej w trybie uczenia i wnioskowania. Najbardziej godnymi uwagi przykładami są warstwy Normalizacja wsadowa i Warstwy rezygnacji. W przypadku BN podczas treningu używamy średniej i wariancji mini-partii do przeskalowania wejścia. Z drugiej strony podczas wnioskowania wykorzystujemy średnią ruchomą i wariancję oszacowaną podczas treningu.

Keras wie, w którym trybie ma działać, ponieważ ma wbudowany mechanizm o nazwie faza_nauki. Faza uczenia się kontroluje, czy sieć jest w pociągu, czy w trybie testowym. Jeśli użytkownik nie ustawi tego ręcznie, podczas fit () sieć działa z learning_phase = 1 (tryb pociągu). Podczas tworzenia prognoz (na przykład gdy wywołujemy metody predykcji () i oceniania () lub na etapie walidacji funkcji fit ()) sieć działa z learning_phase = 0 (tryb testowy). Mimo że nie jest to zalecane, użytkownik może również zmienić statyczną fazę uczenia się na określoną wartość, ale musi to nastąpić przed dodaniem jakiegokolwiek modelu lub tensora do wykresu. Jeśli faza uczenia się jest ustawiona statycznie, Keras zostanie zablokowany w dowolnym trybie wybranym przez użytkownika.

1.4 W jaki sposób Keras wdrożył normalizację wsadową w czasie?

Keras kilkakrotnie zmieniał zachowanie Normalizacji wsadowej, ale ostatnia znacząca aktualizacja miała miejsce w Keras 2.1.3. Przed wersją 2.1.3, gdy warstwa BN została zamrożona (trainable = False), aktualizowała swoje statystyki wsadowe, co powodowało epickie bóle głowy u użytkowników.

To nie była tylko dziwna polityka, w rzeczywistości była zła. Wyobraź sobie, że między zwojami istnieje warstwa BN; jeśli warstwa jest zamrożona, żadne zmiany nie powinny się w niej pojawić. Jeśli zaktualizujemy częściowo jego wagi, a kolejne warstwy również zostaną zamrożone, nigdy nie będą miały szansy na dostosowanie się do aktualizacji statystyk mini-partii, co prowadzi do większego błędu. Na szczęście począwszy od wersji 2.1.3, gdy warstwa BN jest zamrożona, nie aktualizuje już swoich statystyk. Ale czy to wystarczy? Nie, jeśli używasz Transfer Learning.

Poniżej opisuję dokładnie na czym polega problem i szkicuję techniczną implementację jego rozwiązania. Podaję również kilka przykładów, aby pokazać wpływ na dokładność modelu przed i po łata jest stosowany.

2.1 Techniczny opis problemu

Problem z obecnym wdrożeniem Keras polega na tym, że gdy warstwa BN jest zamrożona, podczas uczenia nadal korzysta ze statystyk mini-wsadowych. Uważam, że lepszym podejściem, gdy BN jest zamrożone, jest użycie średniej ruchomej i wariancji, których nauczył się podczas treningu. Czemu? Z tych samych powodów, dla których statystyki mini-wsadu nie powinny być aktualizowane po zamrożeniu warstwy: może to prowadzić do słabych wyników, ponieważ kolejne warstwy nie są odpowiednio trenowane.

Załóżmy, że budujesz model wizji komputerowej, ale nie masz wystarczających danych, więc decydujesz się użyć jednej z wcześniej wyszkolonych stacji CNN Keras i dostroić ją. Niestety, w ten sposób nie masz gwarancji, że średnia i wariancja nowego zbioru danych w warstwach BN będą podobne do tych z oryginalnego zbioru danych. Pamiętaj, że w tej chwili podczas treningu Twoja sieć będzie zawsze używać statystyk mini-wsadowych, czy warstwa BN jest zamrożona, czy nie; również podczas wnioskowania użyjesz wcześniej poznanych statystyk zamrożonych warstw BN. W rezultacie, jeśli dostroisz górne warstwy, ich wagi zostaną dostosowane do średniej / wariancji nowa zbiór danych. Niemniej jednak podczas wnioskowania otrzymają dane, które są skalowane różnie ponieważ średnia / wariancja oryginalny zostanie użyty zbiór danych.
Warstwa normalizacji wsadowej Keras jest uszkodzona w PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.
Powyżej przedstawiam uproszczoną (i nierealistyczną) architekturę do celów demonstracyjnych. Załóżmy, że dostrajamy model od splotu k + 1 do góry sieci (prawa strona) i zatrzymujemy dół (lewa strona). Podczas treningu wszystkie warstwy BN od 1 do k wykorzystają średnią / wariancję danych treningowych. Będzie to miało negatywny wpływ na zamrożone ReLU, jeśli średnia i wariancja na każdym BN nie są zbliżone do tych, których nauczyli się podczas przedtreningowego. Spowoduje to również, że reszta sieci (od CONV k + 1 i później) będzie trenowana za pomocą danych wejściowych, które mają różne skale w porównaniu z tym, co otrzyma podczas wnioskowania. Podczas treningu Twoja sieć może dostosować się do tych zmian, niemniej jednak w momencie przełączenia na tryb przewidywania Keras użyje różnych statystyk standaryzacyjnych, co przyspieszy dystrybucję danych wejściowych kolejnych warstw, prowadząc do słabych wyników.

2.2 Jak możesz sprawdzić, czy jesteś dotknięty?

Jednym ze sposobów wykrycia tego jest statyczne ustawienie fazy uczenia Keras na 1 (tryb pociągu) i 0 (tryb testowy) i ocena modelu w każdym przypadku. Jeśli istnieje znacząca różnica w dokładności tego samego zbioru danych, występuje problem. Warto zwrócić uwagę, że ze względu na sposób implementacji mechanizmu learning_phase w Keras, zazwyczaj nie zaleca się majstrowania przy nim. Zmiany w fazie learning_phase nie będą miały wpływu na modele, które są już skompilowane i używane; jak widać na przykładach w następnych podrozdziałach, najlepszym sposobem na to jest rozpoczęcie od czystej sesji i zmiana fazy uczenia się, zanim jakikolwiek tensor zostanie zdefiniowany na wykresie.

Innym sposobem wykrycia problemu podczas pracy z klasyfikatorami binarnymi jest sprawdzenie dokładności i wartości AUC. Jeśli dokładność jest bliska 50%, ale AUC jest bliskie 1 (a także obserwujesz różnice między trybem pociągu / testu w tym samym zestawie danych), może to oznaczać, że prawdopodobieństwa są poza skalą ze względu na statystyki BN. Podobnie w przypadku regresji można użyć korelacji MSE i Spearmana, aby ją wykryć.

2.3 Jak możemy to naprawić?

Uważam, że problem można rozwiązać, jeśli zamrożone warstwy BN są w rzeczywistości takie: trwale zablokowane w trybie testowym. Pod względem implementacji flaga możliwa do nauczenia musi być częścią grafu obliczeniowego, a zachowanie BN musi zależeć nie tylko od fazy uczenia się, ale także od wartości dającej się trenować właściwości. Szczegóły mojej realizacji znajdziesz na Github.

Po zastosowaniu powyższej poprawki, gdy warstwa BN jest zamrożona, nie będzie już używać statystyk mini-wsadowych, ale zamiast tego użyje statystyk wyuczonych podczas treningu. W rezultacie nie będzie rozbieżności między trybami treningowymi i testowymi, co prowadzi do zwiększenia dokładności. Oczywiście, gdy warstwa BN nie zostanie zamrożona, będzie nadal używać statystyk mini-wsadu podczas uczenia.

2.4 Ocena wpływu poprawki

Mimo że niedawno napisałem powyższą implementację, idea, która za nią stoi, została mocno przetestowana na rzeczywistych problemach przy użyciu różnych obejść, które mają ten sam efekt. Na przykład rozbieżności między trybami uczenia i testowania można uniknąć, dzieląc sieć na dwie części (zamrożoną i odmrożoną) i przeprowadzając szkolenie w pamięci podręcznej (jednokrotne przekazywanie danych przez zamrożony model, a następnie wykorzystanie ich do uczenia niezablokowanej sieci). Niemniej jednak, ponieważ „zaufaj mi, że już to zrobiłem” zazwyczaj nie ma żadnego znaczenia, poniżej przedstawiam kilka przykładów, które pokazują efekty nowego wdrożenia w praktyce.

Oto kilka ważnych punktów dotyczących eksperymentu:

  1. Użyję niewielkiej ilości danych, aby celowo dopasować model do zbytniego dopasowania i wytrenować i zweryfikować model na tym samym zestawie danych. W ten sposób oczekuję niemal idealnej dokładności i identycznej wydajności w zestawie danych pociągu / walidacji.
  2. Jeśli podczas walidacji uzyskam znacznie niższą dokładność na tym samym zbiorze danych, będę miał wyraźne wskazanie, że obecna polityka BN wpływa negatywnie na wydajność modelu podczas wnioskowania.
  3. Wszelkie przetwarzanie wstępne będzie miało miejsce poza generatorami. Ma to na celu obejście błędu, który został wprowadzony w wersji 2.1.5 (obecnie naprawiony w nadchodzącej wersji 2.1.6 i najnowszej wersji głównej).
  4. Zmusimy Keras do korzystania z różnych faz uczenia się podczas oceny. Jeśli zauważymy różnice w raportowanej dokładności, będziemy wiedzieć, że mają na nas wpływ obecne zasady BN.

Kod eksperymentu przedstawiono poniżej:

import numpy as np
from keras.datasets import cifar10
from scipy.misc import imresize

from keras.preprocessing.image import ImageDataGenerator
from keras.applications.resnet50 import ResNet50, preprocess_input
from keras.models import Model, load_model
from keras.layers import Dense, Flatten
from keras import backend as K


seed = 42
epochs = 10
records_per_class = 100

# We take only 2 classes from CIFAR10 and a very small sample to intentionally overfit the model.
# We will also use the same data for train/test and expect that Keras will give the same accuracy.
(x, y), _ = cifar10.load_data()

def filter_resize(category):
   # We do the preprocessing here instead in the Generator to get around a bug on Keras 2.1.5.
   return [preprocess_input(imresize(img, (224,224)).astype('float')) for img in x[y.flatten()==category][:records_per_class]]

x = np.stack(filter_resize(3)+filter_resize(5))
records_per_class = x.shape[0] // 2
y = np.array([[1,0]]*records_per_class + [[0,1]]*records_per_class)


# We will use a pre-trained model and finetune the top layers.
np.random.seed(seed)
base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
l = Flatten()(base_model.output)
predictions = Dense(2, activation='softmax')(l)
model = Model(inputs=base_model.input, outputs=predictions)

for layer in model.layers[:140]:
   layer.trainable = False

for layer in model.layers[140:]:
   layer.trainable = True

model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit_generator(ImageDataGenerator().flow(x, y, seed=42), epochs=epochs, validation_data=ImageDataGenerator().flow(x, y, seed=42))

# Store the model on disk
model.save('tmp.h5')


# In every test we will clear the session and reload the model to force Learning_Phase values to change.
print('DYNAMIC LEARNING_PHASE')
K.clear_session()
model = load_model('tmp.h5')
# This accuracy should match exactly the one of the validation set on the last iteration.
print(model.evaluate_generator(ImageDataGenerator().flow(x, y, seed=42)))


print('STATIC LEARNING_PHASE = 0')
K.clear_session()
K.set_learning_phase(0)
model = load_model('tmp.h5')
# Again the accuracy should match the above.
print(model.evaluate_generator(ImageDataGenerator().flow(x, y, seed=42)))


print('STATIC LEARNING_PHASE = 1')
K.clear_session()
K.set_learning_phase(1)
model = load_model('tmp.h5')
# The accuracy will be close to the one of the training set on the last iteration.
print(model.evaluate_generator(ImageDataGenerator().flow(x, y, seed=42)))

Sprawdźmy wyniki w Keras v2.1.5:

Epoch 1/10
1/7 [===>..........................] - ETA: 25s - loss: 0.8751 - acc: 0.5312
2/7 [=======>......................] - ETA: 11s - loss: 0.8594 - acc: 0.4531
3/7 [===========>..................] - ETA: 7s - loss: 0.8398 - acc: 0.4688 
4/7 [================>.............] - ETA: 4s - loss: 0.8467 - acc: 0.4844
5/7 [====================>.........] - ETA: 2s - loss: 0.7904 - acc: 0.5437
6/7 [========================>.....] - ETA: 1s - loss: 0.7593 - acc: 0.5625
7/7 [==============================] - 12s 2s/step - loss: 0.7536 - acc: 0.5744 - val_loss: 0.6526 - val_acc: 0.6650

Epoch 2/10
1/7 [===>..........................] - ETA: 4s - loss: 0.3881 - acc: 0.8125
2/7 [=======>......................] - ETA: 3s - loss: 0.3945 - acc: 0.7812
3/7 [===========>..................] - ETA: 2s - loss: 0.3956 - acc: 0.8229
4/7 [================>.............] - ETA: 1s - loss: 0.4223 - acc: 0.8047
5/7 [====================>.........] - ETA: 1s - loss: 0.4483 - acc: 0.7812
6/7 [========================>.....] - ETA: 0s - loss: 0.4325 - acc: 0.7917
7/7 [==============================] - 8s 1s/step - loss: 0.4095 - acc: 0.8089 - val_loss: 0.4722 - val_acc: 0.7700

Epoch 3/10
1/7 [===>..........................] - ETA: 4s - loss: 0.2246 - acc: 0.9375
2/7 [=======>......................] - ETA: 3s - loss: 0.2167 - acc: 0.9375
3/7 [===========>..................] - ETA: 2s - loss: 0.2260 - acc: 0.9479
4/7 [================>.............] - ETA: 2s - loss: 0.2179 - acc: 0.9375
5/7 [====================>.........] - ETA: 1s - loss: 0.2356 - acc: 0.9313
6/7 [========================>.....] - ETA: 0s - loss: 0.2392 - acc: 0.9427
7/7 [==============================] - 8s 1s/step - loss: 0.2288 - acc: 0.9456 - val_loss: 0.4282 - val_acc: 0.7800

Epoch 4/10
1/7 [===>..........................] - ETA: 4s - loss: 0.2183 - acc: 0.9688
2/7 [=======>......................] - ETA: 3s - loss: 0.1899 - acc: 0.9844
3/7 [===========>..................] - ETA: 2s - loss: 0.1887 - acc: 0.9792
4/7 [================>.............] - ETA: 1s - loss: 0.1995 - acc: 0.9531
5/7 [====================>.........] - ETA: 1s - loss: 0.1932 - acc: 0.9625
6/7 [========================>.....] - ETA: 0s - loss: 0.1819 - acc: 0.9688
7/7 [==============================] - 8s 1s/step - loss: 0.1743 - acc: 0.9747 - val_loss: 0.3778 - val_acc: 0.8400

Epoch 5/10
1/7 [===>..........................] - ETA: 3s - loss: 0.0973 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0828 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0851 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0897 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0928 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0936 - acc: 1.0000
7/7 [==============================] - 8s 1s/step - loss: 0.1337 - acc: 0.9838 - val_loss: 0.3916 - val_acc: 0.8100

Epoch 6/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0747 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0852 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0812 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0831 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0779 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0766 - acc: 1.0000
7/7 [==============================] - 8s 1s/step - loss: 0.0813 - acc: 1.0000 - val_loss: 0.3637 - val_acc: 0.8550

Epoch 7/10
1/7 [===>..........................] - ETA: 1s - loss: 0.2478 - acc: 0.8750
2/7 [=======>......................] - ETA: 2s - loss: 0.1966 - acc: 0.9375
3/7 [===========>..................] - ETA: 2s - loss: 0.1528 - acc: 0.9583
4/7 [================>.............] - ETA: 1s - loss: 0.1300 - acc: 0.9688
5/7 [====================>.........] - ETA: 1s - loss: 0.1193 - acc: 0.9750
6/7 [========================>.....] - ETA: 0s - loss: 0.1196 - acc: 0.9792
7/7 [==============================] - 8s 1s/step - loss: 0.1084 - acc: 0.9838 - val_loss: 0.3546 - val_acc: 0.8600

Epoch 8/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0539 - acc: 1.0000
2/7 [=======>......................] - ETA: 2s - loss: 0.0900 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0815 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0740 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0700 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0701 - acc: 1.0000
7/7 [==============================] - 8s 1s/step - loss: 0.0695 - acc: 1.0000 - val_loss: 0.3269 - val_acc: 0.8600

Epoch 9/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0306 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0377 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0898 - acc: 0.9583
4/7 [================>.............] - ETA: 1s - loss: 0.0773 - acc: 0.9688
5/7 [====================>.........] - ETA: 1s - loss: 0.0742 - acc: 0.9750
6/7 [========================>.....] - ETA: 0s - loss: 0.0708 - acc: 0.9792
7/7 [==============================] - 8s 1s/step - loss: 0.0659 - acc: 0.9838 - val_loss: 0.3604 - val_acc: 0.8600

Epoch 10/10
1/7 [===>..........................] - ETA: 3s - loss: 0.0354 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0381 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0354 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0828 - acc: 0.9688
5/7 [====================>.........] - ETA: 1s - loss: 0.0791 - acc: 0.9750
6/7 [========================>.....] - ETA: 0s - loss: 0.0794 - acc: 0.9792
7/7 [==============================] - 8s 1s/step - loss: 0.0704 - acc: 0.9838 - val_loss: 0.3615 - val_acc: 0.8600

DYNAMIC LEARNING_PHASE
[0.3614931714534759, 0.86]

STATIC LEARNING_PHASE = 0
[0.3614931714534759, 0.86]

STATIC LEARNING_PHASE = 1
[0.025861846953630446, 1.0]

Jak widać powyżej, podczas treningu model bardzo dobrze uczy się danych i osiąga na zestawie treningowym niemal idealną dokładność. Nadal pod koniec każdej iteracji, oceniając model na tym samym zestawie danych, otrzymujemy znaczące różnice w stratach i dokładności. Zauważ, że nie powinniśmy tego otrzymywać; celowo wyposażyliśmy model w określonym zbiorze danych, a zestawy danych szkolenia / walidacji są identyczne.

Po zakończeniu szkolenia oceniamy model przy użyciu 3 różnych konfiguracji learning_phase: dynamicznej, statycznej = 0 (tryb testowy) i statycznej = 1 (tryb treningowy). Jak widać, pierwsze dwie konfiguracje dadzą identyczne wyniki pod względem strat i dokładności, a ich wartość odpowiada zgłoszonej dokładności modelu na zestawie walidacyjnym w ostatniej iteracji. Niemniej jednak po przejściu do trybu treningowego obserwujemy ogromną rozbieżność (poprawę). Dlaczego tak się stało? Jak powiedzieliśmy wcześniej, wagi sieci są dostrajane, oczekując otrzymania danych skalowanych ze średnią / wariancją danych uczących. Niestety te statystyki różnią się od tych przechowywanych w warstwach BN. Ponieważ warstwy BN zostały zamrożone, statystyki te nigdy nie były aktualizowane. Ta rozbieżność między wartościami statystyki BN prowadzi do pogorszenia dokładności podczas wnioskowania.

Zobaczmy, co się stanie, gdy zastosujemy łata:

Epoch 1/10
1/7 [===>..........................] - ETA: 26s - loss: 0.9992 - acc: 0.4375
2/7 [=======>......................] - ETA: 12s - loss: 1.0534 - acc: 0.4375
3/7 [===========>..................] - ETA: 7s - loss: 1.0592 - acc: 0.4479 
4/7 [================>.............] - ETA: 4s - loss: 0.9618 - acc: 0.5000
5/7 [====================>.........] - ETA: 2s - loss: 0.8933 - acc: 0.5250
6/7 [========================>.....] - ETA: 1s - loss: 0.8638 - acc: 0.5417
7/7 [==============================] - 13s 2s/step - loss: 0.8357 - acc: 0.5570 - val_loss: 0.2414 - val_acc: 0.9450

Epoch 2/10
1/7 [===>..........................] - ETA: 4s - loss: 0.2331 - acc: 0.9688
2/7 [=======>......................] - ETA: 2s - loss: 0.3308 - acc: 0.8594
3/7 [===========>..................] - ETA: 2s - loss: 0.3986 - acc: 0.8125
4/7 [================>.............] - ETA: 1s - loss: 0.3721 - acc: 0.8281
5/7 [====================>.........] - ETA: 1s - loss: 0.3449 - acc: 0.8438
6/7 [========================>.....] - ETA: 0s - loss: 0.3168 - acc: 0.8646
7/7 [==============================] - 9s 1s/step - loss: 0.3165 - acc: 0.8633 - val_loss: 0.1167 - val_acc: 0.9950

Epoch 3/10
1/7 [===>..........................] - ETA: 1s - loss: 0.2457 - acc: 1.0000
2/7 [=======>......................] - ETA: 2s - loss: 0.2592 - acc: 0.9688
3/7 [===========>..................] - ETA: 2s - loss: 0.2173 - acc: 0.9688
4/7 [================>.............] - ETA: 1s - loss: 0.2122 - acc: 0.9688
5/7 [====================>.........] - ETA: 1s - loss: 0.2003 - acc: 0.9688
6/7 [========================>.....] - ETA: 0s - loss: 0.1896 - acc: 0.9740
7/7 [==============================] - 9s 1s/step - loss: 0.1835 - acc: 0.9773 - val_loss: 0.0678 - val_acc: 1.0000

Epoch 4/10
1/7 [===>..........................] - ETA: 1s - loss: 0.2051 - acc: 1.0000
2/7 [=======>......................] - ETA: 2s - loss: 0.1652 - acc: 0.9844
3/7 [===========>..................] - ETA: 2s - loss: 0.1423 - acc: 0.9896
4/7 [================>.............] - ETA: 1s - loss: 0.1289 - acc: 0.9922
5/7 [====================>.........] - ETA: 1s - loss: 0.1225 - acc: 0.9938
6/7 [========================>.....] - ETA: 0s - loss: 0.1149 - acc: 0.9948
7/7 [==============================] - 9s 1s/step - loss: 0.1060 - acc: 0.9955 - val_loss: 0.0455 - val_acc: 1.0000

Epoch 5/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0769 - acc: 1.0000
2/7 [=======>......................] - ETA: 2s - loss: 0.0846 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0797 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0736 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0914 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0858 - acc: 1.0000
7/7 [==============================] - 9s 1s/step - loss: 0.0808 - acc: 1.0000 - val_loss: 0.0346 - val_acc: 1.0000

Epoch 6/10
1/7 [===>..........................] - ETA: 1s - loss: 0.1267 - acc: 1.0000
2/7 [=======>......................] - ETA: 2s - loss: 0.1039 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0893 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0780 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0758 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0789 - acc: 1.0000
7/7 [==============================] - 9s 1s/step - loss: 0.0738 - acc: 1.0000 - val_loss: 0.0248 - val_acc: 1.0000

Epoch 7/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0344 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0385 - acc: 1.0000
3/7 [===========>..................] - ETA: 3s - loss: 0.0467 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0445 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0446 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0429 - acc: 1.0000
7/7 [==============================] - 9s 1s/step - loss: 0.0421 - acc: 1.0000 - val_loss: 0.0202 - val_acc: 1.0000

Epoch 8/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0319 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0300 - acc: 1.0000
3/7 [===========>..................] - ETA: 3s - loss: 0.0320 - acc: 1.0000
4/7 [================>.............] - ETA: 2s - loss: 0.0307 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0303 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0291 - acc: 1.0000
7/7 [==============================] - 9s 1s/step - loss: 0.0358 - acc: 1.0000 - val_loss: 0.0167 - val_acc: 1.0000

Epoch 9/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0246 - acc: 1.0000
2/7 [=======>......................] - ETA: 3s - loss: 0.0255 - acc: 1.0000
3/7 [===========>..................] - ETA: 3s - loss: 0.0258 - acc: 1.0000
4/7 [================>.............] - ETA: 2s - loss: 0.0250 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0252 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0260 - acc: 1.0000
7/7 [==============================] - 9s 1s/step - loss: 0.0327 - acc: 1.0000 - val_loss: 0.0143 - val_acc: 1.0000

Epoch 10/10
1/7 [===>..........................] - ETA: 4s - loss: 0.0251 - acc: 1.0000
2/7 [=======>......................] - ETA: 2s - loss: 0.0228 - acc: 1.0000
3/7 [===========>..................] - ETA: 2s - loss: 0.0217 - acc: 1.0000
4/7 [================>.............] - ETA: 1s - loss: 0.0249 - acc: 1.0000
5/7 [====================>.........] - ETA: 1s - loss: 0.0244 - acc: 1.0000
6/7 [========================>.....] - ETA: 0s - loss: 0.0239 - acc: 1.0000
7/7 [==============================] - 9s 1s/step - loss: 0.0290 - acc: 1.0000 - val_loss: 0.0127 - val_acc: 1.0000

DYNAMIC LEARNING_PHASE
[0.012697912137955427, 1.0]

STATIC LEARNING_PHASE = 0
[0.012697912137955427, 1.0]

STATIC LEARNING_PHASE = 1
[0.01744014158844948, 1.0]

Przede wszystkim obserwujemy, że sieć zbiega się znacznie szybciej i osiąga doskonałą dokładność. Widzimy również, że nie ma już rozbieżności pod względem dokładności, gdy przełączamy się między różnymi wartościami learning_phase.

2.5 Jak działa poprawka na rzeczywistym zbiorze danych?

Jak więc działa łatka w bardziej realistycznym eksperymencie? Użyjmy wstępnie wyszkolonego ResNet50 firmy Keras (oryginalnie pasującego do imagenet), usuńmy najwyższą warstwę klasyfikacji i dostrójmy ją z poprawką i bez niej i porównajmy wyniki. W przypadku danych użyjemy CIFAR10 (standardowy podział pociąg / test dostarczony przez Keras) i zmienimy rozmiar obrazów do 224 × 224, aby były zgodne z rozmiarem wejściowym ResNet50.

Zrobimy 10 epok, aby wytrenować najwyższą warstwę klasyfikacji za pomocą RSMprop, a następnie zrobimy kolejne 5, aby dostroić wszystko po 139. warstwie za pomocą SGD (lr = 1e-4, pęd = 0.9). Bez naszywki nasz model osiąga dokładność 87.44%. Korzystając z łatki, uzyskujemy dokładność 92.36%, prawie 5 punktów wyższą.

2.6 Czy powinniśmy zastosować tę samą poprawkę do innych warstw, takich jak Dropout?

Normalizacja wsadowa nie jest jedyną warstwą, która działa inaczej w trybie pociągu i testu. Dropout i jego warianty również mają ten sam efekt. Czy powinniśmy zastosować te same zasady do wszystkich tych warstw? Wierzę, że nie (chociaż bardzo chciałbym usłyszeć Twoje przemyślenia na ten temat). Powodem jest to, że Dropout jest używany w celu uniknięcia nadmiernego dopasowania, więc zablokowanie go na stałe w trybie przewidywania podczas treningu zniweczy jego cel. Co myślisz?

Jestem głęboko przekonany, że ta rozbieżność musi zostać rozwiązana w Kerasie. Widziałem jeszcze głębsze efekty (od 100% do 50% dokładności) w rzeczywistych aplikacjach spowodowanych tym problemem. ja planuje wysłać już wysłałem PR do Keras z poprawką i miejmy nadzieję, że zostanie zaakceptowana.

Jeśli podobał Ci się ten post na blogu, poświęć chwilę, aby udostępnić go na Facebooku lub Twitterze. 🙂

Znak czasu:

Więcej z Skrzynka odniesienia