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 (x1x_1, od bardziej zielonego do bardziej czerwonego) i miękkości (x2x_2, od twardszego do miększego). Etykiety określają, czy jabłko jest niedojrzałe (0), czy dojrzałe (1).

JabłkoKolor (x1x_1)Miękkość (x2x_2)Klasa
A1-10.5-0.5Niedojrzałe (0)
B0.5-0.5+0.5+0.5Niedojrzałe (0)
C+0.5+0.5+0.5+0.5Dojrzałe (1)
D+1+100Dojrzał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.

Dwa wejścia i jeden neuron wyjściowyKolor i miękkość jednego jabłka trafiają do pojedynczego neuronu przez wagi w1 i w2. Neuron dodaje bias i przewiduje klasę 1 dla dodatniego wyniku, a klasę 0 w pozostałych przypadkach. Nie ma warstwy ukrytej.Wektor wejściowyNeuron wyjściowyKlasax₁x₂w₁w₂Σ + bs > 0?0 lub 1

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 WW, a dwa wejścia jabłka — w wektorze kolumnowym x\mathbf{x}:

W=[w1w2],x=[x1x2]W=\begin{bmatrix}w_1&w_2\end{bmatrix}, \qquad \mathbf{x}=\begin{bmatrix}x_1\\x_2\end{bmatrix}

Aby obliczyć wynik dla jednego jabłka, mnożymy macierz wag WW przez jego wektor wejściowy x\mathbf{x} i dodajemy bias bb. Wynik oznaczamy jako s(x)s(\mathbf{x}), gdzie ss pochodzi od angielskiego score, a x\mathbf{x} zawiera pomiary koloru i miękkości jabłka:

s(x)=Wx+b=[w1w2]×[x1x2]+b=w1x1+w2x2+bs(\mathbf{x})=W\mathbf{x}+b =\begin{bmatrix}w_1&w_2\end{bmatrix}\times\begin{bmatrix}x_1\\x_2\end{bmatrix}+b =w_1x_1+w_2x_2+b

Tutaj WxW\mathbf{x} oblicza jeden iloczyn skalarny dwóch wektorów: wektora wag neuronu (w1,w2)(w_1,w_2), zapisanego jako wiersz WW, oraz wektora wejściowego jabłka (x1,x2)(x_1,x_2). Mnożymy każdą wagę przez odpowiednie wejście i sumujemy iloczyny. Dodanie biasu daje wynik wyjściowy.

Mnożenie WxW\mathbf{x} już jest przekształceniem liniowym z R2\mathbb{R}^2 do R\mathbb{R}: 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 y^\hat y:

y^=step(s(x))={1gdy s(x)>0(dojrzałe),0gdy s(x)0(niedojrzałe)\hat y=\operatorname{step}(s(\mathbf{x}))= \begin{cases} 1 & \text{gdy }s(\mathbf{x})>0 \quad \text{(dojrzałe)},\\ 0 & \text{gdy }s(\mathbf{x})\le0 \quad \text{(niedojrzałe)} \end{cases}

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

1.00
1.00
-0.50
x₁-11x₂-11Jabłko A: Kolor -1, Miękkość -0.5, Wynik -2.00, Predykcja 0Jabłko B: Kolor -0.5, Miękkość 0.5, Wynik -0.50, Predykcja 0Jabłko C: Kolor 0.5, Miękkość 0.5, Wynik 0.50, Predykcja 1Jabłko D: Kolor 1, Miękkość 0, Wynik 0.50, Predykcja 1ABCD
Niedojrzałe (0)Dojrzałe (1)

Wzór nad wykresem to obliczenie wyniku neuronu z podstawionymi bieżącymi parametrami. Suwaki sterują wagami w1w_1 i w2w_2 oraz biasem bb 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 w1=1w_1=1, w2=1w_2=1 i b=0.5b=-0.5 wynik wynosi s(x)=x1+x20.5s(\mathbf{x})=x_1+x_2-0.5. Granica decyzyjna zawiera punkty, dla których wynik jest zerowy:

x1+x20.5=0x_1+x_2-0.5=0

To prosta. Na przykład dla punktu C o współrzędnych (0.5,0.5)(0.5,0.5) podstawiamy x1=0.5x_1=0.5 i x2=0.5x_2=0.5 do wzoru: s(xC)=0.5+0.50.5=0.5s(\mathbf{x}_C)=0.5+0.5-0.5=0.5. Wynik jest dodatni, więc przewidywana klasa to dojrzałe (1). Dla punktu B o współrzędnych (0.5,0.5)(-0.5,0.5) podstawiamy x1=0.5x_1=-0.5 i x2=0.5x_2=0.5: s(xB)=0.5+0.50.5=0.5s(\mathbf{x}_B)=-0.5+0.5-0.5=-0.5. 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 XX, z jednym jabłkiem w wierszu oraz kolorem i miękkością w dwóch kolumnach:

X=[10.50.50.50.50.510]X=\begin{bmatrix} -1&-0.5\\ -0.5&0.5\\ 0.5&0.5\\ 1&0 \end{bmatrix}

Parametry nadal obejmują te same dwie wagi i jeden bias. Jeśli zaczniemy od w1=1w_1=1, w2=1w_2=1 i b=0.5b=-0.5, jak w widżecie, macierz wag i jej transpozycja mają postać:

W=[11],W=[11]W=\begin{bmatrix}1&1\end{bmatrix}, \qquad W^\top=\begin{bmatrix}1\\1\end{bmatrix}

Bias b=0.5b=-0.5 jest oddzielny od WW i dodajemy go do sumy ważonej każdego jabłka.

Dla jednego jabłka zapisanego jako wektor kolumnowy używaliśmy WxW\mathbf{x}. Gdy jabłka są wierszami, używamy XWXW^\top: 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:

S=XW+b1=[10.50.50.50.50.510]×[11]+[0.50.50.50.5]=[20.50.50.5]S=XW^\top+b\mathbf{1} =\begin{bmatrix} -1&-0.5\\ -0.5&0.5\\ 0.5&0.5\\ 1&0 \end{bmatrix} \times\begin{bmatrix}1\\1\end{bmatrix} +\begin{bmatrix}-0.5\\-0.5\\-0.5\\-0.5\end{bmatrix} =\begin{bmatrix}-2\\-0.5\\0.5\\0.5\end{bmatrix}

Tutaj 1\mathbf{1} to kolumna czterech jedynek, więc b1b\mathbf{1} powtarza bias dla wszystkich jabłek. Wiersze SS zawierają wyniki w kolejności A–D. Dla jabłka A pierwszy wiersz oblicza 1(1)+1(0.5)0.5=21(-1)+1(-0.5)-0.5=-2, dokładnie jak przy osobnym przetwarzaniu. Funkcja progowa daje predykcje (0,0,1,1)(0,0,1,1), 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 (x1x_1) i miękkością (x2x_2). Ręcznie zmieniamy ich wartości wejściowe (x1,x2)(x_1,x_2), 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łkoKolor (x1x_1)Miękkość (x2x_2)Klasa
A0.5-0.50.5-0.5Niedojrzałe (0)
B+0.5+0.5+0.5+0.5Niedojrzałe (0)
C0.5-0.5+0.5+0.5Dojrzałe (1)
D+0.5+0.50.5-0.5Dojrzałe (1)

To znany układ XOR. Przesunęliśmy standardowe współrzędne binarne 0 i 1 do 0.5-0.5 i +0.5+0.5, 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 w1=1w_1=1, w2=1w_2=1 i b=0.5b=-0.5 poprawnie klasyfikują tylko jedno jabłko z czterech. Otwórz ustawienia widżetu i zmień w2w_2 na 1-1: uzyskasz trzy poprawne odpowiedzi, ale C nadal będzie błędnie sklasyfikowane.

Zmień wagi i bias
1.00
1.00
-0.50

s(x) = 1.00x₁ + 1.00x₂ − 0.50

x₁-11x₂-11Jabłko A: Kolor -0.5, Miękkość -0.5, Wynik -1.50, Predykcja 0Jabłko C: Kolor -0.5, Miękkość 0.5, Wynik -0.50, Predykcja 0Jabłko D: Kolor 0.5, Miękkość -0.5, Wynik -0.50, Predykcja 0Jabłko B: Kolor 0.5, Miękkość 0.5, Wynik 0.50, Predykcja 1ABCD

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 (h1,h2)(h_1,h_2), w których neuron wyjściowy rozdzieli klasy prostą:

Dwa wejścia, dwa neurony ukryte i jeden neuron wyjściowyOba wejścia łączą się z oboma neuronami ukrytymi przez W. Każdy dodaje swój bias i stosuje ReLU, otrzymując h1 lub h2. Neuron wyjściowy łączy je przez V, dodaje c i przewiduje klasę 1 dla dodatniego wyniku, a klasę 0 w pozostałych przypadkach.Wektor wejściowyWarstwa ukrytaNeuron wyjściowyWVx₁x₂h₁Σ + b₁ReLUh₂Σ + b₂ReLUΣ + cs > 0?Klasa: 0 lub 1

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ą x\mathbf{x} oznaczamy wektor wejściowy jednego jabłka, zawierający kolor x1x_1 i miękkość x2x_2. Dla jabłka C zapisujemy go jako kolumnę:

xC=[0.50.5]R2\mathbf{x}_C = \begin{bmatrix} -0.5 \\ 0.5 \end{bmatrix} \in \mathbb{R}^2

R2\mathbb{R}^2 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.

Jabłko C jako wektor na płaszczyźnie wejściowejStrzałka biegnie od początku układu do jabłka C o kolorze −0.5 i miękkości 0.5. Przerywane linie łączą jej koniec z tymi wartościami na osiach. Strzałka i jej koniec reprezentują tę samą parę współrzędnych.Miękkość (x₂)Kolor (x₁)C(−0.5, 0.5)

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 xC\mathbf{x}_C jest kolumną. W NumPy ta jednowymiarowa tablica pełni rolę wektora wejściowego przy mnożeniu przez WW.

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 XX oznacza macierz wszystkich czterech jabłek, z jednym wektorem wejściowym w każdym wierszu.

X=[0.50.50.50.50.50.50.50.5]R4×2X = \begin{bmatrix} -0.5 & -0.5 \\ 0.5 & 0.5 \\ -0.5 & 0.5 \\ 0.5 & -0.5 \end{bmatrix} \in \mathbb{R}^{4 \times 2}

Wiersze odpowiadają przykładom — czterem jabłkom; kolumny odpowiadają cechom: kolorowi i miękkości. Etykiety dojrzałości są przechowywane osobno.

Wektory wszystkich czterech jabłek na tej samej płaszczyźnie wejściowejCztery strzałki biegną od początku układu do A (−0.5, −0.5), B (0.5, 0.5), C (−0.5, 0.5) i D (0.5, −0.5). Turkusowe koła oznaczają niedojrzałe jabłka A i B, a koralowe kwadraty — dojrzałe C i D. Wszystkie cztery wektory mają po dwie współrzędne.Miękkość (x₂)Kolor (x₁)A(−0.5, −0.5)B(0.5, 0.5)C(−0.5, 0.5)D(0.5, −0.5)

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 XX 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: XX przechowuje reprezentacje, a macierz wag WW 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 28×2828 \times 28 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:

e1=[10],e2=[01]\mathbf{e}_1=\begin{bmatrix}1\\0\end{bmatrix}, \qquad \mathbf{e}_2=\begin{bmatrix}0\\1\end{bmatrix}

Tworzą one standardową bazę płaszczyzny wejściowej. Umieszczając je w kolumnach, otrzymujemy macierz jednostkową:

I=[e1  e2]=[1001]I=[\,\mathbf{e}_1\;\mathbf{e}_2\,] =\begin{bmatrix}1&0\\0&1\end{bmatrix}

Macierz bazy pozwala odtworzyć dowolny wektor z jego współrzędnych w tej bazie. Załóżmy, że definiujemy wektor jako

x=[x1x2]\mathbf{x}=\begin{bmatrix}x_1\\x_2\end{bmatrix}

Pierwsza współrzędna x1x_1 skaluje pierwszą kolumnę e1\mathbf{e}_1, a druga x2x_2 — drugą kolumnę e2\mathbf{e}_2. Używamy kombinacji liniowej wektorów bazowych, ze współczynnikami x1x_1 i x2x_2, aby odtworzyć pierwotny wektor:

Ix=x1e1+x2e2kombinacja liniowa wektoroˊw bazowych=x1[10]+x2[01]=[x1x2]=xI\mathbf{x}=\underbrace{x_1\mathbf{e}_1+x_2\mathbf{e}_2}_{\text{kombinacja liniowa wektorów bazowych}} =x_1\begin{bmatrix}1\\0\end{bmatrix} +x_2\begin{bmatrix}0\\1\end{bmatrix} =\begin{bmatrix}x_1\\x_2\end{bmatrix}=\mathbf{x}

Oznacza to, że aby zdefiniować przekształcenie liniowe, możemy wybrać nowe współrzędne obrazów wektorów bazowych e1\mathbf{e}_1 i e2\mathbf{e}_2 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 e1\mathbf{e}_1 do [a,b][a,b]^\top, a e2\mathbf{e}_2 do [c,d][c,d]^\top. Macierz zapisuje te nowe położenia jako swoje kolumny:

M=[acbd],Me1=[ab],Me2=[cd]M=\begin{bmatrix}a&c\\b&d\end{bmatrix}, \qquad M\mathbf{e}_1=\begin{bmatrix}a\\b\end{bmatrix}, \qquad M\mathbf{e}_2=\begin{bmatrix}c\\d\end{bmatrix}

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ę MM przez x1x_1, a drugą przez x2x_2, po czym dodajemy wyniki:

M[x1x2]=x1[ab]+x2[cd]=[ax1+cx2bx1+dx2]M\begin{bmatrix}x_1\\x_2\end{bmatrix} =x_1\begin{bmatrix}a\\b\end{bmatrix} +x_2\begin{bmatrix}c\\d\end{bmatrix} =\begin{bmatrix}ax_1+cx_2\\bx_1+dx_2\end{bmatrix}

Współrzędne wejściowe (x1,x2)(x_1,x_2) stały się współrzędnymi wyjściowymi (ax1+cx2,  bx1+dx2)(ax_1+cx_2,\;bx_1+dx_2). 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 WW 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 dd wejść i mm neuronów macierz WW ma mm wierszy i dd 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 WW ma dwa wiersze i dwie kolumny. Każdy wiersz zawiera wagę jednego neuronu dla koloru (x1x_1) i miękkości (x2x_2).

W=[10.80.81]W = \begin{bmatrix} 1 & -0.8 \\ -0.8 & 1 \end{bmatrix}

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 x\mathbf{x} przez WW w kolejności WxW\mathbf{x}. Obliczamy w ten sposób jedną sumę ważoną na neuron.

Oprócz macierzy wag warstwa ukryta ma wektor biasów b\mathbf{b}, po jednym dla każdego neuronu. Dodanie b\mathbf{b} daje z=Wx+b\mathbf{z}=W\mathbf{x}+\mathbf{b}, 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 b\mathbf{b}. W naszym przykładzie b=0\mathbf{b}=\mathbf{0}, więc nie ma przesunięcia, a odwzorowanie pozostaje liniowe.

b=[00]\mathbf{b}=\begin{bmatrix}0\\0\end{bmatrix}

Następnie ReLU zastępuje każdą ujemną współrzędną z\mathbf{z} zerem, dając h=(h1,h2)\mathbf{h}=(h_1,h_2) — wyjście warstwy ukrytej. To nowa reprezentacja jabłka, z której neuron wyjściowy obliczy wynik. Pełna sekwencja wygląda tak:

x    W    Wx    +b    z    ReLU    h\mathbf{x} \;\xrightarrow{\;W\;}\; W\mathbf{x} \;\xrightarrow{\;+\mathbf{b}\;}\; \mathbf{z} \;\xrightarrow{\;\text{ReLU}\;}\; \mathbf{h}

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ęcia

Jako przykład wykorzystamy jabłko A, ale widżet poniżej pozwala prześledzić dowolne z czterech jabłek przez tę samą macierz WW. Pierwotny wektor A to

xA=[0.50.5]\mathbf{x}_A=\begin{bmatrix}-0.5\\-0.5\end{bmatrix}

Korzystając z reguły kombinacji kolumn, obliczymy WxAW\mathbf{x}_A krok po kroku z pierwotnych współrzędnych i kolumn WW.

  1. Odczytaj kolumny WW jako obrazy wektorów bazowych e1\mathbf{e}_1 i e2\mathbf{e}_2. Pierwsza kolumna pokazuje, dokąd trafia e1=(1,0)\mathbf{e}_1=(1,0), a druga — dokąd trafia e2=(0,1)\mathbf{e}_2=(0,1):

    W=[10.80.81],We1=[10.8],We2=[0.81].W=\begin{bmatrix}1&-0.8\\-0.8&1\end{bmatrix}, \qquad W\mathbf{e}_1=\begin{bmatrix}1\\-0.8\end{bmatrix}, \qquad W\mathbf{e}_2=\begin{bmatrix}-0.8\\1\end{bmatrix}.

    To pierwotne wektory bazowe po zastosowaniu WW.

  2. 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:

    WxA=0.5(We1)+(0.5)(We2)W\mathbf{x}_A=-0.5(W\mathbf{e}_1)+(-0.5)(W\mathbf{e}_2)

    Współczynniki pozostają takie same; mnożone przez nie wektory są teraz przekształconymi kierunkami. Pierwsza współrzędna 0.5-0.5 skaluje pierwszą kolumnę WW, czyli obraz pierwszego wektora bazowego We1W\mathbf{e}_1. Druga współrzędna, również 0.5-0.5, skaluje drugą kolumnę — obraz drugiego wektora bazowego We2W\mathbf{e}_2.

    Pełne obliczenie wygląda więc tak:

    WxA=0.5[10.8]+(0.5)[0.81]=[(0.5)×1(0.5)×(0.8)]+[(0.5)×(0.8)(0.5)×1]=[0.50.4]+[0.40.5]=[0.5+0.40.4+(0.5)]=[0.10.1]\begin{aligned} W\mathbf{x}_A &=-0.5\begin{bmatrix}1\\-0.8\end{bmatrix} +(-0.5)\begin{bmatrix}-0.8\\1\end{bmatrix}\\[6pt] &=\begin{bmatrix}(-0.5)\times 1\\(-0.5)\times(-0.8)\end{bmatrix} +\begin{bmatrix}(-0.5)\times(-0.8)\\(-0.5)\times 1\end{bmatrix}\\[6pt] &=\begin{bmatrix}-0.5\\0.4\end{bmatrix} +\begin{bmatrix}0.4\\-0.5\end{bmatrix} =\begin{bmatrix}-0.5+0.4\\0.4+(-0.5)\end{bmatrix} =\begin{bmatrix}-0.1\\-0.1\end{bmatrix} \end{aligned}

Dwa wkłady częściowo się znoszą, pozostawiając (0.1,0.1)(-0.1,-0.1). 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ą +0.5+0.5:

WxB=0.5[10.8]+0.5[0.81]=[0.10.1]W\mathbf{x}_B =0.5\begin{bmatrix}1\\-0.8\end{bmatrix} +0.5\begin{bmatrix}-0.8\\1\end{bmatrix} =\begin{bmatrix}0.1\\0.1\end{bmatrix}

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 WW, 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 (0.5,0.5)(-0.5,-0.5) do (0.1,0.1)(-0.1,-0.1).

Jabłko:
-1-1110eeJabłko A: (−0.50, −0.50)A
Początkowe: (−0.50, −0.50)Teraz: (−0.50, −0.50)

Działanie przekształcenia widać na dwóch kierunkach przekątnych:

W[11]=0.2[11],W[11]=1.8[11]W\begin{bmatrix}1\\1\end{bmatrix} =0.2\begin{bmatrix}1\\1\end{bmatrix}, \qquad W\begin{bmatrix}1\\-1\end{bmatrix} =1.8\begin{bmatrix}1\\-1\end{bmatrix}

Kierunek A–B kurczy się do jednej piątej długości, a C–D rozciąga 1.81.8 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:

W=[10.80.81],xA=[0.50.5]WxA=[0.10.1]W=\begin{bmatrix}1&-0.8\\-0.8&1\end{bmatrix}, \qquad \mathbf{x}_A=\begin{bmatrix}-0.5\\-0.5\end{bmatrix} \quad\longmapsto\quad W\mathbf{x}_A=\begin{bmatrix}-0.1\\-0.1\end{bmatrix}

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 x1x_1 i dla x2x_2. Mamy dwa neurony, a każdy dostarcza jeden wiersz wag:

NeuronWaga dla x1x_1Waga dla x2x_2Wiersz WW
1110.8-0.8(1,0.8)(1,-0.8)
20.8-0.811(0.8,1)(-0.8,1)

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 x1x_1. Dla wejścia e1=(1,0)\mathbf{e}_1=(1,0) wagi te dają dwie współrzędne wyjściowe We1=(1,0.8)W\mathbf{e}_1=(1,-0.8). Kolumna łączy więc wkłady obu neuronów.

Złożenie tych wierszy daje macierz WW o rozmiarze 2×22\times2. Ogólniej, dla warstwy gęstej z dd współrzędnymi wejściowymi i mm neuronami mamy

WRm×d,xRd    WxRmW\in\mathbb{R}^{m\times d}, \qquad \mathbf{x}\in\mathbb{R}^d \;\longmapsto\; W\mathbf{x}\in\mathbb{R}^m

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 2×32\times3, 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 WxA=(0.1,0.1)W\mathbf{x}_A=(-0.1,-0.1), 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ą: fi(x)=wixf_i(\mathbf{x})=\mathbf{w}_i\cdot\mathbf{x}. Dla pierwszego neuronu ta funkcja liniowa o wartościach skalarnych ma postać f1(x)=x10.8x2f_1(\mathbf{x})=x_1-0.8x_2 i odwzorowuje R2\mathbb{R}^2 na R\mathbb{R}. 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ą T(x)=(f1(x),f2(x))=WxT(\mathbf{x})=(f_1(\mathbf{x}),f_2(\mathbf{x}))^\top=W\mathbf{x}, odwzorowanie z R2\mathbb{R}^2 do R2\mathbb{R}^2.

Przy zerowych biasach wyniki tych funkcji są też współrzędnymi przed aktywacją. Rozpisanie iloczynów skalarnych daje

z=Wx=[w1xw2x]=[1x1+(0.8)x2(0.8)x1+1x2]\mathbf{z}=W\mathbf{x} =\begin{bmatrix} \mathbf{w}_1\cdot\mathbf{x}\\ \mathbf{w}_2\cdot\mathbf{x} \end{bmatrix} =\begin{bmatrix} 1\cdot x_1+(-0.8)\cdot x_2\\ (-0.8)\cdot x_1+1\cdot x_2 \end{bmatrix}

Dla tego samego jabłka A oba neurony otrzymują (0.5,0.5)(-0.5,-0.5):

NeuronObliczenieWynikowa współrzędna
11(0.5)+(0.8)(0.5)=0.11(-0.5)+(-0.8)(-0.5)=-0.1z1=0.1z_1=-0.1
2(0.8)(0.5)+1(0.5)=0.1(-0.8)(-0.5)+1(-0.5)=-0.1z2=0.1z_2=-0.1

Zbierając te dwie liczby, otrzymujemy zA=(0.1,0.1)\mathbf{z}_A=(-0.1,-0.1) — 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 WW jest symetryczna, więc jej wiersze i kolumny zawierają akurat te same liczby, ale pełnią różne role.

x1[10.8]+x2[0.81]kombinacja kolumn=[x10.8x20.8x1+x2]=[(1,0.8)(x1,x2)(0.8,1)(x1,x2)]iloczyny skalarne wierszy\underbrace{ x_1\begin{bmatrix}1\\-0.8\end{bmatrix} +x_2\begin{bmatrix}-0.8\\1\end{bmatrix} }_{\text{kombinacja kolumn}} =\begin{bmatrix}x_1-0.8x_2\\-0.8x_1+x_2\end{bmatrix} =\underbrace{ \begin{bmatrix} (1,-0.8)\cdot(x_1,x_2)\\ (-0.8,1)\cdot(x_1,x_2) \end{bmatrix} }_{\text{iloczyny skalarne wierszy}}

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_A

Dodanie biasu sprawia, że funkcja neuronu jest afiniczna, a zastosowanie ReLU czyni ją nieliniową. Wyjścia razem tworzą reprezentację ukrytą h\mathbf{h}. 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 C

Gdy 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 XX, a ich dwie współrzędne w kolumnach.

Dla jednego wektora kolumnowego używaliśmy WxW\mathbf{x}. Ponieważ batch przechowuje wektory w wierszach, używamy XWXW^\top: transpozycja WW 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 XX, a wagi neuronów — wierszami WW. Gdyby wagi przechowywano po jednym neuronie w kolumnie, używalibyśmy bezpośrednio XWXW. Transpozycja dopasowuje wymiary, nie zmienia zamierzonego przekształcenia.

Przy naszych zerowych biasach obliczenie wygląda tak:

Z=XW=[0.50.50.50.50.50.50.50.5]×[10.80.81]=[0.10.10.10.10.90.90.90.9]Z=XW^\top =\begin{bmatrix} -0.5&-0.5\\ 0.5&0.5\\ -0.5&0.5\\ 0.5&-0.5 \end{bmatrix} \times \begin{bmatrix} 1&-0.8\\ -0.8&1 \end{bmatrix} =\begin{bmatrix} -0.1&-0.1\\ 0.1&0.1\\ -0.9&0.9\\ 0.9&-0.9 \end{bmatrix}

Każdy wiersz ZZ zawiera przekształcony wektor jednego jabłka; każda kolumna — sumy ważone jednego neuronu w całym batchu. Pierwszy wiersz nadal wynosi (0.1,0.1)(-0.1,-0.1) dla A, dokładnie jak przy osobnym przetwarzaniu. Macierz WW jest symetryczna, więc WW^\top ma te same elementy co WW, 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]]  D

Batch 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ą z=Wx\mathbf{z}=W\mathbf{x}, bez przesunięcia, bo biasy są zerowe. Oto współrzędne przed aktywacją:

PunktKlasaz=Wx\mathbf{z}=W\mathbf{x}
A0(0.1,0.1)(-0.1,-0.1)
B0(0.1,0.1)(0.1,0.1)
C1(0.9,0.9)(-0.9,0.9)
D1(0.9,0.9)(0.9,-0.9)

Odcinek A–B jest krótszy, a C–D dłuższy, ale nadal się przecinają. Prosta nie może rozdzielić obu klas.

Przed W
-1-111x₁x₂ABCD
Po W
-1-111z₁z₂ABCD

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 h\mathbf{h}:

ReLU(z)=max(0,z),h=ReLU(z)\operatorname{ReLU}(z)=\max(0,z), \qquad \mathbf{h}=\operatorname{ReLU}(\mathbf{z})

Zachowuje ona wartości dodatnie, a ujemne zastępuje zerem. Dwie współrzędne ukryte przyjmują postać

h1=max(0,x10.8x2),h2=max(0,x20.8x1)h_1=\max(0,x_1-0.8x_2), \qquad h_2=\max(0,x_2-0.8x_1)

h1h_1 mierzy dodatnią część x10.8x2x_1-0.8x_2, a h2h_2 — dodatnią część x20.8x1x_2-0.8x_1. 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 WW. Początkowo wybrane jest A, którego wektor zmienia się z (0.1,0.1)(-0.1,-0.1) na (0,0)(0,0). Wybór innego jabłka resetuje animację do jego współrzędnych sprzed aktywacji.

Jabłko:
-1-1110z₁z₂A
Przed ReLU: (−0.10, −0.10)Teraz: (−0.10, −0.10)

Oto współrzędne wszystkich czterech jabłek przed i po ReLU, zgodne z wynikami w widżecie:

PunktKlasaPrzed ReLU: z\mathbf{z}Po ReLU: h\mathbf{h}
A0(0.1,0.1)(-0.1,-0.1)(0,0)(0,0)
B0(0.1,0.1)(0.1,0.1)(0.1,0.1)(0.1,0.1)
C1(0.9,0.9)(-0.9,0.9)(0,0.9)(0,0.9)
D1(0.9,0.9)(0.9,-0.9)(0.9,0)(0.9,0)

A trafia do początku układu, bo obie współrzędne są ujemne. B pozostaje w (0.1,0.1)(0.1,0.1), 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. ]]  D

ReLU nie ogranicza wyjścia do 0 lub 1. Tutaj zachowuje wartości takie jak 0.10.1 i 0.90.9. 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: h1h_1 i h2h_2.

Dwa wejścia, dwa neurony ukryte i jeden neuron wyjściowyOba wejścia łączą się z oboma neuronami ukrytymi przez W. Każdy dodaje swój bias i stosuje ReLU, otrzymując h1 lub h2. Neuron wyjściowy łączy je przez V, dodaje c i przewiduje klasę 1 dla dodatniego wyniku, a klasę 0 w pozostałych przypadkach.Wektor wejściowyWarstwa ukrytaNeuron wyjściowyWVx₁x₂h₁Σ + b₁ReLUh₂Σ + b₂ReLUΣ + cs > 0?Klasa: 0 lub 1

Ten neuron ma własne wagi zapisane w VV oraz bias cc. Uczenie wyznaczałoby te parametry razem z wagami i biasami warstwy ukrytej. Na potrzeby demonstracji wybieramy je ręcznie:

V=[11],c=0.5V=\begin{bmatrix}1&1\end{bmatrix}, \qquad c=-0.5

Wynik to kolejny iloczyn skalarny, po którym dodajemy bias:

s(h)=Vh+c=[11]×[h1h2]+(0.5)=1h1+1h20.5=h1+h20.5s(\mathbf{h})=V\mathbf{h}+c =\begin{bmatrix}1&1\end{bmatrix} \times\begin{bmatrix}h_1\\h_2\end{bmatrix}+(-0.5) =1\cdot h_1+1\cdot h_2-0.5 =h_1+h_2-0.5

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:

h1+h20.5=0h1+h2=0.5h_1+h_2-0.5=0 \quad\Longrightarrow\quad h_1+h_2=0.5

Próg ten leży między sumami obu klas:

JabłkoReprezentacja ukryta (h1,h2)(h_1,h_2)h1+h2h_1+h_2WynikPredykcja
A(0,0)(0,0)000.5-0.5Niedojrzałe (0)
B(0.1,0.1)(0.1,0.1)0.20.20.3-0.3Niedojrzałe (0)
C(0,0.9)(0,0.9)0.90.90.40.4Dojrzałe (1)
D(0.9,0)(0.9,0)0.90.90.40.4Dojrzałe (1)

Sumy A i B są mniejsze od 0.50.5, a C i D większe. Dlatego prosta h1+h2=0.5h_1+h_2=0.5 rozdziela klasy. Nasz próg nie jest jedynym możliwym: dowolna wartość ściśle między 0.20.2 a 0.90.9 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ć (x1,x2,s(x))(x_1,x_2,s(\mathbf{x})): 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
Neuron ukryty 1: h1 = ReLU(1.00x₁ + (-0.80)x₂ + (0.00))
1.00
-0.80
0.00
Neuron ukryty 2: h2 = ReLU(-0.80x₁ + (1.00)x₂ + (0.00))
-0.80
1.00
0.00
Wyjście: s = 1.00h₁ + (1.00)h₂ + (-0.50)
1.00
1.00
-0.50

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

x₁-11x₂-11Jabłko A: Kolor -0.5, Miękkość -0.5, Wynik -0.50, Predykcja 0Jabłko B: Kolor 0.5, Miękkość 0.5, Wynik -0.30, Predykcja 0Jabłko C: Kolor -0.5, Miękkość 0.5, Wynik 0.40, Predykcja 1Jabłko D: Kolor 0.5, Miękkość -0.5, Wynik 0.40, Predykcja 1ABCD

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 HH obliczamy S=HV+cS=HV^\top+c, 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=1

Dlaczego samo ReLU nie rozwiązuje tego przykładu

Co by było, gdyby warstwa ukryta używała macierzy jednostkowej II i zerowych biasów? Obliczałaby h=ReLU(Ix)=ReLU(x)\mathbf{h}=\operatorname{ReLU}(I\mathbf{x})=\operatorname{ReLU}(\mathbf{x}), 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 (0,0)(0,0), B do (0.5,0.5)(0.5,0.5), C do (0,0.5)(0,0.5), a D do (0.5,0)(0.5,0). 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 WW. Oba warianty warstwy ukrytej zaczynają od pierwotnych wektorów wejściowych i mają zerowe biasy.

Oryginalne wejścia: przekątne obu klas przecinają się.

-1-1-0.5-0.500.50.511x₁x₂A: (−0.5, −0.5), Etykieta 0AB: (0.5, 0.5), Etykieta 0BC: (−0.5, 0.5), Etykieta 1CD: (0.5, −0.5), Etykieta 1D

Każdy kwadrat ma rozmiar 0.1 × 0.1. Skala osi jest taka sama we wszystkich trzech widokach.

A: (−0.5, −0.5)B: (0.5, 0.5)C: (−0.5, 0.5)D: (0.5, −0.5)

Żadna prosta nie rozdziela obu klas.

Macierz WW najpierw zmienia współrzędne tak, aby ReLU działało na użyteczniejszym układzie. Umieszcza B w (0.1,0.1)(0.1,0.1), a C i D w (0.9,0.9)(-0.9,0.9) i (0.9,0.9)(0.9,-0.9). 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 WW, 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 R2\mathbb{R}^2 na R2\mathbb{R}^2, tworząc reprezentację ukrytą, a wyjściowa — R2\mathbb{R}^2 na R\mathbb{R}, 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 FF, a funkcję warstwy wyjściowej GG:

F(x)=ReLU(Wx+b),G(h)=Vh+cF(\mathbf{x})=\operatorname{ReLU}(W\mathbf{x}+\mathbf{b}), \qquad G(\mathbf{h})=V\mathbf{h}+c

Propagacja w przód, którą śledziliśmy w widżetach, ma więc postać

xFhGs(x)\mathbf{x}\xrightarrow{F}\mathbf{h}\xrightarrow{G}s(\mathbf{x})

Podstawienie wyjścia warstwy ukrytej do funkcji warstwy wyjściowej daje

s(x)=G(F(x))=V×ReLU(Wx+b)+cs(\mathbf{x})=G(F(\mathbf{x})) =V\times\operatorname{ReLU}(W\mathbf{x}+\mathbf{b})+c

To właśnie oznacza tutaj składanie funkcji. Typowy zapis złożenia to GFG\circ F: najpierw zastosuj FF, potem GG. Głębsza sieć działa według tej samej zasady FLF2F1F_L\circ\cdots\circ F_2\circ F_1: 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 WxW\mathbf{x}:

sbez ReLU(x)=V(Wx)+c=(VW)x+cs_{\text{bez ReLU}}(\mathbf{x}) =V(W\mathbf{x})+c =(VW)\mathbf{x}+c

Możemy najpierw pomnożyć VV i WW, a ich iloczynu użyć jako pojedynczej macierzy wag:

VW=[11]×[10.80.81]=[11+1(0.8)1(0.8)+11]=[0.20.2]VW= \begin{bmatrix}1&1\end{bmatrix} \times\begin{bmatrix}1&-0.8\\-0.8&1\end{bmatrix} =\begin{bmatrix}1\cdot1+1\cdot(-0.8)&1\cdot(-0.8)+1\cdot1\end{bmatrix} =\begin{bmatrix}0.2&0.2\end{bmatrix}

Otrzymana sieć oblicza

sbez ReLU(x)=0.2x1+0.2x20.5s_{\text{bez ReLU}}(\mathbf{x})=0.2x_1+0.2x_2-0.5

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 0.7-0.7, 0.3-0.3, 0.5-0.5 i 0.5-0.5, 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:

V(Wx+b)+c=(VW)x+(Vb+c)V(W\mathbf{x}+\mathbf{b})+c =(VW)\mathbf{x}+(V\mathbf{b}+c)

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 WW pokazują, dokąd trafiają e1\mathbf{e}_1 i e2\mathbf{e}_2 po pierwszym przekształceniu. Zastosowanie VV do tych obrazów pokazuje, dokąd trafią po obu:

VW=[V(We1)V(We2)]=[0.20.2]VW=\begin{bmatrix}V(W\mathbf{e}_1)&V(W\mathbf{e}_2)\end{bmatrix} =\begin{bmatrix}0.2&0.2\end{bmatrix}

Każdy wejściowy wektor bazowy trafia do 0.20.2 na wyjściowej osi liczbowej. Dla dowolnego wejścia ta sama reguła kombinacji kolumn daje więc (VW)x=0.2x1+0.2x2(VW)\mathbf{x}=0.2x_1+0.2x_2.

Ogólniej, mnożenie dwóch macierzy przekształceń stosuje lewą macierz do każdej kolumny prawej. Jeśli zastosujemy M1M_1, a potem M2M_2, ich iloczyn zapisze końcowe obrazy wejściowych wektorów bazowych:

xM1M1xM2M2(M1x)=(M2M1)x\mathbf{x}\xrightarrow{M_1}M_1\mathbf{x} \xrightarrow{M_2}M_2(M_1\mathbf{x}) =(M_2M_1)\mathbf{x}

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 WW a VV. Neuron wyjściowy otrzymuje ReLU(Wx)\operatorname{ReLU}(W\mathbf{x}), więc nie dodaje już po prostu dwóch współrzędnych z WW. Dodaje ich części nieujemne:

s(x)=max(0,x10.8x2)+max(0,x20.8x1)0.5s(\mathbf{x}) =\max(0,x_1-0.8x_2)+\max(0,x_2-0.8x_1)-0.5

Współrzędne C po WW wynoszą na przykład (0.9,0.9)(-0.9,0.9). Bez ReLU znoszą się, dając wynik 0.5-0.5. Z ReLU neuron wyjściowy otrzymuje (0,0.9)(0,0.9) i daje wynik 0.40.4. Te same wagi wyjściowe dają teraz poprawną predykcję, bo otrzymują inną reprezentację.

VReLU(Wx)(VW)xw ogoˊlnosˊciV\operatorname{ReLU}(W\mathbf{x})\neq(VW)\mathbf{x} \quad\text{w ogólności}

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 h1+h2=0.5h_1+h_2=0.5. Podstawienie funkcji warstwy ukrytej daje tę samą granicę we współrzędnych wejściowych:

max(0,x10.8x2)+max(0,x20.8x1)=0.5\max(0,x_1-0.8x_2)+\max(0,x_2-0.8x_1)=0.5

Tam, gdzie aktywny jest tylko pierwszy neuron, otrzymujemy x10.8x2=0.5x_1-0.8x_2=0.5. Gdy tylko drugi, otrzymujemy x20.8x1=0.5x_2-0.8x_1=0.5. Gdy aktywne są oba, wynik to 0.2(x1+x2)=0.50.2(x_1+x_2)=0.5. 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:

X4×2    Z4×2    ReLU    H4×2    S4×1\underbrace{X}_{4\times2} \;\longrightarrow\; \underbrace{Z}_{4\times2} \;\xrightarrow{\;\operatorname{ReLU}\;}\; \underbrace{H}_{4\times2} \;\longrightarrow\; \underbrace{S}_{4\times1}

Stosujemy tę samą konwencję jednego przykładu w wierszu: Z=XW+bZ=XW^\top+\mathbf{b}, H=ReLU(Z)H=\operatorname{ReLU}(Z) i S=HV+cS=HV^\top+c, 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]

XX i HH opisują te same przykłady w różnych przestrzeniach. WW i VV 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.