100796635

Page 1


Spis treści

Przedmowa

Podziękowania

O tej książce

O autorze

1. Wprowadzenie do głębokiego uczenia: dlaczego warto się tego nauczyć

W tym rozdziale

Zapraszamy do grokowania głębokiego uczenia

Dlaczego warto opanować głębokie uczenie się

Czy trudno będzie się tego nauczyć?

Dlaczego powinieneś przeczytać tę książkę

Co jest potrzebne, aby zacząć

potrzebna jest jakaś znajomość Pythona

Podsumowanie

2. Podstawowe koncepcje: jak maszyny się uczą?

tym rozdziale

5.

Jak obrazować sieci neuronowe:

9. Modelowanie prawdopodobieństwa i nieliniowość:

10. Neuronowe uczenie się krawędzi i narożników: wprowadzenie

11. Sieci neuronowe rozumiejące język:

12. Sieci neuronowe piszące jak Szekspir:

Czym jest sieć neuronowa?

Oto nasza pierwsza sieć neuronowa.

Aby uruchomić sieć neuronową, otwieramy notatnik Jupyter i uruchamiamy poniższy kod:

weight = 0.1

def neural_network(input, weight):

prediction = input * weight

return prediction

Następnie uruchamiamy poniższy fragment:

number_of_toes = [8.5, 9.5, 10, 9]

input = number_of_toes[0]

pred = neural_network(input,weight) print(pred)

Sieć

Jak użyć sieci, aby coś przewidzieć

Właśnie zbudowaliśmy naszą pierwszą sieć neuronową i użyliśmy jej do przewidywań! Gratulacje! Ostatni wiersz powoduje wypisanie prognozy (pred). Powinna ona wynosić 0.85. Zatem czym jest sieć neuronowa? Na razie jest to jedna lub więcej wag, przez które możemy mnożyć dane wejściowe, aby uzyskać prognozę.

Czym są dane wejściowe?

Jest to liczba, którą zarejestrowaliśmy gdzieś w rzeczywistym świecie. Zazwyczaj jest to coś łatwo dostępnego, jak dzisiejsza temperatura, średnia uderzeń baseballisty (BA) czy wczorajsza cena jakiejś akcji.

Czym jest prognoza?

Prognoza jest tym, co sieć neuronowa może nam powiedzieć na podstawie otrzymanych danych wejściowych, na przykład „dla podanej temperatury istnieje 0% prawdopodobieństwa, że ludzie będą dziś nosić dresy” albo „dla podanego BA danego gracza istnieje 30% prawdopodobieństwa, że uderzenie doprowadzi do home run”, albo „na podstawie wczorajszej ceny akcji dzisiejsza cena będzie wynosić 101,52”.

Czy te prognozy są zawsze poprawne?

Nie. Sieci neuronowe będą popełniać błędy, ale mogą się na nich uczyć. Dla przykładu jeśli uzyskana prognoza będzie zbyt wysoka, sieć skoryguje swoją wagę, aby następnym razem podać niższą prognozę i vice versa.

Rozdział 3 Wprowadzenie do prognozowania neuronowego

Jak sieć się uczy?

Metodą prób i błędów! Najpierw próbuje dokonać jakiejś prognozy. Następnie może stwierdzić, czy prognoza ta była zbyt wysoka, czy zbyt niska. Następnie zmienia wagę (w górę lub w dół), aby przewidywać bardziej dokładnie następnym razem, gdy otrzyma te same dane wejściowe.

Co robi ta sieć neuronowa?

Mnoży dane wejściowe przez wagę. „Skaluje” dane wejściowe o określony czynnik.

W poprzednim podpunkcie wykonaliśmy pierwszą prognozę przy użyciu sieci neuronowej. W swej najprostszej postaci sieć neuronowa wykorzystuje siłę mnożenia. Przyjmuje wejściową obserwację (w tym przypadku liczbę 8,5) i mnoży ją przez wagę. Gdyby waga była równa 2, wówczas sieć neuronowa podwoiłaby dane wejściowe. Jeśli waga to 0,01, sieć podzieli dane wejściowe przez 100. Jak można zauważyć, niektóre wartości wagi powiększają, a inne zmniejszają dane wejściowe.

 Pusta sieć

Tędy wchodzą dane wejściowe

Tu pojawiają się prognozy

weight = 0.1 def neural_network(input, weight): prediction = input * weight return prediction .1

# toes win?

Interfejs sieci neuronowej jest prosty. Akceptuje ona zmienną input jako informację oraz zmienną weight (waga) jako wiedzę, po czym zwraca prediction (prognozę). Każda sieć neuronowa, jaką kiedykolwiek spotkamy, będzie działać w ten sposób. Wykorzystuje wiedzę zawartą w wagach do interpretowania informacji w danych wejściowych. Później zobaczymy sieci neuronowe akceptujące większe, bardziej złożone wartości zmiennych input i weight, ale w tle zawsze będą te same przesłanki.

 Wstawianie jednej obserwacji wejściowej

Dane wejściowe (# toes)

8.5

number_of_toes = [8.5, 9.5, 10, 9] input = number_of_toes[0] pred = neural_network(input, weight) print(pred) .1

Czym jest funkcja aktywacji?

Jest to funkcja stosowana do neuronów pewnej warstwy podczas prognozowania.

Funkcja aktywacji jest funkcją stosowaną do neuronów warstwy podczas prognozowania. Powinno to wyglądać znajomo, gdyż używaliśmy już funkcji aktywacji o nazwie relu (pokazanej obok w trójwarstwowej sieci neuronowej). Funkcja relu powodowała zmianę wszystkich wartości ujemnych na 0.

Upraszczając, funkcja aktywacji to dowolna funkcja, która może przyjąć jedną wartość i zwrócić inną. Jednak liczba najróżniejszych istniejących funkcji jest nieskończona i zdecydowanie nie wszystkie nadają się do wykorzystania jako funkcje aktywacji.

weights_1_2

layer_2

weights_0_1 relu

layer_1

layer_0

Istnieje kilka ograniczeń zapewniających, że jakaś funkcja może być funkcją aktywacji. Jak się przekonamy, używanie funkcji spoza tych ograniczeń jest zazwyczaj kiepskim pomysłem.

Ograniczenie 1: Funkcja musi być ciągła i mieć

nieograniczoną dziedzinę.

Pierwsze ograniczenie budujące właściwą funkcję aktywacji jest takie, że musi ona zwracać wartość wyjściową dla dowolnej wartości wejściowej. Mówiąc inaczej, nie możemy być w stanie znaleźć takiej liczby, dla której z jakiegoś powodu nie istniałaby wartość tej funkcji.

To mała przesada, ale czy widać, że funkcja po lewej (cztery oddzielne linie) nie ma wartości y dla każdej wartości x? Jest zdefiniowana tylko dla czterech przedziałów.

(y = x * x)

y (wartość wyjściowa)

y (wartość wyjściowa)

x (wartość wejściowa)

x (wartość wejściowa)

Czym jest funkcja aktywacji?

Byłaby to koszmarna funkcja aktywacji. Natomiast funkcja po prawej jest ciągła i ma nieograniczoną dziedzinę. Nie istnieje taka wartość wejściowa (x ), dla której nie moglibyśmy obliczyć wyjścia (y).

Ograniczenie 2: Dobre funkcje aktywacji są monotoniczne, nigdy nie zmieniają kierunku.

Drugie ograniczenie mówi, że funkcja jest monotoniczna. Nigdy nie może zmienić kierunku. Innymi słowy, musi albo zawsze być nierosnąca, albo zawsze niemalejąca1.

Jako przykład rozważmy poniższe dwie funkcje. Ich kształty odpowiadają na pytanie „Jeśli x to wejście, jaką wartość y opisuje ta funkcja?”. Funkcja po lewej (y = x * x) nie jest idealną funkcją aktywacji, gdyż nie jest stale nierosnąca ani stale niemalejąca.

Jak możemy to stwierdzić? Zauważmy, że mamy tu wiele przypadków, w których dwóm (różnym) wartościom x odpowiada pojedyncza wartość y (jest to prawdą dla każdej liczby z wyjątkiem 0). Natomiast funkcja po prawej jest zawsze rosnąca! Nie istnieje taki punkt, dla którego dwie wartości x dawałyby tę samą wartość y:

(y = x * x)

y (wartość wyjściowa)

(y = x)

y (wartość wyjściowa)

x (wartość wejściowa)

x (wartość wejściowa)

To konkretne ograniczenie nie jest wymagane z technicznego punktu widzenia. W przeciwieństwie do funkcji, dla których brakuje pewnych wartości (o nieciągłej dziedzinie), można optymalizować funkcje, które nie są monotoniczne. Trzeba jednak rozważyć implikacje sytuacji, gdy wiele wartości wejściowych przekształcanych jest na tę samą wartość wyjściową.

Podczas uczenia się sieci neuronowej poszukujemy właściwych konfiguracji wag, zapewniających określone wyjście. Problem ten może stać się znacznie trudniejszy, jeśli będzie istnieć wiele poprawnych odpowiedzi. Jeśli istnieje wiele sposobów

1 W niektórych architekturach sieci neuronowych (np. RBF) możemy jednak spotkać niemonotoniczne funkcje aktywacji, lecz takie sieci wymagają specyficznego procesu uczenia (przyp. red.).

Macierze i relacje macierzowe

Tłumaczenie świateł ulicznych na matematykę.

Matematyka nie rozpoznaje świateł ulicznych. Jak wspomniałem w poprzednim punkcie, chcemy nauczyć sieć neuronową, aby tłumaczyła wzorzec świateł ulicznych na właściwy wzorzec stój/idź. Istotnym słowem w tym stwierdzeniu jest wzorzec. Tym, co naprawdę chcemy zrobić, jest przedstawienie wzorców świateł ulicznych jako liczb. Spróbuję pokazać, co mam na myśli.

Światła uliczne Wzorzec świateł

Zauważmy, że pokazany tu wzorzec liczb naśladuje wzorzec świateł ulicznych przez jedynki i zera. Każdemu światłu odpowiada jego kolumna (w sumie trzy kolumny, ponieważ mamy trzy światła). Zauważmy też, że jest tu sześć wierszy, odpowiadających sześciu różnym obserwowanym kombinacjom świateł.

Taką strukturę jedynek i zer nazywamy macierzą. Taka zależność między wierszami i kolumnami jest typowa dla macierzy, szczególnie dla macierzy danych (jak nasze światła).

W macierzach danych stosowana jest konwencja przypisująca pojedynczy wiersz każdemu zarejestrowanemu przykładowi. Konwencja ta nakazuje też przypisanie pojedynczej kolumnykażdej rejestrowanej rzeczy (zjawisku). Dzięki temu macierze są łatwe do odczytania.

Tak więc kolumna zawiera każdy stan, jaki został zarejestrowany dla danej rzeczy. W tym przypadku kolumna zawiera każdy stan włączone/wyłączone dla określonego światła. Każdy wiersz zawiera jednoczesne stany wszystkich świateł w określonej chwili. Ponownie taki układ jest typowy.

Dobre macierze danych doskonale naśladują świat zewnętrzny.

Macierz danych nie musi składać się jedynie z jedynek i zer. Co by było, gdyby nasze światła były podłączone przez ściemniacze i włączały się oraz wyłączały na różnych poziomach intensywności? Macierz świateł mogłaby wówczas wyglądać raczej tak, jak poniżej:

Światła uliczne

Macierz świateł A

Macierz A jest całkowicie poprawna. Naśladuje ona wzorce, które istnieją w świecie rzeczywistym (światła uliczne), zatem możemy zażądać, aby komputer je zinterpretował. Czy kolejna macierz również będzie poprawna?

Światła uliczne

Macierz świateł B

Macierz B jest poprawna. Odpowiednio przechwytuje ona zależności między różnymi przykładami szkoleniowymi (wiersze) i światłami (kolumny). Zauważmy, że ( ). Oznacza to, że macierze te są skalarnymi wielokrotnościami siebie nawzajem.

Wykorzystanie autograd do uczenia sieci

neuronowej

Nie musimy już pisać logiki propagacji wstecznej!

Wszystko to może wydawać się sporym kawałkiem pracy inżynierskiej, ale wysiłek ten się opłaca. Teraz, gdy uczymy sieć neuronową, nie musimy już pisać żadnej logiki propagacji wstecznej! Jako szkolny przykład, oto sieć neuronowa z ręcznie dodaną propagacją wsteczną:

import numpy np.random.seed(0)

data = np.array([[0,0],[0,1],[1,0],[1,1]])

target = np.array([[0],[1],[0],[1]])

weights_0_1 = np.random.rand(2,3) weights_1_2 = np.random.rand(3,1)

for i in range(10):

layer_1 = data.dot(weights_0_1) Przewidywanie layer_2 = layer_1.dot(weights_1_2)

diff = (layer_2 - target) Porównanie sqdiff = (diff * diff) loss = sqdiff.sum(0) Średni błąd kwadratowy (strata)

layer_1_grad = diff.dot(weights_1_2.transpose()) weight_1_2_update = layer_1.transpose().dot(diff) weight_0_1_update = data.transpose().dot(layer_1_grad)

weights_1_2 -= weight_1_2_update * 0.1

weights_0_1 -= weight_0_1_update * 0.1 print(loss[0])

0.4520108746468352

0.33267400101121475

0.25307308516725036

0.1969566997160743

0.15559900212801492

0.12410658864910949

0.09958132129923322

0.08019781265417164

0.06473333002675746

0.05232281719234398

Uczenie się; tu następuje propagacja wsteczna

Musimy wykonywać propagację w przód w taki sposób, by layer_1, layer_2 oraz diff istniały jako zmienne, gdyż potrzebujemy ich później. Następnie musimy propagować wstecznie każdy gradient przez odpowiednią macierz wag i wykonać aktualizację wag we właściwy sposób.

Rozdział 13 Przedstawiamy automatyczną optymalizację

import numpy np.random.seed(0)

data = Tensor(np.array([[0,0],[0,1],[1,0],[1,1]]), autograd=True)

target = Tensor(np.array([[0],[1],[0],[1]]), autograd=True)

w = list()

w.append(Tensor(np.random.rand(2,3), autograd=True))

w.append(Tensor(np.random.rand(3,1), autograd=True))

for i in range(10):

pred = data.mm(w[0]).mm(w[1]) Przewidywanie

loss = ((pred - target)*(pred - target)).sum(0) loss.backward(Tensor(np.ones_like(loss.data))) Uczenie się

for w_ in w: w_.data -= w_.grad.data * 0.1 w_.grad.data *= 0

print(loss)

Jednak przy naszym wspaniałym systemie autograd kod jest znacznie prostszy. Nie musimy przechowywać żadnych tymczasowych zmiennych (ponieważ ich śledzeniem zajmuje się dynamiczny graf obliczeń) i nie musimy implementować żadnej logiki propagacji wstecz (gdyż obsługuje to metoda .backward()). Nie tylko jest to wygodniejsze, ale mamy mniejsze możliwości popełniania głupich błędów przy pisaniu kodu propagacji wstecznej, zmniejszając ryzyko zepsucia programu!

[0.58128304]

[0.48988149]

[0.41375111]

[0.34489412]

[0.28210124]

[0.2254484]

[0.17538853]

[0.1324231]

[0.09682769]

[0.06849361]

Zanim przejdziemy dalej, chciałbym wypunktować ważną cechę stylistyczną tej nowej implementacji. Zwróćmy uwagę, że wszystkie parametry umieściłem w liście, po której mogę iterować podczas wykonywania aktualizacji wag. Jest to zapowiedź kolejnego elementu funkcjonalności. Gdy mamy już system autograd, implementacja stochastycznej metody gradientowej staje się trywialna (to po prostu ta pętla for na końcu ostatniego fragmentu kodu). Spróbujmy uczynić z niej jej własną klasę.

Porównanie

Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.