Blog (16)
Komentarze (1.1k)
Recenzje (0)

C: Sieć Neuronowa typu FeedForward

Strona główna@revcoreyC: Sieć Neuronowa typu FeedForward
11.04.2013 20:04

Dzisiejszym tematem będzie implementacja sieci feedforward w języku C. Przed rozpoczęciem lektury należy zapoznać się z moim poprzednim artykułem o sieciach neuronowych ponieważ będziemy wykorzystywać także matlaba: Poprzedni wpis

Implementacja w Matlabie

Źródłem naszych próbek będzie układ w simulinku. Jest implementacja metody polowej sterowania silnikiem indukcyjnym typu FOC. Generalnie tego typu metody właśnie wymagają estymatorów. Silnik indukcyjny jest dostępny w poprzednim artykule i równie dobrze można z niego uzyskać próbki(aczkolwiek jest to sam silnik bez metody sterowania FOC). Tym razem będziemy wykorzystywać napięcia między fazowe i prądy fazowe, aby było można je odczytać z modelu silnika indukcyjnego opartego o układ alfa-beta należy dokonać odwrotnej transformacji clarka, niemniej osoby nie obeznane nie muszą tego robić i wystarczy że po prostu użyją poprzedniego przykładu, a poniżej pokazany kod odpowiednio zmodyfikują co nie będzie trudne.

Sterowanie FOC w simulink
Sterowanie FOC w simulink

Zgodnie z tym o czym wspomniałem w poprzednim artykule pomiędzy neuronami znajdują się wagi. Sposobów obliczania wag jest parę. Niemniej w tym przykładzie nie zajmiemy się pisaniem metody uczącej sieć. Powód jest prozaiczny, napisałem implementację metody uczącej opartej o wsteczną propagację błędu, niestety wymaga ona wielu iteracji i wykonuje się długo(zależnie od liczby próbek od 1,5 h i więcej). Aby skrócić czas tak jak poprzednio nauczymy sieć w matlabie(matlab domyślnie korzysta z metody wstecznej metody propagacji błędu z optymalizacją L-M), proces taki przebiega znacznie szybciej niż gdyby samemu opracować w matlabie taką metodę. Po nauczeniu sieci wygenerujemy model układu w simulinku po czym odczytamy poszczególne wagi między neuronami. Kolejnym krokiem będzie stworzenie sieci w m-codzie w matlabie, a na końcu implementacja w języku C. Uczenie sieci X=[ia_p';ib_p';ic_p';uab_p';ubc_p'];% prąd w fazach silnika i napięcia międzyfazowe net = newff([-15 16; -13 15; -13 15; -12 178; -9 12 ],[5 4 1],{'logsig','tansig','purelin'}); siec_t=train(siec,X,me0'); gensim(net); %generowanie sieci w simulinku

Moment podczas uczenia
Moment podczas uczenia

Po wykonaniu polecenia gensim na nauczonej sieci zostaje stworzona automatycznie sieć w simulinku możemy z niej korzystać od razu w simulinku(sygnały na wejście przekazujemy blokiem Mux). My tylko odczytamy z niej wagi i dodatkowo może służyć jako opis tego jak wygląda sieć neuronowa jednokierunkowa.

Sieć neuronowa w Simulink
Sieć neuronowa w Simulink

Klikamy ppm na blok sieci i wybieramy Look under mask. Następnie wchodzimy do bloku Layer 1. W nim znajduje się neuron,wagi i zmienna b(próg). Gdy wejdziemy do bloku LW odnajdziemy wagi. Wybieramy najpierw IW{1,1}(1,:)' . Są to wagi dla pierwszego neuronu w warstwie.

W warstwie 1
W warstwie 1
Wagi neuronu
Wagi neuronu

Otwiera się nam okno z wagą, jest to tablica o jednym wierszu i n-kolumnach(zależna od liczby neuronów warstwy). Wagi są tak ułożone iż pierwszy element w wierszu jest przeznaczony dla wejścia pierwszego, drugi dla drugiego wejścia itd. Odczytujemy tak wagi dla każdego z neuronów. Potem wracamy do warstwy z neuronem i odczytujemy próg b, jest to także tablica. Następnie powtarzamy operację dla wszystkich warstw. Plik z wagami powinien wyglądać tak, proszę zwrócić uwagę iż w pliku odczytane wagi składam w odpowiedni sposób w odpowiednie tablicę. Chodzi o to aby mieć jedną tablicę dwu wymiarową dla każdej z warstw. Przykład pliku z wagami

M-Code

Algorytm sieci w matalbie przedstawia się następująco: wejsc=5;%liczba wejść neuronow1=5;%liczba neuronów w 1 warstwie neuronow2=4;%liczba neuronów w 2 warstwie neuronow3=1;%liczba wyjść wynik_t=zeros(100001,1);

bEiQsbdz

for dd=1:100001 hidenneurons = zeros(neuronow1,1);%tworzenie wektorów w których będą przechowywane wartości wyjściowe poszczególnych warstw hidenneurons2 = zeros(neuronow2,1); outputneurons= zeros(neuronow3,1); %tmp=zeros(5,1); dane_wejsc=[ia_p(dd),ib_p(dd),ic_p(dd),uab_p(dd),ubc_p(dd)];%dane wejściowe

for x=1:neuronow1 % liczba neuronów pierwszej warstwie

%hidenneurons(x)=0;

for ka=1:wejsc %liczba wejść

bEiQsbdF

hidenneurons(x)=hidenneurons(x)+dane_wejsc(ka)*waga1(x,ka);

end hidenneurons(x)=hidenneurons(x)+b1(x,1); % dodawanie progu hidenneurons(x)=logsig(hidenneurons(x)); %funkcja aktywacji czyli obliczanie wyjścia neuronu

end % koniec pierwszej warstwy

for x=1:neuronow2

bEiQsbdG

%hidenneurons2(x)=0;

for ka=1:neuronow1

hidenneurons2(x)=hidenneurons2(x)+hidenneurons(ka)*waga2(x,ka);

end hidenneurons2(x)=hidenneurons2(x)+b2(x,1);

bEiQsbdH

hidenneurons2(x)=tansig(hidenneurons2(x));

end

for x=1:neuronow3

%outputneurons(x)=0;

bEiQsbdI

for kz=1:neuronow2

outputneurons(x)=outputneurons(x)+hidenneurons2(kz)*waga3a(kz,x);

end

outputneurons(x)=outputneurons(x)+b3;

end

wynik_t(dd)=outputneurons(1);% wartość wyjściowa sieci dla jednego zestawu próbek end disp('koniec');

Algorytm jak widać nie jest skomplikowany. Wszystko opiera się najkrócej mówiąc: -Najpierw wykonujemy obliczenia dla pierwszego neuronu w warstwie. Czyli sygnały wejściowe przemnażamy przez wszystkie wagi połączone z pierwszym neuronem. -Następnie dodajemy do wyjścia pierwszego neuronu wartość b -Tak obliczoną wartość podajemy na funkcję aktywacji,której wynikiem jest stopień pobudzenia neuronu pierwszego. -Tak powtarzamy operację dla każdego neuronu w warstwie -Teraz wejściem kolejnej warstwy jest wyjście poprzedniej. I proces jest identyczny jak poprzednio. I to w zasadzie tyle. Jeśli ktoś ma kłopoty niech uruchomi kod w trybie debbugowania i prześledzi działanie krok po kroku. Kod jest napisany na tyle ogólnie że odpali się w scilab/octave/matlab. Aby dodać kolejną warstwę wystarczy wbić się w dowolne miejsce w warstwie ukrytej z dwoma pętlami for.

Wyniki:

Porównanie wartości estymowanej z rzeczywistą
Porównanie wartości estymowanej z rzeczywistą

Jeszcze o teorii

Wracając do samych wag i uczenia. Niektórzy mogą zauważyć iż w zależności jak przebiega proces wartości na wyjściu mogą być bardziej lub mniej prawdziwie. Odpowiedź na pytanie „jak zrobić to aby było idealnie” nie jest prosta. Dużo zależy od samego typu zadania. Są różne rodzaje sieci NN które radzą sobie lepiej lub gorzej w zależności od rodzaju zadania. Dużo zależy także od próbek uczenia tzn. jak są zróżnicowane, jak dużo ich jest, jak długo trwało uczenie i algorytmów. W tym przypadku mamy uczenie z nauczycielem. Gdzie wagi inicjalizuję się losowo po czym następuje proces uczenia gdzie przedstawiamy wartość pożądaną wyjścia(moment elek.) i algorytmy próbują dobrać tak wagi aby różnice były jak najmniejsze. Oczywiście możemy trafić na zjawisko przeuczenia gdzie dla danego zestawu próbek wyniki będą świetne ale gdy w normalnej pracy pojawią się inne przebiegi, układ zgłupieje. Jak dobrać liczbę iteracji? Liczbę warstw? Liczbę neuronów? Odpowiedzi są na ten temat dość nie jasne. Są pewne twierdzenia(np. Kołomogorowa) ale są one raczej wskazówkami niż twardymi wytycznymi. Co do rodzajów sieci jak wspominałem tu mamy sieć która jest uczona w taki sposób. Ale np. sieć Hebba tylko na podstawie próbek wejściowych próbuje znaleźć zależność w próbkach wejściowych i tak dobrać wagi aby stworzyć płaszczyznę w której wyłania się neuron zwycięzca gdzie jego waga jest najważniejsza a w jego pobliżu neurony maja wagi o coraz mniejszym znaczeniu im dalej od niego, w takiej sieci można znaleźć paru takich zwycięzców.

Implementacja w C

Implementacja ta była pisana z myślą o procesorze DSP TMS320F2812. Nie jest ona do końca zoptymalizowana ponieważ korzysta z bezpośrednio z tablicy dwuwymiarowej. Tylko dla tego aby kod był nieco bardziej przejrzysty. Optymalizował będą ją nie długo. Ponieważ aby sprawdzać szybko wyniki działania sieci, połączyłem kod C z C++(na pc preferuję) tu przedstawiona wersja działa na PC. Kod C++ wczytuje pliki z próbkami. Są to pliki tekstowe które wyeksportowano z matlaba za pomocą polecenia: dlmwrite('ua.txt', uab_p,'precision','%.16f'); Czyli zapisywanie tablicy uab_p(i kolejnych) do pliku. Po czym po wykonaniu programu odczytuje próbki w matlabie za pomocą: wynik=dlmread('plik.txt');

Kod C:

#include <stdio.h> #include <stdlib.h> #include <math.h> #include <iostream> #include <fstream> #include <cstdlib> #define rs 0.0700 #define Tn 0.0032 #define wejsc 5 #define neuronow1 5 #define neuronow2 4 #define neuronow3 1 using namespace std;

struct ADCDATA{ float dane_wejsc[wejsc]; }; struct PSI{

float me_neuron; } stream; struct WAGI{ float *b1; float *b2; float *b3; }wagi;

float waga1[5][5]={ {-0.169620309360023,0.181579347051114,-0.203043916419013,0.310469366728142,0.644744659946716}, {-0.110914229099399,0.116512049801144,0.160505722933892,-0.0562188808746302,0.653662513851392}, {0.210166491385323,0.0833496955052487,-0.0375170169342036,-0.0728979400692505,-0.00275971312123287}, {-0.241418165773091,0.125316363613881,0.0296955086323383,-0.200811970809962,0.35667862951691}, {-0.205883270204237,0.308659622157109,0.00085197899474902,0.0453355560273968,0.636371327748412} }; float waga2[neuronow2][neuronow1]={ {-5.8906294068068, 2.99159412145464, -1.76695946841516, 1.44367587981953, 5.02155412550802}, {0.210490248723649, 1.82462186911722, 2.23893929083297, -2.4300246951556, 1.87568495257527}, {-2.18202635076847, 1.78615986315426, -2.43164735590554, -0.971074037321619, 2.40478490863304}, {-1.3397256771942, 1.15115461509606, -2.09263538393463, -2.15313653918613, -2.25244099665734} }; float waga3[neuronow2]={-3.268285408841,-8.63802843298144,-4.49832706636809,-4.86740913332076 };

void NeuralEstimator(struct ADCDATA * adc_ptr); int main(void) {

string linia; fstream plik;

plik.open("/home/radek/mgr/plik.txt", ios::out | ios::app); ifstream mojStrumien1("/home/radek/mgr/ua.txt"); ifstream mojStrumien2("/home/radek/mgr/ub.txt"); ifstream mojStrumien3("/home/radek/mgr/ia.txt"); ifstream mojStrumien4("/home/radek/mgr/ib.txt"); ifstream mojStrumien5("/home/radek/mgr/ic.txt"); char msg[50]; float ua,ub,ia,ib,ic; struct ADCDATA data_adc; float b1a[5]={0.292832263405886,-2.96844494053588,-1.21712200925735,0.11314722454424,1.34972339762468}; float b2a[4]={-0.0530217834012098,-0.357615216982098,0.978781124442281,-2.97645859040641}; float b3a=4.56220069637717; while(getline(mojStrumien1, linia)){

ua = atof(linia.c_str()); // w stylu C :) getline(mojStrumien2, linia); ub = atof(linia.c_str()); getline(mojStrumien3, linia); ia = atof(linia.c_str()); getline(mojStrumien4, linia); ib = atof(linia.c_str()); getline(mojStrumien5, linia); ic = atof(linia.c_str());

data_adc.dane_wejsc[0] = ia; data_adc.dane_wejsc[1] = ib; data_adc.dane_wejsc[2] = ic; data_adc.dane_wejsc[3] = ua; data_adc.dane_wejsc[4] = ub; wagi.b1 = &b1a[0]; wagi.b2 = &b2a[0]; wagi.b3 = &b3a; NeuralEstimator(&data_adc); sprintf(msg,"%f\n\0",stream.me_neuron); plik << msg;

}

return EXIT_SUCCESS; } void NeuralEstimator(struct ADCDATA * adc_ptr){ int x,y,z,k,t,h; float hiden_neurons[neuronow1]={0,0,0,0,0}; float hiden_neurons2[neuronow2]={0,0,0,0}; float output_neurons=0; float *hn_ptr=hiden_neurons; float *hn_ptr2=hiden_neurons2; float *dane_ptr=adc_ptr->dane_wejsc;

for(x=0;x<neuronow1;x++){ *hn_ptr=0; for(y=0;y<wejsc;y++){ *hn_ptr=*hn_ptr+*dane_ptr*(waga1[x][y]); dane_ptr++; } *hn_ptr=*hn_ptr+(*wagi.b1); *hn_ptr=1/(1+exp(*hn_ptr*-1)); hn_ptr++; wagi.b1++; dane_ptr=&adc_ptr->dane_wejsc[0]; } hn_ptr=&hiden_neurons[0]; for(z=0;z

Uwaga TMS320F281 jaki PC operują na zmiennych 32 bitowych. Jeśli ktoś zamierza użyć 8-bitowca, uważajcie na zakresy! W następnej części zajmiemy się dwoma innymi sieciami.

bEiQsbev