LUCRARE DE LICENŢĂ -...

61
Universitatea Politehnica Bucureşti Facultatea de Automatică şi Calculatoare Catedra de Automatică şi Ingineria Sistemelor LUCRARE DE LICENŢĂ Prelucrare neuronală de imagini pentru controlul unui vehicul Absolvent: Daniel CAZACU Coordonator: Prof.dr.ing. Dan ȘTEFĂNOIU Bucureşti, 2013

Transcript of LUCRARE DE LICENŢĂ -...

Universitatea Politehnica Bucureşti

Facultatea de Automatică şi Calculatoare

Catedra de Automatică şi Ingineria Sistemelor

LUCRARE DE LICENŢĂ

Prelucrare neuronală de imagini pentru

controlul unui vehicul

Absolvent:

Daniel CAZACU

Coordonator:

Prof.dr.ing. Dan ȘTEFĂNOIU

Bucureşti, 2013

1

Cuprins:

1. Introducere ...................................................................................................................... 5 2. Rețele neuronale artificiale ............................................................................................ 7

2.1 Modele ale neuronului artificial ........................................................................... 8

2.2 Arhitecturi ........................................................................................................... 10

2.3 Algoritmi de învățare .......................................................................................... 11

2.4 Aplicații din viața de zi cu zi .............................................................................. 12

2.5 Dezavantaje ale rețelelor neuronale .................................................................... 12

3. Descrierea componentelor hardware ale sistemului ................................................. 13 3.1 Vehiculul ............................................................................................................ 13

3.2 Camera pentru înregistrarea imaginilor .............................................................. 13

3.3 Modulul de procesare ......................................................................................... 14

3.4 Arduino Duemilanove ........................................................................................ 15

3.5 Conexiunile hardware ......................................................................................... 16

4. Descrierea modulelor software .................................................................................... 18 4.1 Software Arduino ............................................................................................... 19

4.2 Software Android ............................................................................................... 20

4.3 Aplicatie Java VACRN ...................................................................................... 23

5. Antrenarea rețelei neuronale ....................................................................................... 31 5.1 Propagare înainte și Propagare inversă ............................................................... 32

5.2 Metode de generalizare ....................................................................................... 35

5.3 Determinarea numărului de neuroni din stratul ascuns ...................................... 36

5.4 Metoda Gradient cu Propagare Inversă (traingd) ............................................... 39

5.5 Propagare inversă flexibilă (trainrp) ................................................................... 40

5.6 Propagarea inversă BFGS quasi-Newton (trainbfg) ........................................... 42

5.7 Propagarea inversă OSS (trainoss) ..................................................................... 43

5.8 Propagarea inversă Levenberg-Marquardt (trainlm) .......................................... 44

ALGORITMI CU GRADIENT-CONJUGAT ............................................................. 46

5.9 Actualizarea Fletcher-Reeves (traincgf) ............................................................. 47

5.10 Actualizarea Polak-Ribiere (traincgp) ................................................................ 49

5.11 Resetarea Powell-Beale (traincgb) .................................................................... 50

5.12 Algoritmul Gradientului Conjugat Scalat (trainscg) .......................................... 51

6. Concluzii și planuri de viitor ....................................................................................... 56

7. Bibliografie .................................................................................................................... 60

2

Lista figurilor Figura 2.1 Model simplu al neuronului artificial 8

Figura 2.2 Modelul MCP (McCulloch – Pitts) al neuronului artificial 9

Figura 2.3 Funcția de activare treaptă 9

Figura 2.4 Funcția de activare liniară 10

Figura 2.5 Funcția de activare log-sigmoid 10

Figura 2.6 Funcția de activare tan-sigmoid 10

Figura 2.7 Arhitectura RNA 11

Figura 2.8 Mecanismul de învățare supervizată 12

Figura 3.1 Vehiculul utilizat 13

Figura 3.2 Telefon HTC Droid Eris 14

Figura 3.3 Utilizare Apache Ant 14

Figura 3.4 Arduino Duemilanove 15

Figura 3.5A Schema electrică a telecomenzii 16

Figura 3.5B Microcipurile de Transmisie-Receptie 17

Figura 3.6 Conectarea telecomenzii la Arduino 17

Figura 3.7 Schema comunicațiilor din sistem 18

Figura 4.1 Formatul YUV420 22

Figura 4.2 Informații accelerometru Android 23

Figura 4.3 Interfața sistemului 28

Figura 4.4 Comunicația între limbajele de programare 31

Figura 5.1 Legătura dintre matricele X și y 32

Figura 5.2 Mecanismul de propagare înainte 32

Figura 5.3 Algoritmul de propagare inversă 33

Figura 5.4 Reprezentarea funcțiilor convexe/non-convexe 34

Figura 5.5 Reprezentarea schematică a problemei generalizării 35

Figura 5.6 Determinarea numărului de neuroni din stratul ascuns 37

Figura 5.7 Vizualizarea conexiunilor din Theta1 38

Figura 5.8 Importanța punctului inițial în determinarea parametrilor θ 39

Figura 5.9 Performanțele Metodei Gradient pe setul de date 40

Figura 5.10 Performanțele Metodei Propagare inversă flexibilă pe setul de date 41

Figura 5.11 Clasificarea rezultatelor folosind RProp 42

Figura 5.12 Performanțele Metodei OSS pe setul de date 44

Figura 5.13 Clasificarea rezultatelor folosind OSS 44

Figura 5.14 O comparație între viteza de convergența a metodei gradient (verde) și metoda

gradienților conjugați (roșu) 46

Figura 5.15 Modul de funcționare a metodelor cu gradient conjugat. 47

Figura 5.16 Performanțele Metodei Gradient Conjugat cu actualizarea Fletcher-Reves pe setul

de date 48

Figura 5.17 Clasificarea rezultatelor folosind CGF 49

Figura 5.18 Clasificarea rezultatelor folosind CGP 50

Figura 5.19 Performanțele Metodei Gradient Conjugat cu actualizarea Polak-Ribiere pe setul

de date 50

Figura 5.20 Performanțele Metodei Gradient Conjugat cu resetarea Powell-Beale pe setul de

date 51

Figura 5.21 Antrenarea rețelei cu metoda SCG 55

Figura 5.22 Performanțele Metodei Gradient Conjugat pe setul de date 56

Figura 5.23 Clasificarea rezultatelor folosind SCG 56

Figura 5.24 Gradul de utilizare al sistemului în timpul antrenării cu algoritmi cu gradient

conjugat 57

Figura 6.1 Exemplu de traseu pentru antrenare 58

Figura 6.2 Interfața sistemului. 59

3

Notații și convenții

Vectori – se vor nota cu litere mici ale alfabetului latin

mulțimea numerelor reale;

spațiul Euclidian n dimensional al vectorilor coloană x cu n componente reale

∈ pentru orice i =

produsul scalar a doi vectori ∈

norma Euclidiană a vectorului ∈

Matrici – se vor nota cu litere mari ale alfabetului latin

spațiul Euclidian al matricilor cu m linii și n coloane cu elemente ∈

pentru orice i = și j =

matrice pozitiv (semi) definită

matricea identitate de ordinul n

elementul matricei A situat pe linia i și coloana j

transpusa matricei A

- inversa/pseudoinversa matricei A

Funcții

∈ gradientul funcției , adică

pentru orice

i =

∈ Hessiana funcției , adică

pentru orice

i,j =

∈ Jacobianul funcției vectoriale cu ,

adică

pentru orice i = și j =

4

Abrevieri

engl. – traducerea cuvântului/expresiei în engleză

RNA/RN – rețea neuronală artificială/rețea neuronală

VACRN – vehicul autonom controlat cu rețele neuronale

BP – propagare inversă (backpropagation)

GD – gradient descendent (gradient descent)

Rprop – propagare inversă flexibilă (resilient backpropagation)

BFGS – algoritmul Broyden-Fletcher-Goldfarb-Shanno

OSS – algoritmul One Step Secant

LM – algoritmul Levenberg-Marquandt

CGF – gradient conjugat cu actualizarea Fletcher-Reeves

CGP – gradient conjugat cu actualizarea Polak-Ribiere

CGB – gradient conjugat cu resetarea Powell-Beale

SCG – gradient conjugat scalat

Mph – mile pe oră

5

1. Introducere

Nu există niciun dubiu că de la începutul anilor ‘90 până în prezent, omenirea a făcut un

progres uimitor în domeniul roboticii. Roboții moderni pot replica mișcările și acțiunile

oamenilor, iar pasul următor constă în învățarea roboților să gândească și să reacționeze în

condiții cât mai diverse. Inteligența artificială promite să ofere aparatelor abilitatea de a gândi

analitic, folosind ultimele concepte și descoperiri din domeniul calculatoarelor, robotizărilor

și matematicii.

Se dorește ca roboții să preia acțiunile monotone cum ar fi tunderea ierbii, datul cu

aspiratorul sau executarea task-urilor de precizie cum ar fi asamblarea anumitor obiecte din

fabrici și uzine. Astfel de automatizări ar trebui să permită mașinăriilor să execute sarcini

periculoase pentru oameni, cum ar fi mineritul sau stinsul incendiilor. Unele țări au început

deja să folosească roboți inteligenți pentru dezarmarea minelor de teren sau prelucrarea

materialelor radioactive pentru a limita riscul pentru oameni. Tot pentru protecția umană se

fac multe cercetări și în domeniul vehiculelor autonome. Există deja multe mașini dotate cu

senzori și echipamente capabile să evite accidente sau dacă nu, măcar să atenționeze șoferul

de potențialele pericole. În acest domeniu, cele mai avansate modele de mașini care au reușit

cu succes să se conducă singure, chiar și în condiții de trafic intens, sunt cele construite de

compania Google. Mașinile Google sunt la bază mașini Toyota Prius dotate cu camere,

senzori și radare. Toate aceste dotări de ultimă generație au ca scop depistarea tuturor

celorlalți participanți la trafic, dar și diferite obstacole, astfel încât mașina să meargă singură,

fără ca șoferul să intervină în direcția ei și în frânare. Dar acestea au sisteme destul de

complexe care nu pot fi construite la scară industrială încă și au un cost destul de mare, motiv

pentru care cred că va mai dura cel puțin 10-20 ani până când publicul va avea acces la ele.

Deși cele mai bune rezultate din acest punct de vedere , al vehiculelor autonome, a fost

înregistrat de curând, el este un subiect care i-a intrigat tot timpul pe cercetători. Un exemplu

în acest sens este proiectul ALVINN (Autonomous Land Vehicle In a Neural Network)

desfășurat în anii ‘95 de către Universitatea Carnegie Mellon în colaborare cu armata

americană, proiect care avea ca scop realizarea unui vehicul care să ia decizii de control

folosind rețele neuronale. ALVINN este un sistem care învață să controleze vehiculul prin

urmărirea unui om conducând. Abordarea pentru acest sistem este diferită deoarece el nu se

bazează pe senzori, radare, laser și GPS, ci are ca date de intrare imagini de dimensiune 30x32

pixeli de la o cameră video montată deasupra mașinii. Ca date de ieșire, sistemul ar trebui să

genereze direcția în care vehiculul ar trebui să meargă pentru a rămâne pe drum. Deși modelul

este unul destul de simplu în comparație cu cel de la Google, el a înregistrat rezultate destul

de bune. ALVINN a reușit să conducă cu o viteză maximă de 70 de mph pe o autostradă

publică de lângă orasul Pittsburg, USA. De asemenea, vehiculul s-a deplasat cu o viteză

normală de 30-40 mph pe drumuri nepavate și pe străzi cu benzi din suburbii. Limitările erau

faptul că nu putea conduce eficient pe tipuri de drum pe care nu a fost antrenat și că nu era

prea robust la schimbări de luminozitate. Cu toate acestea, în comparație cu algoritmii

tradiționali de prelucare de imagini, care aveau nevoie să proceseze imaginea pentru a găsi

drumul și apoi să îl urmărească, ajungând la viteze de doar 3-4 mph, ALVINN era mult mai

rapid.

Unul dintre motivele pentru care m-am înscris la Facultatea de Automatică și Calculatoare

a fost pentru a crea sisteme inteligente de conducere și a aplica concepte matematice cu

ajutorul cărora să pot implementa soluții care ușurează munca oamenilor și previn accidentele

provenite din erori umane. ALVINN reprezintă modelul pe care se bazează realizarea acestei

lucrări, în care, folosind obiecte accesibile oricui dar și puterea de calcul existentă în zilele

noastre, am reușit să construiesc un sistem, care după doar 2-3 minute de înregistrare de date,

a reușit să funcționeze în mod autonom, reproducând mișcările din timpul antrenării.

6

Am ales această temă și acest proiect deoarece pentru o asemenea lucrare sunt necesare

cunoștințe din diferite domenii pe care le-am studiat în timpul facultății, dar și în afara ei, cum

ar fi cele de electronică, programare orientată pe obiecte, tehnici de optimizare, sisteme

avansate de control, programare în timp real și chiar programarea pe sisteme de tipul Android

și microcontrolere pe platforma Arduino.

Lucrarea este divizată în 5 capitole, fără a-l include și pe acesta, în care este explicat

demersul științific pentru realizarea proiectului :

În capitolul doi, intitulat Rețele neuronale artificiale, este realizată o scurtă descriere a

rețelelor neuronale pentru a familiariza cititorul cu acestă tehnică și a oferi o viziune asupra

modului în care funcționează. Sunt prezentate elemente de bază ale unei rețele neuronale cum

ar fi :

- neuronul artificial, modele ale acestuia și cum afecteză alegerea modelului rezultatul

final

- arhitecturi, adică structuri de legătură între diferiți neuroni și problemele care pot

apărea din alegerea unei arhitecturi inadecvate

- algoritmi de învățare, sunt exemplificate diferite metode de învățare și se justifică

alegerea uneia dintre ele pentru cazul nostru

În finalul capitolului sunt exemplificate câteva aplicații ale rețelelor neuronale pentru a

ilustra utilitatea acestora dar și câteva probleme care apar când se folosește o astfel de tehnică.

În capitolul trei, sunt prezentate componentele hardware necesare realizării unui model în

miniatură capabil să funcționeze ca ALVINN. Sunt descrise detalii despre vehiculul ales,

structura și limitările acestuia, despre senzorul folosit pentru captarea imaginilor și despre

modulul central de procesare. Tot aici sunt oferite detalii despre mediile de

programare/interpretare de pe unitatea de procesare. În ultimele două subcapitole este precizat

modul în care cele trei componente descrise anterior comunică, componentele hardware

necesare pentru realizarea comunicației și conexiunile fizice pe care le implică această

comunicare. În finalul capitolului, sunt expuse implicațiile pe care le are arhitectura fizică

aleasă asupra programelor ce vor prelucra datele.

În capitolul patru, sunt descrise modulele software necesare pentru implementarea

sistemului. La începutul capitolului, sunt fixate unele obiective ce țin de modul în care trebuie

sa funcționeze aplicația principală pentru ca aceasta să fie cât mai flexibilă și ușor de

implementat pe orice sistem. Pe tot parcursul capitolului se va urmări ca aceste obiective să

fie atinse ținându-se evident cont și de restricțiile impuse de hardware, menționate în capitolul

precedent. Capitolul acesta este divizat în trei subcapitole, în primul se prezintă programul de

pe microcontrolerul ATMega cu ajutorul căruia se vor transmite comenzi către vehicul și se

va impune un protocol de comunicație între acesta și aplicația centrală. În cel de-al doilea

subcapitol, este prezentat programul conceput pentru captarea și transmiterea imaginilor de pe

un telefon ce folosește sistemul Android către un server prin intermediul internetului wireless

și la fel ca mai sus se impune un protocol de transmitere a informațiilor. În ultimul subcapitol

este expus modul de funcționare a aplicației centrale, cea care va rula pe modulul de procesare

și va fi responsabilă de primirea imaginilor, de la aplicația descrisă anterior, să genereze

comenzi prin intermediul rețelei neuronale pe baza imaginilor și să transmită comenzi prin

serial către microcontroler care la rândul lui va acționa vehiculul. Pentru a face acest lucru,

aplicația centrală va trebui în prealabil să poată înregistra imagini și comenzi care vor

constitui setul de antrenament pentru rețea și să încarce apoi rețeaua antrenată în Matlab prin

intermediul unor fișiere.

Capitolul cinci, cel mai întins capitol (12 subcapitole) , este cel în care se prezintă modul

în care este antrenată rețeaua neuronală. Folosind un set de date obținut cu ajutorul sistemului

descris anterior și o învățare supervizată, se parcurg diferiți algoritmi pentru a găsi cel mai

7

eficient mod de antrenare. În primul subcapitol se explică concepte de bază din teoria rețelelor

neuronale cum ar fi cele de propagare înainte și propagare inversă, ce înseamnă acestea, cum

funcționează aceste mecanisme și cum se aplică algoritmul de propagare inversă din punct de

vedere matematic. Tot în prima parte, s-au stabilit definiții pentru anumite elemente

matematice ce vor fi folosite de-a lungul capitolului. În cel de-al doilea, subcapitol sunt

prezentate două metode de generalizare a rețelei, modul în care se aplică acestea pentru cazul

nostru și care sunt avantajele și dezavantajele lor. Următorul subcapitol este dedicat explicării

modului în care a fost determinat numărul de neuroni din stratul ascuns și tot aici se realizează

primele teste de performanță a rețelei. În următoarele nouă subcapitole, sunt descrise diferite

tehnici de optimizare cu ajutorul cărora este antrenată rețeaua neuronală începând cu tehnici

de ordinul 1, cum ar fi cea de gradient descendent, până la tehnici mai complicate cum ar fi

algoritmul Levenberg-Marquardt sau algoritmul Gradientului Conjugat Scalat. După

descrierea matematică a proceselor ce au loc folosind tehnicile respective, în fiecare din cele

nouă subcapitole s-a efectuat câte o simulare pe setul de antrenament, care au produs rezultate

mai mult sau mai puțin satisfăcătoare.

În capitolul șase și ultimul al acestei lucrări, sunt analizate și comparate rezultatele

obținute în simulări, atât cele numerice cât și cele practice (efectiv prin folosirea rețelei

antrenate). Se prezintă câteva detalii legate de performanță, din punct de vedere al vitezei de

procesare, dar și justificarea unor alegeri făcute de-a lungul lucrării care au fost ulterior

demonstrate prin rezultate. Tot aici sunt analizate potențiale probleme ale sistemului și

eventuale soluții, dar și metode de îmbunătățire și planuri de viitor ce ar putea fi aplicate.

Lucrarea de față constituie un experiment în ceea ce privește utilizarea rețelelor neuronale

pentru controlul unui vehicul prin utilizarea unor componente relativ simple și o putere de

calcul specifică secolului XXI și are ca scop demonstrarea cunoștințelor dobândite din

domeniul automaticii dar și din domeniile conexe.

2. Rețele neuronale artificiale

Rețelele neuronale artificiale (RNA, engl. ANN - artificial neural network) sunt o

ramură din știința inteligenței artificiale, și constituie totodată, principial, un obiect de

cercetare și pentru neuroinformatică. Rețelele neuronale artificiale caracterizează ansambluri

de elemente de procesare simple, puternic interconectate și operând în paralel, care urmăresc

să interacționeze cu mediul înconjurător într-un mod asemănător creierelor biologice și care

prezintă capacitatea de a învăța. Ele sunt compuse din neuroni artificiali, sunt parte a

inteligenței artificiale și își au, concepțional, originea ca și neuronii artificiali, în biologie. Nu

există pentru RNA o definiție general acceptată a acestor tipuri de sisteme, dar majoritatea

cercetătorilor sunt de acord cu definirea rețelelor neuronale artificiale ca rețele de elemente

simple puternic interconectate prin intermediual unor legături numite interconexiuni prin care

se propagă informație numerică.

Originea acestor rețele trebuie căutată în studierea rețelelor bioelectrice din creier

formate de neuroni și sinapsele acestora. Principala trăsătură a acestor rețele este capacitatea

de a învăța pe bază de exemple, folosindu-se de experiența anterioară pentru a-și îmbunătăți

performanțele.

Caracteristicile RNA :

8

reprezentarea distribuită a informației : informația din rețea este stocată în mod

distribuit ( în structura de ponderi ), ceea ce face ca efectul unei anumite intrări asupra

ieșirii să depindă de toate ponderile din rețea.

capacitatea de generalizare în cazul unor situații care nu apar în datele de instruire:

această caracteristică depinde de numărul de ponderi, adică de dimensiunea rețelei . Se

constată că o creștere a dimensiunii rețelei duce la o bună memorare a datelor de

instruire , dar scad performanțele asupra datelor de testare , ceea ce înseamna că RNA

a pierdut capacitatea de generalizare . Stabilirea numărului optim de neuroni din

stratul ascuns , care este o etapă-cheie în proiectarea unei RNA , se poate face alegând

valoarea de la care începe să descrească performanța RNA pe setul de testare. Există

desigur și alte tehnici prin care se poate îmbunătăți capacitatea de generalizare a unei

RNA din timpul antrenării.

toleranța la zgomot : RNA pot fi instruite , chiar dacă datele sunt afectate de zgomot ,

diminuându-se, evident, performanța ei. Cu cât setul de date este mai mare cu atât

zgomotul va fi mai bine tolerat și se vor obține rezultate mai bune în momentul

utilizării rețelei.

rapiditate în calcul : RNA consumă mult timp pentru instruire , dar odată antrenate vor

calcula rapid ieșirea rețelei pentru o anumită intrare.

Rețelele neuronale artificiale se pot caracteriza pe baza a 3 elemente:

modelul adoptat pentru elementul de procesare individual

structura particulară de interconexiuni (arhitectura)

mecanismele de ajustare a legăturilor (de învățare).

2.1 Modele ale neuronului artificial

Sunt mai multe criterii de clasificare a modelelor neuronului elementar, ce implică:

domeniul de definiție a semnalelor folosite, natura datelor folosite, tipul funcției de activare,

prezența memoriei.

Figura 2.1 Model simplu al neuronului artificial

În cazul de față, modelul neuronului este cel din Figura 2.1 , adică un model aditiv , în

care fiecare intrare xi contribuie prin însumare la ieșirea y. Acest lucru este determinat de

ponderile w determinate în timpul antrenării rețelei. Vectorul de intrare din figura de mai sus

este x=[x1 x2…xn] la care a fost adăugat si elementul x0 , element de deplasare, pe care îl vom

nota în continuare cu ‘b’ din englezescul bias.

9

Figura 2.2 Modelul MCP (McCulloch – Pitts) al neuronului artificial

Un neuron cu un vector de intrare având n elemente este prezentat în figura 2.2.

Intrările individuale x1, x2…xn sunt înmulțite cu ponderile w1,w2…wn și valorile ponderate se

însumează. Suma se poate nota Wp, adică produsul scalar al matricei W (cu o linie) și

vectorul x.

Neuronul are deplasarea b, care se adună cu intrările ponderate, rezultând argumentul

sum al funcției de activare(transfer) f : (2.1)

Această expresie poate fi scrisă în codul MATLAB: sum = W*x + b ; a = f ( sum ) . (2.2)

Descrierea elementelor din figura 2.2 :

n este numărul elementelor din vectorul de intrare;

vectorul de intrare x este reprezentat de bara groasă verticala din stânga, dimensiunile

lui x sunt indicate sub simbolul x (n×1);

constanta 1 intră în neuron și multiplică deplasarea b;

intrarea sum în funcția de activare (transfer) f este dată de (2.1);

a este ieșirea neuronului.

Neuronul biologic se activează (dă un semnal la ieșire) numai dacă semnalul de intrare

depășește un anumit prag. În RNA acest efect este simulat aplicând sumei ponderate a

intrărilor (sum) o funcție de transfer (activare) f, pentru a obține semnalul a la ieșire.

Patru dintre cele mai utilizate funcții de activare sunt reprezentate mai jos :

a) Funcția treaptă

Figura 2.3 Funcția de activare treaptă

b) Funcția de activare liniară

10

Figura 2.4 Funcția de activare liniară

c) Funcția log-sigmoid

Figura 2.5 Funcția log-sigmoid

d) Funcția tan-sigmoid

Figura 2.6 Funcția tan-sigmoid

În pătratul din dreapta fiecărui grafic se prezintă simbolul asociat funcției de activare.

Aceste simboluri vor înlocui simbolul general f în schemele RNA pentru a arăta funcția

particulară de transfer care este folosită. Pentru aplicația prezentată a fost folosită doar

funcția log-sigmoid.

2.2 Arhitecturi

Arhitectura unei rețele neuronale reprezintă modul în care sunt realizate conexiunile

dintre neuronii ce compun această rețea. Există numeroase modalități de interconectare a

neuronilor elementari, dar pot fi identificate două clase de arhitecturi:

cu propagare a informației numai dinspre intrare spre ieșire, rețele de tip feedforward

rețele recurente (cu reacție).

Un dezavantaj al rețelelor neuronale îl constituie lipsa teoriei care să precizeze tipul

rețelei și numărul de neuroni elementari, precum și modalitatea de interconectare. Există

câteva tehnici de tip “reducere” (engl. pruning) sau de tip “învață și crește” (engl. learn and

grow), dar acestea sunt în intense cercetări.

11

Proiectarea structurii rețelei (tip, nr. straturi, nr. neuroni , nr. intrări, conexiunile între

neuroni) – factori avuţi în vedere:

• scopul urmărit – natura problemei,

• complexitatea,

• datele disponibile.

În acest caz, rețeaua este una de tip propagare-înainte (engl. feedforward) cu arhitectura

din Figura.2.7 . Motivele pentru care am ajuns la o astfel de arhitectură vor fi detaliate

ulterior, în urma analizării datelor problemei.

Figura 2.7 Arhitectura RNA

2.3 Algoritmi de învățare

Principala deosebire a rețelelor neuronale față de alte sisteme (electronice) de

prelucrare a informațiilor îl constituie capacitatea de învățare în urma interacțiunii cu mediul

înconjurător, și îmbunătățirii performanțelor. O reprezentare corectă a informațiilor, care să

permită interpretarea, predicția și răspunsul la un stimul extern, poate permite rețelei să

construiască un model propriu al procesului analizat. Acest model va putea răspunde astfel

unor stimuli neutilizați în procesele anterioare de învățare ale rețelei RNA. Informațiile

utilizate în procesul de învățare pot fi informații disponibile a priori sau perechi intrare-ieșire

(care stabilesc relații de tipul cauză-efect), iar modul de reprezentare internă urmărește un set

de reguli bine documentate. Acești algoritmi pot fi clasificați după mai multe criterii cum ar

fi: disponibilitatea răspunsului dorit la ieșirea rețelei, existența unui model analitic, tipul

aplicației în care sunt utilizați, dar cele mai multe documentații se rezumă la două clase

mari: învățarea supravegheată (care presupune existența în orice moment a unei valori dorite a

fiecărui neuron din stratul de ieșire) și învățarea nesupravegheată (în care rețeaua extrage

singură anumite caracteristici importante ale datelor de ieșire, în urma unui tip de competiție

între neuronii elementari).

12

Învăţarea supravegheată a RNA presupune existenţa perechilor de învățare intrări –

ieşiri. Se adaptează ponderile sinapselor astfel că la aplicarea intrărilor, RNA să calculeze

tocmai ieşirile pereche. Apare o problema de minimizare a unei funcții a erorilor unde eroarea

(depinde de ponderile sinapselor) este egală cu diferenţa între ieşirea furnizată de RNA prin

calcul şi valoarea “aşteptată” din setul de antrenament.

Figura 2.8 Mecanismul de învățare supervizată

Condiţii necesare antrenamentului sunt ca mulţimea de antrenament să fie

reprezentativă adică să se poată obține informații utile din setul de date , numărul de perechi

intrări-ieşire să fie suficient de mare, metoda (algoritmul) de învăţare să fie eficient și să se

utilizeze calculatoare performante capabile să prelucreze multe date și să execute mai multe

operații în paralel.

În acest caz este vorba despre o învățare supravegheată, în care în fiecare moment unui

set de date de intrare (o imagine) îi este asociată o comandă de ieșire (direcția vehiculului).

Pentru acest tip de învățare, cea mai populară metodă de antrenare este propagarea inversă

(engl. back-propagation) care presupune determinarea ponderilor w prin micșorarea erorilor

obținute din diferențele dintre ținta impusă de utilizator și ieșirea rețelei. Rolul antrenării din

această aplicație este găsirea unui tipar, a unui model pentru ca rețeaua să fie capabilă să

acționeze în modul dorit la apariția unei imagini, astfel obținându-se un clasificator (anumite

imagini vor fi asociate cu anumite comenzi).

2.4 Aplicații din viața de zi cu zi

Domeniile în care rețelele neuronale realizează modele eficiente sunt : aproximări de

funcții, predicții a unor serii temporale, clasificări, recunoaștere de tipare, recunoaștere

vocală, scanarea retinei.

Alte implementari ale rețelelor neuronale, legate de sectorul business, se întâlnesc în:

previziuni financiare, controlul proceselor industriale, cercetări de piață, validări de date pe

bază de clasificări și de tipare, management de risc, previziuni de marketing. Rețele neuronale

folosind algoritmi genetici pot fi folosite în controlul roboților industriali. Un alt domeniu de

interes pentru rețelele neuronale este medicina și stemele biomedicale. În acest moment se

utilizează rețele neuronale pentru descoperirea de boli, prin recunoașterea unor tipare de

pe cardiograme etc.

2.5 Dezavantaje ale rețelelor neuronale

Procesul de învăţare la RNA este complicat din cauza dificultății în a alege setul de

antrenament, de lungă durată în funcție de metoda de antrenare și de mărimea setului de

antrenament și din cauză că necesită mijloace de calcul deosebit de performante. Pentru

13

antrenament este nevoie de un volum extrem de mare de date, iar stabilirea bazei de

antrenament este o operaţie dificilă, ea trebuie să acopere satisfăcător tot domeniul de căutare

a soluției. Fenomenele care se petrec în cadrul unei RNA (modificarea ponderilor pentru

fiecare neuron al rețelei) nu au putut încă să fie explicate satisfăcător din punct de vedere

formal.

3. Descrierea componentelor hardware ale sistemului

La fel ca la ALVINN, se dorește construirea unui sistem inteligent și adaptiv de

urmărire a drumului (engl. road follower). Componentele sistemului sunt:

3.1 Vehiculul

Elementul de acționare din acest sistem este desigur vehiculul, reprezentat de o

mașinuță electrică ce este comandată cu o telecomandă prin unde radio. Modelul este unul

destul de simplu, cu o rază suficient de mare pentru a parcurge un traseu de 5-8 metri, cu un

motor la roțile din spate care controlează sensul de mers față/spate și un motor cu 3 trepte în

partea din față ( 45 grade spre stânga , 0 grade - normal , 45 grade spre dreapta) pentru

setarea direcției de mers. Dezavantajele acestei mașinuțe simple sunt existența a doar 3 trepte

de direcție, puterea relativ mică (motorul de sens este de ~ 3.3 V) și dimensiunea destul de

mare (~21 cm lungime si ~9 cm latime) ceea ce face destul de dificilă conducerea ei și mai

ales executarea întoarcerilor. Telecomanda este și ea una destul de simplă. Conține 4 butoane

pentru față/spate , stânga/dreapta și transmite prin unde radio pe frecvența 27.145 MHz către

receptorul mașinuței comenzile aplicate.

Figura 3.1 Vehiculul utilizat

3.2 Camera pentru înregistrarea imaginilor

Senzorul sistemului trebuia să fie o cameră capabilă să înregistreze imagini video, dar

să le înregistreze nu era suficient , acestea trebuiau și prelucrate/interpretate. Ca primă soluție

am luat în considerare folosirea unei camere web wireless, care capta imaginile și le trimitea

către un laptop prin wireless. Problemele cu această soluție erau faptul că o astfel de cameră

era scumpă și avea nevoie de o sursă de alimentare. Având un consum destul de mare, nu se

preta la folosirea unor baterii. Următoarea soluție, cea pe care am și implementat-o, a fost

folosirea camerei unui telefon. Majoritatea telefoanelor din ziua de azi este dotată cu camere

suficient de performante, capabile să înregistreze, iar dacă telefonul este destul de puternic,

prelucrarea/interpretarea imaginilor poate fi facută chiar pe el, dar ar fi o soluție complicată.

14

Figura 3.2 Telefon HTC Droid Eris

Am folosit un telefon HTC Droid Eris (Figura 3.2), capabil să se conecteze la rețele

wireless și deci capabil să comunice cu alte aparate conectate la rețea. Camera este una ce

suportă înregistrarea captărilor video în trei rezoluții: mare (352x288), medie (320x240) și

mică (176x144). Am optat pentru cea mică pentru a scădea din puterea de calcul necesară

pentru prelucrarea imaginilor. Din nefericire, acest telefon nu este echipat și cu o laternă

pentru filmarea imaginilor în condiții de luminozitate scazută, lucru care l-ar fi făcut mai bine

echipat la schimbările de luminozitate care, vom vedea, afectează precizia rețelei.

3.3 Modulul de procesare

Modulul de procesare și responsabilul de antrenarea rețelei/controlarea

vehiculului/recepționarea și prelucrarea imaginilor a fost laptopul meu personal, echipat cu un

procesor Intel i5 – M460 de 2.67 Ghz , 8 Gb de RAM și bineînțeles capabil să se conecteze și

el la o rețea wireless. Mediile de dezvoltare în care am lucrat au fost “Java (TM) SE Runtime

Environment (build 1.7.0_10-b18)” și „MATLAB 7.11.0.584 (R2010b)”.

Figura 3.3 Utilizare Apache Ant

15

Rularea programelor scrise in mediul Java a fost facută cu ajutorul pachetului

„Apache Ant(TM) Version 1.8.4”. Apache Ant (Figura 3.3) este o unealtă open-source de

dezvoltare a software-ului scrisă în întregime în Java, ce poate fi rulată din linia de comandă

în urma unor configurări rapide. Fișierele de configurare se bazează pe XML care pot fi

generate de către IDE de Java Eclipse. Am optat pentru o astfel de soluție deoarece avem de-a

face cu un sistem în timp real în care resursele sunt foarte prețioase, iar Ant folosește mult

mai puține resurse decât ar face orice altă unealtă, încărcand doar librăriile specificate în

fișierul xml. Un alt avantaj al acestei distribuții este faptul că fișierele modificate (actualizate)

sunt recompilate utilizând o singură comandă și într-un timp foarte scurt. Ultimele versiuni

ale acestei unelte includ și compilatoare pentru Android (dacă au fost instalate librăriile

necesare) și poate fi folosit pentru a genera aplicații pentru Android ce pot fi foarte ușor

instalate apoi pe telefoanele cu acest sistem.

În concluzie, acest sistem este rapid, relativ ușor de folosit, permite rularea aplicațiilor

în timp real și poate fi rulat pe diverse sisteme de operare (Windows, Linux, MacOS etc.)

dând proiectului portabilitate indiferent de mediul în care este utilizat.

3.4 Arduino Duemilanove

Arduino este o platformă electronică de tipul single-board microcontroler, adică acest

tip de plăcuță conține toate circuitele necesare pentru a realiza ușor task-uri de control.

Arduino a fost creat pentru a face proiectele multidisciplinare, ce folosesc componente

electronice interconectate, cât mai simple.

Arduino Duemilanove folosit în cadrul proiectului conține un microcontroler

ATMega328. Pe plăcuța Arduino (Figura 3.4) există 14 pini digitali intrare/ieșire , 6 pini de

ieșire analogici, un oscilator cu frecvența de 16 MHz, o mufă pentru conexiune USB (prin

care se face programarea microcontrolerului) , o mufă de alimentare (deși el poate fi alimentat

și prin USB) și un buton de resetare.

Figura 3.4 Arduino Duemilanove

Domeniul de alimentare a plăcuței este între 6 și 20 de volți. Dacă se alimentează la o

tensiune mai mică de 7 volți, este posibil ca stabilizatorul de tensiune intern să nu asigure o

tensiune de 5 volți constantă și astfel se ajunge la instabilitatea platformei Arduino. Prin

alimentarea acesteia la o tensiune de peste 12 volți se riscă supraîncălzirea sau chiar

deteriorarea stabilizatorului de tensiune și poate chiar a altor componente. Având în vedere

acestea, tensiunea de alimentare optimă a plăcuței poate fi între 7 si 12 volți.

Microcontroler-ul ATMega328 are o memorie flash de 32KB, în care va fi încărcat

codul ce trebuie rulat (2 KB sunt folosiți de către bootloader) și 2 KB de memoria SRAM

16

(Static Random Acces Memory) și 1 KB de memorie EEPROM (Electrically Erasable

Programmable Read-Only Memory).

Printre principalele funcționalități importante ale microprocesorului ATmega 328 se

numără următoarele: 32 de regiștrii de 8 biți, performanțe foarte bune cu un consum foarte

mic de energie, enduranța memoriilor non-volatile ( reținerea datelor timp de 25 de ani la o

temperatură de 85 grade Celsius și 100 de ani la 25 de grade Celsius), 2 timere/countere pe 8

biți, 1 timer/counter pe 16 biți, 6 canale PWM pe 8 biți, șase moduri de economisire a energiei

electrice, gradarea vitezei de procesare (0-20 MHz la 4,5 V– 5,5 V).

Pentru a programa microcontrolerul folosind plăcuța Arduino trebuie descărcat

software-ul open-source de pe pagina lor principală. Acesta este un mic program realizat în

Java care folosește cod C/C++, compilează și generează un fisier .hex . După ce este generat,

acesta poate fi scris pe microcontroler prin intermediul USB-ului sau a pinilor ICSP.

Având în vedere că scopul microcontrolerului va fi doar interfațarea telecomenzii cu

laptop-ul/pc-ul consider că această platforma este ideală deoarece este ușor de folosit, are un

preț relativ mic și performanțe foarte bune (pentru acest proiect).

3.5 Conexiunile hardware

Următorul pas al proiectului este conectarea componentelor alese din punct de vedere

hardware. Componentele sunt compatibile și se poată face o conexiune între ele cât mai

simplu și rapid. Dorim ca sistemul să poată fi rulat de la distanță și să aibă un sistem central

de procesare, ceea ce presupune ca datele de la senzori și comenzile către elementele de

acționare să poată fi recepționate/transmise wireless de către sistemul nostru de prelucrare.

Prima parte, achiziția de date, adică preluarea imaginilor de pe camera telefonului pe

laptop, va fi destul de ușoară din punct de vedere hardware întrucât atât laptopul cât și

telefonul conțin echipamente capabile să transmită/primească informații într-o rețea wireless.

Ca mediu de test va fi folosită o rețea locala wireless, creată cu ajutorul unui rooter TP-Link

300M, în care ne vom baza strict pe accesul la W-LAN și nu pe internet deoarce acest lucru

nu servește scopului proiectului. Pentru acest tip de abordare va trebui configurat un server

(laptopul) și un client (telefonul) și bineînțeles un protocol de recepționare/transmitere a

datelor, lucruri care țin de software și vor fi abordate într-un alt capitol.

Figura 3.5A Schema electrică a telecomenzii

17

Elementele de execuție pentru acest sistem sunt cele 2 motoare ale vehiculului.

Acestea sunt deja comandate prin unde radio pe frecvența 27 MHz de către telecomanda

mașinuței. Așadar ce rămâne de făcut este conectarea telecomenzii la laptop, și așa cum am

menționat la sfârșitul capitolului 3.4 vom folosi platforma Arduino pentru a realiza

interfațarea între cele 2 componente. Altfel spus, dorim ca în loc să fie apasate butoanele

stânga-dreapta ale telecomenzii, să poată fi apăsate butoanele tastaturii laptopului (săgeată

stanga – săgeată dreapta) și ulterior sa fie generate comenzi de către rețeaua neuronală.

Analog pentru butoanele de față-spate. Telecomanda funcționează în modul următor: atunci

când este apăsat un buton, acesta închide o porțiune din circuit și conectează microcipul de

transmisie TX-2B la GND (vezi Figura 3.5), iar acest microcip transmite un semnal către

microcipul din interiorul mașinuței (RX-2B) și astfel sunt acționate motoarele. Dacă nu este

apasăsat niciun buton atunci mașinuța rămâne nemișcată. Deci ar fi suficient să folosim un

pin al plăcuței Arduino pentru a simula apăsarea butonului. Având în vedere faptul că

telecomanda este alimentată la 3V, iar Arduino este alimentat cu 5V (via USB), putem să

renunțăm la bateriile telecomenzii și să utilizăm doar alimentarea prin USB , simplificând

structura electrică.

Figura 3.5B Microchipurile de Transmisie-Receptie

În continuare, voi explica modul în care au fost realizate legăturile (Figura 3.6). A fost

conectat pin-ul VCC-ul de pe plăcuța telecomenzii la VCC-ul de pe plăcuța Arduino (firul

roșu) și la fel pentru pinul de GND (firul negru). După care am conectat pinii RIGHT și LEFT

la pinii 12 și 11 digitali de pe plăcuța Arduino și respectiv pinii FORW și BACK la pinii 9 și

10 (banda gri). Am evitat adăugarea de rezistențe suplimentare sau a unui stabilizator de

tensiune prin programarea eficientă a microcontrolerului de pe Arduino. Un efect neplăcut al

faptului că am scos partea de plastic din jurul plăcuței din telecomandă a fost faptul că am

eliminat suportul pentru antenă și am înlocuit-o cu un fir (alb-albastru) și deci am pierdut din

raza de control.

Figura 3.6 Conectarea telecomenzii la Arduino

18

În acest moment, avem toate componentele fizice necesare pentru a realiza un model

în miniatură al unui vehicul controlat cu o rețea neuronală în urma prelucrarii unor imagini. În

figura 3.7 este o schiță a modului în care va funcțion sistemul. Telefonul montant pe mașinuță

transmite imagini prin rețeaua wireless către laptop, care le prelucrează și transmite comenzi

către telecomanda mașinuței prin intermediul platformei Arduino. Deci ce urmează este

scrierea unui program pe microcontrolerul ATMega368 capabil să interpreteze comenzile

provenite de la laptop, un soft pentru sistemul Android ce va fi pus pe telefon, capabil să

captureze și să transmită imagini către un program server de pe laptop și un program pe laptop

capabil să primească imaginile transmise de către telefon, să transmită semnalele potrivite

către plăcuța Arduino prin USB și să fie capabil să genereze comenzi folosind o rețea

neuronală.

Figura 3.7 Schema comunicațiilor din sistem

4. Descrierea modulelor software

Înainte de a detalia modul de funcționare a programelor software trebuie descrise

unele caracteristici pe care ar trebui să le aibă întreg sistemul. Acesta ar trebui:

să funcționeze in timp-real

să fie portabil – adică să poată fi implementat pe orice sistem (atât din punct de vedere

hardware cat si software – sistem de operare)

să consume minimul de resurse – interfața grafică va fi redusă la minim oferind doar

informații cu adevărat utile utilizatorului astfel încât resursele să fie folosite pentru a

finaliza operațiile necesare conducerii vehicului

să fie modularizat – adică dacă există mai multe procese în paralel acestea să fie

independente unul de celălalt

să fie adaptiv – să poată funcționa indiferent de arhitectura rețelei (se referă la numărul

de neuroni, nu la numărul de straturi)

19

să funcționeze atât în mod manual (pentru a strânge date pentru antrenarea rețelei), cât

și în mod automat (cu comenzi generate de rețeaua neuronală)

antrenarea rețelei va fi făcută offline – ceea ce înseamnă ca vor trebui înregistrate date

inițial și apoi folosite pentru antrenarea rețelei

antrenarea rețelei să fie făcută în mediul Matlab/Octave – am impus aceeastă condiție

deoarece Matlab este unul dintre cele mai puternice limbaje de programare din punct

de vedere computațional și am dezvoltat mai multe aplicații pentru rețele neuronale cu

ajutorul lui, iar Octave este varianta open-source Matlab, puțin mai slab.

Având aceste caracteristici în minte, putem concepe acum un software pentru acest

sistem.

4.1 Software Arduino

Cum am menționat și în capitolul 3.4, Arduino este un IDE special conceput pentru

scrierea programelor pe Platformele Arduino, este scris in Java iar codul este unul similar cu

cel de C/C++. Conține multe librării cu funcții ce fac folosirea platformei cât mai intuitivă.

Ca principiu de funcționare, dacă dorim să mișcăm mașinuța înainte, vom aplica o tensiune pe

pinul 9 – cel conectat la pinul FORW de pe microchipul TX-2B, altfel nu vom aplica nimic.

De aceea am făcut următoarele două funcții pentru activare și respectiv dezactivare:

#define on(pin) {pinMode(pin,OUTPUT); digitalWrite(pin, LOW);} – care are rolul de a

seta pinul selectat ca pin de output și apoi aplică tensiune pe el, deci comandă vehiculul într-o

anumită direcție,

#define off(pin) {pinMode(pin,INPUT);} – setează pinul selectat ca pin de input, ceea ce îl

pune într-o stare de impendanță mărită și nu permite curentului să treacă, deci oprește

transmisia pentru direcția respectivă.

Funcțiile pinMode și digitalWrite sunt funcții specifice librăriilor Arduino.

Următorul pas este asocierea unei “variabile” către fiecare pin folosit printr-o

declarație de tipul #define dreapta 12 , pentru a face codul cât mai intuitiv. Toate programele

Arduino conțin obligatoriu două funcții, una cu denumirea de setup() în care se setează starea

inițială a microcontrolerului și se inițializează componentele folosite – în cazul nostru se

setează toți pinii pe off și se inițializează portul serial pentru o transmisie de 9600 baud.

Aceasta este prima funcție apelată la deschiderea microcontrolerului sau evident după o

resetare. Cea de-a doua funcție se numește loop() și începe imediat după finalizarea funcției

setup() , după care rulează permanent cât timp este alimentat cu energie microcontrolerul. În

funcția loop vom face urmatoarele operații: vom verifica dacă s-a transmis ceva pe serial de la

laptop. Dacă s-a transmis, în funcție de octetul primit, se execută instrucțiunea.

Semnalele primite vor fi de felul următor : 0000 0001 = 0x01 stânga, 0000 0010 = 0x02

dreapta, 0000 0100 = 0x04 față , 0000 1000 = 0x08 spate , dacă bitul corespunzător este 1

atunci execută comanda, dacă este 0 nu o execută, adică o codificare binară. Un alt avantaj al

acestei codificări este că se pot forma cuvinte de tipul 0000 1001 care ar comanda vehiculul

spate pe motorul de sens față-spate și stânga pe motorul de direcție stânga-dreapta, adică se

pot acționa ambele motoare simultan. Un alt lucru ce trebuie făcut în funcția loop() este

includerea unor măsuri de siguranță cum ar fi: să nu se acționeze și stânga și dreapta simultan

sau față-spate (condiții care erau puse fizic în vechiul mecanism). O altă condiție impusă aici

a fost acționarea motorului de sens (față-spate) pentru un timp predefinit (forwardPulse a fost

setat 120 ms) pentru ca mișcarea să funcționeze pe bază de impulsuri. Timpul acesta a fost

găsit empiric și a fost ales așa încât mișcarea să fie fluentă cand vehiculul primește comenzi

de la rețeaua neuronală.

20

4.2 Software Android

În capitolul 3.2 am specificat faptul că telefonul utilizat folosește sistemul de operare

Android. Android este o platformă software și un sistem de operare pentru dispozitive și

telefoane mobile bazată pe nucleul Linux, dezvoltată inițial de compania Google, iar mai

târziu de consorțiul comercial Open Handset Alliance. Android permite dezvoltatorilor să

scrie cod gestionat în limbajul Java, controlând dispozitivul prin intermediul bibliotecilor Java

dezvoltate de Google. Pentru a dezvolta aplicații pentru Android este necesară folosirea sdk-

ului (software development kit) acesta este open-source și se poate downloada de pe site-ul

lor oficial (http://developer.android.com/sdk/) . SDK-ul Android include un set complet de

instrumente de dezvoltare. Acestea includ un program de depanare, biblioteci, un emulator de

dispozitiv , documentație, mostre de cod și tutoriale. Platformele de dezvoltare sprijinite în

prezent includ calculatoare bazate pe x86 care rulează Linux (orice distribuție Linux desktop

modernă), Mac OS X 10.4.8 sau mai recent, Windows XP , Vista sau Seven. Cerințele includ,

de asemenea, Java Development Kit și Apache Ant. Mediul de dezvoltare (IDE) suportat

oficial este Eclipse (3.2 sau mai recent), utilizând plug-in-ul Android Development Tools

(ADT), deși se poate folosi orice editor de text pentru a edita fișiere XML și Java și apoi să

utilizeze unelte din linia de comandă pentru a crea sau depana aplicații Android. Trebuie

menționat că acesta a fost primul meu proiect scris în Android dar exista foarte multă

documentanție în ceea ce privește acest subiect, iar faptul că folosește limbajul Java a fost un

mare plus.

Rolul aplicației este acela de a captura și transmite imagini către un server. Așadar am

structurat proiectul în trei clase : TransmisieDate , AplicațieVACRN (VACRN = Vehicul

Autonom Controlat cu Rețele Neuronale) și TransmiteImaginiVideo pe care le voi detalia în

continuare.

TransmisieDate creează un obiect responsabil de stabilirea unei conexiuni cu un

server (introdus de utilizator) și transmite datele într-o ordine prestabilită (un fel de protocol

de transmisie). Obiectul de tip TransmisieDate are două atribute :

Socket (denumirea clasei) socket (denumirea obiectului) – prin obiectele de tip socket

se lansează o conexiune către un server pe un anumită adresă ip și un anumit port,

conexiunea este una de tipul TCP/IP;

DataOutputStream (denumirea clasei) streamTransmisie (denumirea obiectului) – prin

obiectele de tip DataOutputStream se realizează transmisia de date către serverul de pe

socket;

și patru metode :

TransmisieDate() – constructorul clasei , este gol nu conține nimic

conecteaza(String adresa, int port) – metoda prin care se fixează adresa și portul și se

stabilește o conexiune

transmiteDate(int latime, int inaltime, byte[] date, float[] acceleratii) – metoda prin

care se transmit datele în ordinea definită, datele transmise se vor detalia ulterior

close() – metoda prin care se închide conexiunea la închiderea aplicației

TransmiteImaginiVideo creeaza un obiect responsabil de capturarea imaginilor,

formatarea lor și transmisa lor cu ajutorul obiectului de tip TransmisieDate. Această clasă

extinde clasa ViewGroup – creează un view special care poate conține și alte view-uri în

interior numiți copii și implementeză SurfaceHolder.Callback - primește informații atunci

când suprafața pe care se afișează este modificată, și PreviewCallback - interfață folosită

pentru a prelua cadre de pe cameră în timp ce ele sunt afișate pe ecran. În continuare voi

descrie atributele și metodele clasei.

Atributele :

21

SurfaceView mSurfaceView – oferă o suprafață dedicată pentru desenarea obiectelor

încorporate într-un view

SurfaceHolder mSurfaceHolder – obiect prin care se va apela metoda Callback , care

ne va anunța atunci când suprafața este modificată

Size marimeImagini – reprezintă dimensiunea în pixeli a imaginii, are o componentă

pentru lățime și una pentru lungime

List<Size> listaMarimiSuportate – listă ce va conține toate dimensiunile (rezoluțiile)

la care poate fi folosită camera

Camera mCamera – obiect prin intermediul căruia se apelează camera

byte[] pixeli – un șir/vector de bytes în care se vor reține pixeli imaginii și ulterior vor

fi transmiși

float[] aceleratii – un șir/vector de float în care se vor reține și transmite valorile

accelerației

TransmisieDate td – obiect al clasei TransmisieDate prin care se stabilește conexiunea

cu un server și se transmit datele

Metode :

TransmiteImaginiVideo(Context context, TransmisieDate td) - reprezintă

constructorul clasei , se definește contexul și transmisia de date aici

actualizeazaAcceleratii(float[] date) – metodă pentru actualizarea acelerațiilor

setCamera(Camera camera) - metodă pentru setarea camerei și găsirea listei de mărimi

suportate

switchCamera(Camera camera) – metodă prin care se seteaza camera , se setează

suprafața unde vor fi afișate imaginile și se setează parametrii camerei

onMeasure(int widthMeasureSpec, int heightMeasureSpec) – suprascrie metoda

onMeasure, cu această metodă se setează dimensiunea minimă pentru rezoluția

camerei

onLayout(boolean schimbat, int stanga, int sus, int dreapta, int jos) – suprascrie

metoda onLayout , cu această metodă se setează dimensiunile layoutului astfel încât

imaginea afișată să fie în interiorul suprafeței în care se face afișarea

surfaceCreated(SurfaceHolder holder) - metodă care se ocupă cu preluarea imaginilor

de pe camera și proiectarea acestora pe suprafața creată

surfaceDestroyed(SurfaceHolder holder) - metodă pentru distrugerea suprafeței create

anterior și oprirea afișării imaginilor

cautaDimensiuneMinima(List<Size> valori, int w, int h) – metodă ce caută în lista de

rezoluții care pot fi afișate de cameră și returnează dimensiunea minimă

surfaceChanged(SurfaceHolder holder, int format, int w, int h) – metodă apelată dacă

este modificată dimensiunea imaginilor, după apelarea acestei metode se începe

afișarea cadrelor pe display-ul telefonului, tot aici este alocată memorie pentru

vectorul pixeli în funcție de dimensiunile imaginilor

onPreviewFrame(byte[] data, Camera camera) – acestă metodă este apelată în mod

continuu în timpul funcționării programului, atunci când se primește de la cameră un

set complet de date (o imagine de dimensiunea stabilită), aceasta este prelucrată cu

funcția transformaRGB și transmisă cu ajutorul obiectului td

transformaRGB(byte[] rgb, byte[] yuv420sp, int latime, int inaltime) – camera

telefonului transmite date în formatul YUV420sp , transformarea în format RGB este

destul de simplă mai ales având în vedere că ne interesează doar transformarea pentru

o componentă și nu toate trei pentru a obține nunațe de alb-negru , este suficient să

aplicăm formula 0xff & ((int) yuv420sp[i] – 16 și să avem grijă ca valorile să fie doar

între 0 și 255 (o reprezentare a modului în care funcționează formatul yuv420 se poate

observa în Figura 4.1)

22

Figura 4.1 Formatul YUV420

AplicatieVACRN creează obiectul care este apelat în momentul în care utilizatorul

deschide aplicația de pe telefon. Această clasă extinde clasa Activity, ce reprezintă interfața

vizualizată de utilizator, și implementează SensorEventListener care permite accesul la

senzorii telefonului.

Atributelele clasei sunt următoarele :

TransmiteImaginiVideo vizualizareImagini - obiect prin care se afișează imaginile și

sunt transmise către server , împreună cu accelerațiile

Camera mCamera - obiect care permite accesul la camerele telefonului

int numarDeCamere - reprezintă numărul de camere de pe telefon

int cameraUtilizata – reprezintă id-ul camerei care va fi utilizată

int standardCameraId – reprezintă id-ul default al camerei , prima cameră, cea din

spatele telefonului

String NUME_PREF_FILE – string în care se salvează numele fișierului în care vor fi

salvate informațiile introduse de utilizator

TransmisieDate td - obiect al clasei TransmisieDate prin care se stabilește conexiunea

cu un server și se transmit datele

SensorManager managerSenzori - obiectul care va permite accesul la senzori

double calibrare - variabila de calibrare a senzorului de accelerație

float[] gravity – un șir/vector de float de 3 componente în care se va reține accelerația

gravitațională a telefonului

float[] linear_acceleration – un șir/vector de float în care se va reține accelerația liniară

a telefonului

Metodele clasei :

onCreate(Bundle savedInstanceState) – aceasta este prima metodă care este apelată la

deschiderea aplicației, practic este contructorul clasei. În momentul apelării se creează

interfața, se seteză suprafața în care vor fi afișate imagini pentru obiectul

vizualizareImagini, se detectează camera din spatele telefonului (camera principală) ,

se accesează senzorii telefonului (inclusiv cel de accelerație), se creează căsuțe pentru

input în care utilizatorul va introduce adresa serverului la care se conectează și tot aici,

după introducerea adresei, se realizează conexiunea prin intermediul obiectului td

salveazaAdresaIPinFisier(Context context, String text) – după ce utilizatorul introduce

adresa ip și încearcă să se conecteze la server, aceasta este automat salvată în fișierul

NUME_PREF_FILE

incarcaAdresaIPdinFisier(Context context) - dacă există o adresă de IP salvată în

fisierul NUME_PREF_FILE, atunci aceasta va fi încarcată automat în căsuța în care

utilizatorul introduce IP-ul și poate fi refolosită în cazul în care serverul este același

onResume() – metodă care este apelată atunci când telefonul revine din modul stand-

by și aplicația este deschisă , se reîncarcă ultimele informații din sistem

23

onPause() – metodă apelată atunci cand telefonul intră în stand-by , camera și senzorii

sunt opriți pentru conservarea energiei

onAccuracyChanged(Sensor sensor, int accuracy) – metodă apelată atunci când este

modificată acuratețea senzorului , acest caz nu a fost tratat dar funcția trebuia rescrisă

fiindcă este implementată interfața EventSensorListener și era obligatoriu

onSensorChanged(SensorEvent event) – metodă apelată atunci când senzorul primește

informații noi, se aplică formule (se filtrează accelerația gravitațională și se determină

accelerația liniară pe o anumită direcție , figura 4.2) pentru determinarea accelerației

liniare și aceasta este transmisă către server pentru prelucrări ulterioare .

Figura 4.2 Informații accelerometru Android

Având aplicația construită, folosind Apache Ant, construim fișierul VACRN.apk care

se poate instala folosind o aplicație care se găsește gratuit pe GooglePlay cum ar fi

AppInstaller. Pentru ca aplicația să poată fi rulată pe telefon trebuie să specificăm în fișierul

project.proprerties versiunea minimă a sistemului de operare Android pe care poate fi rulată și

în fișierul AndroidManifest.xml specificat că aplicația are nevoie de acces la Camera și la

Internet, printre altele.

4.3 Aplicatie Java VACRN

Aceasta va fi aplicația centrală a proiectului și trebuie să fie compatibilă cu restul

sistemului, adică :

- să comunice cu telecomanda mașinuței așa cum am stabilit în capitolul 4.1, să

transmită pe interfața serială octeți cu 0 și 1 pentru a comanda motoarele cum dorim

- să fie un server pentru aplicația Android și să primească datele în formatul și ordinea

stabilită în capitolul 4.2

- să înregistreze cadrele și comenzile într-un fișier care să fie apoi prelucrat un Matlab

pentru antrenarea rețelei

- să prelucreze imaginile primite de la telefon , adică să le trecă prin rețeaua neuronală

astfel încât să se obțină comenzi și vehiculul să fie autonom

- să poată încărca fișierele exportate din Matlab pentru a folosi rețeaua determinată

indiferent de arhitectura rețelei

24

Prin folosirea limbajului Java se verifică una din condițiile impuse la începutul capitolului

și anume portabilitate. Java este un limbaj de programare orientat-obiect, puternic tipizat,

conceput de către James Gosling la Sun Microsystems (acum filială Oracle) la începutul

anilor ʼ90, fiind lansat în 1995. Cele mai multe aplicații distribuite sunt scrise în Java, iar

noile evoluții tehnologice permit utilizarea sa și pe dispozitive mobile precum telefon, agendă

electronică, palmtop etc. În felul acesta, se creează o platformă unică, la nivelul

programatorului, deasupra unui mediu eterogen extrem de diversificat. Limbajul împrumută o

mare parte din sintaxă de la C și C++, dar are un model al obiectelor mai simplu și prezintă

mai puține facilități low-level. Un program Java compilat, corect scris, poate fi rulat fără

modificări pe orice platformă care are instalată o mașină virtuală Java (engleză Java Virtual

Machine, prescurtat JVM). Acest nivel de portabilitate (inexistent pentru limbaje mai vechi

cum ar fi C) este posibil deoarece sursele Java sunt compilate într-un format standard numit

cod de octeți (engleză byte-code) care este intermediar între codul mașină (dependent de tipul

calculatorului) și codul sursă. Ca mediu de dezvoltare, am folosit același IDE ca și la aplicația

Android și anume Eclipse.

Proiectul pentru această aplicație este format din șapte clase, două interfețe și o clasă

pentru testarea conexiunii seriale despre care nu voi intra în detalii. Voi prezenta fiecare

clasă/interfață pentru a descrie funcționarea programului.

Conexiunea serială a fost realizată folosind o interfață – InterfataSeriala care conține

două metode print(char c) – pentru transmiterea caracterelor via serial și close() – pentru

închiderea conexiunii. Apoi am importat în proiect pachetul RXTXcomm.jar (versiunea

pentru Windows de 64 de biți). Acesta este un pachet open-source care realizează prin

funcțiile sale comunicația între o aplicație Java și un dispozitiv conectat la PC printr-un port

serial (USB). Acest pachet este inclus și in IDE-ul Arduino și este recomandat chiar si pe site-

ul lor (http://playground.arduino.cc/Interfacing/Java), unde apar și secvențe de cod în care se

explică și cum funcționează metodele din pachet. Având interfața definită și librăria

importată, am creat o clasă RxTxInterfataSeriala care implementează interfața definită

anterior și interfața SerialPortEventListener (din pachetul RXTXcomm) pentru ascultarea

portului serial pentru date primite. Clasa aceasta are două atribut :

- SerialPort port – un obiect de tipul SerialPort , aici va fi reținut portul pe care se

efectuează transferul de date via Serial și se vor seta parametri conexiunii

(BaudRate,DataBits,StopBits,etc)

- PrintStream ps – un obiect de tipul PrintStream care preia date din aplicație și le

transmite pe port

Metode :

- RxTxInterfataSeriala(String numePort) – constructorul clasei primește ca parametru

numele portului (ex COM1), se caută prin toate porturile seriale deschise și se încearcă

conectarea pe cel cu numele numePort , dacă reușește conexiunea atunci se setează toți

parametrii și se setează obiectul ps ca Stream pentru portul numePort

- print(char c) – metodă suprascrisă de la interfața InterfataSeriala, transmite caraterul c

în streamul din ps

- close() – transmite tot ce este în stream și închide stream-ul și portul și deci și

conexiunea

- serialEvent(SerialPortEvent event) – metodă pentru debugging, afișează un mesaj

după transmiterea unui șir de date pe serial, în cazul acesta metoda este apelată după

transmiterea fiecărei comenzi.

Pentru conexiunea prin Wi-Fi este necesar ca acestă aplicație sa funcționeze ca un server

așa cum am specificat și în capitolul 4.2, dar avem nevoie de un mecanism pentru apelarea

datelor primite de către server. De aceea am creat interfața ApeleazaDate care are metoda

25

void date(byte[] pixeli, int latime, int inaltime, float[] acceleratii) prin care se vor accesa

datele primite. Pentru server am creat o clasă ServerDate , această clasă reprezintă serverul

TCP care ascultă portul 6666(același port a fost setat și în aplicația Android) pentru transmisii

de la telefon. Atributele clasei sunt următoarele:

- ServerSocket server – reprezintă portul serverului, prin acest obiect va fi creat serverul

- ApeleazaDate callback – obiect prin care se apelează datele primite

- ThreadInterpretator t – am creat o clasă nouă în interiorul clasei SeverDate, această

clasă este de tipul Thread și în ea este specificat tipul și ordinea în care sunt primite

date pe un socket s (variabilă a clasei ThreadInterpretator). Cât timp este pornit

Threadul, acesta preia date de pe socketul s (în ordinea stabilită în programul Android)

iar după ce primește un set complet de date (adică 25344 de bytes și 3 numere float) ,

acestea sunt trimise către interfața Apelează date prin obiectul callback.

Observație: Bytes primiți sunt de tipul signed bytes ceea ce înseamnă ca înainte să folosim

aceste date pentru calcule, pentru afișarea imaginii etc, aceștia trebuie aduși în intervalul

[0,255]

Este important ca serverul să funcționeze după un thread propriu deoarece el este

independent de restul acțiunilor din sistem și trebuie să fie capabil să primească date în orice

moment de timp.

Ca metode clasa folosește următoarele:

- ServerDate(ApeleazaDate ad) – constructorul cu parametrul interfața , în constructor

se deschide un server pe portul 6666

- startInterpretarea() – cu acestă metodă se acceptă conexiuni pe portul serverului, se

creează un thread de tipul ThreadInterpretator pentru portul serverului și se pornește

threadul, metoda va rula în mod continuu cât este deschisă aplicația.

În continuare voi prezenta clasele responsabile pentru exportarea datelor din aplicație

într-un fișier, astfel încât să fie prelucrate ulterior în Matlab. Pentru acesta avem nevoie de un

obiect care să asocieze imaginea și accelerațiile cu comanda la un anumit moment de timp.

Pentru aceasta am făcut clasă PachetInformatii care conține următoarele atribute:

- byte[] pixeli;

- float[] acceleratie;

- boolean stanga;

- boolean dreapta;

- boolean fata;

- boolean spate;

- int timpModificareStanga;

- int timpModificareDreapta;

- int timpModificareFata;

- int timpModificareSpate;

Nu voi detalia funcțiile lor, întrucât este destul de evident. Metodele :

- int gettimpModificareStanga()

- int gettimpModificareDreapta()

- int gettimpModificareFata()

- int gettimpModificareSpate()

(metode pentru accesarea timpului la care a fost făcut pachetul)

- PachetInformatii(byte[] pixeli, boolean stanga, boolean dreapta, boolean fata, boolean

spate, int timpStanga, int timpDreapta, int timpFata, int timpSpate, float[] acceleratie)

este constructorul clasei, formează pachetul

- byte[] getPixeli()

- float getAcceleratie(int i)

26

(metode pentru accesul la atributele obiectului)

- boolean mergeStanga()

- boolean mergeDreapta()

- boolean mergeFata()

- boolean mergeSpate()

(returnează 1 sau 0 dacă comanda pe direcția respectivă este activă sau nu)

Pentru scrierea efectivă a informațiilor, am creat un obiect care să se ocupe de această

sarcină folosind clasa ScriitorInformatii care implementează intefața Runnable deoarece

vrem ca acest obiect să ruleze în propriul ei Thread și să fie independentă de celelalte obiecte,

scrierea informațiilor se va face asincron. Atributele acestei clase sunt :

- long last – acestă variabilă reține momentul de timp la care a fost scris ultimul pachet

- PrintStream informatiiOut – variabilă prin care se efectuează scrierea în fișier, fluxul

de ieșire a informațiilor

- Queue<PachetInformatii> deScris – reprezintă o coadă de tip FIFO (first in first out)

în care sunt puse pachetele ce urmează să fie scrise în fișier, informațiile vor fi scrise

în fișier în ordinea în care au fost puse în coadă, adică în ordinea apariției lor.

Metodele :

- ScriitorInformatii(PrintStream informatiiOut) – constructorul clasei, aici se setează

fluxul de ieșire

- run() – metodă cerută de interfața Runnable, cât timp există pachete în coadă acestea

sunt scoase pe rând și scrise în fișier cu ajutorul metodei scrieInformații(pachet)

- scrieInformatii(PachetInformatii pachet) – metodă pentru scrierea informațiilor din

pachet, atributele pachetului sunt extrase cu metodele menționate mai sus și apoi

transferate pe fluxul de ieșire, tot aici este afișat timpul de scriere dintre două pachete

consecutive

- adauga(PachetInformatii pachet) – metodă pentru adăugarea pachetelor în coada

deScris

Voi expune în continuare clasele care se ocupă de generarea comenzii. Pentru aceasta

am făcut o clasă pe care am denumit-o ReteaNeuronala, scopul ei este acela de a face obiecte

cu caracteristicile unei rețele neuronale și cu ajutorul ei să putem calcula comanda pentru un

set de intrare. Pentru a ușura implementarea și a mai apropia codul de cel din Matlab am

importat pachetul Apache commons-math3-3.0.jar. Acesta este un pachet open-source și

conține funcții pentru calcul statistic, generare de date (secvențe criptografice, secvențe spab

etc), transformate (FFT, FastCosineTransformer, FastSineTransformer etc) și multe altele, dar

pentru acestă aplicație am avut nevoie de funcțiile de algebră liniară. Aceste funcții permit

crearea obiectelor de tip matrice reală sau vectori reali pe care se pot efectua operații cum ar fi

adunare, înmulțire sau scădere de matrici, înmulțire sau adunare de scalari cu matrice,

transpunerea unei matrici și calcularea normei sau a urmei unei matrici. Pachetul este unul

complex care permite chiar operații de tipul factorizare LU și Cholesky, factorizare QR sau

chiar SVD. Clasa a fost gândită astfel încât rețeaua neuronală să fie cu un singur strat ascuns

dar numărul de neuroni să fie variabil, acesta se va încărca din fișierele din Matlab. Atributele

acestei funcții sunt următoarele:

- RealMatrix theta1Transpus - o matrice de tipul RealMatrix care conține ponderile

pentru conexiunile dintre neuronii de intrare și neuronii ascunși

- RealMatrix theta2Transpus - o matrice de tipul RealMatrix care conține ponderile

pentru conexiunile dintre neuronii din stratul ascuns și neuronii de ieșire.

Au fost denumiți transpuși deoarece operațiunea de transpunere a matricilor se face chiar

de la construirea obiectului.

Metodele acestei clase sunt:

27

- RealMatrix sigmoid(RealMatrix z) - returnează o matrice de tipul RealMatrix care are

aplicată funcția sigmoid pe fiecare valoare

- RealMatrix sigmoidCuTermeniLiberi(RealMatrix z) - aceasta funcție returnează o

matrice de tipul RealMatrix cu 1 pe prima coloană și restul valorilor calculate cu

ajutorul funcției sigmoid

- RealMatrix incarcaMatriceDinFisierMatlab(String numeFisier) – funcție folosită

pentru încărcarea datelor din fișierele exportate din Matlab, formatul fișierelor trebuie

să fie de felul următor :

%% linii: 4

%% coloane: 148

2.9543194e+000 5.5126851e-001 -3.7455494e-001 …

- double[] calculComanda(byte[] date) – funcție folosită pentru calcularea comenzii,

pentru un set de intrare calculează neuroni din stratul de ieșire, funcția returnează un

vector de tipul double cu 4 elemente

Având obiectele de tip ReteaNeuronala cu ajutorul cărora putem să definim rețeaua și să

calculăm ieșirea ei pentru un set de date de intrare, putem acum crea un obiect capabil să

facă acest lucru în orice moment de timp. Am denumit clasa acestui obiect

GeneratorComanda și acestă clasa implementează si ea ca și clasa ScriitorInformații

interfața Runnable și tot la fel ca la aceasta vom forma o coadă de tipul FIFO în care vom

introduce datele de intrare și pe baza cărora vom genera comenzi. Atributele clasei sunt

următoarele :

- double PRAG_INCREDERE – avem nevoie de un prag de încredere un treshhold cu

care să comparăm ieșirile din rețea deoarce comanda poate fi doar 0 sau 1

- în interiorul clasei am creat o interfață denumită CommandListener, adică

“ascultătorul de comenzi” care conține metoda void inTimpulGenerariiComenzii

(double[] comanda, boolean stanga, boolean dreapta, boolean fata, boolean spate),

acestă interfață va fi apelată din programul principal și cu ajutorul ei putem prelua

comenzile generate de obiectul generator de comenzi

- Queue<byte[]> listaImagini – reprezintă coada de tip FIFO din care se vor lua

imaginile (adică datele de intrare pentru rețea) pentru generarea comenzii

- Thread commandThread – reprezintă threadul responsabil de generarea comenzii,

comanda trebuie generată în timp real deci are nevoie de un thread propriu care să

funcționeze în paralel cu restul programului

- CommandListener listener - obiectul listener ce va asculta comenzile și poate acționa

la primirea unor noi comenzi prin metoda inTimpulGenerariiComenzii

- ReteaNeuronala rn – reprezintă rețeaua neuronală.

Iar ca metode acestă clasa are:

- GeneratorComanda(CommandListener listener, ReteaNeuronala rn) – constructorul în

care se setează rețeaua neuronală și “ascultătorul de comenzi” și se pornește threadul

commandThread care va executa operațiile din metoda run()

- void run() – metodă executată în mod continuu de threadul commandThread, dacă

există “imagini” (seturi de date) în listaImagini se va genera comanda folosind metoda

comanda() folosind doar ultima imagine din listă pentru a fi mereu la curent cu ce se

vede pe cameră și să nu generăm comenzi pentru momente anterioare, dacă nu există

“imagini” în listă atunci threadul așteaptă

- adaugaImagini(byte[] date) - metodă ce va fi folosită pentru adăugarea de imagini în

listă ca și în metoda Run a fost folosită specificația synchronized pentru a nu se

încerca nici adăugarea și nici scoaterea de imagini din listă în același timp

- void comanda(byte[] date) - metodă resposabilă cu generarea de comandă, se folosește

metoda calculComanda a rețelei neuronale pentru a determina neuronii de ieșire a

28

rețelei și apoi toate cele patru elemente ale vectorului de ieșire sunt comparate cu

PRAG_INCREDERE astfel obținându-se comenzi pe direcțiile (față , spate , stânga și

dreapta), rezultatele comenzii vor fi transmise prin intermediul obiectului listener către

programul principal .

Figura 4.3 Intefața sistemului

Până acum am vorbit doar de clasele/obiectele care se ocupă de procesele din spatele

aplicației, dar este necesar ca utilizatorul să poată vizualiza anumite informații despre proces,

pentru acest lucru am creat o clasă pe care am denumit-o Afisaj. Această clasă va fi folosită

pentru afișarea imaginilor și a informațiilor despre proces, cu ajutorul ei se va face interfațarea

între om și mașina (Figura 4.3). Avem nevoie de o suprafață în care să “desenăm” imaginile,

de aceea clasa acesta extinde clasa Jpanel, deci avem un panou în care să putem desena.

Atributele clasei sunt următoarele :

- byte[] pixeli – pixelii care formează imaginea, sunt cei primiți de la telefon

- float[] aceleratii – valorile pentru accelerații primite de la telefon

- int latime – numărul de pixeli pe lățime

- int inaltime – numărul de pixeli pe înălțime

- boolean stanga

- boolean dreapta

- boolean fata

- boolean spate

( variabile pentru afișarea comenzii pe cele 4 direcții)

- String comenzi – reprezintă neuronii din stratul de ieșire

- Mod mod

- Viteza modViteza

( variabile importate din programul principal ce țin de modul de funcționare a procesului,

vor fi explicați ulterior)

Și respective următoarele metode:

- Afisaj() – constructorul clasei, este setată dimensiunea panoului, dublu față de

rezoluția imaginilor

- void paintComponent(Graphics g) – metoda acesta va fi apelată continuu pe timpul

rulării programului , cu ea se realizează afișarea imaginilor (pixelii sunt dublați pentru

a acoperi toată dimensiunea panoului), afișarea modurilor în care funcționează

procesul, afișează săgeți pentru a indica comanda aplicată și în funcție de modul în

care este( automat, manulat sau înregistrare) afișează accelerația vehiculului (modul

manual) sau valorile neuronilor de ieșire din rețea (modul automat)

29

- void actualizeaza(byte[] pixeli, int latime, int inaltime, float[] aceleratii) – metodă prin

care imaginea este actualizată în Afisaj

- void actualizeaza(boolean stanga, boolean dreapta, boolean fata, boolean spate, Mod

modProces,Viteza modViteza) – metodă prin care informațiile de proces sunt

actualizate în Afisaj

- void actualizeaza(double[] comenzi) – metodă prin care ieșirile rețelei neuronale sunt

actualizate în Afisaj

Avem în acest moment toate instrumentele necesare pentru a realiza un program cu

caracteristicile descrise la începutul acestui capitol. Am construit o clasă VACRN (vehicul

autonom controlat cu rețele neuronale) care extinde clasa JFrame (avem nevoie să creăm o

fereastră, un cadru în care să punem panoul din clasa Afisaj) și implementează interfețele

ApeleazaDate (preia datele primite pe server) , KeyListener (preia inputul utilizatorului de la

tasatatură) și CommandListener (preia comenzile generate de generatorul de comenzi).

Această clasa va reprezenta programul principal, cel care combină toate elementele descrise

până acum.

Atributele acestei clase sunt obiecte din clasele descrise anterior:

- Afisaj display – panoul pentru afișarea informațiilor

- InterfataSeriala serial – interfața serială pentru transmiterea informațiilor pe serial

- Mod modProces – acesta va fi modul de funcționare a procesului, are trei valori

MANUAL în care utilizatorul conduce vehiculul prin intermediul tastaturii , RECORD

în care datele primite/acționate (imagini/comenzi) sunt salvate într-un fișier pe disc,

AUTOMAT imaginile primite de către serverul TCP sunt transmise direct rețelei

neuronale, iar outputul rețelei este interpretat și comenzile sunt transmise către vehicul

prin interfața serial

- Viteza modViteza – acesta va fi tot un mod de funcționare a procesului, valabil doar

pentru modul AUTOMAT, are două valori NORMAL și FAST. În funcție de acest

mod se vor genera comenzi mai rapid, dar mai imprecis (FAST) sau mai lent

(NORMAL) adică se va impune un delay între comenzi pentru a crește acuratețea

comenzilor

- boolean stanga;

- boolean dreapta;

- boolean fata;

- boolean spate;

( comenzile pentru vehicul 0 oprit, 1 acționează pe direcția respectivă)

- long ultimaModificareStanga

- long ultimaModificareDreapta

- long ultimaModificareFata

- long ultimaModificareSpate

- long ultimaModificare

( variabile ce rețin ultima schimbare de direcție pentru fiecare dintre comenzi, necesar

pentru scrierea în fișier și pentru modul Viteză)

- PrintStream informatiiOut;

- ScriitorInformatii listaPachete;

( variabile necesare pentru salvarea informațiilor în fișier)

- ReteaNeuronala rn

- GeneratorComanda predictor

( variabile necesare pentru generarea comenzii)

Metodele acestei clase sunt :

30

- VACRN(InterfataSeriala serial, File fisierOut, String fisierTheta1,String fisierTheta2)

constructorul clasei, aici se setează interfața serială, se setează fișierul în care se vor

salva datele, se încearcă încărcarea datelor din fișierele din Matlab dacă acestea există,

se va seta dimensiunea cadrului (egală cu dimensiunea panoului display) și se adaugă

panoul display, se adaugă un keyListener pentru a prelua input de la tastatură, se

creează o listaPachete în care se vor introduce pachetele ce trebuie scrise în fișier, se

crează și se pornește un thread pentru scrierea informațiilor în fișier așa cum a fost

definit în clasa ScriitorInformatii și se creeză un predictor de tipul GeneratorComandă

care folosește rețeaua neuronală încărcată anterior

- scriePachet(PachetInformatii pachet) – metodă pentru adăugare de pachete în lista de

pachete și deci pentru a-l scrie în fișier

- comandaPeBazaImaginei(byte[] pixeli) – metodă pentru adăugarea de imagini în lista

de imagini a obiectului predictor , care va genera comenzi pe baza lor

- trimiteComanda(boolean stanga, boolean dreapta, boolean fata, boolean spate) –

metodă prin care este transmisă comanda prin serial , codarea informațiilor va fi făcută

la fel ca în programul arduino (ex (fata + stanga) : 0000 0101)

- keyPressed(KeyEvent e) - metodă din interfața KeyListener , este apelată atunci când

este apăsată o tastă, dacă este apăsată una din săgeti de pe tastatură atunci se setează

variabila de direcție (ex dacă este apăsată sageată stânga atunci variabila stanga este

făcută 1), se reține timpul modificării, se transmite comanda pe serial cu funcția

trimiteComanda și se actualizează display-ul cu noile informații

- keyReleased(KeyEvent e) - metodă din interfața KeyListener, este apelată după

apăsarea unei taste, ca o convenție schimbările de mod se vor face doar pe

keyReleased (ridicarea degetului de pe tastatura) , face același lucru ca și keyPressed

dar inclusiv pentru modificarea modProces și modViteză

- keyTyped(KeyEvent e) – metodă din intefața KeyListner, nu este necesară așa că nu

am adăugat operații

- void date(byte[] pixeli, int latime, int inaltime,float[] aceleratii) – metodă din intefața

ApelezaDate, este apelată de fiecare dată când se primește un nou set de date pe

serverul TCP, dacă sistemul se află în modul INREGISTRARE atunci se creează un

pachet nou cu datele primite și se apelează funcția scriePachet, dacă sistemul se află în

modul AUTOMAT se apelează funcția comandaPeBazaImaginei cu argument ultima

imagine primită și actualizează informația afișată pe display (imaginea și accelerația)

- inTimpulGenerariiComenzii(double[] comenzi, boolean stangaNou, boolean

dreaptaNou,boolean fataNou, boolean spateNou) – metodă din intefața

CommandListener , este apelată de fiecare dată când se generează un set de comenzi,

se verifică modul Viteză și în funcție de acesta se setează timpul de delay dintre

comenzi transmise către serial , dacă ultima comandă este diferită de cea anterioară

aceasta este transmisă către serial indiferent de delay, se salvează noile comenzi și

dacă se respectă condițile menționate mai sus se apelează funcția trimiteComanda și se

salvează momentul la care a fost transmis, tot aici se actualizează display-ul cu

informațiile despre proces

- main(String[] args) – funcția principală a aplicației, va fi apelată la deschiderea

aplicației, dacă nu este specificat portul pe care se face comunicația între aplicație și

Arduino, atunci aplicația nu va fi lansată și utilizatorul va fi avertizat de acest lucru

printr-un mesaj, se creează un fișier temporar în care se vor salva informațiile scrise în

modul INREGISTRARE, se creează un obiect de tipul VACRN pentru care se

specfică interfața serială RxTxInterfataSeriala, fișierul de output, cel creat tot aici și

calea către fișierele exportate din Matlab, și un obiect de tipul ServerDate care

comunică cu obiectul VACRN prin intefața ApeleazaDate, se pornește thread-ul

pentru ascultarea portului pentru date .

31

Figura 4.4 Comunicația între limbaje

Avem acum un sistem capabil să conducă un vehicul printr-o interfață serială, să

primească imagini și alte informații de la senzori, acționând ca un server și să înregistreze

date despre modul în care a fost condus acest vehicul. Aceste date sunt salvate într-un fișier pe

disc, prelucrate folosind un mic script Python și apoi importate în Matlab. Aici avem modulul

de Inteligența Artificială, aici este creată rețeaua neuronală propriu-zisă. În urma antrenării

rețelei, se pot extrage două fișiere care reprezintă legăturile dintre neuronii rețelei (theta1 și

theta2) și cu ele putem genera acum cu succes comenzi pentru controlul vehicului. Acuratețea

comenzilor depinde de cât de eficientă este rețeaua și de tehnicile folosite în antrenarea ei,

lucru pe care îl vom vedea în capitolul următor. În Figura 4.4 avem ilustrată o diagramă a

modului de comunicație între diferitele limbaje din proiect.

Scriptul Phyton a fost folosit pentru a converti fișierul în care sunt informațiile salvate

de aplicația Java VACRN în două fișiere X.dat pentru neuronii de intrare în rețea și respectiv

y.dat pentru neuronii de ieșire din rețea. Ele sunt formatate pentru a putea fi ușor citite în

Matlab folosind doar comanda load(). Tot aici au fost eliminate cadrele care nu au comenzi

(adică au doar comenzi cu 0) și se pot face și alte prelucrări cum ar fi eliminarea comenzilor

de mers înapoi sau limitarea comenzilor de mers înainte până la un anumit număr pentru a

elimina redundanța informațiilor.

5. Antrenarea rețelei neuronale

Cum am menționat și în capitolul 2, rețeaua este una de tip propagare înainte, cu

25345 neuroni de intrare obținuți din 176x144 pixeli (25344, fiecare pixel reprezintă câte un

neuron de intrare) și un termen liber (bias), un singur strat ascuns și stratul de ieșire format

din 4 neuroni, fiecare responsabil de comanda pe o direcție. Fiecare imagine va fi o linie în

matricea X și respectiv fiecare linie din matricea y comanda asociată imaginii, în Figura 5.1

avem un exemplu de aranjare a datelor în termeni de X și y pentru o rețea cu m exemple de

învățare.

32

Figura 5.1 Matricele X și y

5.1 Propagare înainte și Propagare inversă

În Figura 5.2 se poate observa o reprezentare a rețelei neuronale în care au fost

reprezentați inclusiv neuronii de bias, aceasta este o bună reprezentare a fenomenului de

propagare înainte. Ca simbolistică Θ(i) reprezintă conexiunile dintre straturile i și i+1,

matricea ponderilor cea pe care vrem sa o aflăm, a(i) reprezintă valoarea neuronului din

stratul i după aplicarea funcției de activare, funcția de activare nu se aplică pe neuroni de bias,

z(i) reprezintă valoarea obținută prin aplicarea ponderilor din Θ(i) asupra valorilor neuronilor

din stratul i-1, g(z) reprezintă funcția sigmoid funcției sigmoid și h(x) reprezintă output-ul

rețelei la aplicarea datelor de intrare x. Acesta este mecansimul cunoscut sub denumirea de

propagare înainte ( engl. feedforward ).

Figura.5.2 Modelul rețelei

Învățarea va fi una supervizată și se va face cu algoritmul de propagare inversă ( engl.

backpropagation BP). Ideea principală din acest algoritm este că dacă avem o pereche de

intrare-ieșire X(t)-y(t) , întâi trecem datele prin rețeaua neuronală (conexiunile dintre straturi

au fost inițializate în mod aleatoriu) inclusiv aplicând funcțiile de activare ale neuronilor (în

33

cazul acesta funcția sigmoid) și determinăm valorile de ieșire. Apoi pentru fiecare neuron j

din stratul l vrem să găsim diferența d(j,l) care va măsura cât de mult este responsabil

neuronul respectiv pentru erorile de la ieșire. O reprezentare a modului în care funcționează

algoritmul de back-propagation putem vedea în Figura 5.3 , unde g’(z) este derivata funcției

sigmoid ( , iar d(j,l) este „eroarea” neuronului j din stratul l față de

traget-ul dorit, restul elementelor au aceeași semnificație ca cele din Figura 5.2.

Figura 5.3 Algoritmul de propagare inversă

Reprezentând matematic ecuațiile sistemului obținem :

∈ (5.1)

(5.2)

Atunci putem să scriem următoarea funcție cost pentru toate exemplele pe care se

efectuează antrenarea :

(5.3)

Dacă am scrie

adică clasica funcție de eroare

medie pătratică ( engl. mean squared error) atunci am obține o funcție cost non-convexă

ca cea din Figura 5.4 a), adică cu mai multe minime locale, iar acest lucru apare din cauza

funcției neliniare sigmoid. Pe o astfel de funcție utilizarea unei metode simple ca cea de

gradient nu ar garanta convergența către minimul global. De aceea vrem să găsim o funcție ca

cea din figura 5.4 b) pe care convergența către minimul global este garantată.

34

a) Reprezentarea funcției caz non-convex b) Reprezentarea funcției caz convex

Figura 5.4

Fie

(5.4)

Această reprezentare pentru funcția cost este foarte potrivită, în primul rând datorită faptului

că este convexă, iar în al doilea rând deoarece dacă adică exact ce vrem, iar dacă ceea ce înseamnă că penalizăm

algoritmul de învățare producând un Cost foarte mare, dacă se îndepărtează de țintă. La

fel se întamplă și în cazul în care y este 0. Pentru a evita condiția impusă de y putem rescrie

formula din (5.4) în următorul mod echivalent:

(5.5)

Și astfel obținem următoarea formulă pentru funcția :

(5.6)

unde m reprezintă numărul de exemple de învătare și K reprezintă numărul de direcții

posibile (adică 4 în cazul nostru). Scopul nostru este acum de a minimiza această funcție cost

iar pentru a face acest lucru avem nevoie să putem calcula

( unde l indică

conexiunea) și să aplicăm o tehnică de optimizare.

Pentru a calcula gradienți, putem aplica algoritmul BP care poate fi scris în felul

următor :

Există un set de date {(x(1)

,y(1)

), (x(2)

,y(2)

), …, (x(m)

,y(m)

)};

Se seteză variabilele (acestea vor fi folosite pentru calculul

)

Pentru i de la 1 până la m :

Setăm (5.7)

Efectuăm operațiile pe propagarea înainte (vezi Figura 5.2):

(5.8)

(5.9)

(5.10)

(5.11)

Se calculează erorile folosind schema de calcul din Figura 5.3:

(5.12)

(5.13)

; (5.14)

35

; (5.15)

5.2 Metode de generalizare

Pentru îmbunătățirea performanțelor și evitarea fenomenului de suprasaturare (engl.

overfitting ), vom adăuga un termen pentru penalizarea Theta1 și Theta2 astfel încât să nu

devină prea mari. Menținând Theta1 și Theta2 la valori mici putem evita o aproximare

precum cea obținută în graficul (b) și putem obține ceva asemănător celui din (a) (Figura 5.5).

Figura 5.5 Reprezentarea schematică a problemei generalizării

Regularizarea poate fi făcută prin mai multe metode, dar metoda aplicată în acest caz

prin penalizarea ponderilor este cunoscută în literatură ca „Weight-Decay” și se manifestă

prin adăugarea unui termen în funcția cost, de obicei acesta este suma pătratelor ponderilor

( :

unde este parametrul de penalizare și este ales de către utilizator. În cazul acesta a fost setat

1000 (setarea parametrului lambda prea mare poate duce la găsirea unui model care să nu

respecte cerințele dorite) hls (hidden_layer_size) este dimensiunea stratului ascuns pe care

încă nu am găsit-o, ils (input_layer_size) este dimensiunea stratului de input adică 25344 și

ols (output_layer_size) este dimensiunea stratului de output adică 4.

Modificarea funcției cost aduce repercursiuni și asupra gradientului funcției care

devine :

=

(5.17)

Cu observația că nu se aplică pentru termeni liberi.

36

Proprietatea de generalizare reprezintă capabilitatea unei reţele neuronale de a

răspunde la date de intrare ce nu au făcut parte din mulţimea de antrenament. Este evident

faptul că scopul învăţării unei reţele neuronale trebuie să fie obţinerea unei bune capacităţi de

generalizare. Generalizarea poate fi privită, dacă considerăm reţeaua neuronală ca o aplicaţie

între spaţiul datelor de intrare şi spaţiul datelor de ieşire (obţinute la stratul de ieşire), ca fiind

abilitatea de interpolare a aplicaţiei respective. Termenul de penalizare face ponderile să

conveargă la o valoare mai mică decât ar face-o dacă acesta nu ar exista ( . Valori ale

ponderilor mari pot fi dăunătoare pentru generalizarea rezultatului în două moduri. Ponderi

foarte mari către neuronii ascunși pot face ca outputul funcției să fie cu diferențe mari, posibil

chiar cu discontinuități. Ponderi foarte mari care duc la stratul de ieșire pot determina ca unii

neuroni din output sa fie mult mai mari decât restul datelor, dacă funcția de activare nu are un

codomeniu finit. Ponderi mari pot cauza varianță excesivă către output (Geman, Bienenstock,

and Doursat 1992). Conform lui Bartlett (1997), mărimea (norma 1) a ponderilor este mai

importantă decat numărul ponderilor când vine vorba de generalizare.

O altă metodă pe care am folosit-o pentru a îmbunătăți generalizarea este cunosctută în

literatură ca “early-stopping” și se poate aplica folosind următorul algoritm :

1. Se împart datele disponibile în seturi de antrenare și validare.

2. Se utilizează un număr mare de unități ascunse ( nu e neapărat necesar).

3. Se folosesc valori foarte mici și aleatorii pentru inițializarea ponderilor.

4. Se folosește o rată de învățare lentă (un pas mic).

5. Se caculează rata de eroare pe setul de validare periodic în timpul anternării.

6. Se oprește antrenarea dacă eroarea de validare începe să crească.

Avantajele acestei metode ar fi faptul că este rapidă și ușor de implementat, depinde doar

dintr-un singur punct de vedere de utilizator și anume în alegerea setului de validare.

Dezavantajul este că alegerea setului de validare este o sarcină destulă de dificilă și nu există

teorie care să ne spună cum se face acest lucru.

5.3 Determinarea numărului de neuroni din stratul ascuns

O problemă pe care nu am abordat-o încă este determinarea numărului de neuroni din

stratul ascuns. Aceasta este tot o problemă ce ține de generalizarea rezultatului dar nu numai.

Determinarea numărului de neuroni din stratul ascuns depinde într-un mod complex de :

- Numărul de neuroni de intrare și neuroni de ieșire

- Numărul de exemple de învățare

- Complexitatea clasificării

- Arhitectura rețelei

- Tipul funcției de activare

- Algoritmul cu care se face antrenarea

- Metodele de regularizare

Dar încă nu există în literatură specificat clar cum acesta poate fi găsit. Există metode

determinate mai mult empiric, unii spun că numărul de neuroni ar trebui să fie undeva între

dimensiunea stratului de intrare și dimensiunea celui de ieșire, alții spun că ar trebui să fie

exact 2/3 din suma acestora , iar alții spun că pot depăși dimensiunea de intrare dar nu mai

mult decât dublul ei. Având în vedere că părerile sunt împărțite și nu există nici măcar un

rezultat clar după care ar trebui să se facă acestă alegere, vom merge pe o abordare empirică

adică prin încercarea diferitelor dimensiuni se va alege cea care produce o eroare cât mai

mică. Pentru a face acest lucru trebuie importat un set de date din aplicația Java VACRN

(pixelii salvați în fișiere au fost și ei salvați ca signed bytes așa că trebuie făcută conversia

după încărcarea datelor din fișier) și realizat un set de funcții în Matlab :

pentru inițializarea ponderilor ( după formula

37

(5.18)

(rand este funcția rand din Matlab, iar este dimensiunea stratului de ieșire cu care θ

face conexiunea și respectiv dimensiunea stratului de intrare.

pentru calcularea valorii funcției cost și a derivatelor parțiale folosind algoritmul de

propagare înapoi.

pentru calcularea erorii medii pătratice dintre outputul rețelei și valorile dorite

Trebuie formulată o problemă de optimizare pentru determinarea lui ,

acesta se poate scrie foarte simplu ca o problemă de minimizare fară constrângeri : . Tot ce rămâne acum de făcut este alegerea unei tehnici de optimizare.

Pentru început am folosit fmincg (importată din bibleoteca Octave – open-source-

pentru optimizări), acestă funcție minimizează o funcție continuă diferențiabilă multivariabilă.

Pentru folosirea acestei funcții trebuie să avem un punct de start (am inițializat matricele

Theta1 și Theta2 cu valori random mici folosind formula 5.13) și de o funcție care să

returneze valoarea funcției și un vector cu valorile derivatelor parțiale în diferite puncte. Sunt

folosite ecuațiile Polak-Ribiere pentru determinarea direcțiilor de căutare și o regresie liniară

ce folosește aproximări polinomiale cubice și pătratice, sunt aplicate condițiile Wolfe pentru

determinarea pasului alfa inițial și pentru setarea criteriilor de oprire.

Având un set de date și funcțiile descrise mai devreme, am încercat găsirea numărului

optim de neuroni din stratul ascuns, rulând setul de funcții în aceleași condiții pentru numere

de la 32 la 300, asa cum se poate vedea și în Figura 5.6. Am ales un interval destul de mic de

valori deoarece pentru valori mai mari am rămas rapid fără memorie, iar antrenarea se făcea

foarte lent și, așa cum se observă din figură, am găsit o eroare destul de mică și pentru acest

interval. Pe grafic se mai poate observa faptul că pentru majoritatea valorilor am obținut o

valoare de 0.35 a erorii (destul de mare înseamnă ca 35% din predicții au fost greșite) și faptul

că nu există o dependență între creșterea numărului de neuroni și descreșterea erorii. Deși cea

mai mică valoare a erorii (0.1568) a fost găsită la 258 de neuroni, am ales 147 deoarece

computațional este mai rapid și diferența era de doar 0.02.

Figura 5.6 Determinarea numărului de neuroni din stratul ascuns

Avem în acest moment toate elementele pentru structura rețelei, funcții de cost,

gradient, și un sistem pe care să putem testa rezultatele. Ce rămâne de făcut este alegerea unei

tehnici de optimizare eficiente pentru antrenarea rețelei întrucât funcția fmincg nu a produs

rezultate satisfăcătoare. După antrenarea rețelei pe un set de date (2898 de imagini), am

38

obținut un . Trecând datele prin rețea, am obținut o matrice de ieșire PRED (ieșire

predictată) de 2898x4 cu valori în intervalul (0,1). Am comparat aceste rezultate cu un prag de

încredere (am setat 0.4, același ca cel din clasa GeneratorComanda capitolul 4.3) pentru a

obține doar rezultate de 0 și 1 la fel ca în matricea pe care s-a facut antrenarea. În urma

comparării rezultatelor din matricea PRED cu matricea inițială y, am obținut următoarele

rezulatate :

Acuratețea setului de date: 81.380368 – stânga

Acuratețea setului de date: 67.361963 – dreapta

Acuratețea setului de date: 95.920245 – față

Acuratețea setului de date: 100.000000 – spate

Aceste date reprezezintă procentajul cu care rețeaua a reușit să identifice corect

comanda pentru imaginea respectivă. Mașina a reușit să se descurce relativ bine pe traseu, dar

nu suficient pentru toate situațiile.

În figura 5.6 este o reprezentare a conexiunilor obținute, se poate observa cum ele se

pot aplica aproape ca niște filtre asupra imaginilor introduse pentru obținerea direcției dorite.

Figura 5.7 Vizualizarea legaturile din Theta1

În continuare vom încerca antrenarea rețelei folosind diferite tehnici de optimizare cu

ajutorul librăriei de RNA din Matlab. Aceasta este o librărie ușor de folosit care permite

crearea unui obiect de tipul rețea neuronală pentru determinarea unui model și rezolvarea

problemelor de clasificare (engl. patternent). Librăria permite și folosirea procesării paralele

pe mai multe procesoare dar și folosirea unității grafice de procesare pentru a mări viteza

calculelor. Conține funcțiile menționate mai sus pentru generalizarea rezultatelor, module de

preprocesare și postprocesare a datelor, dar și o suită mare de tehnici pentru determinarea

parametrilor theta folosind BP. Am creat o rețea cu 25345 de neuroni în stratul de intrare,

147 în stratul ascuns și 4 neuroni în stratul de ieșire. Am setat funcțiile de activare (funcția

sigmoid) și tot ce urmează este găsirea unei tehnici potrivite pentru antrenarea rețelei. Nu se

vor implementa toate tehnicile existente în librărie, ci am ales dintre ele 9 care am considerat

că merită investigate.

39

5.4 Metoda Gradient cu Propagare Inversă (traingd)

Metoda gradient este una din cele mai vechi și mai cunoscute metode iterative în

optimizare, fiind propusă pentru prima dată de Cauchy în 1847. Metoda gradient mai este

cunoscută și sub numele de “metoda celei mai abrupte descreșteri” ( engl. gradient descend).

Metoda este foarte importantă din punct de vedere teoretic deoarece este una dintre cele mai

simple metode pentru care există o analiză satisfăcătoare cu privire la convergență.

Figura 5.8 Importanța punctului inițial în determinarea parametrilor θ

Metoda gradient se bazează pe următoarea iterație:

(5.19)

Unde

adică este compus din toate elementele din și

adică vectorul și

(5.20)

iar lungimea pasului se poate alege fie constantă, fie variabilă folosind condițiile

Wolfe, backtracking sau alte tehnici, dar în general am preferat . Cu alte cuvinte,

din punctul căutăm de-a lungul direcției opuse gradientului un punct de minim, iar acest

punct de minim este .

După rularea algoritmului de gradient timp de 50 de iterații pe setul de date am obținut

urmatoarele rezultate:

Acuratețea setului de date: 39.877301 - stânga

Acuratețea setului de date: 67.361963 - dreapta

Acuratețea setului de date: 95.920245 – față

Acuratețea setului de date: 99.877301 – spate

40

Figura 5.9 Performanțele Metodei Gradient pe setul de date

În figura 5.9, în partea din stânga, se poate observa evoluția funcției cost (funcția cost

este msereg adică Mean squared error with regularization – eroarea medie pătratică cu

regularizare, adică cea din formula 5.16) pe parcursul celor 50 de iterații și cum acesta a

rămas probabil blocată într-un minim local. În partea din dreapta se observă că și valoarea

gradientului este aproape constantă în 90% dintre iterații, iar în partea de jos avem ilustrat

fenomenul de oprire rapidă ( engl. early-stopping ) care însă nu a produs nicio modificare.

5.5 Propagare inversă flexibilă (trainrp)

Rețeaua noastră folosește funcții de activare sigmoid. Așa cum am precizat și la

începutul lucrării, aceste funcții sunt numite și funcții de “comprimare”, pentru că ele

comprimă un domeniu infinit de la intrare într-un domeniu finit la ieșire. Funcțiile sigmoidale

sunt caracterizate de faptul că panta lor trebuie să fie zero, când intrarea este mare. Aceasta

duce la apariția unei probleme atunci când se folosește gradientul descent la antrenarea unei

rețele multi-strat cu funcții sigmoidale, anume că gradientul fiind foarte mic, provoacă mici

modificări ale valorilor ponderilor și deplasărilor, chiar când acestea sunt departe de valorile

lor optimale, cum am văzut și în exemplul de mai sus ( Capitolul 5.3).

Scopul algoritmului de antrenare prin propagare inversă flexibilă (Rprop) este acela de

a elimina efectele nedorite cauzate de derivatele parțiale. Se folosește numai semnul derivatei

pentru a afla direcția actualizării ponderii; mărimea derivatei nu are niciun efect asupra

actualizării ponderii. Mărimea variației ponderii se determină cu o valoare de actualizare

separată. Valoarea actualizării pentru fiecare pondere și deplasare se amplifică cu un factor

delt-inc dacă derivata funcției cost(de performanță) în raport cu acea pondere are același semn

în două iterații successive. Valoarea actualizată se micșorează cu factorul delt-dec când

derivata în raport cu acea pondere își schimbă semnul față de iterația anterioară. Dacă derivata

are valoarea zero, atunci valoarea actualizată rămâne aceeași. Când ponderile oscilează,

variația ponderii va fi redusă. Daca ponderea continuă să se modifice în aceeași direcție pe

parcursul mai multor iterații, atunci mărimea variației ponderii se va amplifica. Matematic,

acest lucru se poate scrie în următorul fel :

(5.21)

41

Unde , în cazul nostru

(5.22)

Cu o excepție = -

pentru cazul în

care a avut loc o schimbare de semn a derivatei parțiale în pasul precedent și pasul a fost prea

mare, deci a fost “sărit” minimul, atunci ponderea respectivă este adusă inapoi la valoarea

precedentă. În general formula pentru determinarea θ optim este :

(5.33)

Rprop este în general mult mai rapid decât algoritmul standard cu gradient descendent

și cere doar o mică mărire a necesarului de memorie. Este nevoie să stocăm valorile

actualizate pentru fiecare pondere și deplasare, ceea ce este echivalent cu stocarea

gradientului.

După rularea algoritmului am obținut următoarele rezultate :

Acuratețea setului de date: 60.184049 – stânga

Acuratețea setului de date: 68.312883 - dreapta

Acuratețea setului de date: 95.920245 – față

Acuratețea setului de date: 100.000000 - spate

Figura 5.10 Performanțele Metodei Propagare inversă flexibilă pe setul de date

Deși rezultatele sunt mai bune decât cele obținute folosind metoda gradientului

descendent, acestea nu diferă foarte mult. Din cauza variației gradientului (Figura 5.10

dreapta-sus) și a schimbării constante de direcție și deci și a valorilor funcției cost (Figura

5.10 stânga) antrenarea este oprită destul de repede prin condițiile de early stopping. Dacă

elminăm aceste condiții și încercăm din nou antrenarea pentru 50 de iterații obținem

următoarele rezultate :

42

Acuratețea setului de date: 87.515337 - stanga

Acuratețea setului de date: 88.343558 - dreapta

Acuratețea setului de date: 95.920245 – față

Acuratețea setului de date: 100.000000 - spate

Figura 5.11 Clasificarea rezultatelor folosind RProp

Deși am obținut rezultate numerice mai bune, am renunțat la condițiile de

generalizare, ceea ce s-ar putea dovedi foarte dăunător în practică.

5.6 Propagarea inversă BFGS quasi-Newton (trainbfg)

În analiza numerică și optimizare, metoda lui Newton (sau metoda Newton-Raphson)

este o metodă de calcul a rădăcinilor unui sistem de ecuații care are la bază următoarea relație

de recurență :

(5.24)

unde este matricea Hessiana (cu derivatele secunde ale funcției de performanță în

raport cu valorile curente ale ponderilor si deplasărilor). Metoda lui Newton converge deseori

mai rapid decat metodele din capitolele 5.3 și 5.4. Din păcate, este complicat și costisitor să se

calculeze matricea Hessiana pentru o rețea neuronală, mai ales una cu dimensiunile acestea.

Exista însă o categorie de algoritmi bazați pe metoda lui Newton, dar care nu necesită

calcularea derivatelor secundare. Acestea sunt numite metode quasi-Newton. Aceste metode

actualizează la fiecare iterație o matrice Hessiana aproximativă. Actualizarea este calculată ca

o funcție de gradient. O astfel de aproximare este cea Broyden-Fletcher-Goldfarb-Shanno

(BFGS) :

(5.25)

unde

din care se poate obține ușor inversa :

43

(5.26)

Algoritmul funcționează în felul următor :

Cât timp > 0 :

Calculează direcția

Calculează direcția noua iterație și

Evaluează și calculează

Calculează

Algoritmul BFGS necesită multe calcule în fiecare iterație si mai multă memorie decât

metodele cu gradient, deși în general converge în mai puține iterații. Hessianul aproximativ

trebuie stocat, iar dimensiunile sale sunt n× n, unde n este egal cu numărul ponderilor și al

deplasărilor din rețea ( .

Din nefericire, acest algoritm nu a putut fi rulat pentru acestă problemă deoarece

necesită prea multe resurse din punct de vedere al memoriei utilizate. Nici după diminuarea

setului de date și utilizarea opțiunilor pentru optimizare a memoriei nu s-a putut realiza

antrenarea cu acest algoritm. Totuși, pentru rețelele mai mici, trainbfg poate fi o funcție de

antrenare eficientă și îl vom avea în vedere (în funcțiile din matlab) în cazul în care, în viitor,

se va putea utiliza o mașină de calcul mai puternică.

5.7 Propagarea inversă OSS (trainoss)

Din moment ce algoritmul BFGS necesită foarte multă memorie și implică multe

calcule complexe la fiecare iterație, este necesară găsirea unei metode cvasi-Newton care

necesită mai puține resurse. Metoda one step secant (OSS) este o metodă prin care se încearcă

acest lucru. Acest algoritm nu păstrează întreaga hesiană, ci se presupune mereu că la iterația

precedentă Hessiana a fost matricea identitate I. Acestă abordare are ca avantaj faptul că noua

direcție de căutare poate fi calculată fără a se efectua inversări de matrici. Ca și la celelalte

metode, se pornește de la o ecuație iterativă :

, unde reprezintă direcția de căutare și are următoarea formulă :

(5.27)

Unde și

(5.28)

(5.29)

Este ușor de observat ca ecuația (5.27) are nevoie doar de n operații (odată ce este calculat

gradientul folosind BP) pentru calcularea produselor de tipul xTy. Așadar avem de a face cu

un algoritm foarte rapid care ar trebui să fie și eficient.

După antrenarea rețelei am obținut următoarele rezultate :

Acuratețea setului de date: 81.012270 - stânga

Acuratețea setului de date: 67.453988 - dreapta

44

Acuratețea setului de date: 95.920245 – față

Acuratețea setului de date: 100.000000 – spate

Figura 5.12 Performanțele Metodei One Step Secant pe setul de date

Aceste rezultate sunt mai slabe decât cele găsite cu Metoda Rprop și antrenarea a durat

un timp mai îndelungat, 16 minute și 24 de secunde deci aproximativ 20 de secunde pe iterație

în comparație cu 5 secunde, cât a durat la celelalte metode .Un alt mare dezavantaj al acestei

metode este faptul că este foarte afectată de valorile lui θ0 (valorea inițiala a lui theta) și deci

trebuie antrenată de mai multe ori rețeaua pentru a găsi valorile optime ale conexiunilor și

deplasărilor.

Figura 5.13 Clasificarea rezultatelor folosind OSS

Din datele de mai sus, se poate observa că pentru comanda dreapta s-a înregistrat cel

mai mic procentaj, însă dacă ne uităm în diagrama de clasificare din Figura 5.13 se poate

vedea că rețeaua a interpretat greșit toate valorile pentru clasa respectivă (le-a interpretat ca

fiind din clasa 3), iar acest rezultat ne indică faptul că rețeaua aceasta nu ar putea fi folosită în

niciun caz în procesul real cu succes.

5.8 Propagarea inversă Levenberg-Marquardt (trainlm)

Algoritmul Levenberg-Marquandt, care a fost creat în același timp, independent atât

de Kenneth Levenberg cât și de Donald Marquandt, oferă o soluție numerică pentru problema

minimizării unei funcții neliniare. Este o metodă stabilă și converge rapid.

45

Acest algoritm reprezintă o combinație a metodei gradient (metoda celei mai abrupte

descreșteri) cu algoritmul Gauss-Newton. Din fericire, ea moștenește viteza rapidă de

convergență a algoritmului Gauss-Newton și stabilitatea metodei gradient. Este mai robustă

decât Gauss-Newton, deoarece în multe cazuri poate converge bine chiar dacă suprafața pe

care se face căutarea este una mai complexă cu minime locale.

La fel ca toate variantele cvasi-Newton, şi această variantă a fost proiectată să

aproximeze antrenarea de ordinul doi fără a fi nevoie să calculeze matricea hessiană. Atunci

când funcţia de performanţă are forma unei sume de pătrate, matricea hessiană poate fi

aproximată astfel:

(5.30)

(5.31)

Unde :

e – reprezintă vectorul erorilor

t – ținta

y – valoarea outputului rețelei

k – numărul de clase

m – numărul de exemple

(5.32)

Matricea Jacobiană (Ja) este calculată folosind o tehnică BP, care este mult mai puţin

complexă decât calculul matricei Hessiene, dar cu mici diferențe față de tehnica standard. În

primul rând, pentru fiecare model, în algoritmul BP, este nevoie de un singur proces de

propagare inversă, în timp ce în algoritmul Levenberg-Marquardt procesul propagării înapoi

trebuie repetat pentru fiecare ieșire separat pentru a obține rândurile consecutive ale matricei

Jacobiene. Și în al doilea rând, pentru antrenarea LM se folosește o funcție cost de tipul suma

erorilor la pătrat (SSE – sum squared error).

Algoritmul LM foloseşte următoarea actualizare:

(5.33)

Atunci când scalarul μ este zero, algoritmul este chiar metoda Newton cu matricea

hessiană aproximată. Cand μ este mare, aceasta devine “gradientul descendent”, cu un pas

mic. Metoda Newton este mai rapidă și mai precisă în privința minimului erorii, astfel că

ideea este de a ne deplasa către metoda lui Newton, cât mai repede posibil. Ca atare, μ este

micșorat după fiecare pas realizat cu succes (care face o reducere în funcție de performanță) și

este mărit doar când, în urma realizării pasului, crește funcția de performanță. În acest fel,

funcția de performanță va fi întotdeauna redusă la fiecare iterație a algoritmului.

46

Inconvenientul algoritmului Levenberg-Marquardt este cantitatea mare de memorie

necesară în calculul Jacobianului, astfel, în cazul reţelei neuronale implementate acest

algoritm nu poate fi aplicat.

Pentru că aceste tehnici de ordinul doi s-au dovedit prea pretențioase din punct de

vedere computațional, vom explora în continuare câteva tehnici de ordinul unu și anume

metode de gradient conjugat.

ALGORITMI CU GRADIENT-CONJUGAT

În matematică, metoda gradientului conjugat este un algoritm pentru rezolvarea

numerică a unor sisteme de ecuații liniare, și anume cele ale căror matrice este simetrică și

pozitiv-definită. Metoda gradientului conjugat este o metodă iterativă, astfel încât poate fi

aplicată la sisteme rare, care sunt prea mari pentru a fi manipulate prin metode directe, cum ar

fi descompunerea Cholesky. Astfel de sisteme apar de multe ori la rezolvarea numerică a

ecuațiilor cu derivate parțiale.

Metoda gradientului conjugat poate fi, de asemenea, folosit pentru a rezolva

problemele de optimizare fără restricții, cum ar fi minimizarea energiei. Acesta a fost introdus

prima dată de către Magnus Hestenes și Eduard Stiefel.

Figura 5.14 O comparație între viteza de convergența a metodei gradient (verde) și

metoda gradienților conjugați (roșu)

Pentru o problemă generală neconstrânsă ∈ , putem aplica metoda

gradienților conjugați folosind aproximări adecvate. Voi prezenta în cele ce urmează câteva

abordări în această direcție. Voi face următoarele notații: .

Aceste asocieri sunt reevaluate la fiecare pas al metodei. Când aplicăm această metodă la

probleme nepătratice (cum este aceasta), ea nu va produce o soluție în n pași ( ∈ . În

acest caz se continuă procedura, găsind noi direcții și terminând atunci când un anumit criteriu

este satisfăcut ( . Este de asemenea posibil ca după n sau n+1 pași, să reinițializăm

algoritmul cu și să începem metoda gradienților conjugați cu un pas de gradient.

Pentru astfel de probleme fără constrângeri, algoritmul gradienților conjugați ar fi

următorul:

1.

2.

3.

47

4. după n iterații înlocuim cu și repetăm întregul proces

O proprietate atractivă a metodei gradienților conjugați este aceea că nu e nevoie de

căutarea unidimensională, adică nu trebuie să găsim o lungime a pasului. Totuși, această

abordare are dezavantajul că cere evaluarea Hessianei funcției de performanță, despre care am

văzut deja că ne consumă foarte multe resurse și nu poate fi făcută pentru acest proces. De

asemenea, se observă că în cazul general, acestă metodă nu este convergentă.

Din fericire este posibil să se evite folosirea directă a Hessianei. De exemplu, în locul

formulei pentru dat mai sus, putem găsi lungimea pasului printr-o metodă de căutare

unidimensională, iar funcțiile implementate în Matlab folosesc în mod implicit căutarea prin

metoda lui Charalamous.

Metoda lui Charalamous a fost creată special pentru a fi folosită în combinație cu

metodele de gradient conjugat pentru antrenarea rețelelor neuronale. Este o metodă hibridă ce

folosește atât interpolare cubică cât și secționare (asemănătoare metodei secțiunii de aur).

Acestă metodă are nevoie atât de gradientul funcției de performanță (pe care le obținem ușor

din metoda BP) pe lângă calculul valorii acesteia, dar compensează prin găsirea rapidă a

minimului.

Figura 5.15 Modul de funcționare a metodelor cu gradient conjugat

O soluție pentru transformarea algoritmului cu gradient conjugat într-unul convergent

este modificarea adecvată a parametrului . Vom explora în continuare câteva din aceste

soluții.

5.9 Actualizarea Fletcher-Reeves (traincgf)

Ca toți algoritmii de tip gradient conjugat, CGF pornește prin a căuta în direcția celei

mai abrupte descreșteri (gradientul negativ) la prima iterație (pasul 1 din algoritmul de mai

sus). Apoi se realizează o linie de căutare, pentru a determina distanța optimă de mișcare de-a

lungul direcției curente de căutare (pasul 2 folosind Metoda lui Charalamous pentru

determinarea lui ).

Apoi se determină următoarea direcție de căutare, astfel încât ea este “conjugată”

direcțiilor de căutare anterioare. Procedura tipică de determinare a noii direcții de căutare este

48

de a combina noua direcție a celei mai abrupte descreșteri cu direcția de căutare anterioară

(pasul 3).

La algoritmul Fletcher-Reeves modul în care este calculată constanta βk este

următorul :

(5.34)

Acesta este raportul între pătratul normei gradientului curent și pătratul normei

gradientului precedent.

Rezultatele obţinute în urma antrenării cu algoritmul CGF sunt următoarele:

Acuratețea setului de date: 96.257669 – stânga

Acuratețea setului de date: 95.858896 - dreapta

Acuratețea setului de date: 95.736196 – față

Acuratețea setului de date: 100.000000 - spate

Figura 5.16 Performanțele Metodei Gradient Conjugat cu actualizarea Fletcher-Reves pe setul

de date

Figura 5.17 Clasificarea rezultatelor folosind CGF

49

Se poate observa că aceste rezultate sunt foarte bune, cu o medie de peste 95% din

comenzi identificate corect. Minimizarea a fost oprită deoarece a ajuns la numărul maxim de

iterații permise și anume 100, dar se poate observa din graficul din partea stângă că atât

valorile funcției cost pe setul de date pe care se face antrenarea, cât și pe setul de validare au

fost în continuă descreștere pe tot parcursul antrenării. Același lucru se poate spune și despre

valoarea gradientului dacă ne uităm în graficul din dreapta sus, totuși putem constata că

valorile pentru αk au fost aproape constante și foarte mici în intervalul , ceea ce

înseamnă că convergența este una destul de lentă. Ca timp de antrenare, acest algoritm a avut

o medie de 15 secunde pe iterație, una de 3 ori mai mare față de Rprop.

5.10 Actualizarea Polak-Ribiere (traincgp)

Polak si Ribiere au propus o altă variantă a algoritmului cu gradient conjugat. Ca și în

cazul Fletcher-Reeves, direcția de căutare la fiecare iterație se determină cu:

Pentru actualizarea Polak-Ribiere, constanta βk

se calculează cu:

,

(5.35)

Acesta este produsul scalar al variațiilor anterioare în gradient cu gradientul curent,

divizat cu pătratul normei gradientului precedent. În esență, algoritmul este asemănător cu cel

din capitolul 5.8 și ne așteptăm la performanțe similare.

Acuratețea setului de date: 96.196319 - stânga

Acuratețea setului de date: 95.981595 - dreapta

Acuratețea setului de date: 95.920245 – față

Acuratețea setului de date: 99.877301 – spate

Așa cum am menționat, performanțele sunt asemănătoare și destul de ridicate. Acest

lucru se poate observa și mai bine comparând datele din Figura 5.17 cu cele din figura 5.18.

Figura 5.18 Clasificarea rezultatelor folosind CGP

50

La fel ca la algoritmul Fletcher-Reeves, au fost obținute valori foarte mici pentru αk

dar poate puțin mai mari, în intervalul , deși folosesc aceeași tehnică de căutare

unidimensională. Diferența apare la graficele funcției de performanță (Figura 5.19 stânga),

unde se poate vedea că valorile pe setul de validare sunt mai mari decât cele pe setul de

antrenare și ele diverg spre final, lucru care nu este de dorit și indică o mai proastă

generalizare a rezultatelor.

Figura 5.19 Performanțele Metodei Gradient Conjugat cu actualizarea Polak-Ribiere pe setul

de date

Este dificil de prevăzut care algoritm dintre aceștia doi (CGF și CGP) va avea cea mai

bună performanță într-o problemă. Memoria necesară pentru Polak-Ribiere (patru vectori)

este puțin mai mare decât pentru Fletcher-Reeves (trei vectori).

5.11 Resetarea Powell-Beale (traincgb)

Pentru toți algoritmii cu gradient conjugat, direcția de căutare va fi periodic resetată la

negativul gradientului. Punctul standard de resetare apare când numărul de iterații este egal cu

numărul de parametri din rețea (Pasul 4), dar există și alte metode de resetare care pot

îmbunătăți eficiența antrenării. O astfel de metodă de resetare a fost propusă de Powell și

Beale. Această tehnică se aplică dacă există o foarte mică ortogonalitate între gradientul

curent și cel precedent. Acest fapt este testat în următoarea inegalitate:

(5.36)

Dacă această condiție este satisfăcută, direcția de căutare se resetează la negativul

gradientului. La metodele CGF și CGP, resetarea se făcea în mod clasic după 3726307 de

iterații (numărul de parametri din vectorul θ), ceea ce este practic imposibil de atins. Chiar

dacă nu am impune o limitare pe numărul de iterații, cel mai probabil nu s-ar ajunge la un

restart fiindcă s-ar îndeplini alte condiții de oprire. Deci este posibil ca acestă metodă să dea

rezultate mai bune.

În urma rulării algoritmului CGB, am obținut următoarele rezultate :

Acuratețea setului de date: 96.411043 – stânga

Acuratețea setului de date: 95.797546 - dreapta

Acuratețea setului de date: 95.797546 – față

Acuratețea setului de date: 99.815951 – spate

51

Din nou, am găsit rezultate foarte bune și asemănătoare precedentelor, în ciuda

modificărilor aduse la modul în care se face resetarea. Mai mult, dacă ne uităm la graficul

gradientului din figura 5.20 (dreapta sus), se poate constata că acesta are o descreștere

monotonă, deci putem presupune ca nu a fost resetat niciodată. Din graficul funcției de

performanță (figura 5.20 stânga), observăm că spre final valorile pe setul de validare diverg

față de cele pe setul pe care se face antrenarea, comportament similar cu cel întâlnit la metoda

CGP.

Figura 5.20 Performanțele Metodei Gradient Conjugat cu resetarea Powell-Beale pe setul de

date

Deși teoretic algoritmul CGB ar trebui să fie mai bun decât CGP și CGF, performanța

în cazul unei probleme date este dificil de prevăzut. Necesitățile de memorie pentru

algoritmul Powell-Beale (șase vectori) sunt ceva mai mari decât cele pentru Polak-Ribiere

(patru vectori), dar ca timp de execuție am obținut tot 15 secunde pe iterație.

Un lucru important care trebuie menționat în legătură cu cei trei algoritmi de gradient

conjugat pe care i-am discutat până acum este faptul că, pentru a putea efectua aceste teste, a

fost necesar să setăm toleranța pentru căutarea unidimensională a pasului foarte mică. Așa

cum s-a văzut și în grafice, pasul αk a avut valori foarte mici, iar prin setarea acestei toleranțe

am evitat oprirea după 5-6 iterații a algoritmului.

5.12 Algoritmul Gradientului Conjugat Scalat (trainscg)

Fiecare dintre metodele de gradient conjugat utilizate până acum folosesc acea căutare

unidimensională pentru determinarea pasului αk. Acestă căutare este destul de complexă din

punct de vedere al calculelor, din moment ce necesită ca răspunsul reţelei la toate intrările de

antrenare să fie calculat de câteva ori pentru fiecare căutare. Algoritmul Gradientului

Conjugat Scalat (SCG – Scaled Conjugate Gradient), dezvoltat prima dată de Martin Moller,

a fost conceput pentru a evita această căutare unidimensională. Ideea acestui algoritm este de

a combina elemente ale metodei Levenberg-Marquand (Capitolul 5.7) pentru determinarea

pasului αk cu algoritmul clasic de gradient conjugat.

O primă versiune a algoritmului SCG era în felul următor :

1. Se alege un vector inițial și se setează:

52

2. Se calculează informația de ordinul doi:

(5.37)

(5.38)

3. Se calculează mărimea pasului:

(5.39)

(5.40)

4. Se actualizează vectorul ponderilor:

(5.41)

(5.42)

5. Dacă k mod N este 0 atunci se resetează algoritmul:

Altfel se creează o nouă direcție conjugată:

(5.43)

(5.44)

Acesta este oarecum algoritmul clasic de gradient conjugat adaptat pentru calcularea

direcției prin evitarea folosirii hessianei. Dar tot avem nevoie de hessiană în calcularea lui

cel pe care vrem să îl eliminăm. Pentru a face acest lucru, utilizăm definiția derivatei :

(5.45)

Dar nici această aproximare nu este eficientă deoarece ea funcționează (adică

algoritmul converge) doar în cazurile în care hessiana este pozitiv definită. S-a demonstrat că

hessiana funcției de performanță poate ajunge la valori infinite-nedefinite (NaN – not a

number) în diferite zone din spațiul ponderilor, ceea ce explică de ce alogritmul eșuează în

încercarea de a minimiza J. De aceea,încercăm o abordare din metoda LM pentru a modifica

algoritmul. Ideea este să introducem un scalar (multiplicator Lagrange), al cărui rol este de

a regula valorile lui .

(5.46)

Iar la fiecare iterație, este ajustat în funcție de semnul lui (5.38), care ne indică dacă

este pozitiv definită sau nu. Dacă atunci este mărit și este reestimat. Fie

noul notat cu și noul notat cu atunci :

(5.47)

Pentru a determina cu cât trebuie mărit , pornim de la condiția :

(5.48)

Ceea ce înseamnă că dacă vrem ca hessiana să fie pozitiv definită, trebuie să mărim cu

. Nu se cunoaște exact cu cât ar fi optimal să creștem pe , dar o alegere rezonabilă

ar fi :

) (5.49)

53

Aceasta duce la :

(5.50)

Și atunci putem calcula mărimea pasului :

(5.51)

Se poate observa din ecuația (5.51) dependența dintre și și anume faptul că,

dacă crește, scade ceea ce era de așteptat. Totuși, pentru a găsi o bună aproximare a

hessianei, ar trebui găsit un mecanism pentru modificarea lui chiar și când hessiana este

pozitiv definită.

Fie :

(5.52)

Unde

reprezintă o aproximare

Taylor a unui punct din vecinătatea lui . Deci reprezintă un factor ce măsoară cât de

bine aproximează pe , adică cu cât e mai apropiat de 1, cu atât e

mai bună aproximarea. Astfel putem mări și micșora pe după următoarea regulă:

Varianta finală a algoritmului SCG este următoarea :

1. Se alege un vector inițial și scalarii , se setează:

și Test = Adevărat

2. Dacă Test este Adevărat atunci:

3. Se scalează :

4. Dacă , atunci facem hessiana pozitiv definită :

5. Se calcuează pasul:

54

6. Se calculează factorul

7. Dacă atunci:

a. Dacă k mod N este 0 atunci se restează algoritmul :

Altfel se creează o nouă direcție conjugată:

b. Dacă atunci:

8. Dacă atunci:

9. Dacă mergi la pasul 2

Altfel returnează ca fiind minimul căutat.

Pentru fiecare iterație este necesar un calcul pentru și două pentru , ceea

ce înseamnă că algoritmul se poate implementa cu o complexitate de O(2N2) , unde N este

numărul de parametri din Theta. Acesta este mult redus față de algoritmii CGF, CGP, CGB

sau BFGS, care au o complexitate O(2-15 N2) pe iterație din cauza căutarii unidimensionale.

Deși este posibil ca algoritmul SCG să aibă nevoie de mai multe iterații pentru a converge,

numărul de calcule este mai mic și deci compensează. Ca cerințe de memorie SCG nu este

foarte pretențios având aproximativ aceleași cerințe ca CGF.

Rezultatele obținute cu acest algoritm sunt următoarele:

Acuratețea setului de date: 97.975460 – stânga

Acuratețea setului de date: 97.453988 - dreapta

Acuratețea setului de date: 95.797546 – față

Acuratețea setului de date: 100.000000 - spate

Acestea sunt cele mai bune performanțe de până acum, oprirea fiind făcută atât

datorită condiției ca valoarea funcției de performanță să fie mai mică decât 0.01, cât și datorită

fenomenului de oprire rapidă (Figura5.21).

55

Figura 5.21 Antrenarea rețelei cu metoda SCG

Se poate constata din figura de mai sus că algoritmul a funcționat timp de 188 de

iterații, deci mai mult decât ceilalți algoritmi folosiți până acum dar și timpul este unul destul

de bun, de aproximativ 10 secunde pe iterație, adică cu 5 secunde mai puțin decât ceilalți

algoritmi de gradient conjugat.

5.22 Performanțele Metodei Gradient Conjugat pe setul de date

În figura 5.22, în partea dreaptă, se poate observa funcționarea metodei de oprire

rapidă (engl. early-stopping), care oprește antrenarea dacă după 6 iterații consecutive

performanțele pe setul de validare diferă de cele de pe setul de antrenare. Acest lucru se

întamplă, după cum se poate observa în partea stângă a figurii.

56

Figura 5.23 Clasificarea rezultatelor folosind SCG

În figura 5.23, este reprezentat numărul de ieșiri ale rețelei care au clasificat corect

comanda și ne sunt confirmate și prin această modalitate rezultatele foarte bune. Și în figura

5.24, se poate observa gradul de utilizare al resurselor sistemului pentru fiecare din cei 4

algoritmi cu gradient conjugat (de la stanga la dreapta CGF , CGP , CGB și SCG).

Figura 5.24 Gradul de utilizare al sistemului în timpul antrenării cu algoritmi cu gradient

conjugat

6. Concluzii și planuri de viitor

În acestă lucrare am tratat posibilitatea realizării unui sistem capabil să controleze un

vehicul prin urmărirea unei persoane făcând acest lucru, așa cum s-a încercat și în proiectul

ALVINN. Pentru a face acest lucru, am construit sistemul fizic format dintr-o mașinuță cu

telecomandă radio, un telefon cu cameră pentru achiziția și transmiterea imaginilor și un

laptop pentru prelucrarea imaginilor și generarea comenzii prin intermediul unei rețele

neuronale.

57

Rețelele neuronale sunt capabile de a realiza corect orice calcul liniar sau neliniar și pot

face clasificări pentru aproape orice set de date. Totuși, deși o rețea antrenată este, teoretic,

capabilă de realizări corecte, este posibil ca propagarea înapoi și variațiile ei să nu găsească

întotdeauna soluția. O rată de instruire prea mare duce la o instruire instabilă. Reciproc, o rată

de instruire prea mică, duce la un timp de antrenare foarte lung. Alegerea unei bune rate de

instruire pentru o rețea multistrat neliniară nu este deloc ușoară. În cazul algoritmilor de

antrenare rapidă, este indicat să se aleagă valorile implicite ale parametrilor.

Suprafața de eroare a unei rețele neliniare este complexă. Problema este că funcțiile de

transfer nenliniare ale rețelelor neuronale introduc multe minime locale în suprafața de eroare.

Cum gradientul coborâtor se realizează pe suprafața de eroare, este posibil ca soluția să cadă

într-unul din aceste minime locale. Aceasta se poate întâmpla în funcție de condițiile inițiale

de plecare. Căderea într-un minim local poate fi bună sau rea, depinzând de cât de aproape

este minimul local de cel global si cât de mică eroare se cere. În orice caz, trebuie reținut că

deși o rețea multistrat cu propagare inversă și cu suficienți neuroni poate implementa aproape

orice funcție, propagarea inversă nu va gasi întotdeauna ponderile corecte pentru soluția

optimă. Rețeaua va trebui reinițializată și reantrenata de câteva ori pentru a avea garanția

obținerii celei mai bune soluții. Efectuarea mai multor antrenări pe seturi mari de date poate

dura destul de mult, de aceea este importantă găsirea unui algoritm rapid și eficient. În urma

experimerimentării cu diferite tehnici de optimizare, am constatat următoarele lucruri:

- algoritmul cu gradient coborâtor este în general foarte lent, întrucât pentru o instruire

stabilă cere rate de instruire mici

- metoda Rprop este cel mai rapid algoritm în problemele de recunoaștere a formelor, în

acest caz a imaginilor, performanța ei scade pe măsura ce se reduce eroarea impusă.

Cererile de memorie pentru acest algoritm sunt relativ mici în comparatie cu ceilalți

algoritmi.

- algoritmii cu gradient conjugat, în particular SCG, funcționează bine într-o varietate

largă de probleme, în special pentru rețelele cu un număr mare de ponderi, cum este și

acesta. Algoritmul SCG este aproape la fel de rapid ca Rprop în problemele de

recunoaștere a formelor. Performanța lui nu scade la fel de repede ca la Rprop când se

reduce eroarea.

- Algoritmii cu gradient conjugat au cereri relativ mici de memorie și au demonstrat

rezultate foarte bune numerice, cu toate acestea în practică ponderile antrenate cu

metoda SCG s-au dovedit cele mai eficiente

- Algoritmii BFGS și LM sunt teoretic foarte rapizi, dar sunt aproape imposibil de

implementat pe o rețea cu un număr mare de ponderi cum este și cazul acesta, nici

dacă se aplică o tehnică de reducere a memoriei (prin care se reduce de fapt

dimensiunea Jacobianului, prin împărțirea acestuia în două submatrici egale, la

algoritmul LM) nu s-a putut realiza antrenarea

Într-un final, am ales ca algoritm de bază pentru antrenare metoda SCG. În cazul în care

acesta nu dovedește rezultate foarte bune pentru un set de date, se poate oricând încerca o altă

metodă de gradient conjugat sau chiar metoda RProp, care a demonstrate rezultate destul de

bune.

58

Figura 6.1 Exemplu de traseu pentru antrenare

În urma utilizării sistemului final pe diferite trasee ce conțin un drum cu o singură

bandă de tipul celei din Figura 6.1, am remarcat că vehiculul (mașinuța) se poate deplasa cu o

viteză destul de mare, chiar și pe drumuri mai înguste, rețeaua fiind capabilă să genereze

comenzi suficient de bune pentru a urmări traseul impus. Chiar dacă antrenarea s-a făcut pe

un anumit traseu și testarea pe altul, sistemul s-a descurcat destul de bine, singura condiție

este ca drumul și respectiv benzile care îl delimitează să fie de culori diferite și de contrast cât

mai mare. Pentru a evita ieșirea de pe traseu, s-a impus o rată de eșantionare mică astfel încât,

chiar dacă sunt generate eronat unele comenzi, vehiculul să fie redresat în timp util. Un mare

avantaj al acestei metode de generare al comenzilor cu rețea neuronală este faptul că acest

lucru se face foarte rapid (sunt necesare 3726307 de înmulțiri și aplicarea funcției sigmoid pe

151 de numere pe un procesor de 2.53 Ghz deci ~ 20 ms , dar la care se adună timpul în care

se face achiziția și transmisia), permițând impunerea unei rate de eșantionare mică.

După numai 2-3 minute de “condus” și înregistrat exemple, se pot găsi ponderi pentru

o rețea destul de eficientă. O problemă care apare este faptul că în general “șoferul” nu va

părăsi traseul, iar în cazul în care acest lucru se va întâmpla (când sistemul funcționează

automat), rețeaua nu va ști cum să revină pe traseu din moment ce nu a mai întâlnit acest caz

înainte. Acest lucru poate fi rezolvat prin crearea de exemple sintetice prin rotirea imaginilor

pentru a simula cum ar arăta drumul către stânga sau către dreapta. O altă problemă pe care

am întâlnit-o, este faptul că întreg procesul depinde și de luminozitate și pentru aceasta există

două posibile rezolvări. Una prin creerea unor imagini sintetice cu valori ale pixelilor mai

mici (adică mai apropiate de negru, mai întunecate) și antrenarea rețelei și pe aceste exemple.

Cea de-a doua soluție este instalarea unei surse de lumină lângă cameră, astfel încât

luminozitatea să fie constantă.

59

Figura 6.2 Interfața sistemului

În figura 6.2, apare interfața cu care interacționează utilizatorul. În partea stângă sunt

afișate mesaje de control pentru diagnosticarea sistemului, iar în partea dreaptă sunt afișate

imaginile pe care le primește rețeaua. Se poate observa în colțul din stânga sus, cu roșu, că

sistemul este în modul automat (A) și comanda la momentul respectiv este înainte și dreapta

(săgețile), cum ne și asteptam, având în vedere imaginea. În partea de jos numerele afișate

sunt chiar ieșirile rețelei, evident cele mai mari reprezintă și comenzile. În dreapta jos este

afișat cuvântul “NORMAL” care semnifică modul de viteză (NORMAL / RAPID) despre

care am menționat și în capitolul 4.3.

În timpul execuției aplicației, a fost utilizat sub 40% din capacitatea procesorului și

sub 3 Gb de memorie RAM, ceea ce înseamnă că în paralel se pot executa și alte sarcini pe

aceeași unitate de procesare.

Consider că acest experiment a fost unul de succes deoarece condusul unui vehicul

este un proces dintr-un domeniu plin de zgomote și incertitudini în care aproape toate

elementele din jur contribuie cu informație iar rețelele neuronale au acestă capacitate de a

absorbi un volum mare de informații și a păstra doar elementele utile. Un alt argument este

faptul că nu există nicio teorie care să ne spună exact cum să conducem, dar este ușor de

învățat din exemple lucru care ne duce cu gândul la o metodă care poate învăța din exemple,

iar RNA sunt printre candidații principali.

Ca în orice proiect există încă lucruri de îmbunătățit, unul dintre ele ar fi antrenarea

rețelei on-line, lucru care ar duce la rezultate mai bune datorită numărului mare de exemple,

dar ar putea creea și unele inconveninte prin prisma resurselor (computaționale) necesare. O

altă temă de cerecetare pentru viitor este încercarea reducerii dimensiunii imaginilor prin

utilizarea algoritmilor de compresie de date și verificarea dacă vehiculul va păstra aceleași

rezultate bune sau va eșua în urmărirea traseului. Altă idee, poate puțin mai ușor de

implementat, este folosirea tuturor senzorilor, cum ar fi cel de accelerometru (pe care îl avem

achiziționat deja) pentru reglarea vitezei dar și a GPS-ului pentru crearea unor hărți.

La o scară mai mare, adică pe mașini reale, cred că această soluție ar putea fi

implementată cu succes, pentru transportul substanțelor toxice în fabrici pe un traseu

predefinit, transportul minereurilor în mine sau chiar a oamenilor via tren, toate cu

supraveghere umană de la distanță bineînțeles.

60

7. Bibliografie

Ben Krose and Patrick van der Smagt. 1996. An introduction to Neural Network.

University of Amsterdam, Amsterdam

Dumitrescu, D., Costin, H., 1996. Retele neuronale; teorie si aplicatii. Teora. Bucuresti,.

Dumitrache I. 2010. Ingineria reglarii automate. Ed. Politehnica Press, Bucuresti

Andrew Ng. 2010. Machine Learning Course. Stanford, California

Necoara Ion. 2013. Metode de optimizare numerică. Universitatea Politehnica. București

Popa V., Demeter S., Oancea R., Hangan A. 2006. Aplicatii ale reţelelor neuronale în

prelucrarea imaginilor. Bucuresti

Pomerleau, D.A. (1991) Efficient Training of Artificial Neural Networks for Autonomous

Navigation. In Neural Computation 3:1 pp. 88-97.

Reto Meier. 2008. Professional Android™ Application Development. Wiley Publishing.

Indianopolis

Stefan Tanasa, Cristian Olaru, Stefan Andrei. 2003. Java de la 0 la expert. Polirom.

Bucuresti

Bishop, C.M. 1995. Neural Networks for Pattern Recognition. Oxford University Press.

Oxford

Moller F. 1993, A Scaled Conjugate Gradient Algorithm for Fast Supervised Learning.

Neural Networks, Vol. 6, pp. 525-533

Sjöberg, J. and Ljung, L. 1992, Overtraining, Regularization, and Searching for minimum

in Neural Networks. Technical Report LiTH-ISY-I-1297. Department of Electrical

Engineering, Linkoping University. S-581 83 Linkoping. Suedia

Enăchescu, C., 1995 Learning the Neural Networks from the Approximation Theory

Perspective. Intelligent Computer Communication ICC'95 Proceedings, 184-187, Technical

University of Cluj-Napoca, Romania.

Powell, M.J.D.,1977, Restart procedures for the conjugate gradient method. Mathematical

Programming, Vol. 12, pp. 241-254

Scales, L.E., 1985. Introduction to Non-Linear Optimization, New York, Springer-Verlag

Battiti, R. 1992. First and second order methods for learning: Between steepest descent and

Newton's method. Neural Computation, Vol. 4, No. 2, pp. 141-166

Riedmiller. 1993. Proceedings of the IEEE International Conference on Neural Networks

(ICNN), San Francisco. pp. 586-591

BALAN, C. G., 2006 Metode ale Inteligentei Artificiale in Studiul Sistemelor Mecanice, E-

Book, in WEB site: www.ugal.ro – Faculties - Faculty of Mech. Eng.

Engineering Guideline EG 28. 1993. Annotated Glossary of Essential Terms for Electronic

Production SMPTE.

Open Handset Alliance (5 noiembrie 2007). Industry Leaders Announce Open Platform for

Mobile Devices. Press release

Arduino Duemilanove.2009, http://arduino.cc/en/Main/arduinoBoardDuemilanove