Niniejsza praca polega na przeprowadzeniu eksperymentu na danych zebranych w ramach omawianego na późniejszych stronach projektu powstałego z ramienia inicjatywy digitalizacyjnej Chronicling America, t.j. Newspaper Navigator. Z racji tego istnieje naturalna potrzeba wytłumaczenia pojęcia jakimi są głębokie sieci neuronowe ze szczególnym naciskiem na sieci przetwarzające obraz, ponieważ technologia ta jest kluczowym elementem całego projektu Newspaper Navigator.
Najprościej mówiąc i nie wdając się jeszcze zanadto w szczegóły sieć neuronowa jest to stos połączonych ze sobą warstw na które składają się neurony, czyli podstawowe jednostki obliczeniowe w sieciach. Wyróżniamy warstwę wejściową, ukryte warstwy stanowiące środek sieci, a także warstwę wyjściową. Z jednej strony wprowadza się dane, a z drugiej strony otrzymuje się ich przetworzoną postać. Każda warstwa wykonuje serię operacji matematycznych na otrzymanych danych, a także posiada zestaw zmiennych podlegających modyfikacji w celach optymalizacyjnych. Przedstawiony na poniższej rycinie typ sieci neuronowych to najbardziej popularny typ, nazywany W Pełni Połączoną Siecią (\emph{z ang. Fully Connected Network}). W sieci w pełni połączonej każda jednostka wyjściowa jest obliczana jako suma ważona wszystkich wejść. Termin "w pełni połączona" pochodzi właśnie od tego zachowania: każde wyjście jest połączone z każdym wejściem \cite{osinga2018deep}. Neurony zawarte w warstwie wejściowej (\emph{z ang. Input Layer}) wprowadzają do modelu informacje zewnętrzne, nie wykonują się tutaj żadne operacje matematyczne, jedynie wprowadza się dane. Liczba neuronów w warstwie wejściowej zależy od danych treningowych, od ich wymiaru. Tradycyjnie dla warstwy wejściowej wygląda to tak jak na wykresie \ref{neurony_licz}.
Wyrażenie '+ 1' w powyższym wzorze odnosi się do wyrazu wolnego (\emph{z ang. bias}), który intuicyjnie pełni podobne zadanie przy sieciach neuronowych jak ma to miejsce w klasycznej regresji liniowej. Wyraz wolny to po prostu stała wartość. Bias jest wykorzystywany do zrównoważenia wyniku, służy do przesunięcia wyniku funkcji aktywacji (o której więcej w dalszej części pracy) w stronę dodatnią lub ujemną. Cała "magia" sieci neuronowych odbywa się za to w warstwach ukrytych (\emph{z ang. hidden layers}), zgromadzone tam neurony przetwarzają informacje zgromadzone w warstwie wejściowej, a następnie przekazują je do wartstwy wyjściowej. Warstw ukrytych w sieciach może być zero lub kilka, nie jest to w żaden sposób regulowane a dotyczy od danego przypadku oraz wizji twórcy konkretnej sieci. Im bardziej skomplikowany przypadek ma dana sieć rozwiązać tym zazwyczaj z większej ilości ukrytych warstw musi się ona składać \cite{Malik2021Dec}.
Neurony w każda warstwie połączone są krawędziami (reprezentowane na powyższym obrazie jako strzałki łączące poszczególne neurony ze sobą), a każda z krawędzi posiada wagę. Pojedynczy neuron zatem jest sumą ważoną wartości z poprzedniej warstwy a także dodaje się do nich wyraz wolny, w przypadku pierwszej warstwy ukrytej tymi wartościami jest jeden wiesz wartości wszystkich cechy zbioru danych. Wagi podlegają optymalizacji w trakcie procesu uczenia sieci. Ich wartość jest o tyle kluczowa, że niektóre cechy w danych będą miały większe lub mniejsze znaczenie dla przyszłej predykcji, ta właśnie ważność jest regulowana przez wagi. Początkowo wagi mają ustawiane wartości w postaci małych liczb losowych \cite{Jain2021Dec}. Wzór \ref{wyrazwol} określa zachodzącą operacje wewnątrz neuronów.
Zanim wyliczona suma zostanie przekazana do kolejny ukrytej warstwy lub warstwy wyjściowej nakłada się na nią nieliniowość, poprzez tak zwaną funkcje aktywacji (\emph{z ang. activation function}). Funkcja aktywacji dokonuje transformacji powstałej sumy ważonej w dane wyjściowe warstwy, dzieje się to w celu ułatwienia sieci uczenia się złożonych (nieliniowych) wzorców w danych. Funkcje aktywacji zazwyczaj są różniczkowalne, co oznacza, że dla danej wartości wejściowej można obliczyć pochodną pierwszego rzędu. Jest to konieczne, z racji tego, że sieci neuronowe są zwykle trenowane z wykorzystaniem algorytmu wstecznej propagacji, który wymaga wyliczenia pochodnej na błędzie predykcji w celu późniejszej optymalizacji wag sieci \cite{BibEntry2021Jan_active}. Ponadto wartość neurona powinna być kontrolowana i zawierać się w pewnym przedziale. Jeżeli ta wartość nie jest ograniczona do pewnego limitu, wówczas może przybierać bardzo duże wartości, co w przypadku bardzo głębokich sieci neuronowych, które mają miliony parametrów może prowadzić do poważnych problemów obliczeniowych. Ogólna postać funkcji aktywacji prezentuje wzór \ref{activo}.
Po nałożeniu funkcji aktywacji na ważoną sumę cały proces powtarza się dla kolejnych warstw. Ostatnim krokiem jest przekazanie wartości do warstwy wyjściowej sieci, gdzie liczony jest finalny błąd predykcji. Cały taki proces nazywamy propagacją w przód (\emph{z ang. forward propagation}) \cite{Jain2021Dec_activatio}. Na poniższej rycinie możemy zauważyć proces opisany powyżej w wersji wizualnej. W tym przypadku dla uproszczenia sieć neuronowa składa się z trzech warstw: 1 wejściowej, 1 ukrytej i 1 wyjściowej. Warstwy wejściowa i ukryta zawierają trzy neurony, a warstwa wyjściowa zaś tylko jeden. Poszczególne wartości w sieci na każdym z etapów przykładu algorytmu propagacji w przód prezentują rysunek \ref{layers}.
Identycznie postępujemy dla kolejnych neuronów warstwy ukrytej, a następnie dla warstwy wyjściowej. Z racji tego, że inicjalne wagi są losowe, wartość neuronu wyjściowego jest daleka od wartości prawdziwej. W tym przypadku błąd predykcji równy jest +0,77, ponieważ wartość docelowa dla punktu danych na podstawie którego została wyliczona predykcja jest równa 0. Aby przybliżyć wartość predykcji do wartości rzeczywistej następuje proces uczenia sieci, tak zwany algorytm propagacji wstecznej (\emph{z ang backward propagation}), czyli krótko mówiąc optymalizacji wag sieci \cite{BibEntry2020Aug_nn_guide}.
Zanim jednak przejdziemy do algorytmu propagacji wstecznej stosownym wydaje się być wytłumaczenie pojęcia funkcji kosztu lub inaczej straty (\emph{z ang. cost/loss function}). W obrębie zagadnień związanych z uczeniem maszynowym pod tym pojęciem określa się funkcje, która bada różnice pomiędzy wartościami oczekiwanym na wyjściu sieci neuronowej, a wartościami jakie zostały zwrócone (predykcjami). Funkcja ta określa jak bardzo sieć myli się podczas predykcji wartości, poprzez wyliczenie błędu jakimi obciążone są jej wyniki. Pojęcie funkcji kosztu różni się w zależności od przypadku, tj. regresji, klasyfikacji binarnej czy klasyfikacji wieloklasowej. W przypadku regresji wyliczany jest dystans poszczególnych wartości rzeczywistych od krzywej predykcji \cite{BibEntry2021Mar_loss_cost}. Celem modelu podczas etapu treningu jest minimalizacja funkcji kosztu, czyli minimalizacji błędów jakie są przez niego popełnianie. Najczęściej używane funkcje kosztu przy regresji to:
MSE, czyli błąd średniokwadratowy (\emph{z ang. mean squared error}) oblicza średnią kwadratowych różnic między wartościami rzeczywistymi a wartościami przewidywanymi. Cechą charakterystyczną tej funkcji jest to, że model jest bardziej karcony za duże błędy.
MAE, czyli średni błąd absolutny (\emph{z ang. mean absolute error}) oblicza średnią sumy różnic bezwzględnych między wartościami rzeczywistymi a wartościami przewidywanymi. Charakteryzuje się większą odpornością na wartości odstające (\emph{z ang. outliers}) występujące w rozkładzie zmiennej objaśnianej.
MSLE, czyli błąd średniokwadratowy zlogarytmizowany (\emph{z ang. mean squared logarithmic error}) obliczany jest tak samo jak błąd średniokwadratowy, z tą różnicą, że używany jest logarytm naturalny wartości przewidywanej i rzeczywistej zmiennej objaśnianej. Stosowany jest najczęściej w sytuacjach, kiedy nie chcemy aby model był tak mocno karany w przypadku dużych wartości błędu jak dzieje się to w przypadku MSE. Oprócz tego często używa się również wersji błędu średniokwadratowego oraz błędu średniokwadratowego zlogarytmizowanego zawierających pierwiastek, są to kolejno RMSE oraz RMSLE. Interpretacja ich jest o tyle prostsza, że operujemy w tych samych jednostkach, a nie jak w przypadku standardowych wersji kwadratach tych jednostek. MAPE to z kolei odpowiednik MAE w którym liczony jest średni błąd absolutny wyrażony procentowo. Te metryki jednak częściej stosowane są przy prezentacji wyników, aniżeli bezpośrednio jako funkcje straty w modelach między innymi dzięki łatwej do zrozumienia, dla osób mniej biegłych technicznie, interpretacji \cite{BibEntry2021Mar_loss_details}. W kontekście klasyfikacji funkcja straty mierzy, jak często model źle klasyfikuje przynależność obserwacji do poszczególnych etykiet. W przypadku klasyfikacji binarnych generalnie problem polega na przewidywaniu wartości 0 lub 1 dla pierwszej lub drugiej klasy. Jest to realizowane jako przewidywanie prawdopodobieństwa, aby zdecydować, czy dany element należy do klasy pierwszej czy drugiej. Najczęściej stosowaną miarą w przypadku problemów klasyfikacyjnych jest BCE.
BCE, czyli binarna entropia krzyżowa (\emph{z ang. binary cross-entropy}) jest powszechnie stosowaną funkcją straty w problemie klasyfikacji binarnej (istnieją tylko dwie klasy, 0 lub 1). Mierzy on wydajność modelu klasyfikacyjnego, którego wyjście jest wartością prawdopodobieństwa pomiędzy 0 a 1. Funcja straty wzrasta, jeśli przewidywane prawdopodobieństwo różni się od rzeczywistej etykiety. Teoretycznie doskonały model ma binarną stratę cross-entropii równą 0 \cite{Kumar2021Dec_class_loss}. Klasyfikacja wieloklasowa to te problemy modelowania predykcyjnego, w których przykłady są przypisane do jednej z więcej niż dwóch klas. Problem jest często implementowany jako przewidywanie prawdopodobieństwa przynależności przykładu do każdej znanej klasy. W przypadku tego rodzaju problemów najpopularniejszą funkcją straty jest również entropia krzyżowa. Tym razem obliczana jest binarna entropia krzyżowa dla każdej klasy osobno, a następnie sumuje się ją dla pełnej straty \cite{Martinek2022Mar_muulti_class_loss}.
Skoro pojęcie funkcji straty zostało już omówione możliwe jest przejście do algorytmu propagacji wstecznej, który odpowiada za tak zwany proces uczenia się w przypadku sieci neuronowych. Jak wskazuje już sama nazwa podąża się tym razem w odwrotnym kierunki aniżeli podczas algorytmu propagacji w przód (od warstwy wejściowej, przez warstwy ukryte, aż do warstwy wyjściowej). Celem w tym przypadku jest minimalizacja funkcji kosztu poprzez dostosowanie wag sieci. Wagi aktualizowane są w ten sposób, aby ich zmiany powodowały, że rzeczywiste wyjście sieci jest bliższe wyjściu docelowemu, minimalizując w ten sposób błąd dla każdego neuronu wyjściowego i sieci jako całości. Zmiany wag zachodzą sekwencyjnie od ostatniej do pierwszej warstwy. Poziom dostosowania jest określany przez gradienty funkcji kosztu względem wag. Gradient funkcji jest wektorem pochodnych cząstkowych tej funkcji. Pochodna funkcji mierzy wrażliwość na zmianę wartości wyjściowej w odniesieniu do zmiany jej wartości wejściowej. Innymi słowy, pochodna mówi nam, w jakim kierunku zmierza funkcja. Gradient zaś pokazuje, jak bardzo musi zmienić się jej parametr (w kierunku dodatnim lub ujemnym), aby zminimalizować daną funkcje. Rozważmy następujący wycinek sieci przedstawiający jeden neuron wraz z połączeniami. Wycinek ten przedstawia rycina \ref{neuuro}.
Zakładając, że chcemy zbadać jaka zmiana w1 spowoduje minimalizacje funkcji kosztu, oznaczmy ją jako E, musimy zastosować regułę łańcuchową (\emph{z ang. chain rule}) służącą do obliczania pochodnych funkcji złożonych. Opisuje to wzór \ref{chain}.
Powyższy proces nosi nazwę metody gradientu prostego (\emph{z ang. gradient descent}). Pod symbolem 'lr' kryje się parametr oznaczający długość kroku (\emph{z ang. learning rate}), jaki zostanie wykonany w stronę minimum funkcji podczas jednego przejścia procesu optymalizacji wag. Parametr ten jest ustawiany z góry podczas deklarowania parametrów sieci. Używany jest on w celu uniknięcia sytuacji, w której algorytm pominie minimum funkcji z powodu wykonanej zbyt dużej modyfikacji wagi względem jej pierwotnego stanu. Ponieważ proces propagacji wstecznej jest wykonywany wielokrotnie, tak samo jak propagacji w przód, a nie jednorazowo (1 epoka = 1 przejście sieci w przód i w tył) wagi mogą być uaktualniane o mniejsze wartości tym samym zabezpieczając przed pominięciem minimum funkcji. Oczywiście krok ten nie może być również zbyt mały, ponieważ proces optymalizacji będzie wówczas trwał zbyt długo, a przy tym pochłaniał zbyt dużo mocy obliczeniowej. Dobór tego parametru jest zazwyczaj serią prób i błędów, zanim dojdzie się do jego odpowiedniej wartości. Wpływ długości kroku na znajdowanie minimum funkcji obrazuje rycina \ref{minim}.
Cały proces opisany powyżej wykonywany jest dla każdej wagi jaka znajduje się w sieci, idąc po kolei warstwa po warstwie, a zaczynając od warstwy wyjściowej sieci \cite{BibEntry2020Aug_nn_guide}. Podejście do procesu optymalizacji wag przy dzisiejszych architekturach sieci neuronowych zależne jest od wybranego optymalizatora. Do najpopularniejszych aktualnie optymalizatorów należą optymalizatory adaptacyjne, które dostosowują długość kroku automatycznie w trakcie procesu uczenia się sieci, takie jak: ADAM, RMSprop, czy AdaDelta. Po wykonaniu się ostatniej epoki, aktualne wartości wag stają się finalnymi wagami w sieci i będą wykorzystywane przy predykcji na zbiorze testowym.
\newline
\section{Konwolucyjne sieci neuronowe przetwarzające obraz}
W przypadku danych wejściowych, którymi są obrazy do czynienia mamy z ich reprezentacją macierzową. Jej elementy to poszczególne występujące na zdjęciu piksele. Jeżeli rozważylibyśmy przepuszczenie takich danych przez, wcześniej omówione, klasyczne w pełni połączone sieci wówczas z dużą dozą prawdopodobieństwa bylibyśmy świadkami ich problemów ze zbyt dużą złożonością obliczeniową zadania. Jest to spowodowane, tym że obrazy z natury są bardzo duże, ich rozdzielczość określa wielkość reprezentującej jej macierzy. W tym momencie warto dodać, że na tym liczba elementów wchodzących w skład jednego wejścia do modelu się nie kończy, bowiem każdy piksel może przybierać jeden z 3 kolorów (wedle modelu przestrzeni barw RGB). Wówczas pojedyncze wejście modelu dla obrazu o popularnej rozdzielczości 256x256 pikseli w rzeczywistości reprezentuje liczba 256×256×3. Zakładając model o jednej warstwie ukrytej z 1000 neuronów, warstwa ta dysponować będzie prawie 200 milionami parametrów, podlegającymi procesom optymalizacji podczas przechodzenia algorytmu propagacji wstecznej. Z racji tego, że modele obrazów wymagają dość dużej liczby warstw, aby dobrze radzić sobie z klasyfikacją, to wówczas używając sieci w pełni połączonych, otrzymalibyśmy miliardy parametrów. Przy tak dużej liczbie parametrów uniknięcie problemu nadmiernego dostosowania modelu jest prawie niemożliwe. Korzystając z architektury konwolucyjnych sieci neuronowych (\emph{z ang. convolutional neural networks - CNN}) otrzymujemy znacznie mniejszą liczbę parametrów, skuteczniejsze modele, a także dużo krótszy czas ich trenowania \cite{osinga2018deep}. CNN to wyspecjalizowane sieci do danych o topologii przypominającej siatkę, dlatego właśnie świetnie spisują się w przypadku obrazów. Stosuje się je również do szeregów czasowych, gdzie za takową siatkę odpowiada interwał czasowy. Sama architektura tych sieci znacznie różni się od tej przedstawionej w pierwszym podrozdziale choć istota pozostaje wciąż taka sama, co przedstawia rycina \ref{convner}.
Istotą konwolucyjnych sieci neuronowych jak wskazuje już na to sama ich nazwa jest operacja matematyczna zwaną konwolucją. Konwolucja pozwala wyostrzyć pewne elementy obrazu poprzez użycie tak zwanego jądra lub inaczej filtra (\emph{z ang. kernel lub filter}).
Pozwala to lepiej klasyfikować obraz, gdyż jego cechy są dla sieci, w cudzysłowie, lepiej widoczne. Filtry to mniejsze macierze o losowych parametrach, których to wartości koryguje algorytm propagacji wstecznej, dlatego intuicyjnie można utożsamiać je z wagami jakie występowały w przypadku w pełni połączonych sieci. Filtrów zazwyczaj nakłada się wiele, dla przykładu przy zadaniu rozpoznawania twarzy jeden filtr może uwypuklać oczy, drugi uszy, kolejny linie włosów i tak dalej. Oprócz tego takie podejście redukuje również szum, czyli niepotrzebne elementy obrazu przestają mieć znaczenie. Przykładowe filtry i ich wpływ na obraz przedstawione zostały w tabeli na obrazku powyżej. Wracając jednak do istoty konwolucji, dokonuje ona modyfikacji części obrazu. Filtr przechodzi krok po kroku po zdjęciu i przemnaża wartości zwracając wynik (zarówno wielkość filtra, jak i krok to parametry modyfikowalne podczas implementacji sieci). Wizualizacja tego procesu przedstawiona została na rysunku \ref{fe}.
Wobec tego konwolucja tworzy nam nowy, mniejszy obraz zawierający już wyekstrahowane cechy obrazu pierwotnego, który posłuży jako wejście kolejnej warstwy sieci. Taki obraz nazywany jest mapą cech (\emph{z ang. feature map}). Wyekstrahowane cechy w postaci mapy są o tyle znaczące, że dzięki temu ich lokalizacja na obrazie przestaje mieć znaczenie, a liczy się jedynie fakt ich występowania. Przy późniejszej predykcji zmiana położenia danej cechy nie będzie zatem znacząca w kontekście wyniku. Oczywiście jak każda architektura sieci neuronowych może być ona wielowarstwowa, wówczas wszystko wygląda identycznie, a proces powtarzany jest kilkukrotnie powodując wykrywanie subcech poszczególnych cech. Oprócz wspomnianych wcześniej dwóch hiperparametrów, tj. kroku (\emph{z ang. stride}) oraz wielkości (\emph{z ang. size}), dysponujemy także wypełnieniem (\emph{z ang. padding}). Wypełnienie odpowiada za poszerzenie granic obrazu, dzięki czemu możliwa jest ekstrakcja większej ilości cech, a co za tym idzie większa ilość informacji jest przetwarzana wewnątrz sieci. Takie działanie odbywa się kosztem wydajności. Następnym krokiem jest nałożenie na otrzymaną mapę cech nieliniowości, poprzez zastosowanie funkcji aktywacji, podobnie jak miało to miejsce w przypadku w pełni połączonych sieci. W przypadku CNN praktycznie zawsze jest to funkcja ReLU. Nieliniowość jest przyczyną tak dobrej skuteczności sieci neuronowych, ponieważ pozwala znajdować skomplikowane zależności w danych, których nie sposób zapisać jako kombinacje liniową. Funkcja aktywacji w tym wypadku pozwala zadecydować, czy dany neuron będzie miał znaczący udział w późniejszej predykcji, czy też nie. W przypadku konwolucyjnych sieci neuronowych przetwarzane wartości piksele interpretować możemy jako neurony, zaś parametry macierzy filtrów jako wagi \cite{albawi2017understanding}.
Zgodnie z rysunkiem 2.5 kolejnym elementem architektury konwolucyjncyh sieci neuronowych jest krok o nazwie łączenie (\emph{z ang. pooling}). Konwolucyjna warstwa, której wyjście stanowi wejście dla warstwy pooling'u, powoduje redundancje danych. Część tych samych cech została wyekstrahowana w kolejnych przesunięciach filtra. Na potrzeby wizualizacji tego tematu, jeżeli użyty zostanie filtr wykrywający krawędzie poziome na obrazie i znajdzie on silną krawędź w określonym miejscu, to wówczas z dużą dozą prawdopodobieństwa ta sama cecha znajdzie się również w miejscu przesunięciu filtra o przykładowo 1 piksel w lewo, czy prawo. Zatem po wykonaniu konwolucji wypadałoby nieco zmniejszyć wymiarowość przetwarzanych danych. Zmniejszając rozmiar obrazu, zmniejszamy liczbę parametrów do wytrenowania, czym skracamy zarówno czas działania sieci, jak i poziom jej obliczeniowego skomplikowania. Za właśnie ten proces odpowiada warstwa pooling'u. Co warte podkreślenia pomimo redukcji wymiaru nie tracimy wówczas praktycznie żadnych informacji. Dzieje się tak, ponieważ nie interesuje nas wartość każdego piksela po konwolucji, a jednie te wartości, które dają silny sygnał sieci. W praktyce proces ten wygląda w ten sposób, że kilka sąsiednich pikseli przykładowo o rozmiarze 2x2 mapowana jest na 1 piksel (rozmiar podobnie jak w przypadku filtrów jest hiperparametrem deklarowanym podczas implementacji sieci). Zadeklarowane okno, również jak w przypadku filtrów, przechodzi wzdłuż i w szerz po całym obrazie. 2 najpopularniejsze i praktycznie jedyne stosowane rodzaje łączenia to:
Dzięki zastosowania warstwy pooling'u sieci łatwiej jest identyfikować te cechy obrazu, których sygnał jest najsilniejszy \cite{Yamashita2018Aug}. Proces ten wizualizuje rysunek \ref{poole}.
Ostatnią warstwą CNN jest, znana już z opisywanej w poprzednim podrozdziale klasycznej architektury sieci neuronowych, warstwa w pełni połączona (\emph{z ang. fully connected layer}). Warstwa ta odpowiedzialna jest za klasyfikacje. Używając funkcji aktywacji softmax (lub wcześniej scharakteryzowana funkcja sigmoid), opisanej wzorem \ref{wzorel}, otrzymaną predykcją będzie prawdopodobieństwo przynależności obrazu do danej klasy.
Tak samo jak w klasycznej architekturze sieci w tej warstwie liczony jest błąd predykcji, który korygowany będzie poprzez algorytm propagacji wstecznej, czyli optymalizacji wag, a w tym przypadku parametrów macierzy filtrów. W CNN możemy zadeklarować więcej niż jedną warstwę w pełni połączoną, jest to podyktowane już charakterystyką danego zbioru treningowego. To samo dotyczy zresztą pozostałych warstw, co zostało już poruszone wcześniej w tym podrozdziale, a także demonstrowane jest na, znajdującym się wyżej, rysunku 2.5 \cite{BibEntry2022Jun_cnn_output}.