Samouczek dotyczący głębokiego uczenia się: od perceptronów do głębokich sieci
Opublikowany: 2022-03-11W ostatnich latach nastąpiło odrodzenie w dziedzinie sztucznej inteligencji. Rozprzestrzenia się poza świat akademicki, a główni gracze, tacy jak Google, Microsoft i Facebook, tworzą własne zespoły badawcze i dokonują imponujących przejęć.
Niektóre z nich można przypisać obfitości nieprzetworzonych danych generowanych przez użytkowników sieci społecznościowych, z których większość wymaga analizy, rozwojowi zaawansowanych rozwiązań z zakresu nauki o danych, a także taniej mocy obliczeniowej dostępnej za pośrednictwem GPGPU.
Ale poza tymi zjawiskami, to odrodzenie było w dużej mierze napędzane przez nowy trend w sztucznej inteligencji, w szczególności w uczeniu maszynowym, znany jako „głębokie uczenie”. W tym samouczku przedstawię kluczowe koncepcje i algorytmy stojące za głębokim uczeniem, zaczynając od najprostszej jednostki kompozycji i budowania do koncepcji uczenia maszynowego w Javie.
(Dla pełnego ujawnienia: jestem również autorem biblioteki głębokiego uczenia Java, dostępnej tutaj, a przykłady w tym artykule są zaimplementowane przy użyciu powyższej biblioteki. Jeśli Ci się spodoba, możesz ją wesprzeć, nadając jej gwiazdkę na GitHub , za co byłbym wdzięczny (instrukcje użytkowania są dostępne na stronie głównej.)
Trzydziestosekundowy samouczek dotyczący uczenia maszynowego
Jeśli nie jesteś zaznajomiony, zapoznaj się z tym wprowadzeniem do uczenia maszynowego:
Ogólna procedura wygląda następująco:
- Mamy pewien algorytm, który podaje garść przykładów oznaczonych etykietami, powiedzmy 10 zdjęć psów z etykietą 1 („Pies”) i 10 zdjęć innych rzeczy z etykietą 0 („Nie pies”) — pamiętaj, że głównie się trzymamy do nadzorowanej, binarnej klasyfikacji dla tego posta.
- Algorytm „uczy się” identyfikować wizerunki psów, a po podaniu nowego obrazu ma nadzieję, że otrzyma prawidłową etykietę (1, jeśli jest to wizerunek psa, a 0 w przeciwnym razie).
To ustawienie jest niezwykle ogólne: Twoje dane mogą być objawami, a oznaczać choroby; lub Twoje dane mogą być obrazami odręcznych znaków, a Twoje etykiety są rzeczywistymi znakami, które reprezentują.
Perceptrony: algorytmy wczesnego uczenia głębokiego
Jednym z najwcześniejszych nadzorowanych algorytmów uczących jest perceptron, podstawowy element budulcowy sieci neuronowej.
Powiedzmy, że na płaszczyźnie mamy n punktów, oznaczonych jako „0” i „1”. Dostajemy nowy punkt i chcemy odgadnąć jego etykietę (jest to podobne do scenariusza „Pies” i „Nie pies” powyżej). Jak to robimy?
Jednym ze sposobów może być spojrzenie na najbliższego sąsiada i zwrócenie etykiety tego punktu. Ale nieco bardziej inteligentnym sposobem na to byłoby wybranie wiersza, który najlepiej oddziela oznaczone dane i użycie go jako swojego klasyfikatora.
W tym przypadku każda część danych wejściowych byłaby reprezentowana jako wektor x = ( x_1, x_2 ), a nasza funkcja byłaby czymś w rodzaju „'0' jeśli poniżej linii, '1' jeśli powyżej”.
Aby przedstawić to matematycznie, niech nasz separator będzie zdefiniowany przez wektor wag w i pionowe przesunięcie (lub przesunięcie) b . Następnie nasza funkcja łączyłaby dane wejściowe i wagi z funkcją transferu sumy ważonej:
Wynik tej funkcji przenoszenia byłby następnie wprowadzany do funkcji aktywacji w celu wytworzenia oznakowania. W powyższym przykładzie nasza funkcja aktywacji była progiem odcięcia (np. 1, jeśli jest większa niż jakaś wartość):
Trening perceptronu
Uczenie perceptronu polega na podaniu mu wielu próbek uczących i obliczeniu wyników dla każdej z nich. Po każdej próbce wagi w są dopasowywane w taki sposób, aby zminimalizować błąd wyjścia , definiowany jako różnica pomiędzy wyjściami pożądanymi (docelowymi) i rzeczywistymi . Istnieją inne funkcje błędu, takie jak błąd średniokwadratowy, ale podstawowa zasada treningu pozostaje taka sama.
Wady pojedynczego perceptronu
Podejście z jednym perceptronem do głębokiego uczenia ma jedną poważną wadę: może uczyć się tylko funkcji liniowo rozdzielonych. Jak poważna jest ta wada? Weź XOR, stosunkowo prostą funkcję, i zauważ, że nie można jej sklasyfikować za pomocą separatora liniowego (zwróć uwagę na nieudaną próbę poniżej):
Aby rozwiązać ten problem, będziemy musieli użyć wielowarstwowego perceptronu, znanego również jako feedforward neural network: w efekcie skomponujemy razem kilka tych perceptronów, aby stworzyć potężniejszy mechanizm uczenia się.
Sieci neuronowe ze sprzężeniem zwrotnym do głębokiego uczenia
Sieć neuronowa to tak naprawdę tylko kompozycja perceptronów, połączonych na różne sposoby i działających na różnych funkcjach aktywacji.
Na początek przyjrzymy się sieci neuronowej typu feedforward, która ma następujące właściwości:
- Wejście, wyjście i co najmniej jedna warstwa ukryta . Powyższy rysunek przedstawia sieć z 3-jednostkową warstwą wejściową, 4-jednostkową warstwą ukrytą i warstwą wyjściową z 2 jednostkami (terminy jednostki i neurony są wymienne).
- Każda jednostka to pojedynczy perceptron, taki jak ten opisany powyżej.
- Jednostki warstwy wejściowej służą jako dane wejściowe dla jednostek warstwy ukrytej, podczas gdy jednostki warstwy ukrytej są danymi wejściowymi dla warstwy wyjściowej.
- Każde połączenie między dwoma neuronami ma wagę w (podobną do wag perceptronów).
- Każda jednostka warstwy t jest zwykle połączona z każdą jednostką poprzedniej warstwy t - 1 (chociaż można je rozłączyć, ustawiając ich wagę na 0).
- Aby przetworzyć dane wejściowe, „zaciskasz” wektor wejściowy do warstwy wejściowej, ustawiając wartości wektora jako „wyjścia” dla każdej jednostki wejściowej. W tym konkretnym przypadku sieć może przetwarzać trójwymiarowy wektor wejściowy (ze względu na 3 jednostki wejściowe). Na przykład, jeśli twój wektor wejściowy to [7, 1, 2], wtedy ustawisz wyjście górnej jednostki wejściowej na 7, środkowej jednostki na 1 i tak dalej. Wartości te są następnie propagowane w przód do jednostek ukrytych przy użyciu funkcji transferu sumy ważonej dla każdej jednostki ukrytej (stąd termin propagacja w przód), które z kolei obliczają ich wyjścia (funkcja aktywacji).
- Warstwa wyjściowa oblicza swoje dane wyjściowe w taki sam sposób, jak warstwa ukryta. Wynikiem warstwy wyjściowej jest wynik sieci.
Poza liniowością
Co by było, gdyby każdy z naszych perceptronów mógł używać tylko liniowej funkcji aktywacji? Wtedy ostateczny wynik naszej sieci nadal będzie jakąś liniową funkcją danych wejściowych, po prostu dopasowaną tonami różnych wag, które są zbierane w całej sieci. Innymi słowy, liniowa kompozycja wielu funkcji liniowych jest nadal tylko funkcją liniową. Jeśli jesteśmy ograniczeni do liniowych funkcji aktywacji, to sprzężenie do przodu nie jest potężniejsze niż perceptron, bez względu na to, ile ma warstw.
Z tego powodu większość sieci neuronowych wykorzystuje nieliniowe funkcje aktywacji, takie jak logistyczna, tanh, binarna czy prostownikowa. Bez nich sieć może uczyć się tylko funkcji, które są liniowymi kombinacjami jej wejść.
Trening Perceptronów
Najpopularniejszy algorytm głębokiego uczenia do nadzorowanego treningu wielowarstwowych perceptronów jest znany jako propagacja wsteczna. Podstawowa procedura:
- Próbka szkoleniowa jest prezentowana i propagowana w sieci.
Obliczany jest błąd wyjściowy, zwykle błąd średniokwadratowy:
Gdzie t jest wartością docelową, a y jest rzeczywistą wydajnością sieci. Dopuszczalne są również inne obliczenia błędów, ale MSE to dobry wybór.
Błąd sieci jest minimalizowany przy użyciu metody zwanej stochastycznym spadkiem gradientu.
Zejście gradientowe jest uniwersalne, ale w przypadku sieci neuronowych byłby to wykres błędu uczenia w funkcji parametrów wejściowych. Optymalną wartością dla każdej wagi jest ta, przy której błąd osiąga globalne minimum . Podczas fazy uczenia wagi są aktualizowane małymi krokami (po każdej próbce uczącej lub mini-partii kilku próbek) w taki sposób, że zawsze starają się osiągnąć globalne minimum — ale nie jest to łatwe zadanie, ponieważ często kończą w lokalnych minimach, jak to po prawej stronie. Na przykład, jeśli waga ma wartość 0,6, należy ją zmienić na 0,4.
Ta liczba przedstawia najprostszy przypadek, w którym błąd zależy od jednego parametru. Jednak błąd sieci zależy od każdej wagi sieci, a funkcja błędu jest znacznie, znacznie bardziej złożona.
Na szczęście wsteczna propagacja zapewnia metodę aktualizowania każdej wagi między dwoma neuronami w odniesieniu do błędu wyjściowego. Samo wyprowadzenie jest dość skomplikowane, ale aktualizacja wagi dla danego węzła ma następującą (prostą) postać:
Gdzie E jest błędem wyjścia, a w_i jest wagą wejścia i do neuronu.
Zasadniczo celem jest poruszanie się w kierunku gradientu w odniesieniu do wagi i . Kluczowym terminem jest oczywiście pochodna błędu, która nie zawsze jest łatwa do obliczenia: jak znaleźć tę pochodną dla losowej wagi losowego węzła ukrytego w środku dużej sieci?
Odpowiedź: poprzez wsteczną propagację. Błędy są najpierw obliczane w jednostkach wyjściowych, których formuła jest dość prosta (oparta na różnicy między wartościami docelowymi i przewidywanymi), a następnie propagowane przez sieć w sprytny sposób, co pozwala nam skutecznie aktualizować nasze wagi podczas treningu i (miejmy nadzieję) osiągnąć minimum.
Ukryta warstwa
Szczególnie interesująca jest ukryta warstwa. Dzięki uniwersalnemu twierdzeniu o aproksymacji można wytrenować pojedynczą sieć warstw ukrytych ze skończoną liczbą neuronów w celu aproksymowania dowolnej funkcji losowej. Innymi słowy, pojedyncza ukryta warstwa jest wystarczająco potężna, aby nauczyć się dowolnej funkcji. To powiedziawszy, często uczymy się lepiej w praktyce z wieloma ukrytymi warstwami (tj. głębszymi sieciami).
Warstwa ukryta to miejsce, w którym sieć przechowuje swoją wewnętrzną abstrakcyjną reprezentację danych treningowych, podobnie jak ludzki mózg (dużo uproszczona analogia) ma wewnętrzną reprezentację świata rzeczywistego. W dalszej części samouczka przyjrzymy się różnym sposobom zabawy z ukrytą warstwą.
Przykładowa sieć
Możesz zobaczyć prostą (4-2-3-warstwową) sieć neuronową ze sprzężeniem do przodu, która klasyfikuje zestaw danych IRIS zaimplementowany w Javie za pomocą metody testMLPSigmoidBP . Zbiór danych zawiera trzy klasy roślin tęczówki z cechami takimi jak długość działki, długość płatka itp. Sieć dostarcza 50 próbek na klasę. Funkcje są przypisane do jednostek wejściowych, podczas gdy każda jednostka wyjściowa odpowiada jednej klasie zestawu danych: „1/0/0” wskazuje, że roślina należy do klasy Setosa, „0/1/0” oznacza Versicolour, a „ 0/0/1” oznacza Wirginicę). Błąd klasyfikacji wynosi 2/150 (tzn. błędnie klasyfikuje 2 próbki ze 150).
Problem z dużymi sieciami
Sieć neuronowa może mieć więcej niż jedną ukrytą warstwę: w takim przypadku wyższe warstwy „budują” nowe abstrakcje na poprzednich warstwach. Jak wspomnieliśmy wcześniej, często można nauczyć się lepiej w praktyce z większymi sieciami.
Jednak zwiększenie liczby ukrytych warstw prowadzi do dwóch znanych problemów:
- Znikające gradienty: gdy dodajemy coraz więcej ukrytych warstw, propagacja wsteczna staje się coraz mniej przydatna w przekazywaniu informacji do niższych warstw. W efekcie, gdy informacja jest przekazywana z powrotem, gradienty zaczynają zanikać i stają się małe w stosunku do wag sieci.
- Overfitting: być może główny problem w uczeniu maszynowym. Krótko mówiąc, overfitting opisuje zjawisko zbyt ścisłego dopasowania danych treningowych, być może do zbyt złożonych hipotez. W takim przypadku uczeń naprawdę dobrze dopasuje dane treningowe, ale na prawdziwych przykładach będzie działał znacznie gorzej.
Przyjrzyjmy się kilku algorytmom głębokiego uczenia, które pomogą rozwiązać te problemy.
Autokodery
Większość wprowadzających zajęć z uczenia maszynowego zwykle kończy się na sieciach neuronowych ze sprzężeniem do przodu. Ale przestrzeń możliwych sieci jest znacznie bogatsza – więc kontynuujmy.
Autoenkoder jest zazwyczaj sprzężoną siecią neuronową, której celem jest nauczenie się skompresowanej, rozproszonej reprezentacji (kodowania) zbioru danych.
Koncepcyjnie sieć jest szkolona do „odtworzenia” danych wejściowych, tj. dane wejściowe i docelowe są takie same. Innymi słowy: próbujesz wyprowadzić to samo, co dane wejściowe, ale w jakiś sposób skompresowane. To mylące podejście, spójrzmy więc na przykład.
Kompresowanie danych wejściowych: obrazy w skali szarości
Załóżmy, że dane treningowe składają się z obrazów w skali szarości 28x28, a wartość każdego piksela jest przypisana do jednego neuronu warstwy wejściowej (tj. warstwa wejściowa będzie miała 784 neurony). Wtedy warstwa wyjściowa miałaby taką samą liczbę jednostek (784) jak warstwa wejściowa, a wartością docelową dla każdej jednostki wyjściowej byłaby wartość skali szarości jednego piksela obrazu.
Intuicja stojąca za tą architekturą jest taka, że sieć nie nauczy się „mapowania” między danymi treningowymi a ich etykietami, ale zamiast tego nauczy się wewnętrznej struktury i cech samych danych. (Z tego powodu warstwa ukryta jest również nazywana wykrywaczem cech .) Zwykle liczba jednostek ukrytych jest mniejsza niż warstw wejścia/wyjścia, co zmusza sieć do uczenia się tylko najważniejszych cech i prowadzi do redukcji wymiarowości.
W efekcie chcemy, aby kilka małych węzłów w środku naprawdę nauczyło się danych na poziomie koncepcyjnym, tworząc zwartą reprezentację, która w pewien sposób oddaje podstawowe cechy naszych danych wejściowych.
Choroba grypy
Aby dalej zademonstrować autokodery, spójrzmy na jeszcze jedną aplikację.
W tym przypadku użyjemy prostego zestawu danych składającego się z objawów grypy (podziękowanie za ten pomysł na blogu). Jeśli jesteś zainteresowany, kod tego przykładu można znaleźć w metodzie testAEBackpropagation .
Oto jak rozkłada się zestaw danych:
- Dostępnych jest sześć funkcji wejść binarnych.
- Pierwsze trzy to objawy choroby. Na przykład 1 0 0 0 0 0 oznacza, że pacjent ma wysoką temperaturę, podczas gdy 0 1 0 0 0 0 oznacza kaszel, 1 1 0 0 0 0 oznacza kaszel i wysoką gorączkę itp.
- Ostatnie trzy cechy to objawy „przeciw”; gdy pacjent ma jeden z nich, jest mniej prawdopodobne, że jest chory. Na przykład 0 0 0 1 0 0 oznacza, że pacjent ma szczepionkę przeciw grypie. Możliwe są kombinacje dwóch zestawów cech: 0 1 0 1 0 0 oznacza szczepionego pacjenta z kaszlem i tak dalej.
Pacjenta uznamy za chorego, gdy ma co najmniej dwie z trzech pierwszych cech, a zdrowego, jeśli ma co najmniej dwie z pozostałych trzech (z przerwami więzów na korzyść pacjentów zdrowych), np.:
- 111000, 101000, 110000, 011000, 011100 = chory
- 000111, 001110, 000101, 000011, 000110 = zdrowy
Wytrenujemy autoenkoder (używający wstecznej propagacji błędów) z sześcioma jednostkami wejściowymi i sześcioma jednostkami wyjściowymi, ale tylko dwiema jednostkami ukrytymi .
Po kilkuset iteracjach obserwujemy, że gdy każda z „chorych” próbek jest prezentowana w sieci uczenia maszynowego, jedna z dwóch jednostek ukrytych (ta sama jednostka dla każdej „chorej” próbki) zawsze wykazuje wyższą wartość aktywacji niż inny. Wręcz przeciwnie, gdy prezentowana jest „zdrowa” próbka, druga ukryta jednostka ma wyższą aktywację.
Powrót do uczenia maszynowego
Zasadniczo, nasze dwie ukryte jednostki nauczyły się zwięzłej reprezentacji zestawu danych dotyczących objawów grypy. Aby zobaczyć, jak to się ma do uczenia się, wracamy do problemu overfittingu. Ucząc naszą sieć, aby nauczyła się zwięzłej reprezentacji danych, faworyzujemy prostszą reprezentację zamiast wysoce złożonej hipotezy, która przesadnie pasuje do danych uczących.
W pewnym sensie, faworyzując te prostsze reprezentacje, próbujemy poznać dane w prawdziwszym sensie.
Ograniczone maszyny Boltzmann
Następnym logicznym krokiem jest spojrzenie na ograniczone maszyny Boltzmanna (RBM), generatywną stochastyczną sieć neuronową, która może nauczyć się rozkładu prawdopodobieństwa na zbiorze danych wejściowych .

RBM składają się z warstwy ukrytej, widocznej i stronniczości. W przeciwieństwie do sieci feedforward, połączenia między warstwą widoczną i ukrytą są nieskierowane (wartości mogą być propagowane zarówno w kierunku widoczny-do-ukrytym, jak i ukryty-do-widocznym) i w pełni połączone (każda jednostka z danej warstwy jest połączona z każda jednostka w następnej — gdybyśmy pozwolili dowolnej jednostce w dowolnej warstwie połączyć się z dowolną inną warstwą, mielibyśmy maszynę Boltzmanna (a nie ograniczoną maszynę Boltzmanna ).
Standardowy RBM ma binarne ukryte i widoczne jednostki: to znaczy, że aktywacja jednostki wynosi 0 lub 1 w rozkładzie Bernoulliego, ale istnieją warianty z innymi nieliniowościami.
Chociaż naukowcy wiedzieli o RBM od jakiegoś czasu, niedawne wprowadzenie algorytmu treningu nienadzorowanej rozbieżności kontrastowej ponownie wzbudziło zainteresowanie.
Rozbieżność kontrastowa
Jednoetapowy algorytm kontrastywnej dywergencji (CD-1) działa w następujący sposób:
- Faza pozytywna :
- Próbka wejściowa v jest dociskana do warstwy wejściowej.
- v jest propagowany do warstwy ukrytej w podobny sposób jak w sieciach ze sprzężeniem do przodu. Wynikiem aktywacji warstwy ukrytej jest h .
- Faza negatywna :
- Propaguj h z powrotem do widocznej warstwy z wynikiem v' (połączenia między widoczną i ukrytą warstwą są nieskierowane, co pozwala na ruch w obu kierunkach).
- Propaguj nowe v' z powrotem do ukrytej warstwy z wynikiem aktywacji h' .
Aktualizacja wagi :
Gdzie a jest szybkością uczenia się, a v , v' , h , h' i w są wektorami.
Intuicja stojąca za algorytmem jest taka, że faza dodatnia ( h podane v ) odzwierciedla wewnętrzną reprezentację sieci danych ze świata rzeczywistego . Tymczasem faza negatywna reprezentuje próbę odtworzenia danych w oparciu o tę wewnętrzną reprezentację ( v' podane h ). Głównym celem jest, aby generowane dane były jak najbardziej zbliżone do rzeczywistego świata , co znajduje odzwierciedlenie w formule aktualizacji wagi.
Innymi słowy, sieć ma pewne wyobrażenie o tym, jak dane wejściowe mogą być reprezentowane, więc próbuje odtworzyć dane w oparciu o tę percepcję. Jeśli jego reprodukcja nie jest wystarczająco zbliżona do rzeczywistości, dokonuje korekty i próbuje ponownie.
Powrót do grypy
Aby zademonstrować rozbieżność kontrastową, użyjemy tego samego zestawu danych dotyczących objawów, co poprzednio. Sieć testowa to RBM z sześcioma widocznymi i dwiema ukrytymi jednostkami. Wytrenujemy sieć za pomocą kontrastowej dywergencji z objawami v przyczepionymi do widocznej warstwy. Podczas badania objawy są ponownie prezentowane na widocznej warstwie; następnie dane są propagowane do warstwy ukrytej. Ukryte jednostki reprezentują stan chorobowy/zdrowy, bardzo podobną architekturę do autoenkodera (rozprowadzanie danych z warstwy widocznej do warstwy ukrytej).
Po kilkuset iteracjach możemy zaobserwować ten sam wynik, co w przypadku autokoderów: jedna z ukrytych jednostek ma wyższą wartość aktywacji, gdy prezentowana jest dowolna z „chorych” próbek, podczas gdy druga jest zawsze bardziej aktywna dla próbek „zdrowych”.
Przykład ten można zobaczyć w akcji w metodzie testContrastiveDivergence .
Głębokie sieci
Wykazaliśmy teraz, że ukryte warstwy autokoderów i RBM działają jak skuteczne wykrywacze funkcji; ale rzadko możemy bezpośrednio korzystać z tych funkcji. W rzeczywistości powyższy zestaw danych jest raczej wyjątkiem niż regułą. Zamiast tego musimy znaleźć sposób na pośrednie wykorzystanie tych wykrytych funkcji.
Na szczęście odkryto, że struktury te można układać w stosy, tworząc głębokie sieci. Sieci te można łapczywie trenować, jedna warstwa po warstwie, aby pomóc przezwyciężyć zanikający gradient i problemy związane z przesadnym dopasowaniem związane z klasyczną propagacją wsteczną.
Powstałe struktury są często dość potężne, dając imponujące rezultaty. Weźmy na przykład słynną gazetę Google o „kotach”, w której używają oni specjalnego rodzaju głębokich autokoderów do „uczenia się” rozpoznawania twarzy ludzi i kotów na podstawie nieoznakowanych danych.
Przyjrzyjmy się bliżej.
Autokodery ułożone w stos
Jak sama nazwa wskazuje, sieć ta składa się z wielu ułożonych w stos autokoderów.
Ukryta warstwa autokodera t działa jako warstwa wejściowa dla autokodera t + 1 . Warstwa wejściowa pierwszego autokodera jest warstwą wejściową dla całej sieci. Chciwy trening warstwowy działa tak:
- Trenuj pierwszy autoenkoder ( t=1 lub czerwone połączenia na powyższym rysunku, ale z dodatkową warstwą wyjściową) indywidualnie, stosując metodę wstecznej propagacji błędów ze wszystkimi dostępnymi danymi uczącymi.
- Trenuj drugi autoenkoder t=2 (zielone połączenia). Ponieważ warstwa wejściowa dla t=2 jest warstwą ukrytą dla t=1 , nie interesuje nas już warstwa wyjściowa dla t=1 i usuwamy ją z sieci. Uczenie rozpoczyna się od przyciśnięcia próbki wejściowej do warstwy wejściowej t=1 , która jest propagowana do warstwy wyjściowej t=2 . Następnie wagi (wejście-ukryte i ukryte-wyjście) t=2 są aktualizowane przy użyciu wstecznej propagacji. t=2 wykorzystuje wszystkie próbki uczące, podobnie jak t=1 .
- Powtórz poprzednią procedurę dla wszystkich warstw (tj. usuń warstwę wyjściową poprzedniego autokodera, zastąp ją jeszcze innym autoenkoderem i trenuj z propagacją wsteczną).
- Kroki 1-3 nazywane są treningiem wstępnym i pozostawiają wagi prawidłowo zainicjowane. Jednak nie ma mapowania między danymi wejściowymi a etykietami wyjściowymi. Na przykład, jeśli sieć jest nauczona rozpoznawania obrazów odręcznych cyfr, nadal nie jest możliwe zmapowanie jednostek z ostatniego detektora cech (tj. warstwy ukrytej ostatniego autokodera) na typ cyfry obrazu. W takim przypadku najczęstszym rozwiązaniem jest dodanie jednej lub więcej w pełni połączonych warstw do ostatniej warstwy (niebieskie połączenia). Cała sieć może być teraz postrzegana jako wielowarstwowy perceptron i jest trenowana za pomocą wstecznej propagacji (ten krok jest również nazywany dostrajaniem ).
Ułożone w stos autoenkodery mają zatem zapewnić skuteczną metodę wstępnego uczenia w celu inicjowania wag sieci, pozostawiając złożony, wielowarstwowy perceptron, który jest gotowy do trenowania (lub dostrajania ).
Sieci głębokich przekonań
Podobnie jak w przypadku autokoderów, możemy również układać maszyny Boltzmanna, aby stworzyć klasę znaną jako głębokie sieci przekonań (DBN) .
W tym przypadku ukryta warstwa RBM t działa jako widoczna warstwa dla RBM t+1 . Warstwa wejściowa pierwszego RBM jest warstwą wejściową dla całej sieci, a zachłanne wstępne szkolenie w warstwie działa tak:
- Wytrenuj pierwszy RBM t=1 , stosując kontrastową rozbieżność ze wszystkimi próbkami uczącymi.
- Wytrenuj drugi RBM t=2 . Ponieważ widoczna warstwa dla t=2 jest warstwą ukrytą dla t=1 , uczenie rozpoczyna się od przyciśnięcia próbki wejściowej do widocznej warstwy t=1 , która jest propagowana do warstwy ukrytej t=1 . Te dane służą następnie do zainicjowania treningu rozbieżności kontrastowej dla t=2 .
- Powtórz poprzednią procedurę dla wszystkich warstw.
- Podobnie jak w przypadku autokoderów ustawionych w stos, po wstępnym nauczeniu sieć można rozszerzyć, łącząc jedną lub więcej w pełni połączonych warstw z końcową warstwą ukrytą RBM. Tworzy to wielowarstwowy perceptron, który można następnie dostroić za pomocą wstecznej propagacji.
Ta procedura jest podobna do tej w przypadku autokoderów ze stosem, ale z autoenkoderami zastąpionymi przez RBM, a propagację wsteczną zastąpiono algorytmem rozbieżności kontrastowej.
(Uwaga: aby uzyskać więcej informacji na temat konstruowania i trenowania ułożonych autokoderów lub sieci głębokich przekonań, zapoznaj się z przykładowym kodem tutaj.)
Sieci konwolucyjne
Jako ostateczną architekturę uczenia głębokiego przyjrzyjmy się sieciom konwolucyjnym, szczególnie interesującej i specjalnej klasie sieci ze sprzężeniem do przodu, które są bardzo dobrze przystosowane do rozpoznawania obrazów.
Zanim przyjrzymy się rzeczywistej strukturze sieci splotowych, najpierw zdefiniujemy filtr obrazu lub obszar kwadratowy z powiązanymi wagami. Na cały obraz wejściowy jest stosowany filtr i często stosuje się wiele filtrów. Na przykład możesz zastosować cztery filtry 6x6 do danego obrazu wejściowego. Następnie piksel wyjściowy o współrzędnych 1,1 jest ważoną sumą kwadratu 6x6 pikseli wejściowych z lewym górnym rogiem 1,1 i wag filtra (co również jest kwadratem 6x6). Piksel wyjściowy 2,1 jest wynikiem kwadratu wejściowego z lewym górnym rogiem 2,1 i tak dalej.
Uwzględniając to, sieci te są zdefiniowane przez następujące właściwości:
- Warstwy splotowe stosują szereg filtrów do danych wejściowych. Na przykład pierwsza splotowa warstwa obrazu może mieć cztery filtry 6x6. Wynik jednego filtra zastosowanego w obrazie nazywa się mapą cech (FM), a liczba map cech jest równa liczbie filtrów. Jeśli poprzednia warstwa jest również splotowa, filtry są nakładane na wszystkie jej FM o różnych wagach, więc każdy wejściowy FM jest połączony z każdym wyjściowym FM. Intuicja stojąca za wspólnymi wagami w całym obrazie polega na tym, że cechy zostaną wykryte niezależnie od ich lokalizacji, podczas gdy mnogość filtrów pozwala każdemu z nich wykryć inny zestaw cech.
- Warstwy podpróbkowania zmniejszają rozmiar danych wejściowych. Na przykład, jeśli wejście składa się z obrazu 32x32, a warstwa ma obszar podpróbkowania 2x2, wartość wyjściowa będzie obrazem 16x16, co oznacza, że 4 piksele (każdy kwadrat 2x2) obrazu wejściowego są łączone w jeden wynik piksel. Istnieje wiele sposobów na podpróbkowanie, ale najbardziej popularne to łączenie maksymalne, średnie i stochastyczne.
- Ostatnia warstwa podpróbkowania (lub splotowa) jest zwykle połączona z jedną lub większą liczbą w pełni połączonych warstw, z których ostatnia reprezentuje dane docelowe.
- Uczenie odbywa się przy użyciu zmodyfikowanej propagacji wstecznej, która uwzględnia warstwy podpróbkowania i aktualizuje wagi filtra splotowego na podstawie wszystkich wartości, do których ten filtr jest stosowany.
Możesz zobaczyć kilka przykładów sieci splotowych wytrenowanych (z wsteczną propagacją błędów) na zbiorze danych MNIST (obrazy w skali szarości odręcznych liter) tutaj, a konkretnie w metodach testLeNet* (polecam testLeNetTiny2 ponieważ osiąga niski poziom błędu około 2% w stosunkowo krótkim czasie). Jest tu też ładna wizualizacja JavaScript podobnej sieci.
Realizacja
Teraz, gdy omówiliśmy najczęstsze warianty sieci neuronowych, pomyślałem, że napiszę trochę o wyzwaniach związanych z implementacją tych struktur głębokiego uczenia.
Ogólnie mówiąc, moim celem przy tworzeniu biblioteki Deep Learning było (i nadal jest) zbudowanie struktury opartej na sieci neuronowej, która spełniałaby następujące kryteria:
- Wspólna architektura, która jest w stanie reprezentować różne modele (na przykład wszystkie warianty sieci neuronowych, które widzieliśmy powyżej).
- Umiejętność korzystania z różnorodnych algorytmów treningowych (propagacja wsteczna, dywergencja kontrastywna itp.).
- Przyzwoita wydajność.
Aby spełnić te wymagania, przyjąłem warstwowe (lub modułowe) podejście do projektowania oprogramowania.
Struktura
Zacznijmy od podstaw:
- NeuralNetworkImpl to klasa podstawowa dla wszystkich modeli sieci neuronowych.
- Każda sieć zawiera zestaw warstw.
- Każda warstwa ma listę połączeń, gdzie połączenie jest połączeniem między dwiema warstwami, tak że sieć jest ukierunkowanym grafem acyklicznym.
Ta struktura jest wystarczająco elastyczna, aby można ją było wykorzystać w klasycznych sieciach typu feedforward, a także w RBM i bardziej złożonych architekturach, takich jak ImageNet.
Pozwala także warstwie być częścią więcej niż jednej sieci. Na przykład warstwy w Deep Belief Network są również warstwami w odpowiadających im RBM.
Ponadto ta architektura umożliwia przeglądanie DBN jako listy ułożonych w stos RBM podczas fazy szkolenia wstępnego oraz sieci ze sprzężeniem do przodu podczas fazy dostrajania, co jest zarówno intuicyjnie przyjemne, jak i programowo wygodne.
Propagacja danych
Kolejny moduł zajmuje się propagacją danych w sieci, dwuetapowym procesem:
- Określ kolejność warstw. Na przykład, aby uzyskać wyniki z wielowarstwowego perceptronu, dane są „zaciskane” w warstwie wejściowej (stąd jest to pierwsza warstwa do obliczenia) i propagowane aż do warstwy wyjściowej. Aby zaktualizować wagi podczas wstecznej propagacji błędów, błąd wyjściowy musi być propagowany przez każdą warstwę w kolejności wszerz, zaczynając od warstwy wyjściowej. Osiąga się to za pomocą różnych implementacji LayerOrderStrategy , które wykorzystują strukturę grafową sieci, wykorzystując różne metody przechodzenia przez grafy. Niektóre przykłady obejmują strategię wszerz i kierowanie na określoną warstwę. Kolejność jest w rzeczywistości określana przez połączenia między warstwami, więc strategie zwracają uporządkowaną listę połączeń.
- Oblicz wartość aktywacji. Każda warstwa ma skojarzony ConnectionCalculator , który pobiera listę połączeń (z poprzedniego kroku) i wartości wejściowe (z innych warstw) i oblicza wynikową aktywację. Na przykład, w prostej sigmoidalnej sieci ze sprzężeniem do przodu, Kalkulator Połączeń warstwy ukrytej pobiera wartości warstw wejściowych i warstw odchylenia (które są odpowiednio danymi wejściowymi i tablicą 1s ) oraz wagi między jednostkami (w przypadku pełnego połączenia warstwy, wagi są faktycznie przechowywane w połączeniu FullyConnected jako Matrix ), oblicza sumę ważoną i podaje wynik do funkcji sigmoidalnej. Kalkulatory połączeń implementują różne funkcje transferu (np. suma ważona, splotowa) i aktywacji (np. logistyczne i tanh dla perceptronu wielowarstwowego, binarne dla RBM). Większość z nich może być wykonywana na GPU za pomocą Aparapi i może być używana z treningiem mini-batch.
Obliczenia GPU za pomocą Aparapi
Jak wspomniałem wcześniej, jednym z powodów, dla których sieci neuronowe odrodziły się w ostatnich latach, jest to, że ich metody treningowe bardzo sprzyjają paralelizmowi, co pozwala znacznie przyspieszyć trening z wykorzystaniem GPGPU. W tym przypadku wybrałem pracę z biblioteką Aparapi, aby dodać obsługę GPU.
Aparapi nakłada kilka ważnych ograniczeń na kalkulatory połączeń:
- Dozwolone są tylko jednowymiarowe tablice (i zmienne) pierwotnych typów danych.
- Tylko metody członkowskie samej klasy jądra Aparapi mogą być wywoływane z kodu wykonywalnego GPU.
W związku z tym większość danych (wagi, tablice wejściowe i wyjściowe) jest przechowywana w instancjach Matrix , które wewnętrznie używają jednowymiarowych tablic zmiennoprzecinkowych. Wszystkie kalkulatory połączeń Aparapi używają albo AparapiWeightedSum (dla w pełni połączonych warstw i funkcji wprowadzania sum ważonych), AparapiSubsampling2D (dla warstw podpróbkowania) lub AparapiConv2D (dla warstw splotowych). Niektóre z tych ograniczeń można przezwyciężyć, wprowadzając architekturę systemu heterogenicznego. Aparapi also allows to run the same code on both CPU and GPU.
Szkolenie
The training module implements various training algorithms. It relies on the previous two modules. For example, BackPropagationTrainer (all the trainers are using the Trainer base class) uses feedforward layer calculator for the feedforward phase and a special breadth-first layer calculator for propagating the error and updating the weights.
My latest work is on Java 8 support and some other improvements, will soon be merged into master.
Wniosek
The aim of this Java deep learning tutorial was to give you a brief introduction to the field of deep learning algorithms, beginning with the most basic unit of composition (the perceptron) and progressing through various effective and popular architectures, like that of the restricted Boltzmann machine.
The ideas behind neural networks have been around for a long time; but today, you can't step foot in the machine learning community without hearing about deep networks or some other take on deep learning. Hype shouldn't be mistaken for justification, but with the advances of GPGPU computing and the impressive progress made by researchers like Geoffrey Hinton, Yoshua Bengio, Yann LeCun and Andrew Ng, the field certainly shows a lot of promise. There's no better time to get familiar and get involved like the present.
Appendix: Resources
If you're interested in learning more, I found the following resources quite helpful during my work:
- DeepLearning.net: a portal for all things deep learning. It has some nice tutorials, software library and a great reading list.
- An active Google+ community.
- Two very good courses: Machine Learning and Neural Networks for Machine Learning, both offered on Coursera.
- The Stanford neural networks tutorial.