Kurs: sieci neuronowe od środka · Lekcja 2 z 12 · 14 min
Sieć wielowarstwowa na cyfrach
W skrócie
Jeden neuron rysuje jedną prostą. Warstwa ukryta składa wiele takich prostych w jedną decyzję, a macierz pomyłek pokazuje, gdzie ta decyzja wciąż zawodzi.
Rozgrzewka Dwa pytania z kursu ML, na których ta lekcja się opiera. Minuta, a lekcji nie blokuje.
W lekcji 1 jeden neuron rysował jedną prostą i na tym kończyły się jego możliwości. Odręcznie napisana cyfra to coś więcej niż dwa pomiary kwiatu: 64 piksele i 10 możliwych odpowiedzi. Sieć wielowarstwowa ma między wejściem a wyjściem warstwę ukrytą: wiele neuronów, z których każdy robi to samo co neuron z lekcji 1. Sprawdźmy, ile to daje.
Wybierz odpowiedź i pewność.
Odpowiedz, żeby odblokować lab.
Pokaż odpowiedź
Poprawna odpowiedź: Ok. 95%
Po 10 epokach sieć trafia 95,3%, czyli 342 z 359 cyfr testowych; przed treningiem, z losowymi wagami, 8,4%. Cztery kolejne starty od nowa dają od 94,4% do 95,3%.
Spróbuj
- Kliknij „trenuj 10 epok” i patrz, jak rośnie trafność na teście: od 8,4% przed treningiem do 95,3% po 10 epokach.
- Przeczytaj macierz pomyłek: w każdym wierszu dodaj liczby poza przekątną. Kliknij cyfrę, w której wierszu ta suma jest największa. To cel labu.
- Porównaj ten wiersz z kolumną tej samej cyfry. Wiersz mówi, ile razy sieć nie poznała cyfry, a kolumna, ile razy wskazała ją bez powodu.
- Kliknij „od nowa” i wytrenuj jeszcze raz. Nowe wagi początkowe dają inne pomyłki, ale podobną trafność.
Pomysł w prostych słowach
Każdy z 32 neuronów ukrytych liczy ważoną sumę 64 pikseli, dodaje bias i przepuszcza wynik przez funkcję aktywacji (tu tanh). Tak jak neuron z lekcji 1 odpowiada na jedno proste pytanie, na przykład „czy jest tusz w lewej górnej części, a nie ma go pośrodku?”. Odpowiedź nie jest twarda: tanh daje liczbę od −1 do 1, czyli „raczej tak” albo „raczej nie”.
Warstwa wyjściowa dostaje te 32 odpowiedzi. Każdy z 10 neuronów wyjściowych liczy z nich własną ważoną sumę, a sieć wybiera cyfrę z najwyższym wynikiem. Decyzja „to trójka” nie jest jedną prostą, tylko głosowaniem wielu prostych pytań o piksele. Żadnego z tych pytań nikt nie wpisał ręcznie: wagi ustawia spadek gradientu na 1438 cyfrach treningowych.
Na płaszczyźnie widać to najlepiej. Jedna prosta dzieli płaszczyznę na dwie części. Trzy neurony ukryte to trzy proste, a neuron wyjściowy, który mówi „tak” tylko wtedy, gdy wszystkie trzy się zgadzają, rysuje trójkąt. Pokazuje to kod niżej, w warstwie „W kodzie”.
Dlaczego to działa
Złożenie prostych to stary wynik matematyki. Twierdzenie o uniwersalnej aproksymacji (Cybenko, 1989) mówi: jedna warstwa ukryta z dostatecznie wieloma neuronami przybliży dowolnie dokładnie każdą ciągłą funkcję na ograniczonym i domkniętym obszarze. Twierdzenie nie mówi jednak, ilu neuronów trzeba ani czy spadek gradientu je znajdzie. To sprawdza się w praktyce, na danych.
Sieć z labu ma 2410 wag: 64 · 32 + 32 w warstwie ukrytej i 32 · 10 + 10 w wyjściowej. Tyle wystarcza, żeby rozpoznać 95,3% cyfr testowych.
Trafność to jedna liczba. Macierz pomyłek pokazuje, skąd się bierze. Wiersz to prawdziwa cyfra, kolumna to odpowiedź sieci, a przekątna to trafienia. Na pierwszym starcie po 10 epokach najwięcej pomyłek mają trójka i ósemka, po 4. Każdą z czterech ósemek sieć wzięła za inną cyfrę. Za to w kolumnie „8” stoi 5 pomyłek: ósemka jest najczęstszą błędną odpowiedzią. Sieć wskazała ją na dwie trójki, szóstkę, siódemkę i dziewiątkę. Na pięciu pierwszych startach labu najczęściej mylona cyfra to zawsze 3 albo 8, a zero i dwójka nie są pomylone ani razu.
Częsty błąd
Prawda czy fałsz: „Więcej neuronów = lepsza sieć.” Fałsz. W kursie ML na irysach sieć z 32 neuronami ukrytymi trafiała 70,7% kwiatów testowych, a z 2 neuronami 81,3%, choć na treningu była lepsza: 94,7% wobec 81,3%. Na 75 kwiatach treningowych większa sieć zapamiętała przypadkowe szczegóły.
Na cyfrach ten sam trening co w labie, 10 epok i pięć startów wag, daje średnio 95,0% na teście przy 32 neuronach i 95,1% przy 512. Różnica jest mniejsza niż jedna cyfra testowa, a sieć z 512 neuronami ma prawie 16 razy więcej wag. Za mało neuronów szkodzi: przy 2 neuronach średnio 52,6%, bo wszystko, co sieć wie o cyfrze, musi zmieścić się w 2 liczbach. Szerokość ma wystarczyć do zadania. Powyżej tego progu więcej neuronów nie pomaga, a na małych danych może zaszkodzić. Te szerokości porównujemy na teście, bo to tylko ilustracja. Gdy szerokość naprawdę wybierasz, porównuj na walidacji, a test zostaw na koniec.
Quiz
Trzy pytania na sprawdzenie się. Wybierz odpowiedź albo rozwiń odpowiedź pod pytaniem. Z klawiatury: strzałki wybierają, Enter albo spacja zatwierdza.
Najważniejsze wnioski
- Warstwa ukryta to wiele prostych neuronów; wyjście łączy ich odpowiedzi w jedną decyzję.
- Kilka prostych złożonych razem daje granice z narożnikami, których jeden neuron nie narysuje.
- Macierz pomyłek: wiersz mówi, której cyfry sieć nie poznaje, kolumna, którą odpowiedź daje bez powodu.
- Więcej neuronów nie znaczy lepiej: szerokość ma wystarczyć, a resztę rozstrzyga walidacja.
Głębiej: wzór, kod, praktyka
Matematyka
Sieć 64 → 32 → 10 dla obrazka x (64 piksele) liczy:
- warstwa ukryta:
h = tanh(W₁·x + b₁), gdzieW₁ma 32 wiersze po 64 wagi, ab₁to 32 biasy; - wyjście:
z = W₂·h + b₂, gdzieW₂ma 10 wierszy po 32 wagi;softmax(z)daje prawdopodobieństwa 10 cyfr (lekcja 4); - liczba wag:
64·32 + 32 + 32·10 + 10 = 2410; - trafność z macierzy pomyłek
C: suma przekątnej podzielona przez sumę wszystkich pól; pomyłki cyfryd: suma wierszadminusC[d, d].
W kodzie (Python)
Dwie rzeczy w numpy: trzy neurony ukryte, które razem rysują trójkąt, i odczyt macierzy pomyłek z labu (pierwszy start, 10 epok):
import numpy as np
relu = lambda z: np.maximum(0, z)
# warstwa ukryta: trzy neurony, każdy to jedna prosta (wiersz = wagi x i y)
W1 = np.array([[-1, 1], [1, 1], [0, -1]])
b1 = np.array([0, -12, 1]) # proste: y = x, x + y = 12, y = 1
xs, ys = np.meshgrid(np.arange(13), np.arange(6, -1, -1))
P = np.stack([xs.ravel(), ys.ravel()], axis=1)
H = relu(P @ W1.T + b1) # jak daleko punkt leży po złej stronie każdej prostej
inside = 0.5 - H.sum(axis=1) > 0 # neuron wyjściowy: „tak”, gdy żaden neuron ukryty nie protestuje
for row in inside.reshape(xs.shape):
print("".join("#" if v else "." for v in row))
print("points inside:", inside.sum())
# macierz pomyłek z labu: pierwszy start, 10 epok; wiersz = prawdziwa cyfra, kolumna = odpowiedź sieci
cm = np.array([
[36, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 35, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 35, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 33, 0, 1, 0, 0, 2, 0],
[1, 1, 0, 0, 34, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 35, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 35, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 35, 1, 0],
[0, 1, 1, 0, 0, 1, 1, 0, 31, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 1, 33],
])
off = cm - np.diag(np.diag(cm)) # same pomyłki
missed, said = off.sum(axis=1), off.sum(axis=0)
print(f"accuracy: {np.trace(cm)}/{cm.sum()} = {np.trace(cm) / cm.sum():.1%}")
print("missed per true digit:", missed)
print("most missed:", np.flatnonzero(missed == missed.max()), "with", missed.max(), "errors each")
print("wrong answers per digit:", said)
a, b = np.unravel_index(off.argmax(), off.shape)
print(f"most common pair: true {a} -> answer {b}, {off[a, b]} times")
Wynik
......#......
.....###.....
....#####....
...#######...
..#########..
.###########.
.............
points inside: 36
accuracy: 342/359 = 95.3%
missed per true digit: [0 1 0 4 2 1 1 1 4 3]
most missed: [3 8] with 4 errors each
wrong answers per digit: [1 2 2 0 1 3 1 0 5 2]
most common pair: true 3 -> answer 8, 2 times
Neuron wyjściowy mówi „tak” tylko w trójkącie, gdzie żadna z trzech prostych nie protestuje; jeden neuron nie narysuje obszaru zamkniętego z trzech stron. Macierz w kodzie to ta sama, którą lab pokazuje po 10 epokach. Uruchomione w numpy 2.1.3.
Pobierz notebook (.ipynb). Otwórz go w Jupyterze albo wgraj do Google Colab; brakujące dane pobierze sam.
Dla praktyka
- Zacznij od jednej warstwy ukrytej. Szerokość dobieraj na walidacji, od małej do większej, i przestań, gdy wynik przestaje rosnąć.
- Patrz na macierz pomyłek, nie tylko na trafność. Para cyfr mylonych często podpowiada, czego brakuje: więcej przykładów tej pary albo lepsze dane.
- Przy 359 cyfrach testowych jedna cyfra to 0,3 punktu procentowego. Różnice tej wielkości między sieciami to szum.
- W scikit-learn tę samą sieć daje
MLPClassifier(hidden_layer_sizes=(32,)), a macierz pomyłekconfusion_matrix. Wiersz to tam też prawdziwa klasa.
Więcej w Atlasie
Hasła encyklopedii o pojęciach z tej lekcji: