Uczenie maszynowe i sztuczne sieci neuronowe/Ćwiczenia 7: Różnice pomiędzy wersjami

Z Brain-wiki
 
(Nie pokazano 8 pośrednich wersji utworzonych przez tego samego użytkownika)
Linia 1: Linia 1:
  
 
=Wstęp=
 
=Wstęp=
Celem tych ćwiczeń jest zapoznanie się z funkcjonalnością PyBrain wspierającymi klasyfikację za pomocą sieci neuronowych.  
+
Celem tych ćwiczeń jest zapoznanie się z klasyfikacją za pomocą sieci neuronowych.
  
 +
== Importy ==
 +
<source lang = python>
 +
# -*- coding: utf-8 -*-
 +
import matplotlib
 +
%matplotlib notebook
 +
#matplotlib.use('TkAgg')
 +
import numpy as np
 +
import pylab as py
 +
from scipy import diag, arange, meshgrid, where
 +
from numpy.random import multivariate_normal
 +
 +
</source>
 
=Zbiór uczący =
 
=Zbiór uczący =
PyBrain posiada klasę do obsługi zbiorów uczących przeznaczonych do klasyfikacji:
+
Funkcja generująca zbiór uczący:
<tt>ClassificationDataSet</tt>. Jest ona dostarczana przez moduł <tt>pybrain.datasets.classification</tt>.
 
  
Podstawowa składnia to:
+
<source lang = python>
<source lang =python>
+
def gen(ile):
DS = ClassificationDataSet(inputdim, nb_classes=2, class_labels=['Fish','Chips'])
+
    mu = [(-1,0),(2,4),(3,1)]
 +
    cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
 +
    X = np.zeros((ile*3, 2)) # miejsce na dane wejściowe
 +
    Y = np.zeros((ile*3,1),dtype = int) # miejsce na dane wyjściowe
 +
    for klasa in range(3):
 +
        X[klasa*ile:(klasa+1)*ile] = multivariate_normal(mu[klasa],cov[klasa],ile)
 +
        Y[klasa*ile:(klasa+1)*ile] = klasa
 +
    return (X,Y)
 +
       
 
</source>
 
</source>
tzn. musimy zdefiniować:
 
* rozmiar wektorów wejściowych <tt>inputdim</tt>
 
* liczba klas <tt>nb_classes</tt>
 
* opcjonalnie możemy podać nazwy klas: <tt>class_labels</tt>
 
 
Wymiar docelowy ma być 1. Celem są etykiety klasy zaczynające się od zera. Jeśli z jakiegoś powodu nie wiemy wcześniej, ile będzie klas, możliwe jest ustawienie tej informacji później, używając metod <tt>assignClasses()</tt> lub <tt>calculateStatistics()</tt> tak jak w poniższym przykładzie:
 
<source lang = "py">
 
DS = ClassificationDataSet(2, class_labels=['sredni', 'duzy', 'maly'])
 
DS.appendLinked([ 0.1, 0.5 ]  , [0])
 
DS.appendLinked([ 1.2, 1.2 ]  , [1])
 
DS.appendLinked([ 1.4, 1.6 ]  , [1])
 
DS.appendLinked([ 1.6, 1.8 ]  , [1])
 
DS.appendLinked([ 0.10, 0.80 ] , [2])
 
DS.appendLinked([ 0.20, 0.90 ] , [2])
 
 
print DS.calculateStatistics()
 
 
print DS.classHist
 
 
print DS.nClasses
 
  
print DS.getClass(1)
 
  
print DS.getField('target').transpose()
+
Problem klasyfikacji jest zazwyczaj łatwiejszy do rozwiązania jeśli w warstwie wyjściowej umieścimy tyle neuronów ile jest klas i docelowe klasy są kodowane jako wysoki stan jednego z neuronów wyjściowych. Nawiązuje to trochę do rozwiązań jakie wyprowadziliśmy na wykładzie dla [[Uczenie_maszynowe_i_sztuczne_sieci_neuronowe/Wykład_6#Regresja_softmax_jako_GLM | regresji wielorakiej (softmax)]].  
  
 +
<source lang = python>
 +
def convert_to_target(Y):
 +
    target = np.zeros((len(Y), len(np.unique(Y))))
 +
    for i in range(len(Y)):
 +
        target[i,Y[i,0]] = 1
 +
    return target
 
</source>
 
</source>
  
Problem klasyfikacji jest zazwyczaj łatwiejszy do rozwiązania jeśli w warstwie wyjściowej umieścimy tyle neuronów ile jest klas i docelowe klasy są kodowane jako wysoki stan jednego z neuronów wyjściowych. Nawiązuje to trochę do rozwiązań jakie wyprowadziliśmy na wykładzie dla [[STAT:Uczenie_maszynowe_i_sztuczne_sieci_neuronowe/Wykład_6#Regresja_softmax_jako_GLM | regresji wielorakiej (softmax)]].  ClassificationDataSet  posiada metodę <tt>_convertToOneOfMany</tt> pozwalającą na automatyczne przekodowanie klas z numeracji <math>{0,1,\dots,k-1}</math> na kodowanie zer i jedynek na odpowiednich wyjściach.
+
Testujemy:
<source lang = "py">
+
<source lang = python>
DS._convertToOneOfMany(bounds=[0, 1])
+
X,Y = gen(5)
print DS.getField('target')
+
print(Y)
[[1 0 0]
+
py.scatter(X[:,0],X[:,1],c = Y,s = 10)
[0 1 0]
+
target = convert_to_target(Y)
[0 1 0]
+
print(target)
[0 1 0]
 
[0 0 1]
 
[0 0 1]]
 
print DS.getField('class').transpose()
 
[[0 1 1 1 2 2]]
 
>>> DS._convertToClassNb()
 
>>> print DS.getField('target').transpose()
 
[[0 1 1 1 2 2]]
 
 
</source>
 
</source>
  
 
=Klasyfikacja=
 
=Klasyfikacja=
 
W tym przykładzie pokażemy jak przy pomocy sieci neuronowej zbudować klasyfikator. Zadanie będzie polegało na zaliczaniu punktów  do jednego z trzech typów. Dane będą pochodzić z trzech rozkładów normalnych dwuwymiarowych o  różnych parametrach.
 
W tym przykładzie pokażemy jak przy pomocy sieci neuronowej zbudować klasyfikator. Zadanie będzie polegało na zaliczaniu punktów  do jednego z trzech typów. Dane będą pochodzić z trzech rozkładów normalnych dwuwymiarowych o  różnych parametrach.
 +
Przygotujemy zestawy danych. Tablica mu zawiera wektory średnich dla każdego z trzech rozkładów, tablica cov zawiera macierze kowariancji dla tych rozkładów:
  
Najpierw przygotowyjemy grafikę do pracy w trybie interaktywnym
+
<source lang = python>
<source lang = 'py'>
+
N_przykladow =37
# -*- coding: utf-8 -*-
+
X,  Y      = gen(N_przykladow) # przykłady do ciągu uczącego
import matplotlib
+
X_m, Y_m    = gen(N_przykladow) # przykłady do ciągu monitorującego
matplotlib.use('TkAgg')
+
py.figure()
 +
py.scatter(X[:,0],X[:,1],c = Y,s = 10)
 +
py.scatter(X_m[:,0],X_m[:,1],c = Y_m, s = 10, marker = 's')
 
</source>
 
</source>
  
Następnie musimy zaimportować z bibliotek potrzebne klasy i definicje:
+
Warto przekodować te dane tak aby jedna klasa była reprezentowana przez jeden neuron wyjściowy:  
<source lang = 'py'>
+
<source lang = python>
from pybrain.datasets            import ClassificationDataSet
+
target = convert_to_target(Y)
from pybrain.utilities          import percentError
+
target_m = convert_to_target(Y_m)
from pybrain.tools.shortcuts    import buildNetwork
 
from pybrain.supervised.trainers import BackpropTrainer
 
from pybrain.structure.modules  import SoftmaxLayer
 
from pylab import ion, ioff, figure, draw, contourf, clf, show, hold, plot
 
from scipy import diag, arange, meshgrid, where
 
from numpy.random import multivariate_normal
 
 
</source>
 
</source>
  
Teraz przygotujemy zestawy danych. Tablica mu zawiera wektory średnich dla każdego z trzech rozkładów, tablica cov zawiera macierze kowariancji dla tych rozkładów:
 
  
<source lang = 'py'>
+
Teraz wytworzymy sieć.  Rozmiar wejścia i wyjścia muszą się zgadzać z rozmiarami danych wejściowych i wyjściowych, odpowiednio.
mu = [(-1,0),(2,4),(3,1)]
+
<source lang = python>
cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
+
class siec(object):
alldata = ClassificationDataSet(2, 1, nb_classes=3)
+
    def __init__(self, X, Y, N_hid=3):
for n in xrange(400):
+
        self.X = X
    for klasa in range(3):
+
        self.Y = Y
        input = multivariate_normal(mu[klasa],cov[klasa])
+
        self.N_wej = X.shape[1]
        alldata.addSample(input, [klasa])
+
        self.N_wyj = Y.shape[1]
 +
        self.N_hid = N_hid
 +
 +
        # inicjujemy połączenia
 +
        # wagi ułożone są tak, że w kolejnych wierszach są kolejne neurony
 +
        # a w kolumnach wagi od konkretnego neuronu
 +
        # to +1 jest wagą dla obciążenia
 +
        self.w_1 = (2*np.random.random((self.N_hid, self.N_wej+1)) - 1)/self.N_wej # pomiędzy warstwą pierwszą (wejściem) a warstwą ukrytą
 +
        self.w_2 = (2*np.random.random((self.N_wyj, self.N_hid+1)) - 1)/self.N_hid
 +
        self.dw1 = np.zeros((self.N_hid, self.N_wej+1))
 +
        self.dw2 = np.zeros((self.N_wyj, self.N_hid+1))
 +
 +
    def g1(self, x):
 +
        f = np.exp(-x)
 +
        f[f>1e8]=1e8
 +
        y = 1./(1+f)
 +
        return y 
 +
    def g1_prim(self, x):
 +
        y = x*(1-x)
 +
        return y
 +
    def g2(self, x):
 +
        y = x
 +
        return y 
 +
    def g2_prim(self, x):
 +
        y = 1
 +
        return y
 +
    def get_params(self):
 +
        return np.concatenate((self.w_1.reshape(-1), self.w_2.reshape(-1)))
 +
 +
    def predict(self, x):
 +
        # propagacja "w przód"
 +
        self.a_0 = np.vstack((1,x))  # z warstwy wejściowej (zerowej) wychodzi a_0
 +
        z_1 = np.dot( self.w_1, self.a_0 )# na warstwe 1 wchodzą iloczyny skalarne
 +
        self.a_1 = np.vstack((1,self.g1(z_1))) # dokładamy 1 i dostaję wyjście z warstwy 1
 +
        z_2 = np.dot( self.w_2, self.a_1 ) # na warstwe 3 wchodzą iloczyny skalarne
 +
        self.a_2 = self.g2(z_2)
 +
        return self.a_2
 +
   
 +
    def get_error(self,X,trget):
 +
        self.bl = 0
 +
        for i in range(0,self.X.shape[0]):
 +
            # weźmy przykład i-ty       
 +
            x = self.X[i,:].reshape(self.N_wej,1)
 +
            y = self.Y[i,:].reshape(self.N_wyj,1)
 +
            self.a_2 = self.predict(x)
 +
 +
            # delta
 +
            d_2 = (self.a_2 - y)*self.g2_prim(self.a_2)
 +
       
 +
            self.bl += np.dot(d_2.T,d_2)/self.X.shape[0]
 +
        return self.bl
 +
       
 +
    def fit_one_step(self, eta1,eta2):
 +
        self.bl = 0
 +
        D_1 = np.zeros((self.N_hid, self.N_wej+1))
 +
        D_2 = np.zeros((self.N_wyj, self.N_hid+1))
 +
        for i in range(0,self.X.shape[0]):
 +
            # weźmy przykład i-ty       
 +
            x = self.X[i,:].reshape(self.N_wej,1)
 +
            y = self.Y[i,:].reshape(self.N_wyj,1)
 +
            self.a_2 = self.predict(x)
 +
 +
            # propagacja "wstecz"
 +
            d_2 = (self.a_2 - y)*self.g2_prim(self.a_2)
 +
            d_1 = np.dot(self.w_2.T, d_2) * self.g1_prim(self.a_1)#z_2
 +
 +
            # akumulujemy poprawki
 +
            D_2 +=  np.dot( d_2, self.a_1.T)
 +
            D_1 +=  np.dot( d_1[1:], self.a_0.T)
 +
 +
            self.bl += np.dot(d_2.T,d_2)/self.X.shape[0]
 +
        # uaktualniamy wagi
 +
        self.w_1 -=  eta1*D_1 + eta2*self.dw1
 +
        self.w_2 -=  eta1*D_2+  eta2*self.dw2
 +
        self.dw1  =  eta1*D_1
 +
        self.dw2  =  eta1*D_2 
 +
        return self.bl
 +
   
 
</source>
 
</source>
  
Teraz dzielimy ten zbiór danych uczących na część treningową i testową. Podzielimy go w proporcji 3/4 danych uczących i 1/4 testowych. Można oczywiście od razu wygenerować dwa osobne zbiory tak jak to robiliśmy na poprzednich zajęciach. Tu jednak zaprezentujemy narzędzie wspomagające ten proces dostępne w PyBrain.
 
  
<source lang = 'py'>
+
 
tstdata, trndata = alldata.splitWithProportion( 0.25 )
+
Proszę poeksperymentować z ilością i typem neuronów w warstwie ukrytej. Również ilość warstw ukrytych można zmieniać podając dodatkowe liczby pomiędzy parametrami określającymi rozmiar wejścia i wyjścia.  Jako punkt startu zastosujemy 3 domyślnych (sigmoidalnych) neuronów w warstwie  ukrytej:
 +
 
 +
<source lang = python>
 +
S = siec( X, target, N_hid= 3)
 
</source>
 
</source>
  
Warto przekodować te dane tak aby jedna klasa była reprezentowana przez jeden neuron wyjściowy (porównaj: regresja softmax).
+
Przygotowujemy parametry:
<source lang = 'py'>
+
<source lang = python>
trndata._convertToOneOfMany( )
+
# liczba epok uczenia
tstdata._convertToOneOfMany( )
+
N_epochs = 150
 +
eta1 = 0.005
 +
eta2 = 0.9
 
</source>
 
</source>
  
Możemy wypisać trochę informacji o naszym zbiorze danych:
 
<source lang = 'py'>
 
print "Ilość przykładów treningowych: ", len(trndata)
 
print "Rozmiary wejścia i wyjścia: ", trndata.indim, trndata.outdim
 
print "Pierwszy przykład (wejście , wyjście, klasa):"
 
print trndata['input'][0], trndata['target'][0], trndata['class'][0]
 
</source>
 
Teraz wytworzymy sieć. Skorzystamy ze skrótu <tt>buildNetwork</tt>. Rozmiar wejścia i wyjścia muszą się zgadzać z rozmiarami danych wejściowych i wyjściowych, odpowiednio. Do klasyfikacji najlepiej w warstwie wyjściowej umieścić warstwę typu ''softmax''. 
 
  
Proszę poeksperymentować z ilością i typem neuronów w warstwie ukrytej. Również ilość warstw ukrytych można zmieniać podając dodatkowe liczby pomiędzy parametrami określającymi rozmiar wejścia i wyjściaJako punkt startu zastosujemy 5 domyślnych (sigmoidalnych) neuronów w warstwie  ukrytej:
+
Teraz przystępujemy do uczenia sieci (zapuścimy uczenie na 20 epok), przy czym po każdym kroku będziemy podglądać aktualny stan sieci, więc w metodzie <tt>trainEpochs</tt> podajemy 1 krok.  
 +
<source lang = python>
 +
# inicjuję tablice na ewolucje
 +
 
 +
err  = np.zeros(N_epochs) #tablica na błąd zbioru uczącego
 +
err_m = np.zeros(N_epochs) #tablica na błąd zbioru monitorującego
 +
wagi = np.zeros((N_epochs,len(S.get_params()))) #tablica na wagi
  
<source lang = 'py'>
 
fnn = buildNetwork( trndata.indim, 5, trndata.outdim, outclass=SoftmaxLayer )
 
</source>
 
  
Przygotowujemy trenera w standardowy sposób:
+
py.figure()
<source lang = 'py'>
+
for cykl in range(N_epochs):
trainer = BackpropTrainer( fnn, dataset=trndata, momentum=0.1, verbose=True, weightdecay=0.01)
+
    err[cykl] = S.fit_one_step(eta1,eta2) # wykonaj krok uczenia
</source>
+
    err_m[cykl] = S.get_error(X_m,target_m)# normalizuję aby uzyskać średni błąd kwadratowy
 +
    wagi[cykl,:] = S.get_params() #pobieram wagi do zapamiętania
 +
</source>  
  
 +
Narysujmy co działo się z błędem i wagami:
 +
<source lang = python>
 +
#  rysunki
 +
py.figure()
 +
py.subplot(2,1,1) # błędów
 +
py.semilogy(err,'b',label='zb. uczacy')
 +
py.semilogy(err_m,'r',label='zb. monitorujacy')
 +
py.title(u'błąd')
 +
py.legend()
 +
#py.ylim([0,1])
 +
py.subplot(2,1,2) #wag
 +
py.plot(wagi)
 +
py.title('wagi')
 +
py.ylim([-3,3])
 +
</source>
 +
==Rysunki dodatkowe==
 
Tu przygotowujemy siatkę punktów, które będziemy stosować do zilustrowania podziału przestrzeni wejściowej na obszary należące do poszczególnych klas. Funkcja <tt>meshgrid</tt> pobiera na wejście wektor dla x i y, a zwraca tablicę dwuwymiarową reprezentującą siatkę  o brzegach x i y.  
 
Tu przygotowujemy siatkę punktów, które będziemy stosować do zilustrowania podziału przestrzeni wejściowej na obszary należące do poszczególnych klas. Funkcja <tt>meshgrid</tt> pobiera na wejście wektor dla x i y, a zwraca tablicę dwuwymiarową reprezentującą siatkę  o brzegach x i y.  
Punkty siatki pakujemy do obiektu typu <tt>ClassificationDataSet</tt>, aby było je łatwo przepuszczać przez sieć. Ponieważ musimy dodawać do tego obiektu pojedyncze punkty a nie całe tablice to trzeba tablcę ''spłaszczyć'' metodą <tt>ravel</tt> (równoważnie można by zaimplementować tworzenie tego obiektu w dwóch pętlach przebiegających odpowiednio x i y ).  Pomimo, że nie interesuje nas teraz klasa tych punktów coś musimy wpisać (np. 0) i przekodować ten zestaw danych na typ <tt>OneOfMany</tt>, żeby można było łatwo skorzystać z funkcji przepuszczających zbiory danych przez sieć.
+
 
<source lang = 'py'>
+
<source lang = python>
 
ticks = arange(-3.,6.,0.2)
 
ticks = arange(-3.,6.,0.2)
 
X, Y = meshgrid(ticks, ticks)
 
X, Y = meshgrid(ticks, ticks)
# need column vectors in dataset, not arrays
+
 
griddata = ClassificationDataSet(2,1, nb_classes=3)
 
for i in xrange(X.size):
 
    griddata.addSample([X.ravel()[i],Y.ravel()[i]], [0])
 
griddata._convertToOneOfMany() 
 
</source>
 
Teraz przystępujemy do uczenia sieci (zapuścimy uczenie na 20 epok), przy czym po każdym kroku będziemy podglądać aktualny stan sieci, więc w metodzie <tt>trainEpochs</tt> podajemy 1 krok.
 
<source lang = 'py'>
 
for i in range(20):
 
    trainer.trainEpochs( 1 )
 
 
</source>
 
</source>
  
Sprawdzamy działanie sieci na zbiorze uczącym i na testowym. Skorzystamy z metody trenera <tt>testOnClassData()</tt> do policzenia aktualnej klasyfikacji dla zbioru uczącego (domyślnie) i dla danych testowych. Funkcja <tt>percentError</tt> oblicza procentową rozbieżność pomiędzy swoimi argumentami.  Wyniki wypisujemy na konsoli.
 
<source lang = 'py'>
 
    trnresult = percentError( trainer.testOnClassData(), trndata['class'] )
 
    tstresult = percentError( trainer.testOnClassData(dataset=tstdata ), tstdata['class'] )
 
  
    print "krok: %4d" % trainer.totalepochs, \
 
          "  błąd na zbiorze uczącym:  %5.2f%%" % trnresult, \
 
          "  błąd na zbiorze testowym: %5.2f%%" % tstresult
 
</source>
 
  
 
Teraz przygotujemy się do ilustrowania działania sieci graficznie. Przepuścimy przez sieć zbiór danych zawierających siatkę punktów. Dla każdego punktu otrzymamy w zmiennej <tt>out</tt> aktywność neuronów warstwy wyjściowej.  
 
Teraz przygotujemy się do ilustrowania działania sieci graficznie. Przepuścimy przez sieć zbiór danych zawierających siatkę punktów. Dla każdego punktu otrzymamy w zmiennej <tt>out</tt> aktywność neuronów warstwy wyjściowej.  
<source lang = 'py'>
+
<source lang = python>
     out = fnn.activateOnDataset(griddata)
+
     out = ...
 
</source>
 
</source>
 
Za pomocą metody <tt>argmax()</tt> pobieramy indeks neuronu, który miał największą aktywność.  
 
Za pomocą metody <tt>argmax()</tt> pobieramy indeks neuronu, który miał największą aktywność.  
<source lang = 'py'>
+
<source lang = python>
 
     out = out.argmax(axis=1)  # the highest output activation gives the class
 
     out = out.argmax(axis=1)  # the highest output activation gives the class
 
</source>
 
</source>
 
Dopasowujemy kształt wyjścia do kształtu wejść.
 
Dopasowujemy kształt wyjścia do kształtu wejść.
<source lang = 'py'>
+
<source lang = python>
 
     out = out.reshape(X.shape)
 
     out = out.reshape(X.shape)
 
</source>
 
</source>
  
 
Teraz możemy wykonać rysunek.
 
Teraz możemy wykonać rysunek.
<source lang = 'py'>
+
<source lang = python>
     figure(1)
+
     py.figure()
    ioff()  # wyłączamy tryb interaktywny grafiki
+
 
     clf()  # czyścimy rysunek
+
      
 
     hold(True) # włączamy opcję dopisywania do bieżącego rysunku
 
     hold(True) # włączamy opcję dopisywania do bieżącego rysunku
 
     for c in [0,1,2]:  # iterujemy się przez możliwe klasy
 
     for c in [0,1,2]:  # iterujemy się przez możliwe klasy
         here, _ = where(tstdata['class']==c)        # wybieramy indeksy punktów testowych należących do klasy c
+
         here, _ = ...      # wybieramy indeksy punktów testowych należących do klasy c
         plot(tstdata['input'][here,0],tstdata['input'][here,1],'o') # rysujemy kółkami punkty testowe należące do klasy c   
+
         ... # rysujemy kółkami punkty testowe należące do klasy c   
 
     if out.max()!=out.min():  # safety check against flat field
 
     if out.max()!=out.min():  # safety check against flat field
 
         contourf(X, Y, out)  # przy pomocy zapełnionych konturów rysujemy wynik klasyfikacji punktów siatki, daje nam to ilustrację obszarów na jakie sieć aktualnie dzieli przestrzeń wejściową
 
         contourf(X, Y, out)  # przy pomocy zapełnionych konturów rysujemy wynik klasyfikacji punktów siatki, daje nam to ilustrację obszarów na jakie sieć aktualnie dzieli przestrzeń wejściową
     ion()  # przełączamy grafikę w tryb interaktywny
+
     py.show()
    draw()  # odświeżamy rysunek
 
</source>
 
 
 
 
 
Po zakończeniu uczenia czekamy aż użytkownik zamknie ostatni obrazek.
 
<source lang = 'py'>
 
ioff()
 
show()
 
 
</source>
 
</source>
  

Aktualna wersja na dzień 18:53, 6 cze 2017

Wstęp

Celem tych ćwiczeń jest zapoznanie się z klasyfikacją za pomocą sieci neuronowych.

Importy

# -*- coding: utf-8 -*-
import matplotlib
%matplotlib notebook 
#matplotlib.use('TkAgg')
import numpy as np
import pylab as py
from scipy import diag, arange, meshgrid, where
from numpy.random import multivariate_normal

Zbiór uczący

Funkcja generująca zbiór uczący:

def gen(ile):
    mu = [(-1,0),(2,4),(3,1)]
    cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
    X = np.zeros((ile*3, 2)) # miejsce na dane wejściowe
    Y = np.zeros((ile*3,1),dtype = int) # miejsce na dane wyjściowe
    for klasa in range(3):
        X[klasa*ile:(klasa+1)*ile] = multivariate_normal(mu[klasa],cov[klasa],ile)
        Y[klasa*ile:(klasa+1)*ile] = klasa
    return (X,Y)


Problem klasyfikacji jest zazwyczaj łatwiejszy do rozwiązania jeśli w warstwie wyjściowej umieścimy tyle neuronów ile jest klas i docelowe klasy są kodowane jako wysoki stan jednego z neuronów wyjściowych. Nawiązuje to trochę do rozwiązań jakie wyprowadziliśmy na wykładzie dla regresji wielorakiej (softmax).

def convert_to_target(Y):
    target = np.zeros((len(Y), len(np.unique(Y))))
    for i in range(len(Y)):
        target[i,Y[i,0]] = 1
    return target

Testujemy:

X,Y = gen(5)
print(Y)
py.scatter(X[:,0],X[:,1],c = Y,s = 10)
target = convert_to_target(Y)
print(target)

Klasyfikacja

W tym przykładzie pokażemy jak przy pomocy sieci neuronowej zbudować klasyfikator. Zadanie będzie polegało na zaliczaniu punktów do jednego z trzech typów. Dane będą pochodzić z trzech rozkładów normalnych dwuwymiarowych o różnych parametrach. Przygotujemy zestawy danych. Tablica mu zawiera wektory średnich dla każdego z trzech rozkładów, tablica cov zawiera macierze kowariancji dla tych rozkładów:

N_przykladow =37
X,   Y      = gen(N_przykladow) # przykłady do ciągu uczącego
X_m, Y_m    = gen(N_przykladow) # przykłady do ciągu monitorującego
py.figure()
py.scatter(X[:,0],X[:,1],c = Y,s = 10)
py.scatter(X_m[:,0],X_m[:,1],c = Y_m, s = 10, marker = 's')

Warto przekodować te dane tak aby jedna klasa była reprezentowana przez jeden neuron wyjściowy:

target = convert_to_target(Y)
target_m = convert_to_target(Y_m)


Teraz wytworzymy sieć. Rozmiar wejścia i wyjścia muszą się zgadzać z rozmiarami danych wejściowych i wyjściowych, odpowiednio.

class siec(object):
    def __init__(self, X, Y, N_hid=3):
        self.X = X
        self.Y = Y
        self.N_wej = X.shape[1] 
        self.N_wyj = Y.shape[1]
        self.N_hid = N_hid
 
        # inicjujemy połączenia
        # wagi ułożone są tak, że w kolejnych wierszach są kolejne neurony 
        # a w kolumnach wagi od konkretnego neuronu 
        # to +1 jest wagą dla obciążenia
        self.w_1 = (2*np.random.random((self.N_hid, self.N_wej+1)) - 1)/self.N_wej # pomiędzy warstwą pierwszą (wejściem) a warstwą ukrytą
        self.w_2 = (2*np.random.random((self.N_wyj, self.N_hid+1)) - 1)/self.N_hid
        self.dw1 = np.zeros((self.N_hid, self.N_wej+1))
        self.dw2 = np.zeros((self.N_wyj, self.N_hid+1)) 
 
    def g1(self, x):
        f = np.exp(-x) 
        f[f>1e8]=1e8
        y = 1./(1+f)
        return y   
    def g1_prim(self, x):
        y = x*(1-x)
        return y
    def g2(self, x):
        y = x
        return y   
    def g2_prim(self, x):
        y = 1
        return y
    def get_params(self):
        return np.concatenate((self.w_1.reshape(-1), self.w_2.reshape(-1)))
 
    def predict(self, x):
        # propagacja "w przód"
        self.a_0 = np.vstack((1,x))  # z warstwy wejściowej (zerowej) wychodzi a_0
        z_1 = np.dot( self.w_1, self.a_0 )# na warstwe 1 wchodzą iloczyny skalarne 
        self.a_1 = np.vstack((1,self.g1(z_1))) # dokładamy 1 i dostaję wyjście z warstwy 1
        z_2 = np.dot( self.w_2, self.a_1 ) # na warstwe 3 wchodzą iloczyny skalarne 
        self.a_2 = self.g2(z_2)
        return self.a_2
    
    def get_error(self,X,trget):
        self.bl = 0
        for i in range(0,self.X.shape[0]):
            # weźmy przykład i-ty        
            x = self.X[i,:].reshape(self.N_wej,1)
            y = self.Y[i,:].reshape(self.N_wyj,1)
            self.a_2 = self.predict(x)
 
            # delta
            d_2 = (self.a_2 - y)*self.g2_prim(self.a_2)
         
            self.bl += np.dot(d_2.T,d_2)/self.X.shape[0]
        return self.bl
        
    def fit_one_step(self, eta1,eta2):
        self.bl = 0
        D_1 = np.zeros((self.N_hid, self.N_wej+1))
        D_2 = np.zeros((self.N_wyj, self.N_hid+1))
        for i in range(0,self.X.shape[0]):
            # weźmy przykład i-ty        
            x = self.X[i,:].reshape(self.N_wej,1)
            y = self.Y[i,:].reshape(self.N_wyj,1)
            self.a_2 = self.predict(x)
 
            # propagacja "wstecz"
            d_2 = (self.a_2 - y)*self.g2_prim(self.a_2)
            d_1 = np.dot(self.w_2.T, d_2) * self.g1_prim(self.a_1)#z_2
 
            # akumulujemy poprawki 
            D_2 +=  np.dot( d_2, self.a_1.T)
            D_1 +=  np.dot( d_1[1:], self.a_0.T)
 
            self.bl += np.dot(d_2.T,d_2)/self.X.shape[0]
        # uaktualniamy wagi
        self.w_1 -=  eta1*D_1 + eta2*self.dw1
        self.w_2 -=  eta1*D_2+  eta2*self.dw2
        self.dw1  =  eta1*D_1 
        self.dw2  =  eta1*D_2   
        return self.bl


Proszę poeksperymentować z ilością i typem neuronów w warstwie ukrytej. Również ilość warstw ukrytych można zmieniać podając dodatkowe liczby pomiędzy parametrami określającymi rozmiar wejścia i wyjścia. Jako punkt startu zastosujemy 3 domyślnych (sigmoidalnych) neuronów w warstwie ukrytej:

S = siec( X, target, N_hid= 3)

Przygotowujemy parametry:

# liczba epok uczenia
N_epochs = 150
eta1 = 0.005
eta2 = 0.9


Teraz przystępujemy do uczenia sieci (zapuścimy uczenie na 20 epok), przy czym po każdym kroku będziemy podglądać aktualny stan sieci, więc w metodzie trainEpochs podajemy 1 krok.

# inicjuję tablice na ewolucje

err  = np.zeros(N_epochs) #tablica na błąd zbioru uczącego
err_m  = np.zeros(N_epochs) #tablica na błąd zbioru monitorującego
wagi = np.zeros((N_epochs,len(S.get_params()))) #tablica na wagi


py.figure()
for cykl in range(N_epochs):
    err[cykl] = S.fit_one_step(eta1,eta2) # wykonaj krok uczenia
    err_m[cykl] = S.get_error(X_m,target_m)# normalizuję aby uzyskać średni błąd kwadratowy
    wagi[cykl,:] = S.get_params() #pobieram wagi do zapamiętania

Narysujmy co działo się z błędem i wagami:

#  rysunki
py.figure()
py.subplot(2,1,1) # błędów
py.semilogy(err,'b',label='zb. uczacy')
py.semilogy(err_m,'r',label='zb. monitorujacy')
py.title(u'błąd')
py.legend()
#py.ylim([0,1]) 
py.subplot(2,1,2) #wag
py.plot(wagi)
py.title('wagi')
py.ylim([-3,3])

Rysunki dodatkowe

Tu przygotowujemy siatkę punktów, które będziemy stosować do zilustrowania podziału przestrzeni wejściowej na obszary należące do poszczególnych klas. Funkcja meshgrid pobiera na wejście wektor dla x i y, a zwraca tablicę dwuwymiarową reprezentującą siatkę o brzegach x i y.

ticks = arange(-3.,6.,0.2)
X, Y = meshgrid(ticks, ticks)


Teraz przygotujemy się do ilustrowania działania sieci graficznie. Przepuścimy przez sieć zbiór danych zawierających siatkę punktów. Dla każdego punktu otrzymamy w zmiennej out aktywność neuronów warstwy wyjściowej.

    out = ...

Za pomocą metody argmax() pobieramy indeks neuronu, który miał największą aktywność.

    out = out.argmax(axis=1)  # the highest output activation gives the class

Dopasowujemy kształt wyjścia do kształtu wejść.

    out = out.reshape(X.shape)

Teraz możemy wykonać rysunek.

    py.figure()
   
    
    hold(True) # włączamy opcję dopisywania do bieżącego rysunku
    for c in [0,1,2]:  # iterujemy się przez możliwe klasy
        here, _ = ...       # wybieramy indeksy punktów testowych należących do klasy c
        ... # rysujemy kółkami punkty testowe należące do klasy c  
    if out.max()!=out.min():  # safety check against flat field
        contourf(X, Y, out)   # przy pomocy zapełnionych konturów rysujemy wynik klasyfikacji punktów siatki, daje nam to ilustrację obszarów na jakie sieć aktualnie dzieli przestrzeń wejściową
    py.show()

Polecenia dodatkowe

  • Proszę zbadać powtarzalność granic separacji
  • Proszę zbadać klasyfikację punktu odległego od zbioru uczącego:
out = fnn.activate((100, 100))
print out
  • Proszę zbadać zależność separacji i kształty powierzchni rozgraniczających w zależności od:
    • liczby neuronów w warstwie ukrytej
    • współczynnika weightdecay w trenerze
  • Proszę sprawdzić działanie klasyfikatora dla innych konfiguracji klas wejściowych, np: łącząc kilka rozkładów normalnych o różnych parametrach w jedną klasę