Skip to content

Rozpoznawanie obrazów CIFAR-10 – optymalizacja

W poprzednim wpisie opisałem jak zbudować najprostszy model do uczenia głębokiego z użyciem Keras, który to model nauczył się jako tako rozpoznawać obrazki ze zbioru CIFAR-10. Dziś spróbujemy trochę udoskonalić ten model. Wspomnę tu jeszcze, że od czasu ostatniego wpisu w moim środowisku obliczeniowym zaszła już jedna istotna optymalizacja, otóż mam nowy hardware do obliczeń. Liczę teraz na CPU Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz i GPU GeForce RTX 2070 SUPER. Wynikiem czego więcej optymalizacji już nie potrzeba, sieć którą mój stary laptop trenował prawie całą noc, na nowym sprzęcie trenuję w niecałe 5 minut (274 sek. na 42 epoki). Oczywiście optymalizować można i nawet trzeba, bo poza dalszym skróceniem czasu możemy uzyskać także lepsze wyniki.

Pierwszą modyfikacją jaką wykonamy, to dołożenie zaraz za pierwszą warstwą konwolucyjną naszego modelu, warstwy MaxPooling, która zmniejsza rozdzielczość naszych obrazków uzyskanych po ich złożeniu z kolejnymi filtrami konwolucji. Jak działa warstwa MaxPooling2D? Po wejściowym obrazie przesuwamy okienko, w naszym przypaku 2 na 2 piksele, wybierając największą wartość z oglądanej czwórki. Przesuwając próbkujące okienko o jeden piksel po całym obrazie, uzyskujemy w wyniku obraz o zredukowanej o połowie w stosunku do poprzedniej warstwy rozdzielczości. W naszym wypadku uzyskujemy obrazy o wielkości 16×16.

Definicja modelu wygląda teraz następująco:

cnn_model = Sequential([
    layers.Conv2D(32, (3, 3), padding='same', input_shape=(32,32,3), activation='relu'),
    layers.MaxPooling2D(pool_size=(2, 2)),
    layers.Conv2D(64, (2, 2), padding='same', activation='relu'),
    layers.Flatten(),
    layers.Dense(512, activation='relu'),
    layers.Dense(10, activation='softmax')])
 _________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_10 (Conv2D)           (None, 32, 32, 32)        896       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 16, 32)        0         
_________________________________________________________________
conv2d_11 (Conv2D)           (None, 16, 16, 64)        8256      
_________________________________________________________________
flatten_5 (Flatten)          (None, 16384)             0         
_________________________________________________________________
dense_10 (Dense)             (None, 512)               8389120   
_________________________________________________________________
dense_11 (Dense)             (None, 10)                5130      
=================================================================
Total params: 8,403,402
Trainable params: 8,403,402

Zmniejszamy w ten sposób ilość parametrów naszej sieci z 33M do 8M dzięki czemu czas obliczeń (przy 42 epochs) spada do 156 sekund. Co ciekawe poprawia nam to również wynik uzyskany na zbiorze testowym na loss: 2.9567 i acc: 0.6670

Kolejną warstwą stosowaną powszechnie w modelach sieci konwolujcyjnych, a której jeszcze nie mamy w tym modelu jest warstwa Dropout. Ta warstwa usuwa losowo wybrane parametry przekazywane do wejścia następnej warstwy. W naszym wypadku usuniemy na początek losowo 1/4 wartości na kolejnych wejściach. Wynikiem takiego działania jest dalsze zmniejszenie liczby parametrów obliczanych dla naszej sieci ale co ważniejsze, takie losowe zmiany struktury powodują również to, że nasza sieć będzie mniej podatna na przeuczenie na zbiorze treningowym. Model wygląda teraz tak:

cnn_model = Sequential([
                layers.Conv2D(32, (3, 3), padding='same', input_shape=(32,32,3), activation='relu'),
                layers.MaxPooling2D(pool_size=(2, 2)),
                layers.Dropout(0.25),
                layers.Conv2D(64, (2, 2), padding='same', activation='relu'),
                layers.Dropout(0.25),
                layers.Flatten(),
                layers.Dropout(0.25),
                layers.Dense(512, activation='relu'),
                layers.Dropout(0.25),
                layers.Dense(10, activation='softmax')])

I rzeczywiście dokładność modelu na zbiorze treningowym rośnie zdecydowanie wolniej a na zbiorze testowym uzyskujemy wynik loss: 1.1138 i acc: 0.7089.

Z takim zestawem warstw można kombinować i szukać najlepszego modelu. Na początek dodałem trzecią warstwę konwolucyjną i pokombinowałem z parametrami pozostałych warstw i otrzymałem loss: 0.6516 i acc: 0.7973 na poniższym modelu:

 cnn_model = Sequential([
                layers.Conv2D(32, (3, 3), padding='same', input_shape=(32,32,3), activation='relu'),
                layers.MaxPooling2D(pool_size=(2, 2)),
                layers.Dropout(0.3),
                layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
                layers.MaxPooling2D(pool_size=(2, 2)),
                layers.Dropout(0.3),
                layers.Conv2D(128, (3, 3), padding='same', activation='relu'),
                layers.Dropout(0.5),
                layers.Flatten(),
                layers.Dense(512, activation='relu'),
                layers.Dropout(0.5),
                layers.Dense(10, activation='softmax')])

Wynik końcowy nie jest idealny i w sieci można znaleźć lepsze modele ale jest skutkiem jednego wieczoru i kilkunastu uruchomień algorytmu. Pomyśleć, że jeszcze kilka miesięcy temu wymagałoby to kilkunastu nocy. Tak, inwestowanie w sprzęt ma sens bo realnie oszczędza czas, pomimo że wręcza nas od myślenia i optymalnego projektowania algorytmów. Takie czasy.

Facebook Comments

One thought on “Rozpoznawanie obrazów CIFAR-10 – optymalizacja

  1. Pingback: Rozpoznawanie obrazów z CIFAR-10 z użyciem Keras – Majcher.NET

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *