Co macierz wag właściwie robi z przestrzenią wektorową
W poprzednim artykule o tym, jak uczą się sieci neuronowe, skupiliśmy się na aktualizacji wag i biasów podczas uczenia: propagacja wsteczna oblicza gradienty, a spadek gradientowy wykorzystuje je do zmiany parametrów. Tutaj przyjrzymy się propagacji w przód: jak sieć używa bieżących parametrów, aby przekształcać wejścia w wyjścia. Obliczenia te zachodzą zarówno podczas uczenia, jak i inferencji.
Wektory przechowują reprezentacje, a macierze wag je przekształcają. Prześledzimy te operacje na klasyfikatorze tak małym, że wszystko da się obliczyć ręcznie. Zaczniemy od punktów rozdzielanych prostą, a następnie przejdziemy do czterech punktów XOR na przecinających się przekątnych, których żadna prosta nie rozdzieli. Aby liniowa warstwa wyjściowa rozdzieliła klasy XOR, potrzebne jest nieliniowe przekształcenie ich reprezentacji. Warstwa ukryta z ręcznie dobranymi wagami pozwoli zobaczyć, jak to działa.
W większych, wytrenowanych sieciach znajomość sposobu obliczania każdego wyjścia nie wyjaśnia automatycznie, co kodują wewnętrzne reprezentacje ani jak sieć z nich korzysta. Wektory to ogólny sposób kodowania obiektów, ich właściwości i relacji między nimi. Te same narzędzia matematyczne mogą reprezentować pomiary jabłek do klasyfikacji dojrzałości, piksele do rozpoznawania obiektów czy pojęcia i relacje do rozumienia i generowania języka.
Analiza reprezentacji bada, jakie informacje kodują wewnętrzne wektory i jak są one zorganizowane. Interpretowalność mechanistyczna bada, jak elementy sieci wykorzystują te informacje, aby wytworzyć jej zachowanie. Podejścia te częściowo się pokrywają, ale samo znalezienie informacji w reprezentacji nie dowodzi jeszcze, że sieć jej używa.
Artykuł zakłada pewną znajomość poniższych pojęć algebry liniowej. Skupiamy się na pokazaniu, jak sieć neuronowa ich używa, zamiast uczyć ich od podstaw. Przykład klasyfikacji punktów nada im konkretne zastosowanie:
- Wektory i macierze: jak wektor reprezentuje jeden punkt, a macierz przechowuje zbiór punktów.
- Iloczyny skalarne: jak neuron łączy wejścia z wagami, uzyskując skalarną sumę ważoną, zanim doda bias i zastosuje aktywację.
- Przekształcenia liniowe: jak macierz wag tworzy nowe współrzędne i co jej wiersze oraz kolumny mówią o przekształceniu.
- Mnożenie macierzy i składanie: jak łączą się kolejne przekształcenia liniowe i dlaczego nieliniowa aktywacja na ogół uniemożliwia sprowadzenie całej sieci do jednej macierzy.
Klasyfikator z jednym neuronem
Klasyczne uczenie maszynowe oferuje wiele metod klasyfikacji, w tym regresję logistyczną, drzewa decyzyjne i maszyny wektorów nośnych. Tutaj zbudujemy klasyfikator za pomocą sieci neuronowej. Sieci neuronowe stanowią podstawę głębokiego uczenia; nasz mały przykład pokaże te same elementy, zachowując prostotę obliczeń.
Zacznijmy od zbioru danych, który można rozdzielić jedną prostą. Wyobraźmy sobie cztery jabłka tej samej odmiany, oznaczone A, B, C i D. Każdy punkt w poniższym zbiorze reprezentuje jedno jabłko opisane dwoma ciągłymi pomiarami: wskaźnikiem koloru (, od bardziej zielonego do bardziej czerwonego) i miękkości (, od twardszego do miększego). Etykiety określają, czy jabłko jest niedojrzałe (0), czy dojrzałe (1).
| Jabłko | Kolor () | Miękkość () | Klasa |
|---|---|---|---|
| A | Niedojrzałe (0) | ||
| B | Niedojrzałe (0) | ||
| C | Dojrzałe (1) | ||
| D | Dojrzałe (1) |
Wartości koloru i miękkości są przykładowe, wycentrowane i przeskalowane. Zero oznacza poziom odniesienia dla danej cechy: wartości ujemne oznaczają jabłka bardziej zielone lub twardsze, a dodatnie — bardziej czerwone lub miększe względem tych poziomów.
Nasza pierwsza sieć ma dwa wejścia i jeden neuron wyjściowy, bez warstwy ukrytej.
Neuron oblicza wynik dla każdego jabłka, a następnie stosuje próg, aby przewidzieć klasę. Dwie wagi neuronu możemy umieścić w jednowierszowej macierzy , a dwa wejścia jabłka — w wektorze kolumnowym :
Aby obliczyć wynik dla jednego jabłka, mnożymy macierz wag przez jego wektor wejściowy i dodajemy bias . Wynik oznaczamy jako , gdzie pochodzi od angielskiego score, a zawiera pomiary koloru i miękkości jabłka:
Tutaj oblicza jeden iloczyn skalarny dwóch wektorów: wektora wag neuronu , zapisanego jako wiersz , oraz wektora wejściowego jabłka . Mnożymy każdą wagę przez odpowiednie wejście i sumujemy iloczyny. Dodanie biasu daje wynik wyjściowy.
Mnożenie już jest przekształceniem liniowym z do : z płaszczyzny na prostą. Przekształcenie może zamieniać wektor w skalar; nie musi zachowywać liczby współrzędnych. Do interpretacji geometrycznej za pomocą wektorów bazowych wrócimy przy budowie warstwy ukrytej.
Ten model to perceptron, liniowy klasyfikator z jednym neuronem. Jego aktywacja progowa zamienia wynik w przewidywaną etykietę klasy :
Przy oznaczaniu danych wybraliśmy 1 dla jabłek dojrzałych i 0 dla niedojrzałych. Wystarczy jeden neuron wyjściowy: dodatni wynik daje etykietę 1, a zerowy lub ujemny — etykietę 0.
Zobaczmy teraz, jak neuron klasyfikuje cztery jabłka. W domyślnym widoku 2D kolor i miękkość wyznaczają położenie jabłka na płaszczyźnie. Kolor tła pokazuje przewidywaną klasę dla każdej możliwej pary wejść, a prosta oddziela obszary jabłek niedojrzałych i dojrzałych. Kolor znacznika odpowiada znanej etykiecie; różowy pierścień oznacza błędną predykcję.
Zmiana wag i biasu zmienia obszary decyzyjne, ale jabłka pozostają na miejscu. Dobraliśmy pomiary tak, aby klasy były liniowo separowalne: odpowiednie parametry umieszczają wszystkie cztery jabłka we właściwych obszarach.
Przełącz na Widok pod kątem (3D), aby zobaczyć wyniki stojące za predykcjami. Płaszczyzna wejściowa zawiera współrzędne koloru i miękkości, a niebieska płaszczyzna pokazuje obliczony wynik jako wysokość nad nią lub pod nią. Na przecięciu płaszczyzn wynik wynosi zero: to ta sama granica, którą w 2D widzimy jako prostą. Więcej o konstrukcji tych widoków znajdziesz w artykule jak czytać wykresy sieci neuronowych.
s(x) = 1.00x₁ + 1.00x₂ − 0.50
4 z 4 sklasyfikowano poprawnie
Wzór nad wykresem to obliczenie wyniku neuronu z podstawionymi bieżącymi parametrami. Suwaki sterują wagami i oraz biasem pokazanymi na schemacie sieci. To właśnie te parametry byłyby wyznaczane podczas uczenia.
W naszym wcześniejszym przykładzie dopasowania prostej neuron uczył się jednej wagi i biasu. Tutaj mamy dwa pomiary wejściowe, więc wagi są dwie, a bias jeden. Zasada spadku gradientowego pozostaje taka sama: różniczkowalna funkcja straty oparta na wynikach i znanych etykietach dostarcza gradientów do aktualizacji parametrów; twardy próg daje przewidywane klasy.
Prześledźmy obliczenie wyznaczające prostą w widżecie. Przy ustawieniach początkowych , i wynik wynosi . Granica decyzyjna zawiera punkty, dla których wynik jest zerowy:
To prosta. Na przykład dla punktu C o współrzędnych podstawiamy i do wzoru: . Wynik jest dodatni, więc przewidywana klasa to dojrzałe (1). Dla punktu B o współrzędnych podstawiamy i : . Wynik jest ujemny, więc predykcja to niedojrzałe (0). Prosta dzieli całą płaszczyznę na te dwa rodzaje odpowiedzi.
Możemy też obliczyć wyniki wszystkich czterech jabłek jednym mnożeniem macierzy. Tak przetwarzamy partię przykładów, czyli batch: zbieramy wektory wejściowe w macierz , z jednym jabłkiem w wierszu oraz kolorem i miękkością w dwóch kolumnach:
Parametry nadal obejmują te same dwie wagi i jeden bias. Jeśli zaczniemy od , i , jak w widżecie, macierz wag i jej transpozycja mają postać:
Bias jest oddzielny od i dodajemy go do sumy ważonej każdego jabłka.
Dla jednego jabłka zapisanego jako wektor kolumnowy używaliśmy . Gdy jabłka są wierszami, używamy : transpozycja zamienia wiersz wag neuronu w kolumnę, dzięki czemu wiersz każdego jabłka tworzy iloczyn skalarny z tymi samymi wagami. Po dodaniu tego samego biasu do każdego wyniku otrzymujemy:
Tutaj to kolumna czterech jedynek, więc powtarza bias dla wszystkich jabłek. Wiersze zawierają wyniki w kolejności A–D. Dla jabłka A pierwszy wiersz oblicza , dokładnie jak przy osobnym przetwarzaniu. Funkcja progowa daje predykcje , wszystkie poprawne. Przetwarzanie w batchu nie zmienia obliczania wyniku pojedynczego jabłka. Nasz batch obejmuje cały zbiór czterech przykładów; większe zbiory dzieli się zwykle na mniejsze partie, jak opisaliśmy w artykule o wpływie rozmiaru batcha na uczenie.
Cztery jabłka, których jeden neuron nie rozdzieli
Kontynuujmy z czterema jabłkami opisanymi kolorem () i miękkością (). Ręcznie zmieniamy ich wartości wejściowe , zachowując etykiety: A i B pozostają niedojrzałe (0), a C i D dojrzałe (1). Nowe współrzędne umieszczają każdą klasę na innej przekątnej kwadratu:
| Jabłko | Kolor () | Miękkość () | Klasa |
|---|---|---|---|
| A | Niedojrzałe (0) | ||
| B | Niedojrzałe (0) | ||
| C | Dojrzałe (1) | ||
| D | Dojrzałe (1) |
To znany układ XOR. Przesunęliśmy standardowe współrzędne binarne 0 i 1 do i , umieszczając je po przeciwnych stronach zera na obu osiach. Cztery punkty tworzą kwadrat o środku w początku układu: jabłka o przeciwnych znakach wejść mają etykietę dojrzałe (1), a o zgodnych znakach — niedojrzałe (0).
Każda klasa zajmuje inną przekątną kwadratu. Przekątne się przecinają, więc żadna prosta nie umieści wszystkich niedojrzałych jabłek po jednej stronie, a dojrzałych po drugiej. To klasyczna ilustracja ograniczeń perceptronu analizowanych przez Marvina Minsky’ego i Seymoura Paperta w książce Perceptrons z 1969 roku.
Gdy przepuścimy zaktualizowany zbiór przez pierwotny klasyfikator, te same parametry , i poprawnie klasyfikują tylko jedno jabłko z czterech. Otwórz ustawienia widżetu i zmień na : uzyskasz trzy poprawne odpowiedzi, ale C nadal będzie błędnie sklasyfikowane.
Zmień wagi i bias
s(x) = 1.00x₁ + 1.00x₂ − 0.50
Aby rozwiązać XOR, zachowamy dwa wejścia i jeden neuron wyjściowy oraz dodamy warstwę ukrytą z dwoma neuronami używającymi nieliniowej aktywacji ReLU. Przekształci ona pomiary każdego jabłka w nowe współrzędne , w których neuron wyjściowy rozdzieli klasy prostą:
Ilustracja przedstawia perceptron wielowarstwowy (MLP): sieć warstw w pełni połączonych, w której każdy neuron otrzymuje wszystkie wyjścia poprzedniej warstwy. Jest to też sieć jednokierunkowa (FFN): informacja płynie od wejścia do wyjścia bez pętli zwrotnych. Inne architektury używają innych warstw. Konwolucyjne sieci neuronowe (CNN) stosują współdzielone filtry do lokalnych obszarów i mogą też zawierać warstwy w pełni połączone, np. do końcowej klasyfikacji. Rekurencyjne sieci neuronowe (RNN) używają warstw przenoszących stan między krokami sekwencji.
Teraz zbadamy algebrę liniową tej sieci i stopniowo zbudujemy propagację w przód w NumPy. Obliczenia przeprowadzimy dla pojedynczych jabłek, a potem zastosujemy każdą operację do wszystkich czterech jako batcha. Kompletny przykład w NumPy połączy te operacje.
Reprezentowanie jabłek za pomocą wektorów i macierzy
Każde jabłko reprezentuje wektor dwóch liczb: wskaźnika koloru i miękkości. Małą literą oznaczamy wektor wejściowy jednego jabłka, zawierający kolor i miękkość . Dla jabłka C zapisujemy go jako kolumnę:
to przestrzeń wszystkich par liczb rzeczywistych. Jest dwuwymiarowa, ponieważ dwa niezależne kierunki współrzędnych wystarczają do wyznaczenia dowolnego punktu: ruch wzdłuż pierwszej osi, a potem drugiej.
Wektor możemy narysować jako strzałkę od początku układu lub zaznaczyć kropką jego koniec. Oba obrazy kodują te same współrzędne. Tutaj punkty są wygodne, bo klasyfikujemy przykłady.
Rozpocznij przykład w NumPy od tej reprezentacji jabłka C:
import numpy as np
# Reprezentacja jednego jabłka: kolor i miękkość, w tej kolejności.
x = np.array([-0.5, 0.5]) # Kształt (2,)W zapisie matematycznym jest kolumną. W NumPy ta jednowymiarowa tablica pełni rolę wektora wejściowego przy mnożeniu przez .
Przestrzeń reprezentacji to cała płaszczyzna. Nasz zbiór zawiera tylko cztery punkty na niej. Cztery przykłady nie sprawiają, że przestrzeń staje się czterowymiarowa; każdy nadal ma dwie współrzędne.
Możemy zebrać cztery wektory w macierz reprezentacji, po jednym przykładzie w wierszu. Wielka litera oznacza macierz wszystkich czterech jabłek, z jednym wektorem wejściowym w każdym wierszu.
Wiersze odpowiadają przykładom — czterem jabłkom; kolumny odpowiadają cechom: kolorowi i miękkości. Etykiety dojrzałości są przechowywane osobno.
To te same cztery pozycje wejściowe co w widżecie XOR. Strzałki pokazują wprost, że każdy punkt reprezentuje wektor od początku układu.
Macierz reprezentacji może zawierać wektory danych wejściowych, jak tutaj, albo wektory z poprzedniej warstwy. W obu przypadkach wiersz reprezentuje jeden przykład; między warstwami zmienia się informacja w jego współrzędnych, a czasem ich liczba. Odróżniamy tę rolę od macierzy przekształcenia: przechowuje reprezentacje, a macierz wag definiuje stosowane do nich przekształcenie liniowe.
Nasze jabłka mają już dwa pomiary liczbowe, więc utworzenie wektorów wejściowych jest proste. Dla innych danych trzeba zdecydować, jak przedstawić przykład wektorem. Obraz w skali szarości może stać się wektorem 784 wartości pikseli w ustalonej kolejności, a token — wektorem osadzenia. Liczba współrzędnych wyznacza w każdym przypadku liczbę wejść warstwy gęstej.
Nasza warstwa potrzebuje dwóch wejść, nawet jeśli zbierzemy milion dodatkowych przykładów.
Jak wektory bazowe definiują przekształcenie liniowe
Zanim przejdziemy do przekształceń warstwy, zobaczmy, jak macierz opisuje przekształcenie liniowe przez obrazy wejściowych wektorów bazowych. Gdy wiemy, dokąd trafiają te wektory, liniowość określa, dokąd trafi każdy inny wektor wejściowy.
W przestrzeni dwuwymiarowej bazę tworzą dwa liniowo niezależne wektory. Użyjemy standardowych wektorów jednostkowych, po jednym wzdłuż każdej osi:
Tworzą one standardową bazę płaszczyzny wejściowej. Umieszczając je w kolumnach, otrzymujemy macierz jednostkową:
Macierz bazy pozwala odtworzyć dowolny wektor z jego współrzędnych w tej bazie. Załóżmy, że definiujemy wektor jako
Pierwsza współrzędna skaluje pierwszą kolumnę , a druga — drugą kolumnę . Używamy kombinacji liniowej wektorów bazowych, ze współczynnikami i , aby odtworzyć pierwotny wektor:
Oznacza to, że aby zdefiniować przekształcenie liniowe, możemy wybrać nowe współrzędne obrazów wektorów bazowych i oraz umieścić je w kolumnach macierzy. Następnie ta sama kombinacja liniowa, z pierwotnymi współrzędnymi jako współczynnikami, tworzy przekształcony wektor i pokazuje jego położenie w przestrzeni wyjściowej.
Załóżmy, że przekształcenie liniowe wysyła do , a do . Macierz zapisuje te nowe położenia jako swoje kolumny:
Tak jak wcześniej, możemy odtworzyć przekształcony wektor wejściowy jako kombinację liniową przekształconych kierunków, używając pierwotnych współrzędnych jako współczynników. Skalujemy pierwszą kolumnę przez , a drugą przez , po czym dodajemy wyniki:
Współrzędne wejściowe stały się współrzędnymi wyjściowymi . Niektóre przekształcenia liniowe spłaszczają płaszczyznę do prostej lub punktu. Nadal są poprawne, a sieci neuronowe mogą używać ich do odrzucania informacji przy zachowaniu rozróżnień przydatnych w zadaniu.
Ta sama reguła działa dla każdego punktu siatki, również spoza naszego zbioru. Teraz zastosujemy ją do macierzy warstwy ukrytej.
Macierz warstwy definiuje przekształcenie
Najpierw zobaczmy, jak warstwa ukryta tworzy nową reprezentację dowolnego wektora wejściowego. Potem wybierzemy konkretne wagi i prześledzimy mnożenie macierzy dla jabłka A, obliczając, gdzie trafi jego wektor.
Macierz wag warstwy ukrytej jest naszą macierzą przekształcenia: definiuje liniowe przekształcenie wektorów wejściowych lub pochodzących z poprzedniej warstwy. Jej kształt zależy od liczby wejść i neuronów: dla wejść i neuronów macierz ma wierszy i kolumn.
Każdy neuron definiuje jeden wiersz wag, który oblicza jedną współrzędną wyjściową. Jak widzieliśmy w wyjaśnieniu wektorów bazowych, każda kolumna pokazuje obraz jednego wejściowego wektora bazowego we wszystkich współrzędnych wyjściowych. Dodanie neuronu dodaje więc współrzędną wyjściową, a nie kolejny wejściowy wektor bazowy. Możemy przypisać jej oś przestrzeni wyjściowej, ale wektor wag neuronu nie jest sam w sobie tym wektorem bazowym.
Nasza warstwa ma dwa neurony i dwa wejścia, więc ma dwa wiersze i dwie kolumny. Każdy wiersz zawiera wagę jednego neuronu dla koloru () i miękkości ().
Te wagi to parametry, których sieć uczyłaby się podczas treningu. Tutaj dobraliśmy je ręcznie, aby każdy krok był dokładny i łatwy do sprawdzenia.
Aby zastosować przekształcenie, mnożymy wektor wejściowy przez w kolejności . Obliczamy w ten sposób jedną sumę ważoną na neuron.
Oprócz macierzy wag warstwa ukryta ma wektor biasów , po jednym dla każdego neuronu. Dodanie daje , wektor przed aktywacją. Mnożenie i dodanie biasu tworzą przekształcenie afiniczne: przekształcenie liniowe, po którym następuje przesunięcie. Ten sam wektor biasów przesuwa każdy wynik. Przekształcenie liniowe zawsze odwzorowuje początek układu w początek układu; dodanie biasu przenosi go do . W naszym przykładzie , więc nie ma przesunięcia, a odwzorowanie pozostaje liniowe.
Następnie ReLU zastępuje każdą ujemną współrzędną zerem, dając — wyjście warstwy ukrytej. To nowa reprezentacja jabłka, z której neuron wyjściowy obliczy wynik. Pełna sekwencja wygląda tak:
Zdefiniujmy tę samą macierz przekształcenia i bias w NumPy:
# Dwie współrzędne wejściowe -> dwie współrzędne przed aktywacją.
# Wiersze zawierają wagi neuronów; kolumny — obrazy wejściowych wektorów bazowych.
W = np.array([[1.0, -0.8],
[-0.8, 1.0]]) # Kształt (2, 2)
b = np.array([0.0, 0.0]) # Wektor biasów; tutaj bez przesunięciaJako przykład wykorzystamy jabłko A, ale widżet poniżej pozwala prześledzić dowolne z czterech jabłek przez tę samą macierz . Pierwotny wektor A to
Korzystając z reguły kombinacji kolumn, obliczymy krok po kroku z pierwotnych współrzędnych i kolumn .
-
Odczytaj kolumny jako obrazy wektorów bazowych i . Pierwsza kolumna pokazuje, dokąd trafia , a druga — dokąd trafia :
To pierwotne wektory bazowe po zastosowaniu .
-
Użyj pierwotnych współrzędnych A jako współczynników. Jak pokazaliśmy wyżej, kombinacja liniowa przekształconych wektorów bazowych pozwala zbudować wektor A w przekształconej przestrzeni:
Współczynniki pozostają takie same; mnożone przez nie wektory są teraz przekształconymi kierunkami. Pierwsza współrzędna skaluje pierwszą kolumnę , czyli obraz pierwszego wektora bazowego . Druga współrzędna, również , skaluje drugą kolumnę — obraz drugiego wektora bazowego .
Pełne obliczenie wygląda więc tak:
Dwa wkłady częściowo się znoszą, pozostawiając . Użyliśmy pierwotnych współrzędnych A do połączenia przekształconych kierunków bazowych. Ta sama reguła działa dla B, którego oba współczynniki wynoszą :
A i B zbliżają się do początku układu, ale pozostają różnymi punktami. Macierz zmienia ich reprezentacje, nie łącząc ich w jeden punkt.
Animacja poniżej pokazuje oba kroki: strzałki bazowe trafiają w miejsca zapisane w , a wybrane jabłko podąża za ich kombinacją liniową z pierwotnymi współrzędnymi jako współczynnikami. Niebieska siatka podlega temu samemu przekształceniu, a oryginalna pozostaje nieruchoma jako punkt odniesienia.
Wybierz A, B, C lub D, a potem Przekształć. Początkowo wybrane jest A, którego wektor przechodzi z do .
Działanie przekształcenia widać na dwóch kierunkach przekątnych:
Kierunek A–B kurczy się do jednej piątej długości, a C–D rozciąga raza. Żaden nie znika. Zmienia się kształt kwadratu czterech punktów, lecz przekątne klas nadal się przecinają. Samo przekształcenie liniowe nie uczyniło XOR separowalnym. Potrzebna będzie nieliniowość aktywacji ReLU, którą omówimy poniżej.
Tak można zaimplementować to przekształcenie w NumPy, używając zdefiniowanej wyżej macierzy W. Operator @ wykonuje mnożenie macierzy:
x_A = np.array([-0.5, -0.5]) # Początkowe współrzędne A
# Stosujemy W do całego wektora.
transformed_A = W @ x_A
print(transformed_A) # [-0.1 -0.1]Przy mnożeniu macierzy przez wektor W @ x_A jest równoważne skalowaniu każdej kolumny W przez odpowiednią współrzędną x_A i dodaniu otrzymanych wektorów. Możemy zapisać tę kombinację liniową wprost:
# W[:, 0] to pierwsza kolumna; W[:, 1] to druga.
transformed_A = x_A[0] * W[:, 0] + x_A[1] * W[:, 1]
print(transformed_A) # [-0.1 -0.1]Jak neurony wspólnie przekształcają wektor
Wyżej użyliśmy macierzy warstwy do przekształcenia wektora wejściowego jabłka A:
Zobaczmy teraz, jak macierz wynika z neuronów warstwy i wymiarowości każdego wektora wejściowego oraz gdzie w tym obliczeniu pojawia się praca poszczególnych neuronów.
W naszej warstwie w pełni połączonej każdy neuron otrzymuje ten sam kompletny wektor wejściowy. Jabłko ma dwie współrzędne, więc każdy neuron potrzebuje dwóch wag: dla i dla . Mamy dwa neurony, a każdy dostarcza jeden wiersz wag:
| Neuron | Waga dla | Waga dla | Wiersz |
|---|---|---|---|
| 1 | |||
| 2 |
Każdy wiersz zawiera wagi jednego neuronu, a każda kolumna opisuje obraz jednego wejściowego wektora bazowego. Pierwsza kolumna zawiera na przykład wagi obu neuronów dla . Dla wejścia wagi te dają dwie współrzędne wyjściowe . Kolumna łączy więc wkłady obu neuronów.
Złożenie tych wierszy daje macierz o rozmiarze . Ogólniej, dla warstwy gęstej z współrzędnymi wejściowymi i neuronami mamy
Wymiar wejścia określa liczbę kolumn, a liczba neuronów — liczbę wierszy i współrzędnych wyjściowych. Te liczby określają kształt macierzy. Wartości wag decydują, jakie przekształcenie liniowe ona wykonuje. Dwa neurony otrzymujące trójwymiarowe wejścia dałyby na przykład macierz , odwzorowującą trzy współrzędne wejściowe na dwie wyjściowe.
Co ciekawe, kombinacja liniowa kolumn macierzy ze współrzędnymi wejściowymi jako współczynnikami oraz iloczyny skalarne jej wierszy z wektorem wejściowym dają dokładnie ten sam przekształcony wektor.
W poprzedniej sekcji uzyskaliśmy , skalując kolumny przez pierwotne współrzędne A i dodając je. Teraz otrzymamy ten sam wynik przez obliczenia neuronów: każdy oblicza iloczyn skalarny swojego wiersza wag z całym wektorem wejściowym, uzyskując jedną współrzędną wyjściową.
Przy ustalonych wagach iloczyn skalarny definiuje funkcję przyjmującą wektor i zwracającą jedną liczbę skalarną: . Dla pierwszego neuronu ta funkcja liniowa o wartościach skalarnych ma postać i odwzorowuje na . Macierz przekształcenia oraz opisywaną przez nią liniową część warstwy można zatem traktować jako zbiór takich funkcji, po jednej na neuron. Ich wyniki razem tworzą , odwzorowanie z do .
Przy zerowych biasach wyniki tych funkcji są też współrzędnymi przed aktywacją. Rozpisanie iloczynów skalarnych daje
Dla tego samego jabłka A oba neurony otrzymują :
| Neuron | Obliczenie | Wynikowa współrzędna |
|---|---|---|
| 1 | ||
| 2 |
Zbierając te dwie liczby, otrzymujemy — dokładnie wektor uzyskany przez kombinację kolumn. Sumy ważone neuronów wspólnie wykonują mnożenie macierzowe warstwy. Każdy neuron dostarcza jedną współrzędną wyniku, a nie stosuje całej macierzy osobno. Te same wagi są używane dla każdego jabłka.
Oba ujęcia inaczej grupują tę samą arytmetykę. Kolumna zbiera wkłady jednej współrzędnej wejściowej do wszystkich neuronów. Wiersz zbiera wszystkie wagi jednego neuronu potrzebne do obliczenia jego współrzędnej wyjściowej. Żadna kolumna nie należy do pojedynczego neuronu. Wybrana jest symetryczna, więc jej wiersze i kolumny zawierają akurat te same liczby, ale pełnią różne role.
Obliczenia pojedynczych neuronów możemy zapisać jawnie w NumPy:
# Każdy wiersz to wektor wag jednego neuronu.
z1_A = np.dot(W[0], x_A)
z2_A = np.dot(W[1], x_A)
z_A = np.array([z1_A, z2_A])
print(z_A) # [-0.1 -0.1], tak samo jak W @ x_ADodanie biasu sprawia, że funkcja neuronu jest afiniczna, a zastosowanie ReLU czyni ją nieliniową. Wyjścia razem tworzą reprezentację ukrytą . Działanie ReLU omówimy poniżej. Aby kontynuować kod propagacji w przód dla jabłka C z wcześniejszym wektorem wejściowym x, obliczamy sumy obu neuronów i dodajemy ich biasy w jednej operacji:
z = W @ x + b
print(z) # [-0.9 0.9] dla jabłka CGdy zbierzemy wszystkie wektory wejściowe w macierz reprezentacji, możemy zastosować przekształcenie warstwy do wszystkich czterech jabłek jednym mnożeniem macierzy. A, B, C i D umieszczamy w wierszach , a ich dwie współrzędne w kolumnach.
Dla jednego wektora kolumnowego używaliśmy . Ponieważ batch przechowuje wektory w wierszach, używamy : transpozycja umieszcza wagi każdego neuronu w kolumnie, aby wiersz jabłka tworzył z nimi iloczyn skalarny.
Transpozycja wynika z naszej konwencji zapisu: przykłady są wierszami , a wagi neuronów — wierszami . Gdyby wagi przechowywano po jednym neuronie w kolumnie, używalibyśmy bezpośrednio . Transpozycja dopasowuje wymiary, nie zmienia zamierzonego przekształcenia.
Przy naszych zerowych biasach obliczenie wygląda tak:
Każdy wiersz zawiera przekształcony wektor jednego jabłka; każda kolumna — sumy ważone jednego neuronu w całym batchu. Pierwszy wiersz nadal wynosi dla A, dokładnie jak przy osobnym przetwarzaniu. Macierz jest symetryczna, więc ma te same elementy co , ale transpozycja wyraża właściwą operację dla przykładów zapisanych wierszami.
W NumPy, z tymi samymi W i b co wcześniej:
# Jeden wektor wejściowy w wierszu, w kolejności A, B, C, D.
X = np.array([
[-0.5, -0.5],
[ 0.5, 0.5],
[-0.5, 0.5],
[ 0.5, -0.5],
])
# (4 jabłka, 2 wejścia) @ (2 wejścia, 2 neurony) -> (4, 2)
# Wiersz każdego jabłka tworzy iloczyn skalarny z wagami każdego neuronu.
Z = X @ W.T + b # Dodajemy ten sam wektor biasów do każdego wiersza
print(Z)
# [[-0.1 -0.1] A
# [ 0.1 0.1] B
# [-0.9 0.9] C
# [ 0.9 -0.9]] DBatch grupuje obliczenia, nie zmieniając przekształcenia pojedynczego wektora. Każde jabłko przechodzi przez oba neurony z tymi samymi wagami i biasami; jego pomiary nie są łączone z pomiarami innego jabłka.
Aktywacja sprawia, że nowe reprezentacje stają się separowalne
Dotąd obliczyliśmy liniową część warstwy ukrytej: każdy neuron tworzy jedną współrzędną , bez przesunięcia, bo biasy są zerowe. Oto współrzędne przed aktywacją:
| Punkt | Klasa | |
|---|---|---|
| A | 0 | |
| B | 0 | |
| C | 1 | |
| D | 1 |
Odcinek A–B jest krótszy, a C–D dłuższy, ale nadal się przecinają. Prosta nie może rozdzielić obu klas.
Oba wykresy mają tę samą skalę. Odcinki łączą punkty tej samej klasy i nadal przecinają się w początku układu.
Warstwa ukryta ma jeszcze jedną operację do wykonania. Każdy neuron stosuje nieliniową aktywację ReLU do swojej sumy ważonej. Wyniki razem tworzą wektor wyjściowy warstwy ukrytej :
Zachowuje ona wartości dodatnie, a ujemne zastępuje zerem. Dwie współrzędne ukryte przyjmują postać
mierzy dodatnią część , a — dodatnią część . To dwie cechy obliczane przez warstwę ukrytą.
Aby zobaczyć wpływ ReLU na reprezentację każdego jabłka, wybierz A, B, C lub D, a następnie Zastosuj ReLU. Widżet zaczyna od współrzędnych otrzymanych po . Początkowo wybrane jest A, którego wektor zmienia się z na . Wybór innego jabłka resetuje animację do jego współrzędnych sprzed aktywacji.
Oto współrzędne wszystkich czterech jabłek przed i po ReLU, zgodne z wynikami w widżecie:
| Punkt | Klasa | Przed ReLU: | Po ReLU: |
|---|---|---|---|
| A | 0 | ||
| B | 0 | ||
| C | 1 | ||
| D | 1 |
A trafia do początku układu, bo obie współrzędne są ujemne. B pozostaje w , bo obie są dodatnie. C i D tracą ujemną współrzędną i trafiają na różne dodatnie półosie. Odcinki klas już się nie przecinają.
W NumPy stosujemy ReLU do obliczonej wyżej macierzy batcha Z. Operacja maximum porównuje każdą współrzędną z zerem i zachowuje większą wartość. Każdy wiersz H zawiera reprezentację ukrytą jednego jabłka:
# Zachowujemy dodatnie współrzędne; ujemne zastępujemy zerami.
H = np.maximum(0, Z)
print(H)
# [[0. 0. ] A
# [0.1 0.1] B
# [0. 0.9] C
# [0.9 0. ]] DReLU nie ogranicza wyjścia do 0 lub 1. Tutaj zachowuje wartości takie jak i . Dopiero końcowy próg klasyfikacji zwraca wyłącznie 0 albo 1.
Warstwa wyjściowa klasyfikuje reprezentacje ukryte
Warstwa ukryta utworzyła nowy wektor dla każdego jabłka. Neuron wyjściowy otrzymuje jego dwie współrzędne: i .
Ten neuron ma własne wagi zapisane w oraz bias . Uczenie wyznaczałoby te parametry razem z wagami i biasami warstwy ukrytej. Na potrzeby demonstracji wybieramy je ręcznie:
Wynik to kolejny iloczyn skalarny, po którym dodajemy bias:
Jak wcześniej, dodatni wynik oznacza predykcję dojrzałe (1), a zerowy lub ujemny — niedojrzałe (0). Granica decyzyjna zawiera reprezentacje o wyniku równym zero:
Próg ten leży między sumami obu klas:
| Jabłko | Reprezentacja ukryta | Wynik | Predykcja | |
|---|---|---|---|---|
| A | Niedojrzałe (0) | |||
| B | Niedojrzałe (0) | |||
| C | Dojrzałe (1) | |||
| D | Dojrzałe (1) |
Sumy A i B są mniejsze od , a C i D większe. Dlatego prosta rozdziela klasy. Nasz próg nie jest jedynym możliwym: dowolna wartość ściśle między a również rozdzieliłaby te cztery reprezentacje.
Zobaczmy, jak pełna sieć klasyfikuje jabłka w pierwotnej płaszczyźnie wejściowej. Widżet otwiera się w trybie Widok z góry (2D), pokazując obszary decyzyjne i wszystkie cztery jabłka sklasyfikowane poprawnie przy naszych parametrach. Przełącz na Widok pod kątem (3D), aby zobaczyć : pierwotne współrzędne jabłka oraz końcowy wynik jako wysokość. Trzecia oś wizualizuje skalarne wyjście; reprezentacja ukryta nadal ma dwie współrzędne. Niebieska powierzchnia wyniku załamuje się tam, gdzie neurony ukryte przechodzą między zerowym a dodatnim wyjściem. Jej przecięcie z płaszczyzną wejściową tworzy granicę decyzyjną. Zmieniaj wagi i biasy suwakami, aby badać powierzchnię i predykcje, lub wybierz Resetuj, aby przywrócić nasze parametry.
Zmień wagi i biasy
Wagi warstwy ukrytej zmieniają załamania, jej biasy je przesuwają, a wagi wyjściowe zmieniają ich wkład w wynik. Bias wyjściowy podnosi lub obniża całą powierzchnię. Resetuj przywraca wartości z artykułu.
s(x) = Σⱼ vⱼ ReLU(wⱼ₁x₁ + wⱼ₂x₂ + bⱼ) + c
W przestrzeni ukrytej granica jest prosta, ale w przestrzeni wejściowej się załamuje. A i B leżą w obszarze niedojrzałych jabłek, a C i D w obszarze dojrzałych po jego przeciwnych stronach. Przy tych wagach oba dojrzałe obszary łączą się dalej wzdłuż dodatniej przekątnej, poza czterema przykładami. Poprawne sklasyfikowanie czterech przykładów nie określa, co sieć powinna przewidywać wszędzie indziej.
Aby zaimplementować warstwę wyjściową w NumPy, stosujemy jej wagi i bias do wszystkich czterech reprezentacji ukrytych. Z jednym jabłkiem w każdym wierszu obliczamy , dodając ten sam bias do wyniku każdego jabłka:
# Przekształcenie wyjściowe: dwie współrzędne ukryte -> jeden wynik.
V = np.array([[1.0, 1.0]]) # Kształt (1, 2)
c = -0.5 # Bias wyjściowy
# (4 jabłka, 2 współrzędne ukryte) @ (2, 1) -> (4, 1)
# Każdy wiersz tworzy iloczyn skalarny z tymi samymi wagami wyjściowymi.
S = H @ V.T + c
predictions = (S > 0).astype(int) # 1 = dojrzałe, 0 = niedojrzałe
for apple, score, prediction in zip("ABCD", S[:, 0], predictions[:, 0]):
print(f"{apple}: score={score:.1f}, prediction={prediction}")
# A: score=-0.5, prediction=0
# B: score=-0.3, prediction=0
# C: score=0.4, prediction=1
# D: score=0.4, prediction=1Dlaczego samo ReLU nie rozwiązuje tego przykładu
Co by było, gdyby warstwa ukryta używała macierzy jednostkowej i zerowych biasów? Obliczałaby , czyli dokładnie to samo co ReLU zastosowane bezpośrednio do wejść. Wynik nadal tworzyłby układ XOR: ujemne współrzędne stają się zerami, więc A trafia do , B do , C do , a D do . To wierzchołki mniejszego kwadratu, w którym klasy nadal zajmują różne przekątne. Żadna prosta ich nie rozdziela.
Przełączaj między Oryginał, ReLU(x) i ReLU(Wx), aby porównać trzy układy. Drugi widok używa wag macierzy jednostkowej, trzeci wybranej przez nas . Oba warianty warstwy ukrytej zaczynają od pierwotnych wektorów wejściowych i mają zerowe biasy.
Oryginalne wejścia: przekątne obu klas przecinają się.
Każdy kwadrat ma rozmiar 0.1 × 0.1. Skala osi jest taka sama we wszystkich trzech widokach.
Żadna prosta nie rozdziela obu klas.
Macierz najpierw zmienia współrzędne tak, aby ReLU działało na użyteczniejszym układzie. Umieszcza B w , a C i D w i . ReLU pozostawia B blisko początku układu, przenosi C i D dalej wzdłuż dodatnich półosi, a A do początku układu. Otrzymane reprezentacje można rozdzielić prostą.
Ani samo , ani ReLU zastosowane bezpośrednio do wejść nie rozwiązuje tego przykładu; ich połączenie sprawia, że klasy są liniowo separowalne. Warstwa wyjściowa może rozdzielić te reprezentacje, jak widzieliśmy wyżej.
Sieć jako złożenie funkcji
Niektóre książki opisują sieci neuronowe jako złożenie funkcji odwzorowujących wektory z jednej przestrzeni wektorowej do innej. Nasza warstwa ukryta odwzorowuje na , tworząc reprezentację ukrytą, a wyjściowa — na , dając jeden wynik skalarny. Każda funkcja otrzymuje wynik poprzedniej. Funkcje te mogą być liniowe lub nieliniowe, jak widzieliśmy na przykładzie macierzy wag i ReLU.
W naszej sieci nazwijmy funkcję warstwy ukrytej , a funkcję warstwy wyjściowej :
Propagacja w przód, którą śledziliśmy w widżetach, ma więc postać
Podstawienie wyjścia warstwy ukrytej do funkcji warstwy wyjściowej daje
To właśnie oznacza tutaj składanie funkcji. Typowy zapis złożenia to : najpierw zastosuj , potem . Głębsza sieć działa według tej samej zasady : każda warstwa przekształca otrzymaną reprezentację i przekazuje wynik następnej.
Bez nieliniowych aktywacji warstwy łączą się w jedno odwzorowanie afiniczne
Być może czytałeś też, że bez nieliniowych aktywacji wszystkie warstwy sprowadzają się do jednej. Łatwo to zobaczyć na naszych dwóch macierzach wag. Tymczasowo usuńmy ReLU. Ponieważ biasy warstwy ukrytej są zerowe, neuron wyjściowy otrzymuje bezpośrednio :
Możemy najpierw pomnożyć i , a ich iloczynu użyć jako pojedynczej macierzy wag:
Otrzymana sieć oblicza
Wracamy do wyniku tego samego rodzaju co w pierwszym neuronie: dwa ważone wejścia i bias, z granicą decyzyjną będącą prostą. Te wagi dają dla A, B, C i D wyniki , , i , przewidując niedojrzałe dla wszystkich. Zmiana wag mogłaby poprawić predykcje, ale żaden pojedynczy wynik afiniczny nie sklasyfikuje poprawnie całego układu XOR.
Sprowadzenie warstw do jednej dotyczy obliczanej funkcji: pojedyncza warstwa afiniczna może dać dokładnie te same wyjścia co cały stos. Niezerowe biasy nie zmieniają tego wniosku. Łączą się w jeden bias:
Powtarzając to obliczenie, łączymy dowolny stos warstw afinicznych w jedno odwzorowanie afiniczne. Końcowa funkcja progowa nadal daje granicę prostoliniową; sam próg nie zapewnia efektu nieliniowej warstwy ukrytej.
Iloczyn macierzy zapisuje złożone przekształcenie
Wiąże się to bezpośrednio z kolumnami jako obrazami wejściowych wektorów bazowych. Kolumny pokazują, dokąd trafiają i po pierwszym przekształceniu. Zastosowanie do tych obrazów pokazuje, dokąd trafią po obu:
Każdy wejściowy wektor bazowy trafia do na wyjściowej osi liczbowej. Dla dowolnego wejścia ta sama reguła kombinacji kolumn daje więc .
Ogólniej, mnożenie dwóch macierzy przekształceń stosuje lewą macierz do każdej kolumny prawej. Jeśli zastosujemy , a potem , ich iloczyn zapisze końcowe obrazy wejściowych wektorów bazowych:
Najpierw działa macierz po prawej. Iloczyn przedstawia oba przekształcenia liniowe jako jedno.
ReLU zmienia to, co otrzymuje następna warstwa
Teraz przywróćmy ReLU między a . Neuron wyjściowy otrzymuje , więc nie dodaje już po prostu dwóch współrzędnych z . Dodaje ich części nieujemne:
Współrzędne C po wynoszą na przykład . Bez ReLU znoszą się, dając wynik . Z ReLU neuron wyjściowy otrzymuje i daje wynik . Te same wagi wyjściowe dają teraz poprawną predykcję, bo otrzymują inną reprezentację.
Warstwy nadal składają się jako funkcje, ale tej sieci nie możemy zastąpić jedną warstwą afiniczną. ReLU zachowuje lub zeruje każdą współrzędną zależnie od znaku, przez co pełny wynik jest nieliniową funkcją pierwotnego wejścia.
Łączy to również dwa widoki naszego klasyfikatora. W przestrzeni ukrytej granica wyjściowa to prosta . Podstawienie funkcji warstwy ukrytej daje tę samą granicę we współrzędnych wejściowych:
Tam, gdzie aktywny jest tylko pierwszy neuron, otrzymujemy . Gdy tylko drugi, otrzymujemy . Gdy aktywne są oba, wynik to . Te proste fragmenty łączą się pod kątem, tworząc granicę widoczną w płaszczyźnie wejściowej.
Każde nowe jabłko przechodzi przez to samo złożenie: pomiary stają się reprezentacją ukrytą, potem wynikiem i przewidywaną etykietą. Prosta granica w przestrzeni ukrytej może więc odpowiadać nieliniowej granicy w przestrzeni wejściowej.
Uruchomienie modelu dla czterech jabłek w NumPy
Połączmy teraz operacje w jeden uruchamialny przykład NumPy. Każdy wiersz nadal reprezentuje jedno jabłko przez całą propagację w przód:
Stosujemy tę samą konwencję jednego przykładu w wierszu: , i , dodając każdy bias do wszystkich wierszy. Rozmiar batcha i rozmiar reprezentacji to osobne wymiary: cztery przykłady pozostają te same, a dwie współrzędne ukryte każdego jabłka stają się jednym wynikiem wyjściowym.
import numpy as np
# Macierz reprezentacji X o kształcie (4, 2):
# wiersze = jabłka A, B, C, D; kolumny = kolor i miękkość.
X = np.array([
[-0.5, -0.5],
[ 0.5, 0.5],
[-0.5, 0.5],
[ 0.5, -0.5],
])
labels = np.array([0, 0, 1, 1]) # Etykiety docelowe, przechowywane osobno od X
# Macierz przekształcenia W o kształcie (2, 2), wspólna dla wszystkich jabłek.
# Wiersze zawierają wagi neuronów; kolumny to przekształcone wektory bazowe.
W = np.array([
[ 1.0, -0.8],
[-0.8, 1.0],
])
b = np.array([0.0, 0.0])
# Przekształcenie wyjściowe V o kształcie (1, 2): dwie współrzędne -> jeden wynik.
V = np.array([[1.0, 1.0]])
c = -0.5
# Przykłady są w wierszach, więc transponujemy W, aby obliczyć
# iloczyn skalarny każdego jabłka z wagami każdego neuronu: (4, 2) @ (2, 2).
# NumPy dodaje ten sam wektor biasów do każdego wiersza.
Z = X @ W.T + b # Kształt (4, 2)
# ReLU zastępuje ujemne współrzędne zerami.
H = np.maximum(0, Z) # Kształt (4, 2): nowe reprezentacje
# Złożenie przekształcenia wyjściowego z ukrytym, wiersz po wierszu:
# (4, 2) @ (2, 1) -> (4, 1), następnie dodajemy ten sam skalarny bias.
scores = H @ V.T + c # Jeden wynik na jabłko
predictions = (scores[:, 0] > 0).astype(int)
print(H)
# [[0. 0. ] A
# [0.1 0.1] B
# [0. 0.9] C
# [0.9 0. ]] D
print(scores[:, 0]) # [-0.5 -0.3 0.4 0.4]
print(predictions) # [0 0 1 1]i opisują te same przykłady w różnych przestrzeniach. i określają przekształcenia między nimi. Aktywacja pozwala całej sieci wyrazić regułę nieliniową.
Dlaczego zmiana reprezentacji pomaga
Prześledziliśmy cztery jabłka przez sieć, ale celem nie było samo przesuwanie punktów. Chodziło o zbudowanie współrzędnych, w których potrzebne rozróżnienie łatwiej wyrazić.
XOR pokazuje to konkretnie. W pierwotnej płaszczyźnie koloru i miękkości żadna prosta nie rozdziela klas. Macierz wag zmienia kształt układu, a ReLU przekształca go nieliniowo. W nowej reprezentacji wystarczają suma ważona i próg. Problem klasyfikacji pozostał ten sam; zmieniła się reprezentacja używana do jego rozwiązania.
To mały przykład tego, jak sieć może wyrażać regułę za pomocą geometrii i obliczeń. Wektory ukryte kodują pochodne właściwości wejść, a neuron wyjściowy łączy je, aby podjąć decyzję. Regułę realizuje cała sekwencja operacji.
Większe sieci mogą uczyć się reprezentacji o znacznie większej liczbie współrzędnych, kodujących właściwości i relacje przydatne w zadaniach. Architektura określa dostępne wymiary, a uczenie zmienia sposób reprezentowania w nich wejść. Pojęcia nie muszą odpowiadać pojedynczym współrzędnym: informacja może być rozproszona po wielu współrzędnych i wykorzystywana przez następne warstwy. Sieci mogą też reprezentować więcej cech niż mają wymiarów, kodując je w nakładających się kierunkach. To zjawisko nazywamy superpozycją.
Nasza sieć z ręcznie dobranymi wagami pozwala zobaczyć, jak obliczane i wykorzystywane są użyteczne cechy. Algebra liniowa dostarcza przekształceń, nieliniowe aktywacje poszerzają ich możliwości, a uczenie przeszukuje przestrzeń wag i biasów w poszukiwaniu kombinacji minimalizującej stratę. Każda kombinacja definiuje kandydującą funkcję od wejścia do wyjścia.