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

Z Brain-wiki
Linia 8: Linia 8:
  
 
Podstawowa składnia to:  
 
Podstawowa składnia to:  
<source lang = python>
+
<source lang ="py">
 
DS = ClassificationDataSet(inputdim, nb_classes=2, class_labels=['Fish','Chips'])
 
DS = ClassificationDataSet(inputdim, nb_classes=2, class_labels=['Fish','Chips'])
 
</source>
 
</source>
Linia 17: Linia 17:
  
 
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:
 
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 = python>
+
<source lang = "py">
 
DS = ClassificationDataSet(2, class_labels=['sredni', 'duzy', 'maly'])
 
DS = ClassificationDataSet(2, class_labels=['sredni', 'duzy', 'maly'])
 
DS.appendLinked([ 0.1, 0.5 ]  , [0])
 
DS.appendLinked([ 0.1, 0.5 ]  , [0])
Linia 59: Linia 59:
  
 
Najpierw przygotowyjemy grafikę do pracy w trybie interaktywnym
 
Najpierw przygotowyjemy grafikę do pracy w trybie interaktywnym
<source lang = 'py'>
+
<source lang = python>
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8 -*-
 
import matplotlib
 
import matplotlib
Linia 66: Linia 66:
  
 
Następnie musimy zaimportować z bibliotek potrzebne klasy i definicje:
 
Następnie musimy zaimportować z bibliotek potrzebne klasy i definicje:
<source lang = 'py'>
+
<source lang = python>
 
from pybrain.datasets            import ClassificationDataSet
 
from pybrain.datasets            import ClassificationDataSet
 
from pybrain.utilities          import percentError
 
from pybrain.utilities          import percentError
Linia 79: Linia 79:
 
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:  
 
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'>
+
<source lang = python>
 
mu = [(-1,0),(2,4),(3,1)]
 
mu = [(-1,0),(2,4),(3,1)]
 
cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
 
cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
Linia 91: Linia 91:
 
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.
 
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'>
+
<source lang = python>
 
tstdata, trndata = alldata.splitWithProportion( 0.25 )
 
tstdata, trndata = alldata.splitWithProportion( 0.25 )
 
</source>
 
</source>
  
 
Warto przekodować te dane tak aby jedna klasa była reprezentowana przez jeden neuron wyjściowy (porównaj: regresja softmax).  
 
Warto przekodować te dane tak aby jedna klasa była reprezentowana przez jeden neuron wyjściowy (porównaj: regresja softmax).  
<source lang = 'py'>
+
<source lang = python>
 
trndata._convertToOneOfMany( )
 
trndata._convertToOneOfMany( )
 
tstdata._convertToOneOfMany( )
 
tstdata._convertToOneOfMany( )
Linia 102: Linia 102:
  
 
Możemy wypisać trochę informacji o naszym zbiorze danych:
 
Możemy wypisać trochę informacji o naszym zbiorze danych:
<source lang = 'py'>
+
<source lang = python>
 
print "Ilość przykładów treningowych: ", len(trndata)
 
print "Ilość przykładów treningowych: ", len(trndata)
 
print "Rozmiary wejścia i wyjścia: ", trndata.indim, trndata.outdim
 
print "Rozmiary wejścia i wyjścia: ", trndata.indim, trndata.outdim
Linia 112: Linia 112:
 
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 5 domyślnych (sigmoidalnych) neuronów w warstwie  ukrytej:  
 
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 5 domyślnych (sigmoidalnych) neuronów w warstwie  ukrytej:  
  
<source lang = 'py'>
+
<source lang = python>
 
fnn = buildNetwork( trndata.indim, 5, trndata.outdim, outclass=SoftmaxLayer )
 
fnn = buildNetwork( trndata.indim, 5, trndata.outdim, outclass=SoftmaxLayer )
 
</source>
 
</source>
  
 
Przygotowujemy trenera w standardowy sposób:
 
Przygotowujemy trenera w standardowy sposób:
<source lang = 'py'>
+
<source lang = python>
 
trainer = BackpropTrainer( fnn, dataset=trndata, momentum=0.1, verbose=True, weightdecay=0.01)
 
trainer = BackpropTrainer( fnn, dataset=trndata, momentum=0.1, verbose=True, weightdecay=0.01)
 
</source>
 
</source>
Linia 123: Linia 123:
 
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ć.  
 
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)
Linia 133: Linia 133:
 
</source>
 
</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.  
 
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'>
+
<source lang = python>
 
for i in range(20):
 
for i in range(20):
 
     trainer.trainEpochs( 1 )
 
     trainer.trainEpochs( 1 )
Linia 139: Linia 139:
  
 
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.
 
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'>
+
<source lang = python>
 
     trnresult = percentError( trainer.testOnClassData(), trndata['class'] )
 
     trnresult = percentError( trainer.testOnClassData(), trndata['class'] )
 
     tstresult = percentError( trainer.testOnClassData(dataset=tstdata ), tstdata['class'] )
 
     tstresult = percentError( trainer.testOnClassData(dataset=tstdata ), tstdata['class'] )
Linia 149: Linia 149:
  
 
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 = fnn.activateOnDataset(griddata)
 
</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)
 
     figure(1)
 
     ioff()  # wyłączamy tryb interaktywny grafiki
 
     ioff()  # wyłączamy tryb interaktywny grafiki
Linia 178: Linia 178:
  
 
Po zakończeniu uczenia czekamy aż użytkownik zamknie ostatni obrazek.
 
Po zakończeniu uczenia czekamy aż użytkownik zamknie ostatni obrazek.
<source lang = 'py'>
+
<source lang = python>
 
ioff()
 
ioff()
 
show()
 
show()

Wersja z 18:32, 21 maj 2015

Wstęp

Celem tych ćwiczeń jest zapoznanie się z funkcjonalnością PyBrain wspierającymi klasyfikację za pomocą sieci neuronowych.

Zbiór uczący

PyBrain posiada klasę do obsługi zbiorów uczących przeznaczonych do klasyfikacji: ClassificationDataSet. Jest ona dostarczana przez moduł pybrain.datasets.classification.

Podstawowa składnia to:

DS = ClassificationDataSet(inputdim, nb_classes=2, class_labels=['Fish','Chips'])

tzn. musimy zdefiniować:

  • rozmiar wektorów wejściowych inputdim
  • liczba klas nb_classes
  • opcjonalnie możemy podać nazwy klas: class_labels

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 assignClasses() lub calculateStatistics() tak jak w poniższym przykładzie:

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 regresji wielorakiej (softmax). ClassificationDataSet posiada metodę _convertToOneOfMany pozwalającą na automatyczne przekodowanie klas z numeracji [math]{0,1,\dots,k-1}[/math] na kodowanie zer i jedynek na odpowiednich wyjściach.

DS._convertToOneOfMany(bounds=[0, 1])
print DS.getField('target')
[[1 0 0]
 [0 1 0]
 [0 1 0]
 [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]]

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.

Najpierw przygotowyjemy grafikę do pracy w trybie interaktywnym

# -*- coding: utf-8 -*-
import matplotlib
matplotlib.use('TkAgg')

Następnie musimy zaimportować z bibliotek potrzebne klasy i definicje:

from pybrain.datasets            import ClassificationDataSet
from pybrain.utilities           import percentError
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

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:

mu = [(-1,0),(2,4),(3,1)]
cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
alldata = ClassificationDataSet(2, 1, nb_classes=3)
for n in xrange(400):
    for klasa in range(3):
        input = multivariate_normal(mu[klasa],cov[klasa])
        alldata.addSample(input, [klasa])

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.

tstdata, trndata = alldata.splitWithProportion( 0.25 )

Warto przekodować te dane tak aby jedna klasa była reprezentowana przez jeden neuron wyjściowy (porównaj: regresja softmax).

trndata._convertToOneOfMany( )
tstdata._convertToOneOfMany( )

Możemy wypisać trochę informacji o naszym zbiorze danych:

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]

Teraz wytworzymy sieć. Skorzystamy ze skrótu buildNetwork. 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ścia. Jako punkt startu zastosujemy 5 domyślnych (sigmoidalnych) neuronów w warstwie ukrytej:

fnn = buildNetwork( trndata.indim, 5, trndata.outdim, outclass=SoftmaxLayer )

Przygotowujemy trenera w standardowy sposób:

trainer = BackpropTrainer( fnn, dataset=trndata, momentum=0.1, verbose=True, weightdecay=0.01)

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. Punkty siatki pakujemy do obiektu typu ClassificationDataSet, 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ą ravel (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 OneOfMany, żeby można było łatwo skorzystać z funkcji przepuszczających zbiory danych przez sieć.

ticks = arange(-3.,6.,0.2)
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()

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.

for i in range(20):
    trainer.trainEpochs( 1 )

Sprawdzamy działanie sieci na zbiorze uczącym i na testowym. Skorzystamy z metody trenera testOnClassData() do policzenia aktualnej klasyfikacji dla zbioru uczącego (domyślnie) i dla danych testowych. Funkcja percentError oblicza procentową rozbieżność pomiędzy swoimi argumentami. Wyniki wypisujemy na konsoli.

    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

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 = fnn.activateOnDataset(griddata)

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.

    figure(1)
    ioff()  # wyłączamy tryb interaktywny grafiki
    clf()   # czyścimy rysunek
    hold(True) # włączamy opcję dopisywania do bieżącego rysunku
    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
        plot(tstdata['input'][here,0],tstdata['input'][here,1],'o') # 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ą
    ion()   # przełączamy grafikę w tryb interaktywny 
    draw()  # odświeżamy rysunek


Po zakończeniu uczenia czekamy aż użytkownik zamknie ostatni obrazek.

ioff()
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ę