ML Atlas

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.
Z kursu ML, lekcja 7.1: Sieci neuronowe: wiele prostych elementów razem Co daje dodanie neuronów ukrytych?

Pokaż odpowiedź

Poprawna odpowiedź: Daje elastyczność: złożone granice, ale też ryzyko uczenia się na pamięć

Więcej neuronów to więcej możliwych zgięć, które mogą uchwycić prawdziwą strukturę albo przeuczyć model, zależnie od danych.

Z kursu ML, lekcja 7.1: Sieci neuronowe: wiele prostych elementów razem Dlaczego sieć neuronowa potrzebuje funkcji aktywacji?

Pokaż odpowiedź

Poprawna odpowiedź: Bo bez niej kilka warstw działa jak jedna ważona suma i rysuje prostą

Aktywacja zagina sygnał; bez zgięć kolejne warstwy nie dodają nic ponad jeden model liniowy.

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.

Przewidź, potem sprawdź Sieć 64 → 32 → 10 uczy się 10 epok na cyfrach 8×8: 64 piksele na wejściu, 32 neurony ukryte, 10 neuronów wyjściowych, po jednym na cyfrę. Ile cyfr testowych, których nigdy nie widziała, rozpozna?
Jak bardzo jesteś pewny(-a)?

Eksperyment

Ten interaktywny eksperyment działa w przeglądarce z włączonym JavaScriptem. Tekst wokół opisuje, co w nim widać. W eksperymencie: sieć 64→32→10 uczy się rozpoznawać ręcznie pisane cyfry 8×8 w przeglądarce; po 10 epokach trafia w około 95% cyfr testowych, a macierz pomyłek pokazuje, które cyfry myli.

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.

1. Co robi warstwa ukryta w sieci 64 → 32 → 10?
Pokaż odpowiedź

Poprawna odpowiedź: Każdy z 32 neuronów odpowiada na jedno proste pytanie o piksele, a wyjście łączy te odpowiedzi w decyzję

Neuron ukryty to ważona suma pikseli z aktywacją; wyjście głosuje na podstawie 32 takich odpowiedzi.

2. W macierzy pomyłek wiersz to prawdziwa cyfra, kolumna to odpowiedź sieci. Jak znaleźć cyfrę, którą sieć myli najczęściej?
Pokaż odpowiedź

Poprawna odpowiedź: W każdym wierszu dodać liczby poza przekątną i wybrać wiersz z największą sumą

Liczby poza przekątną w wierszu to cyfry tej klasy, którym sieć dała złą odpowiedź.

3. Sieć z 512 neuronami ukrytymi ma prawie 16 razy więcej wag niż sieć z 32. Co pokazuje lab po 10 epokach na cyfrach?
Pokaż odpowiedź

Poprawna odpowiedź: Średnia trafność na teście jest prawie taka sama: 95,1% wobec 95,0%

Gdy neuronów jest już dość do zadania, kolejne nie poprawiają wyniku na nowych cyfrach.

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₁), gdzie W₁ ma 32 wiersze po 64 wagi, a b₁ to 32 biasy;
  • wyjście: z = W₂·h + b₂, gdzie W₂ 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 cyfry d: suma wiersza d minus C[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.

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łek confusion_matrix. Wiersz to tam też prawdziwa klasa.

Więcej w Atlasie

Hasła encyklopedii o pojęciach z tej lekcji: