zuma/w5/zumz5.ipynb
2021-03-17 20:09:43 +01:00

217 KiB
Raw Blame History

Uczenie maszynowe UMZ 2017/2018

5. Sieci neuronowe

%matplotlib inline

import math
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import random

5.1. Perceptron

from IPython.display import YouTubeVideo
YouTubeVideo('cNxadbrN_aI', width=800, height=600)
Frank Rosenblatt
Frank Rosenblatt
perceptron

Pierwszy perceptron liniowy

  • Frank Rosenblatt, 1957
  • aparat fotograficzny podłączony do 400 fotokomórek (rozdzielczość obrazu: 20 x 20)
  • wagi potencjometry aktualizowane za pomocą silniczków

Uczenie perceptronu

Cykl uczenia perceptronu Rosenblatta:

  1. Sfotografuj planszę z kolejnym obiektem.
  2. Zaobserwuj, która lampka zapaliła się na wyjściu.
  3. Sprawdź, czy to jest właściwa lampka.
  4. Wyślij sygnał „nagrody” lub „kary”.

Funkcja aktywacji

Funkcja bipolarna:

$$ g(z) = \left\{ \begin{array}{rl} 1 & \textrm{gdy $z > \theta_0$} \\ -1 & \textrm{wpp.} \end{array} \right. $$

gdzie $z = \theta_0x_0 + \ldots + \theta_nx_n$,
$\theta_0$ to próg aktywacji,
$x_0 = 1$.

def bipolar_plot():
    matplotlib.rcParams.update({'font.size': 16})

    plt.figure(figsize=(8,5))
    x = [-1,-.23,1] 
    y = [-1, -1, 1]
    plt.ylim(-1.2,1.2)
    plt.xlim(-1.2,1.2)
    plt.plot([-2,2],[1,1], color='black', ls="dashed")
    plt.plot([-2,2],[-1,-1], color='black', ls="dashed")
    plt.step(x, y, lw=3)
    ax = plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data',0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data',0))

    plt.annotate(r'$\theta_0$',
                 xy=(-.23,0), xycoords='data',
                 xytext=(-50, +50), textcoords='offset points', fontsize=26,
                 arrowprops=dict(arrowstyle="->"))

    plt.show()
bipolar_plot()

Perceptron schemat

Perceptron zasada działania

  1. Ustal wartości początkowe $\theta$ (wektor 0 lub liczby losowe blisko 0).
  2. Dla każdego przykładu $(x^{(i)}, y^{(i)})$, dla $i=1,\ldots,m$
    • Oblicz wartość wyjścia $o^{(i)}$: $$o^{(i)} = g(\theta^{T}x^{(i)}) = g(\sum_{j=0}^{n} \theta_jx_j^{(i)})$$
    • Wykonaj aktualizację wag (tzw. _perceptron rule): $$ \theta := \theta + \Delta \theta $$ $$ \Delta \theta = \alpha(y^{(i)}-o^{(i)})x^{(i)} $$

$$\theta_j := \theta_j + \Delta \theta_j $$

Jeżeli przykład został sklasyfikowany poprawnie:

  • $y^{(i)}=1$ oraz $o^{(i)}=1$ : $$\Delta\theta_j = \alpha(1 - 1)x_j^{(i)} = 0$$
  • $y^{(i)}=-1$ oraz $o^{(i)}=-1$ : $$\Delta\theta_j = \alpha(-1 - -1)x_j^{(i)} = 0$$

Czyli: jeżeli trafiłeś, to nic nie zmieniaj.

$$\theta_j := \theta_j + \Delta \theta_j $$

Jeżeli przykład został sklasyfikowany niepoprawnie:

  • $y^{(i)}=1$ oraz $o^{(i)}=-1$ : $$\Delta\theta_j = \alpha(1 - -1)x_j^{(i)} = 2 \alpha x_j^{(i)}$$
  • $y^{(i)}=-1$ oraz $o^{(i)}=1$ : $$\Delta\theta_j = \alpha(-1 - 1)x_j^{(i)} = -2 \alpha x_j^{(i)}$$

Czyli: przesuń wagi w odpowiednią stronę.

Perceptron zalety i wady

Zalety:

  • intuicyjny i prosty
  • łatwy w implementacji
  • jeżeli dane można liniowo oddzielić, algorytm jest zbieżny w skończonym czasie

Wady:

  • jeżeli danych nie można oddzielić liniowo, algorytm nie jest zbieżny
def plot_perceptron():
    plt.figure(figsize=(12,3))

    plt.subplot(131)
    plt.ylim(-0.2,1.2)
    plt.xlim(-0.2,1.2)

    plt.title('AND')
    plt.plot([1,0,0], [0,1,0], 'ro', markersize=10)
    plt.plot([1], [1], 'go', markersize=10)

    ax = plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('none')
    ax.spines['bottom'].set_position(('data',0))
    ax.yaxis.set_ticks_position('none')
    ax.spines['left'].set_position(('data',0))

    plt.xticks(np.arange(0, 2, 1.0))
    plt.yticks(np.arange(0, 2, 1.0))


    plt.subplot(132)
    plt.ylim(-0.2,1.2)
    plt.xlim(-0.2,1.2)

    plt.plot([1,0,1], [0,1,1], 'go', markersize=10)
    plt.plot([0], [0], 'ro', markersize=10)

    ax = plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('none')
    ax.spines['bottom'].set_position(('data',0))
    ax.yaxis.set_ticks_position('none')
    ax.spines['left'].set_position(('data',0))

    plt.title('OR')
    plt.xticks(np.arange(0, 2, 1.0))
    plt.yticks(np.arange(0, 2, 1.0))


    plt.subplot(133)
    plt.ylim(-0.2,1.2)
    plt.xlim(-0.2,1.2)

    plt.title('XOR')
    plt.plot([1,0], [0,1], 'go', markersize=10)
    plt.plot([0,1], [0,1], 'ro', markersize=10)

    ax = plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('none')
    ax.spines['bottom'].set_position(('data',0))
    ax.yaxis.set_ticks_position('none')
    ax.spines['left'].set_position(('data',0))

    plt.xticks(np.arange(0, 2, 1.0))
    plt.yticks(np.arange(0, 2, 1.0))

    plt.show()
plot_perceptron()

Funkcje aktywacji

Zamiast funkcji bipolarnej możemy zastosować funkcję sigmoidalną jako funkcję aktywacji.

def plot_activation_functions():
    plt.figure(figsize=(16,7))
    plt.subplot(121)
    x = [-2,-.23,2] 
    y = [-1, -1, 1]
    plt.ylim(-1.2,1.2)
    plt.xlim(-2.2,2.2)
    plt.plot([-2,2],[1,1], color='black', ls="dashed")
    plt.plot([-2,2],[-1,-1], color='black', ls="dashed")
    plt.step(x, y, lw=3)
    ax = plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data',0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data',0))

    plt.annotate(r'$\theta_0$',
                 xy=(-.23,0), xycoords='data',
                 xytext=(-50, +50), textcoords='offset points', fontsize=26,
                 arrowprops=dict(arrowstyle="->"))

    plt.subplot(122)
    x2 = np.linspace(-2,2,100)
    y2 = np.tanh(x2+ 0.23)
    plt.ylim(-1.2,1.2)
    plt.xlim(-2.2,2.2)
    plt.plot([-2,2],[1,1], color='black', ls="dashed")
    plt.plot([-2,2],[-1,-1], color='black', ls="dashed")
    plt.plot(x2, y2, lw=3)
    ax = plt.gca()
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data',0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data',0))

    plt.annotate(r'$\theta_0$',
                 xy=(-.23,0), xycoords='data',
                 xytext=(-50, +50), textcoords='offset points', fontsize=26,
                 arrowprops=dict(arrowstyle="->"))

    plt.show()
plot_activation_functions()

Perceptron a regresja liniowa

Uczenie regresji liniowej:

  • Model: $$h_{\theta}(x) = \sum_{i=0}^n \theta_ix_i$$

  • Funkcja kosztu (błąd średniokwadratowy): $$J(\theta) = \frac{1}{m} \sum_{i=1}^{m} (h_{\theta}(x^{(i)}) - y^{(i)})^2$$

  • Po obliczeniu $\nabla J(\theta)$, zwykły SGD.

Perceptron a dwuklasowa regresja logistyczna

Uczenie dwuklasowej regresji logistycznej:

  • Model: $$h_{\theta}(x) = \sigma(\sum_{i=0}^n \theta_ix_i) = P(1|x,\theta)$$

  • Funkcja kosztu (entropia krzyżowa): $$\begin{eqnarray} J(\theta) &=& -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)}\log P(1|x^{(i)},\theta) \\ && + (1-y^{(i)})\log(1-P(1|x^{(i)},\theta))]\end{eqnarray}$$

  • Po obliczeniu $\nabla J(\theta)$, zwykły SGD.

Perceptron a wieloklasowa regresja logistyczna

Wieloklasowa regresji logistyczna

  • Model (dla $c$ klasyfikatorów binarnych): $$\begin{eqnarray} h_{(\theta^{(1)},\dots,\theta^{(c)})}(x) &=& \mathrm{softmax}(\sum_{i=0}^n \theta_{i}^{(1)}x_i, \ldots, \sum_{i=0}^n \theta_i^{(c)}x_i) \\ &=& \left[ P(k|x,\theta^{(1)},\dots,\theta^{(c)}) \right]_{k=1,\dots,c} \end{eqnarray}$$

  • Funkcja kosztu (przymując model regresji binarnej): $$\begin{eqnarray} J(\theta^{(k)}) &=& -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)}\log P(k|x^{(i)},\theta^{(k)}) \\ && + (1-y^{(i)})\log P(\neg k|x^{(i)},\theta^{(k)})]\end{eqnarray}$$

  • Po obliczeniu $\nabla J(\theta)$, c-krotne uruchomienie SGD, zastosowanie $\mathrm{softmax}(X)$ do niezależnie uzyskanych klasyfikatorów binarnych.

  • Przyjmijmy: $$ \Theta = (\theta^{(1)},\dots,\theta^{(c)}) $$

$$h_{\Theta}(x) = \left[ P(k|x,\Theta) \right]_{k=1,\dots,c}$$

$$\delta(x,y) = \left\{\begin{array}{cl} 1 & \textrm{gdy } x=y \\ 0 & \textrm{wpp.}\end{array}\right.$$

  • Wieloklasowa funkcja kosztu $J(\Theta)$ (kategorialna entropia krzyżowa): $$ J(\Theta) = -\frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{c} \delta({y^{(i)},k}) \log P(k|x^{(i)},\Theta) $$
  • Gradient $\nabla J(\Theta)$: $$ \dfrac{\partial J(\Theta)}{\partial \Theta_{j,k}} = -\frac{1}{m}\sum_{i = 1}^{m} (\delta({y^{(i)},k}) - P(k|x^{(i)}, \Theta)) x^{(i)}_j $$

  • Liczymy wszystkie wagi jednym uruchomieniem SGD

Podsumowanie

  • W przypadku jednowarstowej sieci neuronowej wystarczy znać gradient funkcji kosztu.
  • Wtedy liczymy tak samo jak w przypadku regresji liniowej, logistycznej, wieloklasowej logistycznej itp.
    • Wymienione modele to szczególne przypadki jednowarstwowych sieci neuronowych.
  • Regresja liniowa i binarna regresja logistyczna to jeden neuron.
  • Wieloklasowa regresja logistyczna to tyle neuronów ile klas.

Funkcja aktywacji i funkcja kosztu są dobierane do problemu.

5.2. Wielowarstwowe sieci neuronowe

czyli _Artificial Neural Networks (ANN) lub Multi-Layer Perceptrons (MLP)

Architektura sieci

  • Sieć neuronowa jako graf neuronów.
  • Organizacja sieci przez warstwy.
  • Najczęściej stosowane są sieci jednokierunkowe i gęste.
  • $n$-warstwowa sieć neuronowa ma $n+1$ warstw (nie liczymy wejścia).
  • Rozmiary sieci określane poprzez liczbę neuronów lub parametrów.

Sieć neuronowa jednokierunkowa (_feedforward)

  • Mając daną $n$-warstwową sieć neuronową oraz jej parametry $\Theta^{(1)}, \ldots, \Theta^{(L)} $ oraz $\beta^{(1)}, \ldots, \beta^{(L)} $ liczymy:

    $$a^{(l)} = g^{(l)}\left( a^{(l-1)} \Theta^{(l)} + \beta^{(l)} \right). $$
  • Funkcje $g^{(l)}$ to tzw. funkcje aktywacji.
    Dla $i = 0$ przyjmujemy $a^{(0)} = \mathrm{x}$ (wektor wierszowy cech) oraz $g^{(0)}(x) = x$ (identyczność).
  • Parametry $\Theta$ to wagi na połączeniach miedzy neuronami dwóch warstw.
    Rozmiar macierzy $\Theta^{(l)}$, czyli macierzy wag na połączeniach warstw $a^{(l-1)}$ i $a^{(l)}$, to $\dim(a^{(l-1)}) \times \dim(a^{(l)})$.
  • Parametry $\beta$ zastępują tutaj dodawanie kolumny z jedynkami do macierzy cech.
    Macierz $\beta^{(l)}$ ma rozmiar równy liczbie neuronów w odpowiedniej warstwie, czyli $1 \times \dim(a^{(l)})$.
  • Klasyfikacja: dla ostatniej warstwy $L$ (o rozmiarze równym liczbie klas) przyjmuje się $g^{(L)}(x) = \mathop{\mathrm{softmax}}(x)$.
  • Regresja: pojedynczy neuron wyjściowy jak na obrazku. Funkcją aktywacji może wtedy być np. funkcja identycznościowa.
  • Pozostałe funkcje aktywacji najcześciej mają postać sigmoidy, np. sigmoidalna, tangens hiperboliczny.
  • Mogą mieć też inny kształt, np. ReLU, leaky ReLU, maxout.

5.3. Metoda propagacji wstecznej wprowadzenie

Jak uczyć sievi neuronowe?

  • W poznanych do tej pory algorytmach (regresja liniowa, regresja logistyczna) do uczenia używaliśmy funkcji kosztu, jej gradientu oraz algorytmu gradientu prostego (GD/SGD)
  • Dla sieci neuronowych potrzebowalibyśmy również znaleźć gradnient funkcji kosztu.
  • Co sprowadza się do bardziej ogólnego problemu:
    jak obliczyć gradient $\nabla f(x)$ dla danej funkcji $f$ i wektora wejściowego $x$?

Pochodna funkcji

  • Pochodna mierzy, jak szybko zmienia się wartość funkcji względem zmiany jej argumentów:

$$ \frac{d f(x)}{d x} = \lim_{h \to 0} \frac{ f(x + h) - f(x) }{ h } $$

Pochodna cząstkowa i gradient

  • Pochodna cząstkowa mierzy, jak szybko zmienia się wartość funkcji względem zmiany jej _pojedynczego argumentu.
  • Gradient to wektor pochodnych cząstkowych:

$$ \nabla f = \left( \frac{\partial f}{\partial x_1}, \ldots, \frac{\partial f}{\partial x_n} \right) $$

Gradient przykłady

$$ f(x_1, x_2) = x_1 + x_2 \qquad \to \qquad \frac{\partial f}{\partial x_1} = 1, \quad \frac{\partial f}{\partial x_2} = 1, \quad \nabla f = (1, 1) $$

$$ f(x_1, x_2) = x_1 \cdot x_2 \qquad \to \qquad \frac{\partial f}{\partial x_1} = x_2, \quad \frac{\partial f}{\partial x_2} = x_1, \quad \nabla f = (x_2, x_1) $$

$$ f(x_1, x_2) = \max(x_1 + x_2) \hskip{12em} \\ \to \qquad \frac{\partial f}{\partial x_1} = \mathbb{1}_{x \geq y}, \quad \frac{\partial f}{\partial x_2} = \mathbb{1}{y \geq x}, \quad \nabla f = (\mathbb{1}{x \geq y}, \mathbb{1}{y \geq x}) $$

Własności pochodnych cząstkowych

Jezeli $f(x, y, z) = (x + y) , z$ oraz $x + y = q$, to: $$f = q z, \quad \frac{\partial f}{\partial q} = z, \quad \frac{\partial f}{\partial z} = q, \quad \frac{\partial q}{\partial x} = 1, \quad \frac{\partial q}{\partial y} = 1 $$

Reguła łańcuchowa

$$ \frac{\partial f}{\partial x} = \frac{\partial f}{\partial q} , \frac{\partial q}{\partial x}, \quad \frac{\partial f}{\partial y} = \frac{\partial f}{\partial q} , \frac{\partial q}{\partial y} $$

Propagacja wsteczna prosty przykład

# Dla ustalonego wejścia
x = -2; y = 5; z = -4
# Krok w przód
q = x + y
f = q * z
print(q, f)
(3, -12)
# Propagacja wsteczna dla f = q * z
dz = q
dq = z
# Propagacja wsteczna dla q = x + y
dx = 1 * dq  # z reguły łańcuchowej
dy = 1 * dq  # z reguły łańcuchowej
print([dx, dy, dz])
[-4, -4, 3]
  • Właśnie tak wygląda obliczanie pochodnych metodą propagacji wstecznej!
  • Spróbujmy czegoś bardziej skomplikowanego:
    metodą propagacji wstecznej obliczmy pochodną funkcji sigmoidalnej.

Propagacja wsteczna funkcja sigmoidalna

Funkcja sigmoidalna:

$$f(\theta,x) = \frac{1}{1+e^{-(\theta_0 x_0 + \theta_1 x_1 + \theta_2)}}$$

import math

# Losowe wagi i dane
w = [2,-3,-3]
x = [-1, -2]

# Krok w przód
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot))  # funkcja sigmoidalna

# Krok w tył
ddot = (1 - f) * f  # pochodna funkcji sigmoidalnej
dx = [w[0] * ddot, w[1] * ddot]
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot]

print(dx)
print(dw)
[0.3932238664829637, -0.5898357997244456]
[-0.19661193324148185, -0.3932238664829637, 0.19661193324148185]

Obliczanie gradientów podsumowanie

  • Gradient $f$ dla $x$ mówi jak zmieni się całe wyrażenie przy zmianie wartości $x$.
  • Gradienty łączymy korzystając z reguły łańcuchowej.
  • W kroku wstecz gradienty informują, które części grafu powinny być zwiększone lub zmniejszone (i z jaką siłą), aby zwiększyć wartość na wyjściu.
  • W kontekście implementacji chcemy dzielić funkcję $f$ na części, dla których można łatwo obliczyć gradienty.

5.4. Uczenie wielowarstwowych sieci neuronowych metodą propagacji wstecznej

Mając algorytm SGD oraz gradienty wszystkich wag, moglibyśmy trenować każdą sieć.

  • Niech: $$\Theta = (\Theta^{(1)},\Theta^{(2)},\Theta^{(3)},\beta^{(1)},\beta^{(2)},\beta^{(3)})$$

  • Funkcja sieci neuronowej z grafiki:

$$\small h_\Theta(x) = \tanh(\tanh(\tanh(x\Theta^{(1)}+\beta^{(1)})\Theta^{(2)} + \beta^{(2)})\Theta^{(3)} + \beta^{(3)})$$

  • Funkcja kosztu dla regresji: $$J(\Theta) = \dfrac{1}{2m} \sum_{i=1}^{m} (h_\Theta(x^{(i)})- y^{(i)})^2 $$
  • Jak obliczymy gradienty?

$$\nabla_{\Theta^{(l)}} J(\Theta) = ? \quad \nabla_{\beta^{(l)}} J(\Theta) = ?$$

W kierunku propagacji wstecznej

  • Pewna (niewielka) zmiana wagi $\Delta z^l_j$ dla $j$-ego neuronu w warstwie $l$ pociąga za sobą (niewielką) zmianę kosztu:

$$\frac{\partial J(\Theta)}{\partial z^{l}_j} \Delta z^{l}_j$$

  • Jeżeli $\frac{\partial J(\Theta)}{\partial z^{l}_j}$ jest duża, $\Delta z^l_j$ ze znakiem przeciwnym zredukuje koszt.
  • Jeżeli $\frac{\partial J(\Theta)}{\partial z^l_j}$ jest bliska zeru, koszt nie będzie mocno poprawiony.
  • Definiujemy błąd $\delta^l_j$ neuronu $j$ w warstwie $l$:

$$\delta^l_j \equiv \dfrac{\partial J(\Theta)}{\partial z^l_j}$$ $$\delta^l \equiv \nabla_{z^l} J(\Theta) \textrm{ (zapis wektorowy)} $$

Podstawowe równania propagacji wstecznej

$$ \begin{array}{ccll} \delta^L & = & \nabla_{a^L}J(\Theta) \odot {(g^{L})}^{\prime}(z^L) & (BP1) \\[2mm] \delta^{l} & = & ((\Theta^{l+1})^T \delta^{l+1}) \odot {{(g^{l})}^{\prime}}(z^{l}) & (BP2)\\[2mm] \nabla_{\beta^l} J(\Theta) & = & \delta^l & (BP3)\\[2mm] \nabla_{\Theta^l} J(\Theta) & = & a^{l-1} \odot \delta^l & (BP4)\\ \end{array} $$

Algorytm propagacji wstecznej

Dla jednego przykładu (x,y):

  1. Wejście: Ustaw aktywacje w warstwie cech $a^{(0)}=x$
  2. Feedforward: dla $l=1,\dots,L$ oblicz $$z^{(l)} = a^{(l-1)} \Theta^{(l)} + \beta^{(l)} \textrm{ oraz } a^{(l)}=g^{(l)}(z^{(l)})$$
  3. Błąd wyjścia $\delta^{(L)}$: oblicz wektor $$\delta^{(L)}= \nabla_{a^{(L)}}J(\Theta) \odot {g^{\prime}}^{(L)}(z^{(L)})$$
  4. Propagacja wsteczna błędu: dla $l = L-1,L-2,\dots,1$ oblicz $$\delta^{(l)} = \delta^{(l+1)}(\Theta^{(l+1)})^T \odot {g^{\prime}}^{(l)}(z^{(l)})$$
  5. Gradienty:
    • $\dfrac{\partial}{\partial \Theta_{ij}^{(l)}} J(\Theta) = a_i^{(l-1)}\delta_j^{(l)} \textrm{ oraz } \dfrac{\partial}{\partial \beta_{j}^{(l)}} J(\Theta) = \delta_j^{(l)}$

W naszym przykładzie:

$$\small J(\Theta) = \frac{1}{2}(a^{(L)} - y)^2 $$ $$\small \dfrac{\partial}{\partial a^{(L)}} J(\Theta) = a^{(L)} - y$$

$$\small \tanh^{\prime}(x) = 1 - \tanh^2(x)$$

Algorytm SGD z propagacją wsteczną

Pojedyncza iteracja:

  • Dla parametrów $\Theta = (\Theta^{(1)},\ldots,\Theta^{(L)})$ utwórz pomocnicze macierze zerowe $\Delta = (\Delta^{(1)},\ldots,\Delta^{(L)})$ o takich samych wymiarach (dla uproszczenia opuszczono wagi $\beta$).
  • Dla $m$ przykładów we wsadzie (_batch), $i = 1,\ldots,m$:
    • Wykonaj algortym propagacji wstecznej dla przykładu $(x^{(i)}, y^{(i)})$ i przechowaj gradienty $\nabla_{\Theta}J^{(i)}(\Theta)$ dla tego przykładu;
    • $\Delta := \Delta + \dfrac{1}{m}\nabla_{\Theta}J^{(i)}(\Theta)$
  • Wykonaj aktualizację wag: $\Theta := \Theta - \alpha \Delta$

Propagacja wsteczna podsumowanie

  • Algorytm pierwszy raz wprowadzony w latach 70. XX w.
  • W 1986 David Rumelhart, Geoffrey Hinton i Ronald Williams pokazali, że jest znacznie szybszy od wcześniejszych metod.
  • Obecnie najpopularniejszy algorytm uczenia sieci neuronowych.

5.5. Implementacja sieci neuronowych

import pandas
src_cols = ['łod.dł.', 'łod.sz.', 'pł.dł.', 'pł.sz.', 'Gatunek']
trg_cols = ['łod.dł.', 'łod.sz.', 'pł.dł.', 'pł.sz.', 'Iris setosa?']
data = (
    pandas.read_csv('iris.csv', usecols=src_cols)
    .apply(lambda x: [x[0], x[1], x[2], x[3], 1 if x[4] == 'Iris-setosa' else 0], axis=1))
data.columns = trg_cols
data[:6]
łod.dł. łod.sz. pł.dł. pł.sz. Iris setosa?
0 5.2 3.4 1.4 0.2 1.0
1 5.1 3.7 1.5 0.4 1.0
2 6.7 3.1 5.6 2.4 0.0
3 6.5 3.2 5.1 2.0 0.0
4 4.9 2.5 4.5 1.7 0.0
5 6.0 2.7 5.1 1.6 0.0
m, n_plus_1 = data.values.shape
n = n_plus_1 - 1
Xn = data.values[:, 0:n].reshape(m, n)
X = np.matrix(np.concatenate((np.ones((m, 1)), Xn), axis=1)).reshape(m, n_plus_1)
Y = np.matrix(data.values[:, n]).reshape(m, 1)

print(X[:6])
print(Y[:6])
[[ 1.   5.2  3.4  1.4  0.2]
 [ 1.   5.1  3.7  1.5  0.4]
 [ 1.   6.7  3.1  5.6  2.4]
 [ 1.   6.5  3.2  5.1  2. ]
 [ 1.   4.9  2.5  4.5  1.7]
 [ 1.   6.   2.7  5.1  1.6]]
[[ 1.]
 [ 1.]
 [ 0.]
 [ 0.]
 [ 0.]
 [ 0.]]
from keras.models import Sequential
from keras.layers import Dense

model = Sequential()
model.add(Dense(3, input_dim=5))
model.add(Dense(3))
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

model.fit(X, Y)
Using TensorFlow backend.
Epoch 1/10
150/150 [==============================] - 0s - loss: 2.0678 - acc: 0.6667     
Epoch 2/10
150/150 [==============================] - 0s - loss: 1.9711 - acc: 0.6667     
Epoch 3/10
150/150 [==============================] - 0s - loss: 1.8811 - acc: 0.6667     
Epoch 4/10
150/150 [==============================] - 0s - loss: 1.7793 - acc: 0.6667     
Epoch 5/10
150/150 [==============================] - 0s - loss: 1.6948 - acc: 0.6667     
Epoch 6/10
150/150 [==============================] - 0s - loss: 1.5993 - acc: 0.6667     
Epoch 7/10
150/150 [==============================] - 0s - loss: 1.5162 - acc: 0.6667     
Epoch 8/10
150/150 [==============================] - 0s - loss: 1.4308 - acc: 0.6667     
Epoch 9/10
150/150 [==============================] - 0s - loss: 1.3487 - acc: 0.6667     
Epoch 10/10
150/150 [==============================] - 0s - loss: 1.2676 - acc: 0.6667     
<keras.callbacks.History at 0x7f87f40aa150>
model.predict(np.array([1.0, 3.0, 1.0, 2.0, 4.0]).reshape(-1, 5)).tolist()[0][0]
0.8209257125854492

5.6. Funkcje aktywacji

  • Każda funkcja aktywacji ma swoje zalety i wady.
  • Różne rodzaje funkcji aktywacji nadają się do różnych zastosowań.
import math
import matplotlib.pyplot as plt
import numpy as np
import random

import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, SimpleRNN, LSTM
from keras.optimizers import Adagrad, Adam, RMSprop, SGD

from IPython.display import YouTubeVideo
def plot(fun):
    x = np.arange(-3.0, 3.0, 0.01)
    y = [fun(x_i) for x_i in x]
    fig = plt.figure(figsize=(14, 7))
    ax = fig.add_subplot(111)
    fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)
    ax.set_xlim(-3.0, 3.0)
    ax.set_ylim(-1.5, 1.5)
    ax.grid()
    ax.plot(x, y)
    plt.show()

Funkcja logistyczna

$$ g(x) = \frac{1}{1 + e^{-x}} $$

  • Przyjmuje wartości z przedziału $(0, 1)$.

Funkcja logistyczna wykres

plot(lambda x: 1 / (1 + math.exp(-x)))

Tangens hiperboliczny

$$ g(x) = \tanh x = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}} $$

  • Przyjmuje wartości z przedziału $(-1, 1)$.
  • Powstaje z funkcji logistycznej przez przeskalowanie i przesunięcie.

Tangens hiperboliczny wykres

plot(lambda x: math.tanh(x))

ReLU (_Rectifier Linear Unit)

$$ g(x) = \max(0, x) $$

ReLU zalety

  • Mniej podatna na problem zanikającego gradientu (_vanishing gradient) niż funkcje sigmoidalne, dzięki czemu SGD jest szybciej zbieżna.
  • Prostsze obliczanie gradientu.
  • Dzięki zerowaniu ujemnych wartości, wygasza neurony, „rozrzedzając” sieć (_sparsity), co przyspiesza obliczenia.

ReLU wady

  • Dla dużych wartości gradient może „eksplodować”.
  • „Wygaszanie” neuronów.

ReLU wykres

plot(lambda x: max(0, x))

Softplus

$$ g(x) = \log(1 + e^{x}) $$

  • Wygładzona wersja ReLU.

Softplus wykres

plot(lambda x: math.log(1 + math.exp(x)))

Problem zanikającego gradientu (_vanishing gradient problem)

  • Sigmoidalne funkcje aktywacji ograniczają wartości na wyjściach neuronów do niewielkich przedziałów ($(-1, 1)$, $(0, 1)$ itp.).
  • Jeżeli sieć ma wiele warstw, to podczas propagacji wstecznej mnożymy przez siebie wiele małych wartości → obliczony gradient jest mały.
  • Im więcej warstw, tym silniejszy efekt zanikania.

Sposoby na zanikający gradient

  • Modyfikacja algorytmu optymalizacji (_RProp, RMSProp)
  • Użycie innej funckji aktywacji (ReLU, softplus)
  • Dodanie warstw _dropout
  • Nowe architektury (LSTM itp.)
  • Więcej danych, zwiększenie mocy obliczeniowej

5.7. Wielowarstwowe sieci neuronowe w praktyce

Przykład: MNIST

_Modified National Institute of Standards and Technology database

  • Zbiór cyfr zapisanych pismem odręcznym
  • 60 000 przykładów uczących, 10 000 przykładów testowych
  • Rozdzielczość każdego przykładu: 28 × 28 = 784 piksele
# źródło: https://github.com/keras-team/keras/examples/minst_mlp.py

import keras
from keras.datasets import mnist

# załaduj dane i podziel je na zbiory uczący i testowy
(x_train, y_train), (x_test, y_test) = mnist.load_data()
def draw_examples(examples, captions=None):
    plt.figure(figsize=(16, 4))
    m = len(examples)
    for i, example in enumerate(examples):
        plt.subplot(100 + m * 10 + i + 1)
        plt.imshow(example, cmap=plt.get_cmap('gray'))
    plt.show()
    if captions is not None:
        print(6 * ' ' + (10 * ' ').join(str(captions[i]) for i in range(m)))
draw_examples(x_train[:7], captions=y_train)
      5          0          4          1          9          2          1
num_classes = 10

x_train = x_train.reshape(60000, 784)  # 784 = 28 * 28
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('{} przykładów uczących'.format(x_train.shape[0]))
print('{} przykładów testowych'.format(x_test.shape[0]))

# przekonwertuj wektory klas na binarne macierze klas
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
60000 przykładów uczących
10000 przykładów testowych
model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(num_classes, activation='softmax'))
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_4 (Dense)              (None, 512)               401920    
_________________________________________________________________
dropout_1 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_5 (Dense)              (None, 512)               262656    
_________________________________________________________________
dropout_2 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_6 (Dense)              (None, 10)                5130      
=================================================================
Total params: 669,706
Trainable params: 669,706
Non-trainable params: 0
_________________________________________________________________
print(x_train.shape, y_train.shape)
((60000, 784), (60000, 10))
model.compile(loss='categorical_crossentropy', optimizer=RMSprop(), metrics=['accuracy'])

model.fit(x_train, y_train, batch_size=128, epochs=5, verbose=1,
          validation_data=(x_test, y_test))
Train on 60000 samples, validate on 10000 samples
Epoch 1/5
60000/60000 [==============================] - 14s - loss: 0.2457 - acc: 0.9244 - val_loss: 0.1250 - val_acc: 0.9605
Epoch 2/5
60000/60000 [==============================] - 14s - loss: 0.1021 - acc: 0.9691 - val_loss: 0.0859 - val_acc: 0.9748
Epoch 3/5
60000/60000 [==============================] - 14s - loss: 0.0746 - acc: 0.9773 - val_loss: 0.0884 - val_acc: 0.9744
Epoch 4/5
60000/60000 [==============================] - 13s - loss: 0.0619 - acc: 0.9815 - val_loss: 0.0893 - val_acc: 0.9754
Epoch 5/5
60000/60000 [==============================] - 15s - loss: 0.0507 - acc: 0.9849 - val_loss: 0.0845 - val_acc: 0.9771
<keras.callbacks.History at 0x7f87e996a2d0>
score = model.evaluate(x_test, y_test, verbose=0)

print('Test loss: {}'.format(score[0]))
print('Test accuracy: {}'.format(score[1]))
Test loss: 0.0845102945257
Test accuracy: 0.9771

Warstwa _dropout to metoda regularyzacji, służy zapobieganiu nadmiernemu dopasowaniu sieci. Polega na tym, że część węzłów sieci jest usuwana w sposób losowy.

# Bez warstw Dropout

num_classes = 10

(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.reshape(60000, 784)  # 784 = 28 * 28
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model_no_dropout = Sequential()
model_no_dropout.add(Dense(512, activation='relu', input_shape=(784,)))
model_no_dropout.add(Dense(512, activation='relu'))
model_no_dropout.add(Dense(num_classes, activation='softmax'))
model_no_dropout.summary()

model_no_dropout.compile(loss='categorical_crossentropy',
                         optimizer=RMSprop(),
                         metrics=['accuracy'])

model_no_dropout.fit(x_train, y_train,
                     batch_size=128,
                     epochs=5,
                     verbose=1,
                     validation_data=(x_test, y_test))
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_7 (Dense)              (None, 512)               401920    
_________________________________________________________________
dense_8 (Dense)              (None, 512)               262656    
_________________________________________________________________
dense_9 (Dense)              (None, 10)                5130      
=================================================================
Total params: 669,706
Trainable params: 669,706
Non-trainable params: 0
_________________________________________________________________
Train on 60000 samples, validate on 10000 samples
Epoch 1/5
60000/60000 [==============================] - 11s - loss: 0.2214 - acc: 0.9314 - val_loss: 0.1048 - val_acc: 0.9668
Epoch 2/5
60000/60000 [==============================] - 12s - loss: 0.0838 - acc: 0.9739 - val_loss: 0.0842 - val_acc: 0.9752
Epoch 3/5
60000/60000 [==============================] - 10s - loss: 0.0548 - acc: 0.9829 - val_loss: 0.0806 - val_acc: 0.9773
Epoch 4/5
60000/60000 [==============================] - 9s - loss: 0.0387 - acc: 0.9878 - val_loss: 0.0713 - val_acc: 0.9804
Epoch 5/5
60000/60000 [==============================] - 9s - loss: 0.0297 - acc: 0.9911 - val_loss: 0.0847 - val_acc: 0.9787
<keras.callbacks.History at 0x7f87e82d1350>
# Bez warstw Dropout

score = model_no_dropout.evaluate(x_test, y_test, verbose=0)

print('Test loss (no dropout): {}'.format(score[0]))
print('Test accuracy (no dropout): {}'.format(score[1]))
Test loss (no dropout): 0.0846566448619
Test accuracy (no dropout): 0.9787
# Więcej warstw, inna funkcja aktywacji

num_classes = 10

(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.reshape(60000, 784)  # 784 = 28 * 28
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model3 = Sequential()
model3.add(Dense(2500, activation='tanh', input_shape=(784,)))
model3.add(Dense(2000, activation='tanh'))
model3.add(Dense(1500, activation='tanh'))
model3.add(Dense(1000, activation='tanh'))
model3.add(Dense(500, activation='tanh'))
model3.add(Dense(num_classes, activation='softmax'))
model3.summary()

model3.compile(loss='categorical_crossentropy',
               optimizer=RMSprop(),
               metrics=['accuracy'])

model3.fit(x_train, y_train,
           batch_size=128,
           epochs=10,
           verbose=1,
           validation_data=(x_test, y_test))
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_10 (Dense)             (None, 2500)              1962500   
_________________________________________________________________
dense_11 (Dense)             (None, 2000)              5002000   
_________________________________________________________________
dense_12 (Dense)             (None, 1500)              3001500   
_________________________________________________________________
dense_13 (Dense)             (None, 1000)              1501000   
_________________________________________________________________
dense_14 (Dense)             (None, 500)               500500    
_________________________________________________________________
dense_15 (Dense)             (None, 10)                5010      
=================================================================
Total params: 11,972,510
Trainable params: 11,972,510
Non-trainable params: 0
_________________________________________________________________
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
60000/60000 [==============================] - 212s - loss: 0.7388 - acc: 0.7954 - val_loss: 0.2908 - val_acc: 0.9172
Epoch 2/10
60000/60000 [==============================] - 191s - loss: 0.2390 - acc: 0.9305 - val_loss: 0.1833 - val_acc: 0.9470
Epoch 3/10
60000/60000 [==============================] - 166s - loss: 0.1688 - acc: 0.9517 - val_loss: 0.1555 - val_acc: 0.9549
Epoch 4/10
60000/60000 [==============================] - 166s - loss: 0.1344 - acc: 0.9614 - val_loss: 0.1274 - val_acc: 0.9621
Epoch 5/10
60000/60000 [==============================] - 166s - loss: 0.1074 - acc: 0.9683 - val_loss: 0.1213 - val_acc: 0.9661
Epoch 6/10
60000/60000 [==============================] - 440s - loss: 0.0924 - acc: 0.9725 - val_loss: 0.1066 - val_acc: 0.9709
Epoch 7/10
60000/60000 [==============================] - 169s - loss: 0.0768 - acc: 0.9773 - val_loss: 0.1777 - val_acc: 0.9517
Epoch 8/10
60000/60000 [==============================] - 183s - loss: 0.0657 - acc: 0.9805 - val_loss: 0.1053 - val_acc: 0.9711
Epoch 9/10
60000/60000 [==============================] - 170s - loss: 0.0572 - acc: 0.9832 - val_loss: 0.1044 - val_acc: 0.9717
Epoch 10/10
60000/60000 [==============================] - 166s - loss: 0.0493 - acc: 0.9851 - val_loss: 0.0938 - val_acc: 0.9752
<keras.callbacks.History at 0x7f87f007f610>
# Więcej warstw, inna funkcja aktywacji

score = model3.evaluate(x_test, y_test, verbose=0)

print('Test loss: {}'.format(score[0]))
print('Test accuracy: {}'.format(score[1]))
Test loss: 0.0937788957049
Test accuracy: 0.9752