Projekt_AI-Automatyczny_saper/sieci-neuronowe.ipynb
2021-06-01 17:38:31 +02:00

12 KiB
Raw Permalink Blame History

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:

  1. dla każdego wierzchołka $j$ w warstwie wejściowej $a[j] = x[j]$

  2. dla każdej warstwy $l$ od $2$ do $liczba_warstw$

    1. dla każdego wierzchołka $i$ w warstwie $l$

      1. dla każdej krawędzi z $j$ do $i$

        $sum[i] += w[j,i]*a[j]$

      2. $a[i] = g(sum[i])$

  3. dla każdego wierzchołka $i$ warstwy wyjściowej

    $d[i] = g'(sum[i]) * (y[i] - a[i])$

  4. dla każdej warstwy $l$ od $liczba_warstw-1$ do $1$

    1. dla każdego wierzchołka $j$ w warstwie $l$

      1. $sum = 0$

      2. dla każdej krawędzi z $j$ do $i$

        $sum += w[j,i] * d[i]$

      3. $d[j] = g'(sum[j])*sum$

      4. 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

  1. 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.

  2. Sprawdzić jak na skuteczność klasyfikatora wpływa wielkość warstwy ukrytej (parametr hidden_dim).

  3. Sprawdzić jak na skuteczność klasyfikatora oraz czas trwania procesu uczenia wpłynie wprowadzenie dodatkowych warstw ukrytych.

  4. Przeanalizować wpływ liczby iteracji (parametr n_iter) na skuteczność sieci na zbiorze uczącym i testowym.

  5. Sprawdzić jak na skuteczność perceptronu wielowarstwowego wpłynie pominięcie funkcji aktywacji (ReLU) w definicji sieci.

  6. 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.

  7. (Dla posiadaczy kart graficznych NVIDIA) zmienić procedurę uczenia w taki sposób, żeby obliczenia przebiegały na procesorze karty graficznej.