12 KiB
Sztuczne sieci neuronowe
Perceptron
Schemat uczenia perceptronu
Dopóki nie zostanie spełniony warunek stopu (np. wykonana zostanie pewna ustalona z góry liczba rund) dla każdego przykładu $(x, y)$ wykonaj:
$$w = w + a (y - f(w \cdot x))x$$
Objaśnienia do algorytmu
$x$ -- wektor wejściowy
$y$ -- oczekiwane wyjście dla wektora $x$
$w$ -- wektor wag
$a$ -- stała ucząca
$f$ -- funkcja aktywacji
Sieci wielowarstwowe
Algorytm propagacji wstecznej
Dopóki nie zostanie spełniony warunek stopu dla każdego przykładu $(x,y)$ wykonaj:
dla każdego wierzchołka $j$ w warstwie wejściowej $a[j] = x[j]$
dla każdej warstwy $l$ od $2$ do $liczba_warstw$
dla każdego wierzchołka $i$ w warstwie $l$
dla każdej krawędzi z $j$ do $i$
$sum[i] += w[j,i]*a[j]$
$a[i] = g(sum[i])$
dla każdego wierzchołka $i$ warstwy wyjściowej
$d[i] = g'(sum[i]) * (y[i] - a[i])$
dla każdej warstwy $l$ od $liczba_warstw-1$ do $1$
dla każdego wierzchołka $j$ w warstwie $l$
$sum = 0$
dla każdej krawędzi z $j$ do $i$
$sum += w[j,i] * d[i]$
$d[j] = g'(sum[j])*sum$
dla każdego wierzchołka $i$ w warstwie $l+1$
$w[j,i] += a*a[j]*d[i]$
Objaśnienia do algorytmu
$w[i, j]$ – macierz wag
$a[i]$ – wartości funkcji aktywacji (poza warstwą wejściową, gdzie a[i]=x[i])
$d[i]$ – „delta” (propagowany błąd)
$a$ – stała ucząca
$g$ – funkcja aktywacji (np. $\frac{1}{1+e^{-x}}$)
$g'$ – pochodna $g$
Przykład
Zastosujemy algorytm propagacji wstecznej do wytrenowania sieci neuronowej której zadaniem będzie rozpoznawanie odręcznie zapisanych cyfr. Do uczenia i testowania naszej sieci wykorzystamy bazę danych MNIST, która zawiera zdjęcia (w skali szarości) odręcznie zapisanych cyfr. Zbiory uczący i testowy pobrane z w/w strony i zapisane w bieżącym katalogu można wczytać do Pythona korzystając z biblioteki python-mnist.
from mnist import MNIST
dataset = MNIST('./', gz=True, return_type='numpy')
train_images, train_labels = dataset.load_training()
test_images, test_labels = dataset.load_testing()
Zdjęcia cyfr można wyświetlić korzystając z następującej funkcji
import numpy as np
from matplotlib.pyplot import imshow
def plotdigit(image):
img = np.reshape(image, (-1, 28))
imshow(img, cmap='Greys', vmin=0, vmax=255)
Wyświetlmy pierwsze cztery cyfry ze zbioru uczącego razem z ich etykietami.
plotdigit(train_images[0])
train_labels[0]
plotdigit(train_images[1])
train_labels[1]
plotdigit(train_images[2])
train_labels[2]
plotdigit(train_images[3])
train_labels[3]
Zanim przystąpimy do uczenia sieci znormalizujemy dane wejściowe
train_images = train_images / 255
test_images = test_images / 255
W procesie uczenia skorzystamy z biblioteki PyTorch.
import torch
from torch import nn
from torch import optim
train_images = [torch.tensor(image, dtype=torch.float32) for image in train_images]
train_labels = [torch.tensor(label, dtype=torch.long) for label in train_labels]
test_images = [torch.tensor(image, dtype=torch.float32) for image in test_images]
test_labels = [torch.tensor(label, dtype=torch.long) for label in test_labels]
Na początek zbudujmy sieć złożoną z pojedynczej warstwy neuronów.
input_dim = 28*28
output_dim = 10
model = nn.Sequential(
nn.Linear(input_dim, output_dim),
nn.LogSoftmax()
)
Sieć wytrenujemy korzystając z następującej funkcji
def train(model, n_iter):
criterion = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)
for epoch in range(n_iter):
for image, label in zip(train_images, train_labels):
optimizer.zero_grad()
output = model(image)
loss = criterion(output.unsqueeze(0), label.unsqueeze(0))
loss.backward()
optimizer.step()
print(f'epoch: {epoch:03}')
train(model, 100)
Sprawdźmy jaka jest skuteczność wytrenowanej sieci na zbiorze uczącym
def accuracy(expected, predicted):
return len([_ for e, p in zip(expected, predicted) if e == p])/len(expected)
predicted = [model(image).argmax() for image in train_images]
accuracy(train_labels, predicted)
Wynik uzyskany na zbiorze uczącym jest zwykle zbyt optymistyczny. Sprawdźmy jak nasza sieć zachowuje się na zbiorze danych, który nie był wykorzystywany w procesie uczenia.
predicted = [model(image).argmax() for image in test_images]
accuracy(test_labels, predicted)
Wprowadźmy w naszej sieci kolejną warstwę neuronów, tworząc w ten sposób perceptron wielowarstwowy (ang. MLP -- Multilayer Perceptron).
hidden_dim = 10
model = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim),
nn.LogSoftmax()
)
train(model, 100)
Proces uczenia zdefiniowanej powyższej sieci trwa około godziny na cztero-rdzeniowym
procesorze (2.5 GHz). Jeżeli dysponują Państwo kartą graficzną kompatybilną
z biblioteką CUDA, to
warto przeprowadzić uczenie z wykorzystaniem procesora karty graficznej
(parametr device=mx.gpu()
w wywołaniu powyżej).
W przypadku mojego komputera czas uczenia uległ skróceniu do ok. 70 sekund.
Sprawdźmy skuteczność perceptronu wielowarstwowego na zbiorach uczącym i testowym
predicted = [model(image).argmax() for image in train_images]
accuracy(train_labels, predicted)
predicted = [model(image).argmax() for image in test_images]
accuracy(test_labels, predicted)
Całkiem nieźle, ale sporo jeszcze można poprawić (por. wyniki ze strony MNIST).
Sprawdźmy jeszcze, które cyfry są najczęściej mylone ze sobą
from sklearn.metrics import confusion_matrix
from tabulate import tabulate
tabulate(confusion_matrix(test_labels, predicted), tablefmt='html', showindex=True, headers=[''] + list(range(10)))
Zadanie
Przeanalizować jak wielkość zbioru uczącego wpływa na skuteczność klasyfikatora na zbiorze testowym. Wykorzystać odpowiednio $10 %$, $25 %$, $50 %$ i $100 %$ przykładów ze zbioru uczącego.
Sprawdzić jak na skuteczność klasyfikatora wpływa wielkość warstwy ukrytej (parametr
hidden_dim
).Sprawdzić jak na skuteczność klasyfikatora oraz czas trwania procesu uczenia wpłynie wprowadzenie dodatkowych warstw ukrytych.
Przeanalizować wpływ liczby iteracji (parametr
n_iter
) na skuteczność sieci na zbiorze uczącym i testowym.Sprawdzić jak na skuteczność perceptronu wielowarstwowego wpłynie pominięcie funkcji aktywacji (
ReLU
) w definicji sieci.Zmodyfikować procedurę uczenia w taki sposób, żeby zamiast pojedynczych przykładów akceptowała ona tzw. wsady (ang. batch). Zob np. https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html.
(Dla posiadaczy kart graficznych NVIDIA) zmienić procedurę uczenia w taki sposób, żeby obliczenia przebiegały na procesorze karty graficznej.