Wielowarstwowa sieć neuronowa w Tensorflow do klasyfikacji cyfr z MNIST

W wpisie tym zbudujemy 5-warstwową w pełni połączoną (fully-connected) sieć neuronową klasyfikującą cyfry z zbioru MNIST. W tym celu wykorzystamy Tensorflow oraz wprowadzimy nowe techniki pozwalające na uczenie głębszego modelu takie jak np. funkcje aktywacji: relu, elu, dropout oraz algorytm optymalizacyjny Adam.

Wpis ten jest kontynuację serii tekstów o klasyfikacji cyfr z MNIST z wykorzystaniem Tensorflow, w poprzednim tekście stworzyliśmy jednowarstwową sieć neuronową, która osiągnęła 0.9237 dokładności. Służy ona jako baseline dla dalszych naszych modeli. Wpisy z serii:

Obecnie zajmiemy się budową trzech wariantów pięciowarstwowej sieci neuronowej:

  • wariant 1 – 5 warstwowa sieć z sigmoidalną funkcją aktywacji, uczona przy pomocy „Gradient descent optimizer”
  • wariant 2 – 5 warstwowa sieć z funkcją aktywacji ReLU, która pozwoli nam na szybsze uczenie oraz wykorzystamy do treningu algorytm Adam
  • wariant 3 – tak jak wariant 2, z tym że dodamy dropout

Format danych oraz struktura wejścia i wyjścia sieci

Opis formatu danych oraz wejścia i wyjścia znajduje się w pierwszym wpisie, więc nie chcąc się powtarzać odsyłam do przeczytania bardziej szczegółowego opisu, tutaj przytoczę tylko najważniejsze informacje:

  • zbiór MNIST składa się z 60k obrazów treningowych i 10k testowych, które mają rozmiar 28×28 px
  • na wejściu sieci każdy obrazek traktujemy jako wektor o rozmiarze 784(=28*28)
  • zbiór danych podawany jest dla algorytmu uczącego w paczkach po 100 obrazków (BATCH)
  • wyjście sieci 10-elementowy wektor wykorzystujący one-hot encoding.

Budowa sieci neuronowej

Dla wszystkich 3 wariantów sieci zastosujemy taką samą architekturę z tą samą ilością neuronów w każdej warstwie, jak zauważycie ilość neuronów w kolejnych warstwach maleje od 200 do 10. Oczywiście nie jest to jedyna słuszna architektura, wynika ona z mojej intuicji oraz kilku eksperymentów, które poczyniłem. Jest to idealne miejsce do Waszych eksperymentów, jeżeli komuś z Was uda się osiągnąć lepsze wyniki to napiszcie w komentarzach lub wstawcie linka do gist na github.

Warstwa wejściowa dostaje macierz o wymiarach BATCH_SIZE x 784, czyli wierszowo ułożone obrazki z cyframi (nasze dane wejściowe). W warstwie pierwszej znajduje się 200 neuronów stąd macierz wag pomiędzy wejściem i wyjściem W1  ma wymiary 784×200, czyli każdy piksel z obrazka połączony jest wagą z danym neuronem, b1 zawiera wartości przesunięcia (w literaturze ang. bias) dla neuronów w warstwie pierwszej. Po zsumowaniu iloczynów wag i wartości wejść dodaniu przesunięcia przepuszczamy wynik przez funkcję aktywacji i otrzymujemy wyjście Y1 z warstwy pierwszej. I cała procedura się powtarza, wyjście Y1 jest wejściem dla drugiej warstwy już o mniejszej ilości neuronów (100) itd.

Ostatnia warstwa wyjściowa Y używa już funkcji softmax w celu obliczenia prawdopodobieństw przynależności do jednej z 10 klas (cyfry od 0 do 9).

Kod tworzący sieć

Poszczególne warianty prezentowanej sieci nie naruszają jej ogólnej architektury, tylko dodają lub zamieniają poszczególne elementy, takie jak: zmian funkcji aktywacji z sigmoid na relu, dodanie dropout itp.
Powyższą architekturę implementuje poniższy kawałek kodu:

W pierwszych liniach importujemy niezbędne biblioteki oraz ściągamy zbiór MNIST, następnie tworzymy dwa placeholder’y na dane wejściowe (X) oraz prawidłowe etykiety. Jak zwykle pierwszy wymiar jest ustawiony na None co daje znać dla TF, że zostanie on automatycznie wyliczony w zależności od podanych danych.

Następnie mamy definicję rozmiarów poszczególnych warstw (L1…L5) oraz tworzymy tensorflow’owe zmienne modelu czyli macierze wag pomiędzy warstwami. W ostatniej linie rozpłaszczamy obrazy z 28×28 do postaci wektora o rozmiarze 784.

Sieć neuronowa – wariant 1

Każdy z wariantów modeli jest do siebie podobny, różnią się głównie funkcją aktywacji. Pierwszy model wykorzystuje funkcję sigmoidalną. Aby otrzymać wyjście danej warstwy mnożymy wejście z warstwy poprzedniej (na początku są to nasze dane obrazkowe) przez macierz wag dla danej warstwy, dodajemy bias i stosujemy funkcję aktywacji.

Po definicji modelu tworzymy naszą loss function, w tym wypadku jest to średnia z softmax_cross_entropy na podstawie tej wartości będziemy oceniać jakość dotychczasowego modelu, minimalizacja tej funkcji będzie skutkowała jego uczeniem. W tym wariancie jako algorytm uczący wykorzystujemy prosty GradientDescent. Pamiętajcie, że Tensorflow buduje tylko graf obliczeń, który musimy jawnie „uruchomić”, zrobimy to w kolejnym kroku w pętli uczącej.

Sieć neuronowa – wariant 2

Tutaj model jest podobny do wariantu 1, z tym że zamiast funkcji sigmoid używamy funkcji ReLU, która ma kilka pożądanych cech wspomagających i przyspieszających proces uczenia sieci. Warto zwrócić uwagę na zastosowanie nowszego algorytmu uczącego Adam, jest on obecnie zalecany przy większości zadań optymalizacyjnych związanych z uczeniem.

Sieć neuronowa – wariant 3

W wariancie 3 w stosunku do wariantu 2 dodaliśmy dropout, czyli „usuwanie” z pewnym prawdopodobieństwem poszczególnych neuronów, ma to zapewnić mniejszą wrażliwość modelu na przeuczenie. Dropout można rozumieć jako formę regularyzcji modelu.

Procedura uczenia sieci

Poniżej zaprezentowany został kod z pętla uczącą model. Na początku musimy utworzyć sesje w której inicjujemy wszystkie zmienne modelu sess.run(init) a następnie wykonujemy zadaną ilość iteracji, w każdej pobieramy próbkę danych (BATCH) i na nich dokonujemy aktualizacji modelu. Jednocześnie obliczamy cząstkowe wartości dla funkcji straty i dokładności klasyfikacji na próbce danych oraz na całym zbiorze testowym (acc_trn, loss_trn, acc_tst, loss_tst) w celu ich późniejszej wizualizacji.

W ostatniej linii generujemy wykresy przedstawiające tempo uczenia modelu, wizualizacje przedstwają cząstkowe wartości accuraccy i loss.

Porównanie wyników

Uruchamiając poszczególne skrytpy w projekcie

  • mnist_2.0_5_layer_nn.py
  • mnist_2.1_5_layer_nn_relu_adam.py
  • mnist_2.0_5_layer_nn_relu_adam_dropout.py

Zostaną wygenerowane wykresy z tempem uczenia dla 5000 iteracji.

Zestawienie wyników

Wariant sieci Accuracy
Jednowarstwowa 0.9237
5-warstwowa f-cja aktywacji sigmoid 0.9541
5-warstwowa f-cja aktywacji relu, alg. optymalizacji Adam 0.9817
5-warstwowa f-cja aktywacji relu+dropout, alg. optymalizacji Adam 0.9761

 

Wykresy tempa uczenia sieci

Z powyższego zestawienia widać, że zmiana funkcji aktywacji oraz algorytmu optymalizacji zdecydowanie zwiększyła dokładność rozpoznania. W tym przypadku zaskoczeniem może być zmniejszenie dokładności dla wariantu z dodanym dropout prawdopodobnie dla tak małej ilości iteracji oraz niedużego zbioru dropout nie pomaga. Jego zadaniem jest niedopuszczenie do przeuczenia i to robi 🙂 Zobaczmy jak prezentuje się tempo uczenia na wykresach w powyższych przypadkach:

MNIST 5 layer nn sigmoid

Tempo uczenia zbioru MNIST dla 5 warstwowej sieci f-cja aktywacji sigmoid

 

MNIST 5 layer nn relu

Tempo uczenia zbioru MNIST dla 5 warstwowej sieci f-cja aktywacji ReLU

 

MNIST 5 layer nn relu dropout

Tempo uczenia zbioru MNIST dla 5 warstwowej sieci f-cja aktywacji ReLU wraz z dropout

 

Podsumowanie

W tym artykule przedstawiłem sposób budowania 5-warstwowej sieci neuronowej klasyfikującej cyfry z MNIST. Zbadaliśmy trzy warianty tej sieci z różnymi funkcjami aktywacji, alg. optymalizacji oraz zastosowaniem dropout. Sieć ta ma większą dokładność niż siec jednowarstwowa a użycie funkcji ReLU znacząco podnosi dokładność klasyfikacji.

Kod projektu dostępny na:

 

Dodaj komentarz

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