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