Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00172 005901 19007134 na godz. na dobę w sumie
Uczenie głębokie od zera. Podstawy implementacji w Pythonie - książka
Uczenie głębokie od zera. Podstawy implementacji w Pythonie - książka
Autor: Liczba stron: 224
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-6597-1 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> bazy danych >> inne
Porównaj ceny (książka, ebook (-30%), audiobook).

Uczenie głębokie (ang. deep learning) zyskuje ostatnio ogromną popularność. Jest to ściśle związane z coraz częstszym zastosowaniem sieci neuronowych w przeróżnych branżach i dziedzinach. W konsekwencji inżynierowie oprogramowania, specjaliści do spraw przetwarzania danych czy osoby w praktyce zajmujące się uczeniem maszynowym muszą zdobyć solidną wiedzę o tych zagadnieniach. Przede wszystkim trzeba dogłębnie zrozumieć podstawy uczenia głębokiego. Dopiero po uzyskaniu biegłości w posługiwaniu się poszczególnymi koncepcjami i modelami możliwe jest wykorzystanie w pełni potencjału tej dynamicznie rozwijającej się technologii.

Ten praktyczny podręcznik, poświęcony podstawom uczenia głębokiego, zrozumiale i wyczerpująco przedstawia zasady działania sieci neuronowych z trzech różnych poziomów: matematycznego, obliczeniowego i konceptualnego. Takie podejście wynika z faktu, że dogłębne zrozumienie sieci neuronowych wymaga nie jednego, ale kilku modeli umysłowych, z których każdy objaśnia inny aspekt działania tych sieci. Zaprezentowano tu również techniki implementacji poszczególnych elementów w języku Python, co pozwala utworzyć działające sieci neuronowe. Dzięki tej książce stanie się jasne, w jaki sposób należy tworzyć, uczyć i stosować wielowarstwowe, konwolucyjne i rekurencyjne sieci neuronowe w różnych praktycznych zastosowaniach.

W książce między innymi:

Uczenie głębokie: zrozum, zanim zaimplementujesz!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: Deep Learning from Scratch: Building with Python from First Principles Tłumaczenie: Tomasz Walczak ISBN: 978-83-283-6597-1 © 2020 Helion SA Authorized Polish translation of the English edition of Deep Learning from Scratch ISBN 9781492041412 © 2019 Seth Weidman This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Deep Learning from Scratch, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Helion SA dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Helion SA nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Helion SA ul. Kościuszki 1c, 44-100 Gliwice tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/uczgle Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis treści Wprowadzenie ............................................................................................................ 9 Funkcje Pochodne Matematyka Diagramy Kod Matematyka Diagramy Kod Diagram Matematyka Kod Inny diagram 1. Podstawowe zagadnienia ......................................................................................... 15 16 16 16 17 20 20 20 21 22 22 22 23 23 24 24 24 25 27 27 27 28 29 30 30 30 Matematyka Diagram Kod Matematyka Diagram Kod Matematyka Diagram Kod Funkcje z wieloma danymi wejściowymi Funkcje zagnieżdżone Reguła łańcuchowa Nieco dłuższy przykład 3 Poleć książkęKup książkę Pochodne funkcji z wieloma danymi wejściowymi Diagram Matematyka Kod Funkcje przyjmujące wiele wektorów jako dane wejściowe Matematyka Tworzenie nowych cech na podstawie istniejących Matematyka Diagram Kod Pochodne funkcji z wieloma wektorami wejściowymi Diagram Matematyka Kod Następny etap — funkcje wektorowe i ich pochodne Diagram Matematyka Kod Funkcje wektorowe i ich pochodne w kroku wstecz Grafy obliczeniowe z danymi wejściowymi w postaci dwóch macierzy dwuwymiarowych Matematyka Diagram Kod Ciekawa część — krok wstecz Diagram Matematyka Kod Podsumowanie 31 31 31 32 32 33 33 33 33 34 35 35 36 36 37 37 37 38 38 40 41 43 43 43 44 44 46 50 Wstęp do uczenia nadzorowanego Modele uczenia nadzorowanego Regresja liniowa 2. Wprowadzenie do budowania modeli ........................................................................ 51 52 53 55 55 57 58 58 59 59 60 Regresja liniowa — diagram Regresja liniowa — bardziej pomocny diagram (i obliczenia matematyczne) Dodawanie wyrazu wolnego Regresja liniowa — kod Uczenie modelu Obliczanie gradientów — diagram Obliczanie gradientów — matematyka (i trochę kodu) 4  Spis treści Poleć książkęKup książkę Obliczanie gradientów — (kompletny) kod Używanie gradientów do uczenia modelu Ocena modelu — testowe i treningowe zbiory danych Ocena modelu — kod Analizowanie najważniejszej cechy Budowanie sieci neuronowych od podstaw Krok 1. Zestaw regresji liniowych Krok 2. Funkcja nieliniowa Krok 3. Inna regresja liniowa Diagramy Kod Sieci neuronowe — krok wstecz Uczenie i ocena pierwszej sieci neuronowej Dwa powody, dla których nowy model jest lepszy Podsumowanie 61 62 63 63 65 66 67 67 68 68 70 71 73 74 75 Diagram Kod Elementy sieci neuronowych — warstwy Klasa NeuralNetwork (i ewentualnie inne) Diagramy Elementy z elementów Wzorzec warstwy Warstwa gęsta Definicja procesu deep learning — pierwszy krok Elementy sieci neuronowych — operacje 3. Deep learning od podstaw ......................................................................................... 77 77 79 79 80 82 82 84 86 88 89 89 90 90 92 92 93 95 95 97 98 99 100 Implementowanie treningu na porcjach danych Klasa NeuralNetwork — kod Pierwszy model z dziedziny deep learning (napisany od podstaw) Nauczyciel i optymalizator Optymalizator Nauczyciel Łączenie wszystkich elementów Podsumowanie i dalsze kroki Diagram Kod Klasa Loss Deep learning od podstaw Spis treści  5 Poleć książkęKup książkę Eksperymenty Współczynnik momentum Intuicyjne rozważania na temat sieci neuronowych Funkcja straty — funkcja softmax z entropią krzyżową Komponent nr 1. Funkcja softmax Komponent nr 2. Entropia krzyżowa Uwaga na temat funkcji aktywacji Wstępne przetwarzanie danych Model Eksperyment: wartość straty z użyciem funkcji softmax z entropią krzyżową 4. Rozszerzenia ........................................................................................................... 101 102 104 104 105 108 111 111 112 113 113 114 114 116 116 116 118 119 120 121 122 122 122 123 125 Intuicyjny opis współczynnika momentum Implementowanie współczynnika momentum w klasie Optimizer Eksperyment — algorytm SGD ze współczynnikiem momentum Sposoby zmniejszania współczynnika uczenia Eksperymenty — zmniejszanie współczynnika uczenia Matematyka i kod Eksperymenty — inicjowanie wag Zmniejszanie współczynnika uczenia Dropout Definicja Implementacja Eksperymenty — dropout Podsumowanie Inicjowanie wag Sieci neuronowe i uczenie reprezentacji Inna architektura dla danych graficznych Operacja konwolucji Wielokanałowa operacja konwolucji 5. Konwolucyjne sieci neuronowe ................................................................................ 127 127 128 129 131 131 132 133 Wpływ na implementację Różnice między warstwami konwolucyjnymi a warstwami gęstymi Generowanie predykcji z użyciem warstw konwolucyjnych Warstwy konwolucyjne — warstwa spłaszczania Warstwy agregujące Implementowanie wielokanałowej operacji konwolucji Krok w przód Konwolucja — krok wstecz Porcje danych, konwolucje dwuwymiarowe i operacje wielokanałowe 6  Spis treści 134 135 137 137 140 144 Poleć książkęKup książkę Konwolucje dwuwymiarowe Ostatni element — dodawanie kanałów Używanie nowej operacji do uczenia sieci CNN Operacja Flatten Kompletna warstwa Conv2D Eksperymenty Podsumowanie 145 147 150 150 151 152 153 Powody stosowania sieci RNN Wprowadzenie do sieci RNN Najważniejsze ograniczenie — przetwarzanie odgałęzień Automatyczne różniczkowanie Pisanie kodu do akumulowania gradientów Pierwsza klasa dla sieci RNN — RNNLayer Druga klasa dla sieci RNN — RNNNode Łączenie obu klas Krok wstecz 6. Rekurencyjne sieci neuronowe .................................................................................155 156 158 158 162 163 164 165 166 167 169 170 172 173 175 176 179 Klasa RNNLayer Podstawowe elementy sieci RNNNode Zwykłe węzły RNNNode Ograniczenia zwykłych węzłów RNNNode Pierwsze rozwiązanie — węzły GRUNode Węzły LSTMNode Reprezentacja danych dla opartego na sieci RNN modelu języka naturalnego Sieci RNN — kod na poziomie znaków Inne zadania z obszaru modelowania języka naturalnego Łączenie odmian warstw RNNLayer Łączenie wszystkich elementów Podsumowanie 182 182 183 184 185 7. PyTorch ...................................................................................................................187 187 188 Typ Tensor w bibliotece PyTorch Deep learning z użyciem biblioteki PyTorch Elementy z biblioteki PyTorch — klasy reprezentujące model, warstwę, optymalizator i wartość straty Implementowanie elementów sieci neuronowej za pomocą biblioteki PyTorch — warstwa DenseLayer Przykład — modelowanie cen domów w Bostonie z użyciem biblioteki PyTorch Elementy oparte na bibliotece PyTorch — klasy optymalizatora i wartości straty 189 190 191 192 Spis treści  7 Poleć książkęKup książkę Elementy oparte na bibliotece PyTorch — klasa nauczyciela Sztuczki służące do optymalizowania uczenia w bibliotece PyTorch Sieci CNN w bibliotece PyTorch Klasa DataLoader i transformacje Tworzenie sieci LSTM za pomocą biblioteki PyTorch Postscriptum — uczenie nienadzorowane z użyciem autoenkoderów Uczenie reprezentacji Podejście stosowane w sytuacjach, gdy w ogóle nie ma etykiet Implementowanie autoenkodera za pomocą biblioteki PyTorch Trudniejszy test uczenia nienadzorowanego i rozwiązanie Podsumowanie 193 195 196 198 200 202 203 203 204 209 210 A Skok na głęboką wodę ............................................................................................. 211 211 215 215 Reguła łańcuchowa dla macierzy Gradient dla wartości straty względem wyrazu wolnego Konwolucje z użyciem mnożenia macierzy 8  Spis treści Poleć książkęKup książkę ROZDZIAŁ 3. Deep learning od podstaw Możliwe, że o tym nie wiesz, ale masz już wszystkie matematyczne i konceptualne podstawy, aby poznać odpowiedź na postawione na początku książki najważniejsze pytania o modele z dziedzi- ny deep learning. Wiesz już, jak działają sieci neuronowe (znasz obliczenia związane z mnoże- niem macierzy, wartość straty i pochodne cząstkowe względem wartości straty) i dlaczego używa- ne obliczenia są poprawne (dzięki regule łańcuchowej z analizy matematycznej). Dowiedziałeś się tego, budując sieci neuronowe z użyciem podstawowych zasad. Sieci reprezentowane były jako seria elementów w postaci pojedynczych funkcji matematycznych. W tym rozdziale nauczysz się reprezentować te elementy w formie abstrakcyjnych klas Pythona. Dalej wykorzystasz te klasy do budowania modeli z dziedziny deep learning. Do czasu zakończenia tego rozdziału utworzysz model typu „deep learning od podstaw”. Ponadto przekształcimy opisy sieci neuronowych oparte na elementach w bardziej standardowe opisy modeli z dziedziny deep learning, o których może już słyszałeś. Po lekturze tego rozdziału będziesz na przykład wiedzieć, co oznacza, że model z dziedziny deep learning ma „wiele warstw ukrytych”. Na tym polega prawdziwe zrozumienie tematu — na umiejętności przechodzenia między wysokopoziomowymi opisami a niskopoziomowymi szczegółami operacji. Zacznijmy te- raz drogę do tego przechodzenia. Do tej pory modele były tu opisywane wyłącznie w kategoriach niskopoziomowych operacji. W pierwszej części tego rozdziału taki opis modeli zostanie zastą- piony popularnymi wysokopoziomowymi pojęciami takimi jak warstwy. Te pojęcia później po- zwolą na łatwiejsze opisywanie bardziej skomplikowanych modeli. Definicja procesu deep learning — pierwszy krok Czym jest model z dziedziny „deep learning”? W poprzednim rozdziale model został zdefiniowa- ny jako funkcja matematyczna reprezentowana przez graf obliczeniowy. Przeznaczeniem takiego modelu jest odwzorowywanie danych wejściowych pochodzących ze zbioru danych o wspólnych aspektach (na przykład pojedyncze dane wejściowe reprezentujące różne cechy domów) na dane wyjściowe z powiązanego zbioru (na przykład na ceny tych domów). Odkryliśmy, że jeśli zdefi- niujemy model jako funkcję przyjmującą na wejściu parametry, można dopasować model, by optymalnie opisywał dane. Służy do tego następująca procedura: 77 Poleć książkęKup książkę 1. Wielokrotne przekazywanie obserwacji do modelu i zapisywanie wartości obliczanych w kroku w przód. 2. Obliczanie wartości straty, reprezentującej, jak odległe są predykcje modelu od oczekiwanych danych wyjściowych (zmiennych wyjściowych). 3. Używanie wartości obliczonych w kroku w przód i opartych na regule łańcuchowej obliczeń ustalonych w rozdziale 1., aby obliczyć, w jakim stopniu każdy z wejściowych parametrów wpływa na wartość straty. 4. Modyfikowanie wartości parametrów, tak aby wartość straty zmalała po przekazaniu do mo- delu następnego zbioru obserwacji. Zaczęliśmy od modelu obejmującego serię operacji liniowych przekształcających cechy w od- powiedzi (okazało się to odpowiednikiem tradycyjnego modelu opartego na regresji liniowej). Oczekiwanym ograniczeniem tego podejścia jest to, że nawet po „optymalnym” dopasowaniu model może reprezentować tylko liniowe relacje między cechami a odpowiedziami. Następnie zdefiniowaliśmy strukturę funkcji, w której najpierw wykonywane są operacje liniowe, potem operacja nieliniowa (funkcja sigmoid), a następnie końcowy zestaw operacji liniowych. Okazało się, że po tej modyfikacji model potrafił nauczyć się zależności bardziej zbliżonych do rzeczywistości (czyli nieliniowych relacji między danymi wejściowymi i wyjściowymi), a dodatkową korzyścią była możliwość nauczenia modelu relacji między kombinacjami cech wejściowych a odpowiedziami. Jak łączą się modele tego rodzaju z modelami z dziedziny deep learning? Zaczniemy od nieco nie- zręcznej próby podania definicji: modele z dziedziny deep learning są reprezentowane w formie serii operacji, w której występują przynajmniej dwie nienastępujące po sobie funkcje nieliniowe. Zaraz wyjaśnię, skąd wzięła się ta definicja. Najpierw jednak warto zauważyć, że ponieważ modele z dziedziny deep learning to serie operacji, proces uczenia ich jest identyczny jak dla omawianych wcześniej prostszych modeli. W końcu tym, dzięki czemu proces uczenia działa, jest różniczko- walność modelu względem danych wejściowych. W rozdziale 1. wspomniałem, że funkcja złożona obejmująca funkcje różniczkowalne też jest różniczkowalna. Dlatego jeśli poszczególne operacje tworzące funkcję są różniczkowalne, cała funkcja też taka jest i można uczyć model za pomocą opisanej wcześniej czteroetapowej procedury. Jednak do tej pory uczenie modeli polegało na ręcznym obliczaniu pochodnych za pomocą ręcz- nego pisania kodu kroków w przód i wstecz oraz mnożenia odpowiednich wartości. W prostym modelu opartym na sieci neuronowej z rozdziału 2. wymagało to 17 kroków. Ponieważ modele do tej pory są opisywane na tak szczegółowym poziomie, nie jest jasne, jak zwiększyć złożoność mo- delu (i co dokładnie mogłoby to oznaczać), a nawet jak wprowadzić prostą zmianę, na przykład zastąpić funkcję sigmoidalną inną funkcją nieliniową. Aby móc budować dowolnie „głębokie” i „złożone” modele z dziedziny deep learning, trzeba zastanowić się nad tym, gdzie w tych 17 krokach można utworzyć komponenty wielokrotnego użytku i przyjąć poziom bardziej ogólny niż poszczególnych operacji, co pozwali zastępować te komponenty w celu tworzenia innych mo- deli. By zacząć tworzyć odpowiednie abstrakcje, spróbuję odwzorować używane operacje na tra- dycyjne opisy sieci neuronowych obejmujące „warstwy”, „neurony” itd. W pierwszym kroku spróbujemy utworzyć abstrakcję reprezentującą poszczególne operacje, któ- re stosowaliśmy do tej pory. Dzięki temu nie trzeba będzie wielokrotnie pisać kodu do mnożenia macierzy i dodawania wyrazu wolnego. 78  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę Elementy sieci neuronowych — operacje Klasa Operation będzie reprezentować jedną z funkcji składowych w sieci neuronowej. Wiemy (na podstawie sposobu, w jaki korzystaliśmy z funkcji w modelach), że na ogólnym poziomie po- winna ona zawierać metody forward i backward. Obie te metody powinny przyjmować tablice ndarray i zwracać tablice ndarray. Niektóre operacje, na przykład mnożenie macierzy, mają inny specjalny rodzaj danych wejściowych: parametry; także one są zapisane w tablicy ndarray. W na- szej klasie Operation (lub w innej klasie, pochodnej od Operation) dla parametrów utworzymy dodatkową zmienną instancji params. Następne spostrzeżenie dotyczy tego, że są dwa rodzaje operacji. Niektóre, na przykład mnożenie macierzy, zwracają jako dane wyjściowe tablicę ndarray o kształcie innym niż tablica ndarray otrzymana jako dane wejściowe. Z kolei niektóre operacje, na przykład funkcja sigmoid, ozna- czają zastosowanie jakiejś funkcji do każdego elementu wejściowej tablicy ndarray. Jak więc wy- gląda ogólna reguła dotycząca kształtów tablic ndarray przekazywanych między operacjami? Zastanów się nad takimi tablicami. Każda operacja przesyła dane wyjściowe do przodu w kroku w przód, a w kroku wstecz otrzymuje „gradient dla danych wyjściowych”, który reprezentuje po- chodną cząstkową funkcji straty względem każdego elementu z danych wyjściowych określonej operacji (w obliczeniach biorą udział inne operacje tworzące daną sieć). W kroku wstecz każda operacja przesyła wstecz „gradient dla danych wejściowych”, reprezentujący pochodną cząstkową funkcji straty względem każdego elementu z danych wejściowych. Te stwierdzenia nakładają na działanie operacji kilka ważnych ograniczeń, które pomogą zagwa- rantować, że gradienty są obliczane w poprawny sposób:  Kształt tablicy ndarray z gradientami dla danych wejściowych musi pasować do kształtu danych wyjściowych.  Kształt tablicy gradientów dla danych wejściowych przekazywanych przez operację do tyłu w kroku wstecz musi pasować do kształtu danych wejściowych tej operacji. Stanie się to bardziej zrozumiałe, gdy zobaczysz rysunek. Przyjrzyj mu się teraz. Diagram Wszystkie te rozważania zostały podsumowane na rysunku 3.1 dla operacji O przyjmującej dane wejściowe od operacji N i przekazującej dane wyjściowe do innej operacji, P. Rysunek 3.1. Operacja z danymi wejściowymi i wyjściowymi Elementy sieci neuronowych — operacje  79 Poleć książkęKup książkę Na rysunku 3.2 zilustrowany jest scenariusz dla operacji z parametrami. Rysunek 3.2. Operacja z danymi wejściowymi, danymi wyjściowymi i parametrami (klasa ParamOperation) Kod Po tym omówieniu można napisać kod podstawowego elementu sieci neuronowej — klasy Operation: class Operation(object): Klasa bazowa dla operacji w sieci neuronowej. def __init__(self): pass def forward(self, input_: ndarray): Zapisuje dane wejściowe w zmiennej instancji self._input. Wywołuje funkcję self._output(). self.input_ = input_ self.output = self._output() return self.output def backward(self, output_grad: ndarray) - ndarray: Wywołuje funkcję self._input_grad(). Sprawdza, czy odpowiednie kształty pasują do siebie. assert_same_shape(self.output, output_grad) self.input_grad = self._input_grad(output_grad) assert_same_shape(self.input_, self.input_grad) return self.input_grad def _output(self) - ndarray: Dla każdej operacji trzeba zdefiniować metodę _output. 80  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę raise NotImplementedError() def _input_grad(self, output_grad: ndarray) - ndarray: Dla każdej operacji trzeba zdefiniować metodę _input_grad. raise NotImplementedError() Dla każdej definiowanej operacji trzeba zaimplementować funkcje _output i _input_grad (te nazwy odpowiadają wartościom obliczanym przez te funkcje). Klasy bazowe tego rodzaju definiuję tu głównie w celach edukacyjnych. Ważne jest, aby zbudować model umysłowy wyjaśniający, że wszystkie operacje, jakie napo- tkasz w dziedzinie deep learning, przesyłają dane wejściowe w przód i gradienty wstecz, a także że kształty otrzymywane w kroku w przód pasują do kształtów prze- syłanych do tyłu w kroku wstecz (i na odwrót). Dalej w rozdziale zdefiniowane są konkretne operacje używane do tej pory — mnożenie macierzy itd. Najpierw jednak warto zdefiniować inną klasę, pochodną od klasy Operation. Ta nowa klasa jest przeznaczona dla operacji z użyciem parametrów: class ParamOperation(Operation): Operacja z parametrami. def __init__(self, param: ndarray) - ndarray: Metoda ParamOperation. super().__init__() self.param = param def backward(self, output_grad: ndarray) - ndarray: Wywołuje metody self._input_grad i self._param_grad. Sprawdza odpowiednie kształty. assert_same_shape(self.output, output_grad) self.input_grad = self._input_grad(output_grad) self.param_grad = self._param_grad(output_grad) assert_same_shape(self.input_, self.input_grad) assert_same_shape(self.param, self.param_grad) return self.input_grad def _param_grad(self, output_grad: ndarray) - ndarray: Każda klasa pochodna od ParamOperation musi zawierać implementację metody _param_grad. raise NotImplementedError() Elementy sieci neuronowych — operacje  81 Poleć książkęKup książkę Podobnie jak klasa bazowa Operation klasa ParamOperation musi zawierać definicje funkcji _output i _input_grad; dodatkowo musi też obejmować definicję funkcji _param_grad. Elementy sieci neuronowej używane do tego miejsca w modelach zostały już formalnie opisane. Moglibyśmy przejść teraz do definiowania sieci neuronowych bezpośrednio z użyciem takich operacji, ale najpierw trzeba zdefiniować klasę pomocniczą, wokół której krążymy od półtora rozdziału — klasę Layer. Elementy sieci neuronowych — warstwy W kategoriach operacji warstwy można opisać jako serie operacji liniowych, po których następuje operacja liniowa. Na przykład sieć neuronowa z poprzedniego rozdziału ma pięć ogólnych opera- cji: dwie operacje liniowe (mnożenie wag i dodawanie wyrazu wolnego), po których wykonywana jest funkcja sigmoid, a następnie dwie kolejne operacje liniowe. W tym scenariuszu trzy pierwsze operacje (włącznie z nieliniową) tworzą pierwszą warstwę, a ostatnie dwie operacje znajdują się w drugiej warstwie. Ponadto same dane wejściowe tworzą specjalny rodzaj warstwy, warstwę wej- ściową (nie jest ona uwzględniana w numerowaniu, dlatego można o niej myśleć jak o warstwie zerowej). Podobnie ostatnia warstwa jest nazywana wyjściową. Warstwa środkowa, pierwsza w na- szej numeracji, także ma ważną nazwę. Jest określana mianem warstwy ukrytej, ponieważ jest jedyną warstwą, której wartości nie są zwykle bezpośrednio widoczne w trakcie uczenia. Warstwa wyjściowa jest ważnym wyjątkiem w tej definicji warstw, ponieważ nie wymaga stoso- wania operacji nieliniowej. Wynika to z tego, że często chcemy, aby wartości z tej warstwy znaj- dowały się w przedziale od minus do plus nieskończoności (a przynajmniej od 0 do nieskończo- ności), a funkcje nieliniowe zazwyczaj kompresują dane wejściowe do podzbioru tego przedziału adekwatnego do rozwiązywanego problemu. Na przykład funkcja sigmoid kompresuje dane wej- ściowe do przedziału od 0 do 1. Diagramy Aby powiązania były oczywiste, na rysunku 3.3 pokazany jest diagram sieci neuronowej z po- przedniego rozdziału. Poszczególne operacje są tu pogrupowane w warstwy. Rysunek 3.3. Sieć neuronowa z poprzedniego rozdziału z operacjami pogrupowanymi w warstwy Widać tu, że dane wejściowe są reprezentowane jako warstwa wejściowa, trzy następne operacje (kończące się funkcją sigmoid) reprezentują następną warstwę, a dwie ostatnie operacje to ostat- nia warstwa. 82  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę Jest to oczywiście dość niewygodne. To nie przypadek — reprezentowanie sieci neuronowych w formie poszczególnych operacji wprawdzie dobrze pokazuje, jak działają takie sieci i jak ich uczyć, jest to jednak zbyt niskopoziomowa reprezentacja dla sieci neuronowych bardziej skom- plikowanych niż sieci dwuwarstwowe. To dlatego częściej stosowana jest reprezentacja w formie warstw, pokazana na rysunku 3.4. Rysunek 3.4. Sieć neuronowa z poprzedniego rozdziału przedstawiona w formie warstw Analogie do mózgu Pora przedstawić ostatnie powiązanie między tym, co zostało opisane do tego miejsca, a zagad- nieniem, z którym zapewne się już zetknąłeś. Każda warstwa ma określoną liczbę neuronów, rów- ną liczbie wymiarów wektora reprezentującego każdą obserwację w danych wyjściowych tej war- stwy. Dlatego można stwierdzić, że sieć neuronowa z poprzedniego przykładu ma 13 neuronów w warstwie wejściowej, następnie ponownie 13 neuronów w warstwie ukrytej i jeden neuron w warstwie wyjściowej. Neurony w mózgu przyjmują dane wejściowe od wielu innych neuronów, a następnie „odpalają” i przesyłają sygnał dalej tylko wtedy, jeśli otrzymane sygnały wspólnie dają określoną energię ak- tywacji. Neurony w sieciach neuronowych działają dość podobnie — rzeczywiście przesyłają sy- gnały dalej na podstawie danych wejściowych, przy czym te dane są przetwarzane na dane wyj- ściowe za pomocą funkcji nieliniowej. Ta funkcja nieliniowa jest tu nazywana funkcją aktywacji, a wartości wyjściowe tej funkcji to aktywacje dla danej warstwy1. Po zdefiniowaniu warstw można przedstawić bardziej klasyczną definicję deep learning: modele w dziedzinie deep learning to sieci neuronowe mające więcej niż jedną warstwę ukrytą. Widać, że jest to odpowiednik wcześniejszej definicji (wyrażonej za pomocą samych operacji), ponieważ warstwa to seria operacji z nieliniową operacją na końcu. Po zdefiniowaniu klasy bazowej dla operacji pora pokazać, że może ona stanowić podstawowy element modeli opisanych w poprzednim rozdziale. 1 Spośród wszystkich funkcji aktywacji funkcja sigmoid (odwzorowująca wartości wejściowe na przedział od 0 do 1) najściślej odzwierciedla rzeczywistą aktywację neuronów w mózgu. Jednak ogólnie funkcją aktywacji może być dowolna monotoniczna funkcja nieliniowa. Elementy sieci neuronowych — warstwy  83 Poleć książkęKup książkę Elementy z elementów Jakie konkretne operacje trzeba zaimplementować, aby modele z poprzedniego rozdziału mogły działać? Dzięki doświadczeniu w implementowaniu sieci neuronowej krok po kroku wiemy, że potrzebne są trzy rodzaje operacji:  mnożenie macierzy — danych wejściowych przez macierz parametrów,  dodawanie wyrazu wolnego,  funkcja aktywacji sigmoid. Zacznijmy od operacji WeightMultiply: class WeightMultiply(ParamOperation): Operacja mnożenia wag w sieci neuronowej. def __init__(self, W: ndarray): Inicjowanie operacji wartością self.param = W. super().__init__(W) def _output(self) - ndarray: Obliczanie danych wyjściowych. return np.dot(self.input_, self.param) def _input_grad(self, output_grad: ndarray) - ndarray: Obliczanie gradientu dla danych wejściowych. return np.dot(output_grad, np.transpose(self.param, (1, 0))) def _param_grad(self, output_grad: ndarray) - ndarray: Obliczanie gradientu dla parametrów. return np.dot(np.transpose(self.input_, (1, 0)), output_grad) Jest to prosty kod mnożenia macierzy w kroku w przód oraz reguł przesyłania w tył gradientów dla danych wejściowych i parametrów w kroku wstecz. Używane są tu reguły wywnioskowane w końcowej części rozdziału 1. Dalej zobaczysz, że te operacje można wykorzystać jako elementy dodawane w łatwy sposób do warstw. Teraz zajmijmy się operacją dodawania. Nazwiemy ją BiasAdd: class BiasAdd(ParamOperation): Dodawanie wyrazu wolnego. def __init__(self, 84  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę B: ndarray): Inicjowanie operacji wartością self.param = B. Sprawdzanie kształtu. assert B.shape[0] == 1 super().__init__(B) def _output(self) - ndarray: Obliczanie danych wyjściowych. return self.input_ + self.param def _input_grad(self, output_grad: ndarray) - ndarray: Obliczanie gradientu dla danych wyjściowych. return np.ones_like(self.input_) * output_grad def _param_grad(self, output_grad: ndarray) - ndarray: Obliczanie gradientu dla parametrów. param_grad = np.ones_like(self.param) * output_grad return np.sum(param_grad, axis=0).reshape(1, param_grad.shape[1]) Teraz przejdźmy do funkcji sigmoid: class Sigmoid(Operation): Funkcja aktywacji Sigmoid. def __init__(self) - None: Przekazywanie do typu bazowego. super().__init__() def _output(self) - ndarray: Obliczanie danych wyjściowych. return 1.0/(1.0+np.exp(-1.0 * self.input_)) def _input_grad(self, output_grad: ndarray) - ndarray: Obliczenie gradientu dla danych wejściowych. sigmoid_backward = self.output * (1.0 - self.output) input_grad = sigmoid_backward * output_grad return input_grad Jest to prosta implementacja obliczeń matematycznych opisanych w poprzednim rozdziale. Elementy z elementów  85 Poleć książkęKup książkę W obu klasach, sigmoid i ParamOperation, krok w kroku wstecz, gdzie wykonywane są obliczenia: input_grad = coś * o rutput_grad jest krokiem, gdzie stosowana jest reguła łańcuchowa. Analogiczna reguła w klasie WeightMultiply to: np.dot(output_grad, np.transpose(self.param, (1, 0))) Jest to, jak opisano w rozdziale 1., reguła analogiczna do reguły łańcuchowej w sy- tuacji, gdy funkcją jest mnożenie macierzy. Po precyzyjnym zdefiniowaniu operacji można korzystać z nich jako elementów do definiowania klasy Layer. Wzorzec warstwy Dzięki temu, jak napisane zostały operacje, utworzenie klasy Layer jest łatwe:  Metody forward i backward przesyłają dane wejściowe w przód w serii operacji — dokładnie tak, jak jest to pokazane na diagramach. Jest to najważniejsza informacja związana z działa- niem warstw. Reszta kodu to nakładka na ten proces przeznaczona głównie do wykonywania zadań porządkujących. Oto te zadania:  Definiowanie odpowiedniej serii operacji w funkcji _setup_layer oraz inicjalizowania i zapisywania parametrów w tych operacjach (co też będzie wykonywane w funkcji _setup_layer).  Zapisywanie odpowiednich wartości w polach self.input_ i self.output w metodzie forward.  Sprawdzanie asercji w metodzie backward.  Funkcje _params i _param_grads pobierają parametry i ich gradienty (względem wartości straty) z operacji sparametryzowanych z warstwy. Oto cały ten kod: class Layer(object): Warstwa neuronów w sieci neuronowej. def __init__(self, neurons: int): Liczba neuronów w przybliżeniu odpowiada szerokości warstwy. self.neurons = neurons self.first = True self.params: List[ndarray] = [] self.param_grads: List[ndarray] = [] self.operations: List[Operation] = [] 86  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę def _setup_layer(self, num_in: int) - None: W każdej warstwie trzeba zaimplementować funkcję _setup_layer. raise NotImplementedError() def forward(self, input_: ndarray) - ndarray: Przekazuje dane wejściowe w przód w serii operacji. if self.first: self._setup_layer(input_) self.first = False self.input_ = input_ for operation in self.operations: input_ = operation.forward(input_) self.output = input_ return self.output def backward(self, output_grad: ndarray) - ndarray: Przekazuje output_grad wstecz w serii operacji. Sprawdza kształty danych. assert_same_shape(self.output, output_grad) for operation in reversed(self.operations): output_grad = operation.backward(output_grad) input_grad = output_grad self._param_grads() return input_grad def _param_grads(self) - ndarray: Pobiera _param_grads z operacji z warstwy. self.param_grads = [] for operation in self.operations: if issubclass(operation.__class__, ParamOperation): self.param_grads.append(operation.param_grad) def _params(self) - ndarray: Pobiera _params z operacji z warstwy. self.params = [] for operation in self.operations: if issubclass(operation.__class__, ParamOperation): self.params.append(operation.param) Elementy z elementów  87 Poleć książkęKup książkę Podobnie jak przeszliśmy od abstrakcyjnej definicji operacji do implementacji konkretnych ope- racji na potrzeby sieci neuronowej z rozdziału 2., teraz zaimplementujemy także warstwę z tej sieci. Warstwa gęsta Używane operacje nazwaliśmy WeightMultiply, BiasAdd itd. Jak nazwać warstwę, której używali- śmy do tej pory? Warstwą liniowo-nieliniową (LinearNonLinear). Cechą definiującą tę warstwę jest to, że każdy neuron wyjściowy jest funkcją od wszystkich neuro- nów wejściowych. Tak właśnie działa mnożenie macierzy. Jeśli macierz ma nin wierszy i nout kolumn, mnożenie oblicza nout nowych cech, z których każda jest ważoną liniową kombinacją wszystkich nin cech wejściowych2. Takie warstwy są często nazywane każdy z każdym (ang. fully connected). Od niedawna w popularnej bibliotece Keras są one nazywane warstwami Dense czy gęstymi. Jest to bardziej zwięzła nazwa, która oznacza to samo. Teraz, gdy wiesz już, jak nazywa się omawiana warstwa i dlaczego, pora utworzyć warstwę Dense w kategoriach już zdefiniowanych operacji. Zobaczysz, że z powodu formy definicji klasy bazowej Layer wystarczy dodać zdefiniowane w poprzednim punkcie operacje jako listę w funkcji _setup_layer: class Dense(Layer): Warstwa gęsta dziedzicząca po klasie Layer. def __init__(self, neurons: int, activation: Operation = Sigmoid()) - None: Inicjalizacja wymaga określenia funkcji aktywacji. super().__init__(neurons) self.activation = activation def _setup_layer(self, input_: ndarray) - None: Definiuje operacje warstwy gęstej. if self.seed: np.random.seed(self.seed) self.params = [] # Wagi. self.params.append(np.random.randn(input_.shape[1], self.neurons)) # Wyraz wolny. self.params.append(np.random.randn(1, self.neurons)) self.operations = [WeightMultiply(self.params[0]), BiasAdd(self.params[1]), self.activation] return None 2 W rozdziale 5. zobaczysz, że nie dla wszystkich warstw jest to prawdą. Na przykład w warstwach konwolucyjnych każda cecha wyjściowa jest kombinacją tylko niewielkiego podzbioru cech wejściowych. 88  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę Domyślnie stosowana jest aktywacja liniowa, co tak naprawdę oznacza, że nie używamy aktywacji i stosujemy funkcję tożsamościową do danych wyjściowych warstwy. Jakie elementy należy dodać na poziomie powyżej klas Operation i Layer? Wiadomo, że do ucze- nia modelu potrzebna będzie klasa NeuralNetwork obejmująca obiekty klasy Layer (podobnie jak klasa Layer obejmuje obiekty typu Operation). Nie jest jednak oczywiste, jakie jeszcze klasy będą potrzebne. Dlatego przejdźmy do budowania klasy NeuralNetwork i ustalmy, jakie jeszcze inne klasy będą potrzebne. Klasa NeuralNetwork (i ewentualnie inne) Jakie powinny być możliwości klasy NeuralNetwork? Na ogólnym poziomie ta klasa powinna móc uczyć się na podstawie danych, a bardziej precyzyjnie — przyjmować porcje danych reprezentujące obserwacje (X) i poprawne odpowiedzi (y) oraz uczyć się relacji między X i y, co oznacza ustalenie funkcji, która potrafi przekształcać X na predykcje p bardzo zbliżone do y. Jak dokładnie przebiega to uczenie, jeśli wziąć pod uwagę właśnie zdefiniowane klasy Layer i Operation? Zgodnie z modelem z poprzedniego rozdziału zaimplementujmy proces uczenia tak: 1. Sieć neuronowa powinna przyjmować X i przekazywać je do wszystkich kolejnych warstw (warstwa ta jest tylko wygodną nakładką na przekazywanie danych między wieloma opera- cjami). Wynik reprezentuje predykcje. 2. Następnie predykcje należy porównać z wartością y, aby obliczyć wartość straty i wygenero- wać gradient dla wartości straty, czyli pochodną cząstkową wartości straty względem każdego elementu z ostatniej warstwy sieci (czyli z warstwy, która wygenerowała predykcję). 3. W ostatnim kroku gradient dla wartości straty jest przesyłany wstecz przez wszystkie war- stwy. Obliczane są przy tym gradienty dla parametrów (pochodne cząstkowe wartości straty względem każdego parametru); te gradienty są zapisywane w odpowiednich operacjach. Diagram Na rysunku 3.5 przedstawiony jest opis sieci neuronowej w kategoriach warstw. Rysunek 3.5. Propagacja wsteczna przedstawiona za pomocą warstw zamiast operacji Klasa NeuralNetwork (i ewentualnie inne)  89 Poleć książkęKup książkę Kod Jak zaimplementować ten proces? Przede wszystkim sieć neuronowa powinna używać warstw w taki sam sposób, jak warstwy używają operacji. Na przykład metoda forward powinna przyj- mować X na wejściu i robić coś takiego: for layer in self.layers: X = layer.forward(X) return X Podobnie metoda backward powinna przyjmować argument (nazwijmy go na razie grad) i wyko- nywać operację podobną do tej: for layer in reversed(self.layers): grad = layer.backward(grad) Skąd pochodzi argument grad? Musi pochodzić ze specjalnej funkcji straty, która przyjmuje ar- gumenty prediction i y oraz:  Oblicza wartość reprezentującą błąd sieci generującej daną predykcję.  Przesyła wstecz gradient dla każdego elementu z argumentu prediction względem wartości straty. Ten gradient jest otrzymywany przez ostatnią warstwę sieci jako dane wejściowe funk- cji backward. W przykładzie z poprzedniego rozdziału funkcja straty wyznaczała kwadrat różnicy między predyk- cją a odpowiedzią. Na tej podstawie obliczany był gradient dla predykcji względem wartości straty. Jak to zaimplementować? Wydaje się, że opisany proces jest na tyle ważny, że zasługuje na własną klasę. Tę klasę należy zaimplementować podobnie jak klasę Layer, przy czym metoda forward powinna zwracać jako wartość straty konkretną liczbę (typu float) zamiast tablicy ndarray prze- syłanej w przód do następnej warstwy. Zapiszmy to w formalny sposób. Klasa Loss Klasa bazowa Loss jest podobna do klasy Layer. Metody forward i backward będą sprawdzać, czy kształty odpowiednich tablic ndarray są identyczne. Należy też zdefiniować dwie metody, _output i _input_grad, które powinna być zdefiniowane w każdej podklasie klasy Loss: class Loss(object): Wartość straty w sieci neuronowej. def __init__(self): Operacja pusta. pass def forward(self, prediction: ndarray, target: ndarray) - float: Oblicza wartość straty. assert_same_shape(prediction, target) 90  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę self.prediction = prediction self.target = target loss_value = self._output() return loss_value def backward(self) - ndarray: Oblicza gradient dla wartości straty względem danych wejściowych funkcji straty. self.input_grad = self._input_grad() assert_same_shape(self.prediction, self.input_grad) return self.input_grad def _output(self) - float: Funkcja _output musi być zaimplementowana w każdej klasie pochodnej od Loss. raise NotImplementedError() def _input_grad(self) - ndarray: Funkcja _input_grad musi być zaimplementowana w każdej klasie pochodnej od Loss. raise NotImplementedError() Podobnie jak w klasie Operation należy sprawdzać, czy gradient przesyłany wstecz ma ten sam kształt co argument prediction otrzymany jako dane wejściowe z ostatniej warstwy sieci: class MeanSquaredError(Loss): def __init__(self) Operacja pusta. super().__init__() def _output(self) - float: Obliczanie wartości straty (jako błędu kwadratowego) dla obserwacji. loss = np.sum(np.power(self.prediction - self.target, 2)) / self.prediction.shape[0] return loss def _input_grad(self) - ndarray: Obliczanie gradientu dla wartości straty względem danych wejściowych (używany jest tu błąd średniokwadratowy). return 2.0 * (self.prediction - self.target) / self.prediction.shape[0] Klasa NeuralNetwork (i ewentualnie inne)  91 Poleć książkęKup książkę Zapisaliśmy tu reguły dla kroków w przód i wstecz z użyciem błędu średniokwadratowego jako wartości straty. Jest to ostatni ważny element potrzebny do zbudowania modelu deep learning od podstaw. Pora zoba- czyć, jak poszczególne elementy są powiązane ze sobą, a następnie przejść do budowania modelu. Deep learning od podstaw Ostatecznie chcemy utworzyć klasę NeuralNetwork, używając rysunku 3.5 jako punktu wyjścia. Ta klasa ma umożliwić definiowanie i uczenie modeli z dziedziny deep learning. Zanim zacznie- my pisać kod, warto precyzyjnie opisać, jak taka klasa powinna wyglądać i jak powinna komuni- kować się z już zdefiniowanymi klasami Operation, Layer i Loss: 1. Klasa NeuralNetwork powinna mieć atrybut w postaci listy warstw. Powinny to być warstwy takie jak zdefiniowano wcześniej, z metodami forward i backward. Te metody powinny przyjmować tablice ndarray i zwracać takie tablice. 2. W każdej warstwie zapisana jest lista operacji zapisywanych w atrybucie operations warstwy w funkcji _setup_layer. 3. Te operacje, podobnie jak sama warstwa, mają metody forward i backward, które przyjmują jako argumenty tablice ndarray i zwracają takie tablice jako dane wyjściowe. 4. W każdej operacji kształt argumentu output_grad otrzymanego przez metodę backward musi być taki sam jak kształt atrybutu output w warstwie. To samo dotyczy kształtów tablicy input_grad przekazywanej wstecz w metodzie backward i atrybutu input_. 5. Niektóre operacje mają parametry (zapisane w atrybucie param). Takie operacje dziedziczą po klasie ParamOperation. Opisane ograniczenia dotyczą także kształtów danych wejściowych i wyjściowych w warstwach oraz metod forward i backward warstw. Te metody przyjmują i zwracają tablice ndarray, a kształty atrybutów input i output oraz powiązanych gradientów muszą do siebie pasować. 6. Klasa NeuralNetwork zawiera też obiekt klasy Loss. Klasa Loss przyjmuje dane wyjściowe z ostat- niej operacji z klasy NeuralNetwork i odpowiedź, sprawdza, czy kształty tych elementów są ta- kie same, a następnie oblicza zarówno wartość straty (liczbę), jak i tablicę ndarray loss_grad, która zostanie przekazana do warstwy wyjściowej w celu rozpoczęcia propagacji wstecznej. Implementowanie treningu na porcjach danych Kilkakrotnie opisane zostały już wysokopoziomowe kroki uczenia modelu porcja po porcji. Te kroki są ważne i warto je powtórzyć: 1. Przekazywanie danych wejściowych do funkcji modelu (krok w przód) w celu uzyskania predykcji. 2. Obliczanie liczby reprezentującej wartość straty. 3. Obliczanie gradientu dla wartości straty względem parametrów z wykorzystaniem reguły łań- cuchowej i wartości obliczonych w kroku w przód. 4. Modyfikowanie parametrów na podstawie gradientów. 92  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę Następnie przekazywana jest nowa porcja danych i kroki są powtarzane. Te kroki można łatwo przekształcić na opisany właśnie model z klasą NeuralNetwork: 1. Przyjmowanie X i y jako danych wyjściowych. Oba te elementy to tablice ndarray. 2. Przekazywanie X w przód do wszystkich warstw. 3. Używanie klasy Loss do ustalenia wartości straty i przekazywanego wstecz gradientu dla wartości straty. 4. Używanie gradientu dla wartości straty jako danych wejściowych metody backward sieci. Metoda ta oblicza gradienty param_grads dla każdej warstwy w sieci. 5. Wywołanie funkcji update_params dla każdej warstwy. Funkcja ta używa ogólnego współ- czynnika uczenia dla danego obiektu klasy NeuralNetwork, a także nowo obliczonych gra- dientów param_grads. Wreszcie uzyskaliśmy kompletną definicję sieci neuronowej z uwzględnieniem uczenia na pod- stawie porcji danych. Pora napisać dla niej kod. Klasa NeuralNetwork — kod Kod dla tej klasy jest całkiem prosty: class NeuralNetwork(object): Klasa dla sieci neuronowej. def __init__(self, layers: List[Layer], loss: Loss, seed: float = 1) Sieci neuronowe wymagają warstw i wartości straty. self.layers = layers self.loss = loss self.seed = seed if seed: for layer in self.layers: setattr(layer, seed , self.seed) def forward(self, x_batch: ndarray) - ndarray: Przekazywanie danych w przód serii warstw. x_out = x_batch for layer in self.layers: x_out = layer.forward(x_out) return x_out def backward(self, loss_grad: ndarray) - None: Przekazywanie danych wstecz serii warstw. Deep learning od podstaw  93 Poleć książkęKup książkę grad = loss_grad for layer in reversed(self.layers): grad = layer.backward(grad) return None def train_batch(self, x_batch: ndarray, y_batch: ndarray) - float: Przekazywanie danych w przód serii warstw. Obliczanie wartości straty. Przekazywanie danych wstecz serii warstw. predictions = self.forward(x_batch) loss = self.loss.forward(predictions, y_batch) self.backward(self.loss.backward()) return loss def params(self): Pobieranie parametrów sieci. for layer in self.layers: yield from layer.params def param_grads(self): Pobieranie gradientów dla wartości straty względem parametrów sieci. for layer in self.layers: yield from layer.param_grads W klasie NeuralNetwork można zaimplementować modele z poprzedniego rozdziału w bardziej modułowy, elastyczny sposób, a także zdefiniować inne modele, reprezentujące złożone nielinio- we relacje między danymi wejściowymi i wyjściowymi. Na przykład poniżej pokazane jest, jak można w łatwy sposób utworzyć instancje dwóch modeli omówionych w poprzednim rozdziale — opartych na regresji liniowej i sieci neuronowej3: linear_regression = NeuralNetwork( layers=[Dense(neurons = 1)], loss = MeanSquaredError(), learning_rate = 0.01 ) neural_network = NeuralNetwork( layers=[Dense(neurons=13, activation=Sigmoid()), 3 Wartość współczynnika uczenia równa 0,01 nie jest specjalną liczbą. Wartość ta okazała się optymalna w trakcie eksperymentów związanych z poprzednim rozdziałem. 94  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę Dense(neurons=1, activation=Linear())], loss = MeanSquaredError(), learning_rate = 0.01 ) Kod jest już w zasadzie gotowy. Teraz wystarczy wielokrotnie przekazywać dane w sieci, aby ją wytrenować. Jednak aby ten proces był bardziej przejrzysty i łatwiejszy do rozszerzania na po- trzeby bardziej skomplikowanych scenariuszy z dziedziny deep learning, z jakimi zetkniesz się w następnym rozdziale, warto zdefiniować dodatkowe klasy, która odpowiadają za uczenie, czyli modyfikowanie parametrów obiektu klasy NeuralNetwork na podstawie gradientów obliczo- nych w kroku wstecz. Zdefiniujmy szybko te dwie klasy. Nauczyciel i optymalizator Najpierw warto zwrócić uwagę na podobieństwa między tymi klasami a kodem używanym do uczenia sieci w rozdziale 2. Tam używaliśmy następującego kodu, aby zaimplementować cztery kroki opisane wcześniej na potrzeby uczenia modelu: # Przekazywanie X_batch w przód i obliczanie wartości straty. forward_info, loss = forward_loss(X_batch, y_batch, weights) # Obliczanie gradientu dla wartości straty względem każdej z wag. loss_grads = loss_gradients(forward_info, weights) # Modyfikowanie wag. for key in weights.keys(): weights[key] -= learning_rate * loss_grads[key] Ten kod znajdował się w pętli for, która wielokrotnie przekazywała dane do funkcji definiującej i modyfikującej sieć. Gdy dostępne są już istniejące klasy, opisany proces można wykonać w funkcji fit w klasie Trainer, która jest przede wszystkim nakładką na funkcję train używaną w poprzednim rozdziale. Kom- pletny kod znajdziesz w arkuszu Jupyter Notebook dla tego rozdziału na stronie książki w serwi- sie GitHub (https://oreil.ly/2MV0aZI). Główna różnica polega na tym, że w nowej funkcji dwa pierwsze wiersze z poprzedniego bloku kodu zostaną zastąpione następującym wierszem: neural_network.train_batch(X_batch, y_batch) Modyfikowanie parametrów wykonywane w dwóch kolejnych wierszach będzie odbywać się w odrębnej klasie Optimizer. Ponadto pętla for, która wcześniej obejmowała cały kod związany z uczeniem, znajdzie się w klasie Trainer zawierającej obiekty typów NeuralNetwork i Optimizer. Teraz warto omówić, dlaczego potrzebna jest klasa Optimizer i jak powinna ona wyglądać. Optymalizator W modelu opisanym w poprzednim rozdziale każda warstwa zawiera prostą regułę modyfikowa- nia wag na podstawie parametrów i gradientów. W następnym rozdziale wspomniane jest, że można stosować także wiele innych reguł, na przykład uwzględniających historię zmian gradientów, Nauczyciel i optymalizator  95 Poleć książkęKup książkę a nie tylko modyfikacje gradientów na podstawie konkretnej porcji danych przekazanej w danej iteracji. Utworzenie odrębnej klasy Optimizer daje swobodę podmiany reguł modyfikowania pa- rametrów. Temat ten jest szczegółowo omówiony w następnym rozdziale. Opis i kod Klasa bazowa Optimizer przyjmuje obiekt klasy NeuralNetwork i przy każdym wywołaniu funkcji step modyfikuje parametry sieci na podstawie ich bieżących wartości, gradientów i innych in- formacji zapisanych w klasie Optimizer: class Optimizer(object): Klasa bazowa dla optymalizatora sieci neuronowej. def __init__(self, lr: float = 0.01): Każdy optymalizator musi mieć początkowy współczynnik uczenia. self.lr = lr def step(self) - None: Każdy optymalizator musi implementować funkcję step. pass Tak kod wygląda dla prostej reguły modyfikowania parametrów używanej do tej pory. Ta reguła to SGD (ang. stochastic gradient descent): class SGD(Optimizer): Optymalizator używający metody SGD. def __init__(self, lr: float = 0.01) - None: Operacja pusta. super().__init__(lr) def step(self): Dla każdego parametru wprowadzana jest zmiana w odpowiednim kierunku. Wielkość zmiany jest zależna od współczynnika uczenia. for (param, param_grad) in zip(self.net.params(), self.net.param_grads()): param -= self.lr * param_grad Warto zauważyć, że choć nasza klasa NeuralNetwork nie ma metody _update_params, korzystamy z metod params() i param_grads(), aby pobrać odpowiednie tablice ndarray na potrzeby optymalizacji. To była prosta klasa Optimizer. Przejdźmy teraz do klasy Trainer. 96  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę Nauczyciel Oprócz uczenia modelu w opisany wcześniej sposób klasa Trainer łączy też klasy NeuralNetwork i Optimizer, dbając o to, by ta druga poprawnie uczyła tę pierwszą. Może zauważyłeś w poprzed- nim punkcie, że w ramach inicjalizowania obiektu klasy Optimizer nie jest przekazywany do nie- go obiekt klasy NeuralNetwork. Zamiast tego obiekt klasy NeuralNetwork będzie ustawiany jako atrybut obiektu klasy Optimizer w ramach inicjalizowania obiektu klasy Trainer. Służy do tego następujący wiersz: setattr(self.optim, net , self.net) W następnym punkcie przedstawiam uproszczoną, ale działającą wersję klasy Trainer, która na razie zawiera tylko metodę fit. Ta metoda uczy model przez określoną liczbę epok i po każdej ustalonej liczbie epok wyświetla wartość straty. W każdej epoce kod: 1. Losowo przestawia dane na początku każdej epoki. 2. Przekazuje dane w sieci w porcjach, modyfikując parametry po przetworzeniu każdej porcji. Epoka kończy się po przetworzeniu przez obiekt klasy Trainer całego zbioru treningowego. Kod nauczyciela Dalej pokazany jest kod prostej wersji klasy Trainer. Ukryte są tu dwie oczywiste metody pomoc- nicze używane w funkcji fit: generate_batches (generuje porcje danych na potrzeby uczenia na podstawie kolekcji X_train i y_train) i mute_data (losowo przestawia elementy kolekcji X_train i y_train na początku każdej epoki). W funkcji train znajduje się też argument restart. Jeśli jego wartość to true (jest to ustawienie domyślne), po wywołaniu funkcji train parametry modelu są ponownie inicjalizowane wartościami losowymi: class Trainer(object): Uczy sieć neuronową. def __init__(self, net: NeuralNetwork, optim: Optimizer) Wymaga sieci neuronowej i optymalizatora, aby wykonać proces uczenia. Przypisuje sieć neuronową jako zmienną instancji do optymalizatora. self.net = net setattr(self.optim, net , self.net) def fit(self, X_train: ndarray, y_train: ndarray, X_test: ndarray, y_test: ndarray, epochs: int=100, eval_every: int=10, batch_size: int=32, seed: int = 1, restart: bool = True) - None: Dopasowuje sieć neuronową do danych treningowych przez określoną liczbę epok. Co eval_every epok sprawdza sieć neuronową względem danych testowych. Nauczyciel i optymalizator  97 Poleć książkęKup książkę np.random.seed(seed) if restart: for layer in self.net.layers: layer.first = True for e in range(epochs): X_train, y_train = permute_data(X_train, y_train) batch_generator = self.generate_batches(X_train, y_train, batch_size) for ii, (X_batch, y_batch) in enumerate(batch_generator): self.net.train_batch(X_batch, y_batch) self.optim.step() if (e+1) eval_every == 0: test_preds = self.net.forward(X_test) loss = self.net.loss.forward(test_preds, y_test) print(f Kontrolna wartość straty po {e+1} epokach wynosi {loss:.3f} ) W pełnej wersji tej funkcji w repozytorium książki w serwisie GitHub (https://oreil.ly/2MV0aZI) zaimplementowane jest też przerywanie uczenia (ang. early stopping), które: 1. Zapisuje wartość straty co eval_every epok. 2. Sprawdza, czy kontrolna wartość straty jest niższa niż po ostatnim jej obliczeniu. 3. Jeśli kontrolna wartość straty nie jest niższa, używa modelu sprzed eval_every epok. Wreszcie dostępne są wszystkie elementy potrzebne do uczenia modeli! Łączenie wszystkich elementów Oto kompletny kod do uczenia sieci z użyciem klas Trainer i Optimizer oraz dwóch zdefiniowa- nych wcześniej modeli: linear_regression i neural_network. Współczynnik uczenia jest usta- wiony na 0.01, a maksymalna liczba epok to 50. Ocena modeli odbywa się co 10 epok: optimizer = SGD(lr=0.01) trainer = Trainer(linear_regression, optimizer) trainer.fit(X_train, y_train, X_test, y_test, epochs = 50, eval_every = 10, seed=20190501); Kontrolna wartość straty po 10 epokach wynosi 30.295 Kontrolna wartość straty po 20 epokach wynosi 28.462 Kontrolna wartość straty po 30 epokach wynosi 26.299 Kontrolna wartość straty po 40 epokach wynosi 25.548 Kontrolna wartość straty po 50 epokach wynosi 25.092 Zastosowanie funkcji do oceny modelu z rozdziału 2. i umieszczenie ich w funkcji eval_regre ssion_model pozwala uzyskać następujące wyniki: 98  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę eval_regression_model(linear_regression, X_test, y_test) Średni błąd bezwzględny: 3.52 Pierwiastek błędu średniokwadratowego: 5.01 Są to wartości podobne do wyników dla regresji liniowej z poprzedniego rozdziału. Potwierdza to, że nowa platforma działa. Uruchomienie tego samego kodu dla modelu neural_network z ukrytą warstwą z 13 neuronami daje następujące wyniki: Kontrolna wartość straty po 10 epokach wynosi 27.434 Kontrolna wartość straty po 20 epokach wynosi 21.834 Kontrolna wartość straty po 30 epokach wynosi 18.915 Kontrolna wartość straty po 40 epokach wynosi 17.193 Kontrolna wartość straty po 50 epokach wynosi 16.214 eval_regression_model(neural_network, X_test, y_test) Średni błąd bezwzględny: 2.60 Pierwiastek błędu średniokwadratowego: 4.03 Także te wyniki są podobne do danych z poprzedniego rozdziału i okazują się znacznie lepsze niż dla prostej regresji liniowej. Pierwszy model z dziedziny deep learning (napisany od podstaw) Teraz, po zakończeniu wszystkich przygotowań, zdefiniowanie pierwszego modelu z dziedziny deep learning będzie proste: deep_neural_network = NeuralNetwork( layers=[Dense(neurons=13, activation=Sigmoid()), Dense(neurons=13, activation=Sigmoid()), Dense(neurons=1, activation=LinearAct())], loss=MeanSquaredError(), learning_rate=0.01 ) Nie będę nawet próbował kombinować z ustawieniami (na razie). Dodam tylko warstwę ukrytą o tych samych wymiarach co warstwa pierwsza, tak aby sieć miała teraz dwie warstwy ukryte (każda po 13 neuronów). Uczenie tego modelu z tym samym współczynnikiem uczenia i harmonogramem ocen co dla poprzednich modeli daje następujące wyniki: Kontrolna wartość straty po 10 epokach wynosi 44.134 Kontrolna wartość straty po 20 epokach wynosi 25.271 Kontrolna wartość straty po 30 epokach wynosi 22.341 Kontrolna wartość straty po 40 epokach wynosi 16.464 Kontrolna wartość straty po 50 epokach wynosi 14.604 eval_regression_model(neural_network, X_test, y_test) Średni błąd bezwzględny: 2.45 Pierwiastek błędu średniokwadratowego: 3.82 Łączenie wszystkich elementów  99 Poleć książkęKup książkę Wreszcie dotarliśmy do zastosowania deep learning od podstaw. I rzeczywiście w tym praktycz- nym problemie, bez stosowania żadnych sztuczek (tylko z niewielką zmianą współczynnika ucze- nia), nasz model z dziedziny deep learning okazał się nieco lepszy niż sieć neuronowa z tylko jed- ną warstwą ukrytą. Ważniejsze jest jednak to, że zbudowaliśmy platformę, którą można łatwo rozbudować. Mogliby- śmy w prosty sposób zaimplementować operacje innego rodzaju, umieścić je w nowych war- stwach i od razu zastosować — pod warunkiem, że zdefiniowane byłyby dla nich metody _output i _input_grad, a wymiary danych wejściowych, danych wyjściowych i parametry pasowałyby do wymiarów powiązanych gradientów. Łatwo moglibyśmy także zastosować inne funkcje aktywacji dla istniejących warstw i sprawdzić, czy skutkuje to zmniejszeniem błędów. Zachęcam, aby sklo- nować repozytorium książki z serwisu GitHub (https://oreil.ly/deep-learning-github) i spróbować wykonać opisane zadania. Podsumowanie i dalsze kroki W następnym rozdziale omawiam kilka sztuczek, które są nieodzowne do poprawnego uczenia modeli przeznaczonych dla trudniejszych problemów niż proste zadanie omawiane do tej pory4. Objaśniam przede wszystkim definiowanie klas Loss i Optimizer. Opisuję też dodatkowe sztuczki związane z dostosowywaniem współczynników uczenia i modyfikowaniem ich w jego trakcie. Pokazuję ponadto, jak zastosować te sztuczki w klasach Optimizer i Trainer. W końcowej części rozdziału przedstawiam klasę Dropout. Jest to nowy rodzaj operacji, który okazał się niezbędny do poprawy stabilności uczenia modeli z dziedziny deep learning. Do dzieła! 4 Nawet w tym prostym problemie niewielka zmiana hiperparametrów może spowodować, że model z dziedziny deep learning nie będzie lepszy niż dwuwarstwowa sieć neuronowa. Sklonuj repozytorium z serwisu GitHub (https://oreil.ly/deep-learning-github) i sam się o tym przekonaj. 100  Rozdział 3. Deep learning od podstaw Poleć książkęKup książkę
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Uczenie głębokie od zera. Podstawy implementacji w Pythonie
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: