Recunoașterea autoturismelor în funcție de marcă și model [304200]

Universitatea “Politehnica” [anonimizat]̆

[anonimizat]: [anonimizat]. Ing. [anonimizat]-Agreș

2020

Declarație de onestitate academică

Prin prezenta declar că lucrarea cu titlul “Recunoașterea autoturismelor în funcție de marcă și model”, [anonimizat] a Universității “Politehnica” [anonimizat] , programul de studii Electronică Aplicată este scrisă de mine și nu a mai fost prezentată niciodată la o facultate sau instituție de învățământ superior din țară sau străinătate.

[anonimizat], [anonimizat]. [anonimizat], [anonimizat]. Reformularea în cuvinte proprii a textelor scrise de către alți autori fac referință la sursă. Înțeleg că plagiatul constituie infracțiune și se sancționează conform legilor în vigoare.

[anonimizat], [anonimizat], experimente și măsurători. Înțeleg că falsificarea datelor și rezultatelor constituie fraudă și se sancționează conform regulamentelor în vigoare.

București, 16.05.2020

Absolvent: [anonimizat]-[anonimizat] 1.1 Curba de sensibilitate pentru cele trei tipuri diferite de senzori

Figura 1.2 Aranjarea senzorilor RGB (Sursa: [1])

Figura 1.3 Efectul de tablă de șah expus asupra unei imagini de 256×256 pixeli (stânga) și efectul micșorării numărului de pixeli la 32×32 pixeli (dreapta) (Sursa: [1])

Figura 1.4 Procesul de îmbunătățire al unei imagini folosind filtrarea liniară

Figura 2.1 Schema unui neuron biologic (stânga) și un model matematic al unui neuron (dreapta)(Sursa: [6])

Figura 2.2 [anonimizat] (Sursa: [7])

Figura 2.3 Funcția de activare a [anonimizat] T(Sursa: [7])

Figura 2.4 Arhitectura perceptronului simplu(Sursa: [7])

Figura 2.5 Funcția de activare a perceptronului simplu(Sursa: [7])

Figura 2.6 [anonimizat](Sursa: [7])

Figura 2.7 Funcția de activare folosită de neuronul Adaline (Sursa: [7])

Figura 2.8 Perceptronul simplu (Sursa: [7])

Figura 2.9 Adaline (Sursa: [7])

Figura 2.10 Gradient negativ pentru diferite rate de învățare (Sursa: [11])

Figura 2.11 [anonimizat] (Sursa: [10])

Figura 2.12 [anonimizat] (Sursa: [10])

Figura 2.13 [anonimizat] a funcției cost (Sursa: [10])

Figura 2.14 Funcția de activare sigmoid(Sursa: [6])

Figura 2.15 Funcția de activare ReLU(Sursa: [6])

Figura 3.1 Schema unei rețele neurale artificiale(Sursa: [6])

Figura 3.2 Schema unei rețele neurale convoluționale(Sursa: [6])

Figura 3.3 Structura unui sistem de inteligență artificială(Sursa: [14])

Figura 3.4 Sistemul de prelucrare a datelor de intrare într-un strat convoluțional(Sursa: [15])

Figura 3.5 Aplicarea unui strat de tip Max Polling asupra unei componente de adâncime(Sursa: [11])

Figura 3.6 Aplicarea unui strat de tip Max Polling asupra unui volum de intrare de adâncime mare(Sursa: [11])

Figura 3.7 Bloc component al unei rețele neurale reziduale (Sursa: [17])

Figura 3.8 Model de rețea neurală convoluțională cu n straturi (Sursa: [17])

Figura 3.9 Reprezentarea funcției reziduale(Sursa: [17])

Figura 3.10 Diagrama arhitecturii ResNet-50(Sursa: [17])

Figura 4.1 Procesul de realizare al algoritmului

Figura 4.2 Exemple ale setului de date Stanford Cars Dataset (Sursa: [18])

Figura 4.3 Găsirea minimului local al funcției cost, w fiind ponderea (Sursa: [21])

Figura 4.4 Graficul funcției cost Entropie-Încrucișată pentru o valoare de adevăr = 1 (Sursa: [23])

Figura 4.5 Arhitectura unei rețele neurale cu 34 straturi (stânga) și arhitectura unei rețele neurale reziduale de tip ResNet34 (dreapta) (Sursa: [24])

Figura 4.6 Modulul hardware asamblat

Figura 4.7 Modulul hardware funcțional

Figura 4.8 Ansamblul de fișiere încărcat pe Raspberry

Figura 4.9 Interfața grafică destinată utilizatorului, înaintea clasificării

Figura 4.10 Interfața grafică destinată utilizatorului, după clasificare

Figura 5.1 Rezultatele obținute în urma antrenării și trecerea lor pe un grafic pentru ResNet50 – 3 Clase (stânga) și ResNet34 – 196 Clase (dreapta)

Figura 5.2 Clasificare obținută în urma utilizării algoritmului din terminalul Python (partea de sus) și rezultatele obținute utilizând prototipul hardware (partea de jos)

Figura 5.3 Fișierul istoric.txt unde vor fi scrise rezultatele anterioare

Figura 5.4 Clasificare obținută în urma utilizării algoritmului din terminalul Python pentru rețeaua ResNet34 cu 196 clase

Lista acronimelor

SIFT – Scale Invariant Feature Transform

SURF – Speeded Up Robust features

HOG – Histogram of Oriented Gradient

NNC – Nearest Neighbors Classifier

ANN – Artificial Neural Networks

SVM – Support Vector Machines

VMMR – Vehicle Make And Model Recognition

RGB – Red Green Blue

CNN – Convolutional Neural Network

DBN – Deep Belief Network

DBM – Deep Boltzmann Machines

BVSM – Binary Support Vector Machine

ReLU – Rectified Linear Unit

MLP – Multi Layer Perceptrons

LTG – Linear Threshold Gate

SGD – Stochastic Gradient Descent

DNN – Deep Neural Network

SGD – Stochastic Gradient Descent

GD – Gradient Descent

DIY – Do It Yourself

GPIO – General Purpose In/Out Pins

SSH – Secure Shell

Introducere

Motivație și obiective

În zilele noastre, tratarea cantităților mari de date este un subiect de cercetare foarte activ și căutat, lumea generând continuu cantități mari de date ce ajută cercetătorii și pasionații în domeniu să le proceseze și să extragă informațiile de interes. O mare parte dintre aceste date este constituită de imagini și video-uri, acest lucru datorându-se miliardelor de dispozitive mobile, tablete și camere foto care pot imortaliza diferitele fenomene ale naturii sau obiectele din mediul înconjurător. În consecință, doar pe Facebook au fost încărcate peste 250 de miliarde de fotografii și, în medie, sunt încărcate peste 350 de milioane de noi fotografii în fiecare zi.[2]

În mod similar, YouTube raportează că 400 de ore de videoclip sunt încărcate în fiecare minut. În plus, există date care nu se află încă pe internet, cum ar fi milioanele de camere de supraveghere ale parcărilor, autostrăzilor, aeroporturilor și magazinelor care funcționează 24/7, iar sarcina critică este cum să fie înțeleasă informația și cum să se folosească aceasta în vederea obținerii scopului pe care ni-l propunem.[3]

Fiind unul dintre subiectele fundamentale ale domeniului computer vision, clasificarea imaginilor servește ca un mod de bază a organizării unor imaginii într-un mod nesupervizat. Acest lucru implică dacă o imagine conține sau nu o anumită clasă specifică de obiecte.[4]

Rețelele neurale au avut un impact de mare amploare în domeniul științei actuale, deoarece au fost elaborate pe baza modelului creierului biologic, astfel făcând o corelație foarte puternică între viitoarele cunoștințe. În formarea acestor rețele, se formează anumite legături între neuronii creați pe baza algoritmilor matematici, legături ce pot fi asemănate cu sinapsele umane. În aplicațiile din ziua de azi, aceste rețele sunt din ce în ce mai răspândite, iar domeniul lor de aplicabilitate este extrem de vast, în marea parte a timpului, acestea fiind folosite pentru crearea algoritmilor de clasificare intra-clasă, acest lucru punând în evidență în principal elementele asemănătoare, făcând parte din aceeași clasă, dar cu mici diferențe fine între ele.

Lucrarea de față își propune realizarea unui algoritm pentru recunoașterea unui autoturism după marcă și model iar în cadrul acesteia se va avea în vedere un set de date cu fotografii aparținând diferitelor mărci de autoturisme incluzând anumite modele ale acestora, din mai multe unghiuri, iar astfel se va încerca realizarea clasificării prin aplicarea anumitor filtre imaginilor și utilizarea rețelelor neuronale adânci sau convoluționale pentru extragerea caracteristicilor specifice fiecărui model și obținerea unui procentaj de recunoaștere a autoturismelor în proporție de peste 95%. În final, se dorește implementarea algoritmului realizat pe o plăcuță de dezvoltare de tip Raspberry Pi, alături de un modul pentru captarea imaginilor și un ecran pe care se va afișa clasificarea făcută, astfel încât să ofere utilizatorului posibilitatea de a testa în timp real recunoașterea unui autoturism din viața reală conform claselor de autoturisme utilizate pentru antrenarea algoritmului.

Aplicabilitatea sistemelor VMMR

Sistemele de tip VMMR sunt folosite tot mai des în întreaga lume, în diverse aplicații cum ar fi, supravegherea traficului pentru a afla cu exactitate marca și modelul autoturismului urmărit, utilizarea în parcări și la intrările în orașe a sistemelor de acest tip în vederea elaborării strategiilor noi de marketing pentru convingerea clienților in ceea ce ține de achiziția în materie de autovehicule noi sau rulate si alte aplicații de tip hobby.

Sistemele VMMR clasice folosesc caracteristici locale pentru a reprezenta regiunea de interes a vehiculului, acestea fiind convertite ulterior în caracteristici globale. Tehnicile de extragere a caracteristicilor cum ar fi SIFT, SURF și HOG sunt folosite de majoritatea algoritmilor de recunoaștere a vehiculelor. NNC, ANN și SVM sunt cei mai folosiți clasificatori utili în formarea acestor tipuri de sisteme VMMR.

Acest tip de algoritmi realizați în vederea recunoașterii autoturismelor, reprezintă un rol de o importanță ridicată în sistemele de tip computer vision.Provocările prin care trec sistemele de tip VMMR încep de la achiziția cantităților mari de date necesare pentru elaborarea algoritmilor de recunoaștere, până la variații ale luminii și vremii, umbre, reflexii, plus unul dintre cele mai importante detalii, asemănarea izbitoare ce se poate găsi asupra diferitelor modele de autoturisme.

Spre exemplu, se poate elabora un sistem inteligent de recunoaștere a mărcii și a modelelor autoturismelor care intră și ies dintr-o anumită regiune, având la dispoziție și numerele acestora de înmatriculare pentru a vedea dacă provin din regiunea vizată. Astfel, cu datele colectate, companiile producătoare de autoturisme, pot vizualiza cu ușurință care este impactul pe acea zonă și interesul cumpărătorilor, și astfel să își îmbunătățească strategiile în ceea ce privesc reclamele fizice și televizate pentru public, în vederea sporirii numărului de potențiali cumpărători. Astfel, se evită costurile inutile pentru zonele mari de interes, și se eficientizează zonele cu mai puțin impact asupra clienților.

Structura

Primul capitol al lucrării abordează aspecte teoretice cu privire la imagini și la utilitatea procesării acestora precum și prezentarea unor elemente definitorii cu referire la calitatea unei imagini, cât și a operatorilor folosiți pentru transformarea acestora în cadrul diferitelor proiecte ce susțin clasificarea lor sau simpla îmbunătățire a calității imaginii în vederea utilizării ei în diferite scopuri.

Al doilea capitol prezintă o vedere de ansamblu a modelelor neuronale artificiale, câteva aspecte cu privire la cei mai folosiți algoritmi de clasificare și informații referitoare la funcțiile de activare cele mai des întâlnite ale acestor neuroni.

Capitolul 3 va fi bazat pe diferitele aplicații de clasificare ale obiectelor din mediul înconjurător, câteva aspecte legate de deep learning, urmând să se prezinte arhitectura rețelelor neurale și utilitatea acestora în cadrul lucrării de față.

Al patrulea capitol va scoate în evidență aspectele legate de configurația folosită pentru elaborarea finală a proiectului și datele folosite pentru implementarea algoritmului, cât și arhitectura de calcul folosită în vederea realizării algoritmului de clasificare folosit pentru recunoașterea autoturismelor.

Capitolul 5 își propune prezentarea detaliată a rezultatelor obținute, cât și experimente realizate pe baza diferitelor arhitecturi în vederea testării performanțelor.

Lucrarea se va încheia prin concluziile trase în urma parcurgerii celor 5 capitole, în care au fost elaborate informații strâns legate de domeniul de aplicabilitate al conceptului de machine learning și crearea algoritmului propus în vederea recunoașterii unui autoturism după marcă și model făcând referire la performanțele obținute de acesta pentru numărul restrâns de clase folosite în obținerea antrenării și rezultatul final asupra încărcării a tot ansamblul pe modulul hardware pentru a oferi utilizatorului un produs gata de utilizare.

Capitolul 1.Prelucrarea imaginilor

Imaginea digitală

Procesarea imaginilor susține principalul rol în eficientizarea problemelor ce apar în vederea digitizării și codării imaginilor astfel încât transmiterea sau stocarea lor să fie mai accesibilă, iar în continuare fiind urmărită intensificarea și refacerea acestora pentru ca utilizatorii ce doresc procesarea imaginilor, să poată interpreta datele prezentate.

O imagine pancromatică este o funcție 2D a intensității luminii de forma f(x,y), în care x și y sunt coordonatele spațiale, iar f este proporțională cu lumina în acel punct. Pentru o imagine multispectrală, f(x,y) devine un vector, fiecare componentă a acestuia indicând lumina în acel punct, pe banda spectrală corespondentă ei.

Imaginea digitală reprezintă o imagine f(x,y) care a fost discretizată atât în coordonate spațiale cât și în lumină și este reprezentată de o serie de matrice 2D, fiecare reprezentând câte o bandă de culoare, iar fiecare element dintr-o matrice de acest fel numindu-se pixel, o imagine digitală fiind de forma:

(1.1)

, unde , unde N si G sunt numere întregi pozitive, puteri ale lui 2.[1]

Banda spectrală

O bandă de culoare (banda spectrală) este o gamă de lungimi de undă din spectrul electromagnetic, iar de regulă, o imagine color, este constituită din 3 astfel de benzi de culoare, fiecare fiind capturată de un set de senzori, având o funcție diferită de sensibilitate. Spectrul de lumină ce ajunge la senzor este multiplicat de sensibilitatea funcției senzorului, și înregistrat mai apoi de acesta.

Figura 1.1 Curba de sensibilitate pentru cele trei tipuri diferite de senzori

În majoritatea cazurilor, procesarea de imagini se realizează cu referire la imaginile în tonuri de gri, deoarece pe o imagine color se pot aplica aceleași funcții cu ușurință, aplicându-le fiecărei benzi de culoare separat, iar majoritatea informațiilor importante putând fi extrase dintr-o imagine folosindu-ne de cele 3 benzi de culoare care formează imaginea color.[1]

Senzorii de captare ai imaginilor

Un obiect din lumea reală este iluminat de o cantitate de lumină, dintre care o parte este reflectată și cealaltă absorbită de către obiect, astfel, partea reflectată ajunge la raza senzorilor utilizați pentru captarea imaginii și unul din acești senzori creează un pixel corespondent unei mici părți din imagine. Când un foton de o anumită lungime de undă ajunge la senzor, energia sa este multiplicată de curba sensibilității senzorului, prezentată în Figura 1.1 și astfel este acumulată. Energia totală acumulată de senzor în timpul expunerii este folosită pentru a realiza valoarea de gri a pixelului corespondent acestui senzor.

Camerele digitale dispun de 3 tipuri de senzori, cu diferite curbe de sensibilitate, fiecare pentru culorile roșu, verde, albastru. Astfel, două tipuri de senzori nu pot corespunde exact aceleași mici părți din imagine si de aceea aceștia sunt dispuși la distanțe diferite unul de celălalt, cum este prezentat în Figura 1.2. Înregistrările obținute de către cele 3 tipuri de senzori sunt interpolate și suprapuse și astfel este obținută imaginea color. Camerele digitale de ultima generație dispun de cele 3 tipuri de senzori, suprapuși, astfel încât toate 3 tipurile să corespundă exact aceleași părți din imagine simultan.

Figura 1.2 Aranjarea senzorilor RGB (Sursa: [1])

Prin energia obținută de către senzorii ce captează imaginea, vom forma nivelul de lumină al diferiților pixeli, astfel această energie, împreună cu nivelul de lumină, au o semnificație relativă una față de cealaltă, iar valorile lor absolute fiind nesemnificative, iar comparând valorile luminii din 2 imagini diferite, se poate considera că acestea au sens doar dacă procesul fizic de captare al celor 2 a fost identic sau pixelii au fost normalizați, astfel se poate afirma că senzorii au fost calibrați.

Păstrând nivelurile de gri dintr-o imagine constante și micșorând numărul de pixeli cu care digitizăm aceeași parte a imaginii, va apărea efectul de tablă de șah, așa cum este prezentat asupra imaginii din Figura 1.3.

Figura 1.3 Efectul de tablă de șah expus asupra unei imagini de 256×256 pixeli (stânga) și efectul micșorării numărului de pixeli la 32×32 pixeli (dreapta) (Sursa: [1])

Majoritatea imaginilor au dimensiuni ce reflectă puteri ale lui 2, acest lucru fiind foarte util în ușurința calculelor ce se pot efectua pe aceste imagini. Pentru stocarea unei imagini N x N cu m niveluri de gri, vom avea nevoie de un număr de biți, b, care va fi egal cu b = N x N x m.[1]

Calitatea unei imagini digitale

O imagine digitală este cu atât mai calitativă cu cât este lipsită de zgomot, are o rezoluție ridicată, un contrast cât mai ridicat și să nu fie estompată.

Estomparea unei imagini, adică efectul de blur este datorat unei captări incorecte a imaginii, din pricina mișcării sau a unei camere non focusată, iar nivelul de estompare este dat de așa numita funcție de răspândire a punctelor din sistemul imagistic.

Rezoluția unei imagini reprezintă în esență cât de clară este o imagine pentru observator, și cât de multe detalii putem vizualiza în aceasta și constă în numărul de pixeli ai imaginii (parametrul N prezentat anterior) și în numărul de niveluri de gri (parametrul m prezentat anterior) folosite pentru cuantizarea luminozității. Dacă reducem numărul de pixeli și păstrăm aceleași niveluri de gri, imaginea va suferi de efectul de tablă de șah, iar dacă reducem nivelurile de gri și păstrăm același număr de pixeli, va apărea conturarea falsa și astfel se vor vedea mult mai puține detalii in imagine.

Un contrast bun al imaginii semnifică că valorile de gri prezente în imagine folosesc toată gama de valori de luminozitate de la negru la alb.

Procesarea imaginilor se realizează în vederea îmbunătățirii calității imaginii prin intensificarea ei, proces ce se realizează utilizând creșterea contrastului din aceasta.

Compresia imaginii este reprezentată de folosirea unui număr cât mai mic de biți astfel încât deteriorarea calității imaginii să fie minima. Refacerea imaginii se realizează prin reducerea efectului de blur adică scăderea estompării ei. Extracția detaliilor se folosește în vederea identificării anumitor detalii în imagine și constă în extragerea unor caracteristici specifice aplicației.[1]

Operatorii și funcția de răspândire a punctelor

Această procesare de imagini se realizează prin transformarea imaginii folosind operatori care preiau o imagine de input si produc o altă imagine ca output. Acești operatori sunt de tip liniar sau non liniar, iar în majoritatea aplicațiilor se vor folosi operatori liniari, deoarece cei non liniari nu pot fi clasificați, aceștia fiind mai mult specializați pe o anumită aplicație, pe când cei liniari pot fi caracterizați împreună, deoarece împart foarte multe caracteristici comune.

Un operator O care preia o imagine f, ne va prezenta un rezultat sub forma unei noi imagini, conform relației O(f), iar O este considerat a fi un operator liniar dacă se îndeplinește condiția:

(1.2)

, pentru orice imagine f și g și orice scalari a și b.

Operatorii liniari sunt definiți conform funcției de răspândire a punctelor, funcție ce corespunde rezultatului în urma aplicării operatorului asupra unui punct sursă.

(1.3)

, unde δ(α−x,β−y) este un punct sursă de lumină centrat în (x,y).

Între funcția de răspândire a punctelor unui operator și respectiv a unui aparat imagistic, există relația că amândouă exprimă efectul aplicării lor pe un punct sursă. Utilizând o cameră pentru a captura imaginea unei stele, vom observa în poză doar o pată, camera primind lumina din punctul sursă și împrăștiind-o pentru a deveni o pată, iar cu cât pata este mai mare cu atât imaginea va fi mai blurată, în consecință, funcția de răspândire a punctelor măsoară cantitatea de blur prezentă în imagine. Camera acționează în principiu ca un operator liniar, pentru că primește ca input o funcție ideală a luminii și va reproduce o imagine captată digital.

O imagine reprezintă o colecție de puncte sursă, adică pixelii, fiecare având propriul nivel de lumină, în consecință, putem afirma că imaginea este suma acestor puncte sursa, iar efectul aplicării operatorului caracterizat de h(x, α, y, β) asupra imaginii f(x,y) se poate scrie:

(1.4)

, unde g este imaginea rezultata, iar f este imaginea inițială de N x N pixeli.

Noi ne folosim de imaginea inițială f, ca fiind punctul sursă de lumină localizat în (x,y) și aplicând operatorul asupra sa, vom obține funcția de răspândire a punctelor multiplicată de puterea sursei, adică de valoarea de gri în acel punct, iar în final, pentru că operatorul este liniar, vom aduna toate aceste puncte sursă, adică toți pixelii.

Funcția de răspândire a punctelor h(x,α,y,β) ne arată cât de mult va influența valoarea de input în poziția (x,y), rezultatul la poziția (α,β). Dacă această influență este independentă de poziția actuală și depinde doar de poziția relativă a pixelilor influențați, vom avea o funcție de răspândire a punctelor deplasată invariant:

(1.5)

Astfel, ecuația de mai sus a lui g(α, β) va deveni:

(1.6)

și este o convoluție.

În cazul în care coloanele vor fi influențate independent față de liniile imaginii, funcția de răspândire a punctelor poate fi separată:

(1.7)

Efectul unui operator liniar asupra unei imagini îl putem exprima prin matricea H, iar ecuația de mai sus în formă simplificată devine:

(1.8)

Aceasta este ecuația fundamentală a procesării liniare de imagini, unde H este o matrice pătratică de N2 x N2 care este formată din N x N submatrici de N x N dimensiune fiecare, aranjate ca în relația (1.9).

(1.9)

În aceasta schemă fiecare expresie dintre paranteze reprezintă o submatrice N x N făcută din funcția h(x,α,y,β) pentru valorile fixe ale lui y și β și variabilele x și α având toate valorile posibile în direcțiile arătate de săgeată. Această reprezentare schematică a matricei H corespunde unei partiții a matricei de N2 matrice pătratice.

Pe o imagine se pot aplica un număr nelimitat de operatori liniari și ordinea în care aceștia sunt aplicați asupra imaginii nu contează atâta timp cât aceștia sunt invariabili la schimbare.

Deși înmulțirea matricelor nu este comutativă, ordinea în care operatorii liniari sunt aplicați nu contează. Matricea H, care arată efectul unui operator liniar asupra unei imagini, are o structură particulară după cum a fost prezentată mai sus, incluzând și faptul că fiecare a doua submatrice poate fi creată conform primei submatrici, schimbând toate valorile cu o poziție în jos. O matrice de acest fel se numește matrice circulantă. [1]

Îmbunătățirea unei imagini

Procesul prin care putem îmbunătății calitatea unei imagini semnificativ se numește image enhancement. Avantajul acestei acțiuni este faptul că nu trebuie să cunoaștem exact cum ar trebui sa arate imaginea având calitatea îmbunătățită pentru că odată cu încercarea îmbunătățirii calității acesteia vom distinge mai mult obiectele prezente în imagine sau detaliile ori putem spune că imaginea finală va avea un contrast mai ridicat.

Acest proces îl putem realiza prin eliminarea zgomotului și interferențelor prezente în imagine, îmbunătățirea contrastului acesteia și reducerea efectului de blur. Metodele folosite în vederea satisfacerii condițiilor prezentate anterior sunt, fie filtre de tipul trece-jos pentru netezirea imaginii și filtre de tipul trece-sus pentru ascuțirea detaliilor, fie algoritmi de eliminare a zgomotului atâta timp cât se evită apariția efectului de blur asupra imaginii.

Îmbunătățirea anumitor părți din imagine poate fi realizată prin înmulțirea transformatei Fourier a imaginii cu o anumită funcție specifică care elimină sau modifică o anumită componentă a frecvenței și apoi ia inversul transformatei Fourier, acest proces numindu-se si filtrare liniară. Metodele de îmbunătățire bazate pe filtrările de tipul trece-jos sau trece-sus, se consideră a fi metode de filtrare liniară.

În Figura 1.4 este prezentat procesul de îmbunătățirea al unei imagini folosind tehnica de filtrare liniară prezentată mai sus și astfel pe analiza figurii vom detalia în câteva cuvinte procesul.

Figura 1.4 Procesul de îmbunătățire al unei imagini folosind filtrarea liniară

Conform celor prezentate în cadrul Figurii 1.5, pe prima linie se poate observa că inițial se aplică transformata Fourier semnalului de input, acesta fiind corespondentul imaginii înaintea filtrării. În cadrul semnalului transformatei Fourier, avem în partea stângă banda ce dorim să o păstrăm, iar in partea dreaptă, ce dorim a fi eliminat.

Urmează pe linia din mijloc, prezentarea filtrului ideal în domeniul frecvență (dreapta) și reprezentarea acestuia în domeniul real (stânga). Se va aplica înmulțirea transformatei Fourier a imaginii cu transformata Fourier a filtrului obținând inversa transformatei Fourier a imaginii.

Pe ultima linie, în partea dreaptă avem transformata Fourier a semnalului corespondent imaginii, filtrat, iar în partea stângă, semnalul imaginii filtrat care s-ar putea obține în urma înmulțirii semnalului de pe prima linie, cu filtrul de pe linia din mijloc, dacă acesta ar fi un semnal finit.[1]

Refacerea unei imagini

Procesul de refacere al unei imagini, susține îmbunătățirea acesteia folosind anumite criterii obiective și cunoștințe asupra detaliilor ce reprezintă cum ar trebui să arate imaginea și se numește image restoration.

Refacerea unei imagini este necesară în cazul în care o imagine a fost alterată din cauza pixelilor de gri ce pot avea valori diferite de cele reale sau distorsionării din pricina deplasării anumitor pixeli de la poziția corectă la care ar trebui să se afle aceștia.

Refacerea unei imagini în tonuri de gri se poate realiza folosind procese liniare, astfel că dacă degradarea este omogenă adică valabilă pe toată suprafața imagine atunci problemă se poate rezolva folosind un filtru convoluțional aplicat asupra imaginii degradate pentru a elimina această distorsiune.

În cazul în care degradarea nu este omogenă se pot găsi metode liniare de a rezolva problema însă nu susținând aplicarea unui simplu filtru convoluțional, astfel se preferă metodele de refacere non-liniare pentru procesele generale de degradare a imaginilor.

Diferența dintre procesul de image enhancement și cel de image restoration este aceea că în cazul îmbunătățiri unei imagini se folosesc criterii subiective, iar în cazul refacerii imagini se încearcă refacerea anumitor degradări suferite de imagine folosind criterii obiective și știind cum ar trebui să arate imaginea finală.[1]

Capitolul 2. Modele neuronale și algoritmi de clasificare

Analogia dintre neuronii biologici și cei artificiali

Unitatea de calcul a creierului este reprezentată de neuron. Aproximativ 86 de miliarde de neuroni pot fi găsiți în sistemul nervos uman și sunt conectați cu aproximativ 1014-1015 sinapse. În partea stângă din Figura 2.1 este prezentată schema biologică a unui neuron aparținând creierului uman, iar în partea dreaptă se poate observa un exemplu de model matematic al unui neuron.

Figura 2.1 Schema unui neuron biologic (stânga) și un model matematic al unui neuron (dreapta)

Fiecare neuron primește semnale de intrare de la dendritele sale și produce semnale de ieșire de-a lungul axonului său (unic). Axonul, în cele din urmă, se ramifică și se conectează prin sinapse la dendritele altor neuroni. În modelul matematic al unui neuron, semnalul care circulă de-a lungul axonilor (x0), interacționează multiplicativ (w0x0) cu dendritele celuilalt neuron bazat pe puterea sinaptică la acea sinapsă (w0). Ideea este că puterea sinaptică (weight-urile w) poate fi învățată și controlează puterea de influență și direcția acesteia: excitatoare (weight pozitiv) sau inhibatoare (weight negativ) a unui neuron asupra altuia.

În modelul de bază, dendritele transportă semnalul către celule, unde toate sunt însumate. Dacă suma finală este peste un anumit prag, neuronul poate trage, trimițând un spike de-a lungul axonului său. În modelul matematic, presupunem că sincronizarea precisă a spike-urilor nu contează, ci doar frecvența de tragere comunică informația.

Pe baza interpretării acestui rate code, modelăm rata de tragere a neuronului cu o funcție de activare f, ce reprezintă frecvența spike-urilor de-a lungul axonilor. O alegere comună a funcției de activare, este funcția sigmoid σ, deoarece are nevoie de o intrare valoare reală (puterea semnalului după efectuarea sumei) și o returnează sub forma unei valori între 0 sau 1.

Cu alte cuvinte, fiecare neuron realizează un produs cu intrarea și weight-urile sale, adaugă bias-ul și aplică neliniaritatea (sau funcția de activare), cum se poate observa în relația (2.1) .[6]

(2.1)

Un singur neuron sub forma unui clasificator liniar

Un singur neuron poate fi folosit pentru a implementa un clasificator binar, acesta având capacitatea de a clasifica drept like(activare aproape de 1) sau dislike(activare aproape de 0) anumite regiuni liniare din spațiul de intrare. Prin urmare, cu o funcție de pierdere adecvată la ieșirea

neuronului, putem transforma un singur neuron într-un clasificator liniar, cum ar fi clasificatorul BSVM, care presupune atașarea unei max-margin hinge loss la ieșirea neuronului și antrenarea acestuia astfel încât să obținem un BSVM.[6]

Modelul neuronal McCulloch-Pitts

Primul model neuronal a fost creat în anul 1943 de către neurofizicianul american Warren Sturgis McCulloch și matematicianul Walter Pitts, acesta reprezentând un instrument cu mai multe intrări, însoțite de anumite ponderi, instrument ce era făcut pentru a calcula suma intrărilor, ținându-se cont de ponderea fiecăreia dintre acestea.

Figura 2.2 Modelul neuronului McCulloch-Pitts (Sursa: [7])

În cazul modelului prezentat în Figura 2.2, dacă suma totală calculată este superioară unui anumit prag stabilit, neuronul este activat și emite un semnal de ieșire, în caz contrar acesta rămânând neactivat. [8]

Figura 2.3 Funcția de activare a neuronului McCulloch-Pitts cu pragul T(Sursa: [7])

Intrările xi (i=1, 2, …, n) sunt 0 sau 1, în funcție de absența sau prezența impulsului la momentul k, așa cum se poate observa în Figura 2.4. Semnalul de ieșire al neuronului este „o”. Regula după care neuronul generează un semnal este:

(2.1)

Iar pentru o sinapsă :

Dacă este excitatoare, avem wi=1

Daca este inhibatoare, avem wi=-1

Modelul McCulloch-Pitts al unui neuron este simplu, dar are un potențial semnificativ de calcul. De asemenea, are o definiție matematică precisă. Totuși, acest model este atât de simplist încât generează doar o ieșire binară, iar valorile ponderilor și de prag sunt fixate. Algoritmul de calcul neuronal are caracteristici diverse pentru diverse aplicații cum ar fi implementarea funcțiilor

boolene de tip AND, OR, NOR, etc. Astfel, trebuie să obținem modelul neural cu caracteristici computaționale mai flexibile.

Perceptronul simplu

Mai târziu, în anul 1957, Frank Rosenblatt a dezvoltat algoritmul Perceptron, algoritm ce intenționa să fie o mașinărie, mai degrabă decât un program, utilizată pentru recunoașterea de imagini. Acest algoritm, spre deosebire de cel dezvoltat de McCulloch-Pitts, este unul supervizat, care ajustează ponderile astfel încât eroarea de clasificare să fie minimă.

Perceptronul este cea mai simplă formă a rețelelor neuronale utilizat pentru clasificarea unor pattern-uri liniar separabile. Acesta conține un singur neuron cu sinapse ce pot fi ajustate prin modificarea ponderilor și a bias-ului.

Figura 2.4 Arhitectura perceptronului simplu(Sursa: [7])

Prin algoritmul perceptronului simplu, Rosenblatt a demonstrat că dacă două clase sunt liniar separabile, atunci perceptronul converge către o suprafață de decizie de forma unui hiperplan între cele două clase. Deoarece perceptronul are un singur neuron, acesta este limitat la clasificarea vectorilor în doar două clase. Spre deosebire de algoritmul dezvoltat de McCulloch și Pitts, neuronul nu mai este reprezentat de un prag, ci acesta constituie o funcție de activare, de forma:

Figura 2.5 Funcția de activare a perceptronului simplu(Sursa: [7])

(2.2)

În Figura 2.4 avem reprezentarea arhitecturii ce stă la baza perceptronului simplu, unde X=(1, x1, x2, …, xn), xi aparținând mulțimii numerelor reale și reprezentând intrările în neuron, iar W=(w0, w1, …, wn), wi aparținând mulțimii numerelor reale și reprezentând ponderile neuronului.

Ieșirea neuronului reprezentată în Figura 2.4 cu litera y, este dată de ecuația:

(2.3)

Algoritmul perceptronului simplu

1. În prima fază se vor inițializa aleator ponderile la momentul t=0

2. Se va efectua evaluarea ieșirii reale conform relației (2.4)

(2.4)

, unde j = 1 : N, N – numărul de vectori de intrare.

3. Se va compara ieșirea reală yj cu ieșirea dorită dj conform relației (2.5)

(2.5)

4. Se vor ajusta ponderile astfel încât eroarea să fie micșorată

(2.6)

, unde 𝜂 reprezintă rata de învățare.

5. Se revine la pasul 2, pana când toate corespondențele {(Xj, dj)} sunt corecte.

Teorema convergenței

Dacă algoritmul perceptron este aplicat asupra a două seturi de vectori liniari separabili, se poate demonstra că acesta va converge către o soluție stabilă într-un număr finit de pași. Suprafața de separație între clase va fi un hiperplan dat de ecuația (2.7).

(2.7)

Două mulțimi de vectori se numesc liniar separabile dacă există cel puțin o suprafață liniară (de gradul I) care separă spațiul în două semispații în care clasele sunt disjuncte, adică acestea nu se intersectează.

Una dintre problemele perceptronului simplu este reprezentată de imposibilitatea găsirii unei soluții pentru probleme neseparabile liniar, cum ar fi par-impar sau operația logică XOR, iar în astfel de situații apare problema majoră implicată de soluția de stop, aceasta fiind inexistentă.[7]

Adaline (Adaptive Linear Neuron)

Structura prezentată în Figura 2.6 descrie arhitectura neuronului Adaline, iar aceasta este similară cu cea a perceptronului simplu.

Figura 2.6 Arhitectura Adaline-ului(Sursa: [7])

Diferența față de perceptronul simplu este aceea că Adaline modifică funcția de activare în etapa de învățare, astfel:

Figura 2.7 Funcția de activare folosită de neuronul Adaline (Sursa: [7])

, unde f(x) = purelin(x).

În continuare, se va exemplifica structura si algoritmul perceptronului simplu și cel al neuronului Adaline.

Figura 2.8 Perceptronul simplu (Sursa: [7])

Figura 2.9 Adaline (Sursa: [7])

Ce a fost apreciat în cadrul dezvoltării algoritmului Adaline, în detrimentul perceptronului simplu, a fost adăugarea unei noi filozofii în vederea antrenării rețelei neuronale. S-a introdus astfel,

o funcție cost care măsoară performanța funcționării neuronului, iar în cadrul algoritmului Adaline, aceasta este reprezentată de eroarea pătratică dintre ieșirea dorită și cea reală.

Algoritmul Adaline

1. În prima fază se vor inițializa aleator ponderile la momentul t=0

2. Se va efectua evaluarea ieșirii reale conform relației (2.8)

(2.8)

, unde j = 1 : N, N – numărul de vectori de intrare.

3. Se va calcula eroarea conform relației (2.9)

(2.9)

4. Se vor ajusta ponderile astfel încât eroarea să fie micșorată

(2.10)

, unde 𝜂 reprezintă rata de învățare.

5. Se revine la pasul 2, pana când eroarea medie pătratică pe setul de date scade sub o anumită valoare dorită.

Algoritmul Gradient Negativ

Algoritmul Gradientului Negativ este cel mai frecvent algoritm de optimizare utilizat în domeniul machine learning și deep learning. Este un algoritm de optimizare de ordinul întâi, fapt ce denotă că ia în considerare doar prima derivată atunci când efectuează actualizările parametrilor. La fiecare iterație actualizăm parametrii în direcția opusă gradientului funcției obiectiv J(w) în raport cu parametrii în care gradientul indică direcția celei mai abrupte ascensiuni. Mărimea pasului ales la după fiecare iterație pentru a ajunge la minimul local este determinat de rata de învățare 𝜂. Prin urmare, urmărim direcția pantei până ajungem la un minim local. [9]

Putem considera, din motive de simplitate, că modelul regresiei logistice are doar doi parametri: ponderea w și bias-ul b. Pașii algoritmului vor fi următorii:

1. Inițializăm ponderea w si bias-ul b cu niște valori alese aleator.

2. Alegem o valoare pentru rata de învățare η. Rata de învățare determină cât de mare ar fi pasul pe fiecare iterație, astfel, dacă η este foarte mică, va fi nevoie de mult timp pentru a converge, iar dacă η este prea mare, este posibil să nu reușească să conveargă și să depășească minimul.

Prin urmare, trebuie să expunem grafic funcția cost în raport cu diferite valori ale η și să alegem valoarea η care este chiar înaintea primei valori care nu converge astfel încât să avem un algoritm de învățare foarte rapid care converge, în Figura 2.10 fiind prezentat graficul gradientului negativ pentru diferite rate de învățare.[10]

Figura 2.10 Gradient negativ pentru diferite rate de învățare (Sursa: [11])

În mod uzual, cele mai folosite valori pentru rata de învățare în cadrul algoritmului gradient negativ sunt: 0.001, 0.003, 0.01, 0.03, 0.1, 0.3.

3. Scalarea datelor este foarte importantă dacă datele se află pe o scară foarte diferită. Dacă nu scalăm datele, curbele de nivel (contururile) vor fi mai înguste și mai înalte, ceea ce înseamnă că va dura mai mult timp pană la ajungerea spre convergență, acest lucru fiind demonstrat în graficele din Figura 2.11, respectiv Figura 2.12.

Figura 2.11 Curbele de nivel ale gradientului negativ, nenormalizate (Sursa: [10])

Figura 2.12 Curbele de nivel ale gradientului negativ, normalizate (Sursa: [10])

(2.11)

Scalarea datelor care vor avea avea μ𝑖 = 0 și 𝑠𝑖 = 1, se va efectua cu ajutorul relației (2.11), unde xi = componenta i a vectorului de intrare, μ𝑖 = componenta i a vectorului medie și 𝑠𝑖 = factorul de scalare a componentei i a vectorului de intrare, care de obicei, se află conform relației (2.12).

(2.12)

4. La fiecare iterație, trebuie luată în calcul derivata parțială a funcției cost E(W) pentru fiecare parametru în parte, după cum urmează:

(2.13)

Ecuațiile devenind:

(2.14)

Vom presupune că nu avem bias, pentru a putea ilustra modul în care algoritmul gradient negativ utilizează prima derivată a funcției cost pentru a urmări minimul local. În cazul în care panta valorii curente w>0, înseamnă ca suntem în dreptul optimului w*. Prin urmare, actualizarea va fi negativă și va începe să se apropie de valorile optime ale lui w*. Cu toate acestea, dacă este negativă, actualizarea va fi pozitivă și va crește valorile curente ale lui w pentru a converge la valorile optime ale lui w*. Această ilustrare este realizată în Figura 2.13.

Figura 2.13 Urmărirea minimului local de către algoritmul gradient negativ, folosind prima derivată a funcției cost (Sursa: [10])

Acest proces se va continua până când funcția cost converge. Acest lucru se întâmplă în momentul în care curba de eroare devine plată și nu se schimbă.

În plus, la fiecare iterație, pasul va fi în direcția care oferă o schimbare maximă, deoarece este perpendiculară la curbele de nivel la fiecare pas.

Algoritmul Adam

Adam este un algoritm ce poate fi privit ca o combinație dintre RMSprop și SGD varianta cu moment. Acesta utilizează gradienți pătrați pentru a scala rata de învățare ca și RMSprop și profită de varianta cu moment folosind media mobilă a gradientului în loc de însuși gradientul ca și SGD varianta cu moment. [12]

Acest algoritm reprezintă o metodă de adaptare a ratei de învățare, ceea ce înseamnă că rata de învățare este calculată individual pentru diferiți parametri. Numele de Adam derivă din estimarea momentului adaptiv și motivul pentru care se numește astfel este că Adam folosește estimări ale primului și celui de-al doilea moment de gradient pentru a adapta rata de învățare pentru fiecare pondere a rețelei neuronale, unde momentul unei variabile aleatoare este definit ca valoarea așteptată a acelei variabile la puterea lui n, așa cum este prezentat în relația (2.15).[13]

(2.15)

Primul moment este mediu, iar cel de-al doilea moment este variația necentrată (adică nu se scade media în timpul calculului varianței).

Pentru estimarea momentelor, Adam folosește medii exponențial mobile, calculate pe un gradient evaluat pe un mini-lot actual:

(2.16)

, unde m și v sunt medii mobile, g este este gradientul pe mini-lotul actual și 𝛽 introduce hiper-parametrii noi ai algoritmului. Vectorii mediilor mobile sunt inițializați cu zerouri la prima repetare.

Pentru a vedea cum aceste valori se corelează cu momentul definit ca în prima ecuație, trebuie analizate valorile așteptate ale mediilor mobile. Deoarece m și v sunt estimări ale primului și al doilea moment, dorim să avem următoarea proprietate:

(2.17)

Dacă aceste proprietăți ar fi păstrate, ar însemna că avem estimatori imparțiali. Acum, vom observa că acestea nu sunt valabile pentru mediile noastre mobile [13]. Se extinde valoarea lui m, până când cele mai mici valori ale gradienților contribuie la valoarea globală, deoarece se înmulțesc cu 𝛽 din ce în ce mai mici. Astfel, putem rescrie formula pentru mediul nostru mobil astfel:

(2.18)

Si vom avea corecția bias-ului pentru estimatorul primului moment:

(2.19)

În primul rând, folosim noua formulă pentru media mobilă pentru a extinde m urmând să aproximăm g[i] cu g[t]. Deoarece are loc aproximarea, eroarea ζ apare în formulă. În ultima parte, folosim doar formula pentru suma unei serii geometrice finite.

Urmează procesul de corecție a estimatorului, astfel încât valoarea așteptată să fie cea dorită. [10] Acest pas este, de obicei, denumit corecția bias-ului. Formulele finale pentru estimatorul de corecție de bias pentru primul si cel de-al doilea moment vor fi după cum urmează:

(2.20)

Ultimul lucru rămas este să utilizăm aceste medii mobile pentru a mări gradul de învățare individual pentru fiecare parametru. Modul în care acest lucru este efectuat în algoritmul Adam este unul foarte simplu, și astfel se efectuează actualizarea ponderii:

(2.21)

Funcții de activare

Orice funcție de activare(sau neliniaritate) preia un singur număr și execută o anumită operație matematică fixă pe acesta.

În Figura 2.14 este prezentat graficul funcției de activare neliniară sigmoid, funcție ce preia un număr real și îl returnează sub forma unei valori între 0 și 1.

Figura 2.14 Funcția de activare sigmoid(Sursa: [6])

Funcția de activare neliniară sigmoid are forma matematică ca în relația (2.22)

(2.22)

În particular, numerele negative mari devin 0, iar numerele pozitive mari devin 1. Funcția sigmoid a cunoscut o utilizare frecventă istoric, deoarece are o interpretare bună a vitezei de tragere a unui neuron: de la a nu trage deloc (0), spre o tragere total saturată cu o frecvență maximă asumată. În practică, funcția neliniară sigmoid a decăzut și este rar utilizată în zilele noastre, deoarece are 2 mari dezavantaje:

Sigmoidele saturează și omoară gradienții

Ieșirile sigmoidelor nu sunt centrate in zero

Figura 2.15 Funcția de activare ReLU(Sursa: [6])

ReLU este o funcție de activare al cărei grafic este prezentat in Figura 2.15, care a devenit foarte populară în ultimii ani și calculează funcția prezentată în relația (2.23).

(2.23)

Cu alte cuvinte, activarea este impusă în pragul de 0, iar utilizarea acestei funcții de activare prezintă câteva avantaje, dar și un aspect negativ.

În comparație cu operațiile complicate pe care le execută neuronii sigmoid, ReLU poate fi implementată foarte simplu, atașând pragul de 0 matricei de activare și mai mult, ReLU accelerează foarte mult convergența gradientului stocastic, deoarece este sub formă liniară, nesaturată, spre deosebire de funcția de activare Sigmoid care este de tip neliniar, și saturată.

Din nefericire, unitățile ReLU sunt fragile și este posibil să cedeze în timpul antrenării. În cazul unei antrenări folosind unitățile ReLU, dacă avem un flux larg al gradientului asupra unuia dintre neuroni, weight-urile acestuia pot fi actualizate într-un mod în care acel neuron nu se va mai activa de la acel punct din nou, iar dacă acest lucru se întâmplă, fluxul gradientului asupra acelei unități va fi întotdeauna zero pornind din acel punct.[6]

Capitolul 3.Clasificarea și arhitectura rețelelor neurale

Deep learning

De-a lungul anilor, clasificarea imaginilor în cadrul domeniului computer vision s-a bazat pe caracteristici prelucrate manual, cum ar fi SIFT, HOG și SURF în combinație cu modele discriminative antrenate de adâncime mică. Cu toate acestea, caracteristicile prezentate mai sus pot capta doar informații de nivel inferior.

Progresele recente ale deep learning-ului au făcut posibile obiectivele de recunoaștere a imaginilor și detecția detaliilor prezente în acestea. Deep learning-ul reprezintă un subset de algoritmi de învățare automată care este foarte bun la recunoașterea tiparelor, dar necesită un număr mare de date. Deep learning-ul excelează în recunoașterea obiectelor din imagini, deoarece este implementat folosind 3 sau mai multe straturi de rețele neuronale artificiale în care fiecare strat este responsabil de extragerea unuia sau mai multor caracteristici ale imaginii.

Proiectarea funcțiilor pentru captarea eficientă a informațiilor de nivel mediu, cum ar fi intersecțiile între margini sau reprezentarea la nivel înalt, precum părți ale obiectelor devine mult mai dificilă. Spre deosebire de algoritmii de învățare de adâncime mică, deep learning-ul țintește spre a extrage reprezentări ierarhice din seturi de date foarte mari utilizând modele arhitecturale adânci cu multiple straturi de transformări neliniare. Cu astfel de reprezentări ale caracteristicilor, în loc de valorile brute ale pixelilor sau caracteristici prelucrate manual, o performanță mult mai bună devine realizabilă.[4]

O rețea neuronală profundă este pur și simplu o rețea neuronală artificială care transmite mai multe straturi de unități ascunse între intrările și ieșirile sale. Principiul din spatele succesului realizat de către aceste rețele este de fapt, încercarea lor de a desfășura în mod automat ierarhiile de abstractizare încorporate în datele observate atât în manieră supervizată cât și nesupervizată, prin proiectarea elaborată a adâncimii și a lățimii straturilor și în consecință, selectarea unor caracteristici benefice pentru sarcina de învățare.[5]

Arhitecturile de tip deep learning au diferite variante, cum ar fi CNN , DBN și DBM. Printre acestea, CNN a fost cel mai atrăgător model care a fost utilizat pe seturi mari de date, cum ar fi ImageNet, și a produs cele mai bune rezultate pe cele mai provocatoare clasificări de imagini și detecție a imaginilor cu o marjă foarte mare. În plus, CNN învață reprezentări ale imaginilor generice ce pot fi folosite în afara subiectului pentru a rezolva multe probleme de detecție vizuală. În comparație cu caracteristicile prelucrate manual folosite în metodele anterioare, CNN este capabil să extragă diferite nivele de caracteristici vizuale complexe dintr-un set de date foarte larg utilizând structura de transmitere multi-strat.

Antrenarea arhitecturilor adânci reprezintă o dificultate deoarece numărul larg de parametri care urmează a fi clasificați necesită o cantitate enormă de date de antrenare etichetate, acestea nefiind întotdeauna disponibile. În plus, acestea au nevoie de o putere de calcul foarte bună din partea sistemului pentru a fi antrenate.[4]

Arhitectura rețelelor neurale

Rețelele neurale sunt modelate ca niște colecții de neuroni, ce sunt conectați într-un grafic aciclic. Cu alte cuvinte, ieșirile unor neuroni pot deveni intrările altor neuroni. Ciclii nu sunt permiși, deoarece asta ar implica o buclă infinită ce oprește trecerea mai departe a rețelei.

În loc de un bloc amorf de neuroni conectați, modelele rețelelor neuronale sunt adesea organizate ca straturi distincte de neuroni. Pentru rețelele neuronale obișnuite, cel mai frecvent tip de strat este fully connected layer în care neuronii dintre două straturi adiacente sunt conectați complet în perechi, dar neuronii dintr-un singur strat nu au conexiuni.

Figura 3.1 Schema unei rețele neurale artificiale

În Figura 3.1 este prezentată schema unei rețele neurale cu 3 straturi, 3 intrări, 2 straturi ascunse a câte 4 neuroni fiecare și un strat de ieșire.

Când spunem despre o rețea neurală că are N straturi, nu luăm în considerare și stratul de intrare. Astfel, o rețea neuronală cu un singur strat descrie o rețea fără straturi ascunse(intrarea este mapată direct la ieșire). Acestea mai sunt numite și ANN sau MLP.

În acest sens, SVM-urile mai sunt considerate ca fiind cazuri speciale ale rețelelor neuronale cu un singur strat.

Spre deosebire de celălalte straturi ale rețelei neuronale, neuronii stratului de ieșire cel mai adesea nu au o funcție de activare(se poate considera și că aceștia au o funcție de activare identitate liniară). Acest lucru se întâmplă adesea pentru că stratul de ieșire este folosit pentru a reprezenta scorul clasei, scor reprezentat de valori arbitrare, numere reale.

Cei 2 indici care sunt luați în considerare pentru a măsura dimensiunea unei rețele neuronale sunt, numărul de neuroni, sau mai frecvent numărul de parametri. Conform rețelei neuronale prezentate în Figura 3.1, putem remarca că aceasta are 4+4+1=9 neuroni, [3×4]+[4×4]+ [4×1]=12+16+4=32 weight-uri și 4+4+1=9 bias-uri, în total, 41 de parametrii învățabili. [6]

Rețele neurale convoluționale

Rețelele neurale profunde au prezentat îmbunătățiri semnificative în mai multe domenii, inclusiv în domeniul computer vision și cel al recunoașterii vocale. În domeniul computer vision, un anumit tip de DNN, cunoscut sub numele de rețea neurală convoluțională (CNN), a demonstrat rezultate semnificative în recunoașterea obiectelor și a problemelor legate de detecție.

O rețea neurală convoluțională este formată dintr-un grup de rețele neurale artificiale care utilizează anumite straturi convoluționale pentru a filtra intrări în scopul obținerii unor rezultate utile. Operația de convoluție implică combinarea datelor de intrare cu un anumit kernel (filtru), pentru a forma o hartă a caracteristicilor transformate. Filtrele din straturile convoluționale sunt modificate pe baza parametrilor învățați pentru a extrage cele mai utile informații în vederea rezolvării unei sarcini.

În practică, rețelele neurale convoluționale, sunt ajustate si adaptate automat, pentru a avea cea mai bună si eficientă abordare a hărții caracteristicilor, provenită din setul de date de intrare.

Rețelele convoluționale sunt compuse dintr-un strat de intrare, un strat de ieșire și unul sau mai multe straturi ascunse, numite uzual hidden layers. O rețea convoluțională este diferită de o rețea neurală artificială datorită faptului că neuronii din straturile sale sunt dispuși în trei dimensiuni, lățime, înălțime și adâncime. Adâncimea nu se referă la numărul total de straturi al rețelei, ci la a 3-a dimensiune a volumului de activare. Spre deosebire de ANN-urile clasice, neuronii dintr-un strat nu sunt conectați în totalitate cu stratul precedent, ci doar cu o porțiune din acesta. În practică, asta se rezumă prin faptul că daca considerăm un volum de intrare cu dimensiunile 64x64x3, acesta va conduce în urma antrenării, către un strat de ieșire final comprimat de dimensiune 1x1xn, unde n reprezintă numărul de clase de identificare posibile. Acest lucru permite CNN-ului să transforme volumul de intrare de trei dimensiuni într-un volum de ieșire. Straturile ascunse sunt o combinație de straturi de convoluție, straturi de grupare, numite și pooling layers și straturi conectate complet, numite fully connected layers. CNN-urile utilizează straturi convoluționale multiple pentru a filtra volumele de intrare la un nivel mai ridicat de abstractizare[13].

Figura 3.2 Schema unei rețele neurale convoluționale

Arhitectura unui model artificial inteligent

În cadrul funcționalității unei rețele neurale convoluționale, există un model inteligent, adaptiv în totalitate, ale cărui etape sunt realizate în totalitate de către sistem. Trăsăturile și structura se vor determina adaptiv pe parcursul antrenării rețelei. Pentru aceleași forme de intrare, pentru aplicații diferite, se pot obține etape de prelucrare total diferite. În cazul opus, dacă am alege să utilizăm o rețea de tip shallow, cu un singur strat ascuns, alegerea trăsăturilor și a structurii rețelei se bazează în principiu pe proiectant, iar în urma alegerilor asupra acestor aspecte, ele rămân fixe pe tot parcursul antrenării. În acest caz, pentru același tip de formă de intrare, prelucrările sunt aproximativ la fel. Astfel, singurul dezavantaj prezent în utilizarea modelelor de tip deep, este acela că modelul general va fi prea complex, și fără control total din partea proiectantului, astfel ele trebuind specializate. În Figura 3.3 este prezentat un astfel de model inteligent și adaptiv din punct de vedere al intrărilor oferite de utilizator. [14]

Figura 3.3 Structura unui sistem de inteligență artificială(Sursa: [14])

Componența unei rețele neurale convoluționale

Componența unei CNN, după cum am enunțat mai sus, este alcătuită dintr-un strat de intrare, un strat de ieșire și straturile ascunse. Stratul de intrare este format în principiu, din setul de date pe care utilizatorul îl conferă algoritmului înaintea antrenării, acest set formând ceea ce se numește și harta caracteristicilor inițiale. Stratul de ieșire va reieși în urma antrenării algoritmului, având ca scop reținerea elementelor ce țin de clasificarea în speță pentru care s-a realizat antrenarea. În cele ce urmează, în cadrul subcapitolului 3.8.1 și 3.8.2, vor fi prezentate tipurile de straturi ascunse ce pot face parte dintr-o rețea convoluțională.

Stratul convoluțional

Partea principală a sarcinii computaționale a unei rețele neurale convoluționale, este stratul convoluțional. Parametrii stratului convoluțional constau într-un set de filtre aplicate, fiecare filtru având o dimensiune relativ mică de-a lungul lățimii și înălțimii, dar extindându-se prin toată adâncimea volumului de intrare. Spre exemplu, un filtru al unui strat convoluțional poate avea dimensiunea 4x4x3, adică lățimea și înălțimea acestuia vor fi de 4 pixeli, iar ultimul număr, reprezintă adâncimea imaginii, în cazul nostru 3, pentru 3 canale de culoare reprezentative.

În timpul procesului de filtrare, fereastra filtrului se va glisa, efectuând operația de convoluție, pe lățimea și înălțimea volumului de intrare și se vor calcula produsele scalare între intrările filtrului și intrarea în pozițiile corespunzătoare aparținând imaginii. Pe măsură ce filtrarea are loc prin glisarea ferestrei, vom obține astfel o hartă de activare bidimensională care oferă răspunsurile filtrului aplicat la fiecare poziție din spațiul volumului de intrare și astfel, rețeaua creată va învăța filtrele specifice ce se activează atunci când detectează un anumit tip de caracteristică vizuală, cum ar fi marginile sau petele de culoare pe primul strat, ori modele întregi ale obiectelor prezente în imagini.[11]

Straturile convoluționale sunt capabile să reducă semnificativ complexitatea modelului prin optimizarea ieșirii. Aceste optimizări sunt realizate prin trei hiperparametri: adâncimea, pasul și setarea „zero-padding”.

Adâncimea volumului de ieșire produs de stratul convoluțional poate fi setată manual prin numărul de neuroni din strat. Reducând acest hiperparametru se poate minimiza semnificativ numărul total de neuroni din rețea, dar totodată poate să fie redusă și capabilitatea sistemului de a recunoaște modelul.

Pasul poate fi setat de asemenea manual, acesta reprezentând pasul de deplasare în spațiul de intrare. Setarea „zero-padding” este procesul de adăugare a unei borduri în vectorul de intrare, ceea ce este de o importanță deosebita deoarece avem nevoie de control asupra dimensiunilor de ieșire.

Prin aceste tehnici și prin utilizarea lor, dimensiunile spațiului de ieșire al stratului convoluțional vor fi alterate, iar pentru calculul acestor dimensiuni, se poate folosi relația (3.1).

(3.1)

, unde V reprezintă dimensiunile volumului de intrare (lățime x înălțime x adâncime), R reprezintă dimensiunea câmpului receptiv, adică regiunea de conexiune dintre intrare și stratul convoluțional, Z este valoarea setată pentru zero-padding, iar S este reprezentată de stride (adică pasul). Dacă rezultatul acestui calcul nu este egal cu un număr întreg, atunci valoarea pentru stride a fost setată greșit. [15]

Figura 3.4 Sistemul de prelucrare a datelor de intrare

într-un strat convoluțional(Sursa: [15])

În Figura 3.4 se poate observa că din imaginea inițială se va extrage o porțiune de 3×3, urmând ca după aplicarea filtrului (kernel) asupra porțiunii extrase din imagine, efectuându-se suma ponderată între fiecare pixel și vecinii săi, să se obțină ca rezultat pixelul cu valoarea -8.

Stratul Max-polling

În tratarea algoritmilor destinați clasificării, este foarte comun întâlnită tehnica introducerii periodice a straturilor de tip Max Polling între straturile consecutive convoluționale ale arhitecturii CNN. Acest tip de strat este destinat pentru a reduce progresiv dimensiunea spațială, cantitatea de parametrii și volumul de calcul din rețea, astfel, evitându-se supra-antrenarea. Max Polling-ul funcționează independent la fiecare strat din adâncimea de intrare și o redimensionează spațial, folosindu-se de operația MAX. Cea mai comună formă este un strat pooling cu filtre de dimensiune 2×2 aplicate cu pasul 2, fiecare strat din adâncime de intrare de 2×2, eliminând 75% din activări. Fiecare operație MAX ar lua în acest caz un număr maxim de 4 eșantioane (o regiune de 2×2). Dimensiunea adâncimii va rămâne mereu neschimbată, având următoarele caracteristici: [11]

Considerând că avem un volum de intrare:

W1 x H1 x D1

Spațiul filtrului – F

Pasul – S

După aplicarea stratului de Max Polling, volumul de ieșire produs va avea dimensiunea W2 x H2 x D2, acestea fiind deduse cu ajutorul relației (3.2).

(3.2)

Demonstrația și rezultatul aplicării unui strat de tip Max Polling este prezentat în Figura 3.5, unde se observă transformarea unei matrice de dimensiune 4 x 4, aceasta reprezentând una dintre matricele de adâncime ale unui model mai complex, într-o matrice de 2 x2, alcătuită prin aplicarea operației Max între cele 2 x 2 elemente din matricea de intrare.

Figura 3.5 Aplicarea unui strat de tip Max Polling asupra unei componente de adâncime(Sursa: [11])

În Figura 3.6, este prezentat un volum de intrare de dimensiune [224 x 224 x 64], urmând ca acesta să fie filtrat printr-un strat Max Polling cu filtrul 2×2, pasul 2, având ca volum de ieșire o dimensiune de [112 x 112 x 64] și astfel, se poate observa că adâncimea rămâne neschimbată.

Figura 3.6 Aplicarea unui strat de tip Max Polling asupra unui volum de intrare de adâncime mare(Sursa: [11])

Stratul Fully-Connected

Stratul complet conectat este caracterizat de faptul că neuronii ce fac parte din acesta, au conexiuni complete la toate activările din stratul anterior, așa cum se observă în rețelele neuronale obișnuite (Figura 3.1). Activarea acestora poate fi astfel calculată cu o multiplicare a matricei, adăugându-se bias-ul.

Modelul ResNet

ResNet reprezintă un model de rețea neurală reziduală care a avut un impact important în domeniul Computer Vision în anul 2015 și încă este un model foarte popular, motiv pentru care am ales ca lucrarea de față sa aibă în componenta sa, un algoritm bazat pe acest tip de rețele.

Acest model poate antrena sute, sau chiar mii de straturi ale unei rețele fără a întâmpina efectul de “vanishing gradient” [16], care se manifestă în momentul adăugării mai multor straturi asupra rețelei, fiecare având câte o funcție de activare, iar gradientul funcției cost se apropie de 0, făcând astfel rețeaua greu de antrenat.

Rețeaua reziduală este o arhitectură de rețea neurală convoluțională care suportă un număr mare de straturi convoluționale, având o performanță impresionantă, în timp ce arhitecturile rivale din timpul ei, pierdeau din eficiență la adăugarea fiecărui strat în plus.

Prin tehnica de antrenare a rețelei folosind “backpropagation”, care se bazează pe descendența gradientului pentru a găsi weight-urile optime pentru minimizarea funcției cost, când sunt adăugate noi straturi, repetarea multiplicării derivatei acestora vor face gradientul foarte apropiat de 0, acest lucru însemnând straturile noi, nu doar că nu ajută la antrenare, ci aceasta va fi făcută mai ineficient.

Problema efectului “vanishing gradient” a fost propusă spre rezolvare de către acest model arhitectural, folosind în antrenarea rețelei tehnica “identity shortcut connections”, care presupune ocolirea straturilor identice, refolosind funcția de activare din straturile precedente, iar asta va reduce rețeaua la doar câteva straturi, ceea ce sporește viteza de antrenare a rețelei. Astfel, când rețeaua se va antrena din nou, straturile identice se extind și ajută rețeaua să exploreze mai mult din spațiul trăsăturilor.[17]

În Figura 3.7 se observă conexiunea identitate reprezentată de bucla din partea dreaptă, ocolind cele două straturi, după cum am enunțat mai sus.

Figura 3.7 Bloc component al unei rețele neurale reziduale (Sursa: [17])

Intuiția din spatele blocurilor reziduale

După cum a fost prezentat în informațiile anterioare, la adăugarea unui număr de straturi asupra rețelei, se va degrada foarte rapid acuratețea de clasificare, astfel, s-a dorit o arhitectură care să permită un număr mai mare de straturi care să dispună de o performanță cel puțin la fel de bună ca arhitectura precedentă, cu un număr redus de straturi.

Putem presupune o arhitectură de rețea neurală adâncă ce va avea n straturi, de diferite tipuri. Vom dori să învățăm la finalul fiecărui strat, funcția dată de Ai(x), cum se observă în Figura 3.8, unde A este ieșirea stratului i pentru intrarea x.

În Figura 3.8 nu sunt arătate și funcțiile de activare sau pooling pentru a ușura vizualizarea. Funcțiile ce se doresc a fi învățate la ieșirea fiecărui strat, notate cu Ai, reprezintă funcțiile ideale de care ar avea nevoie rețeaua pentru a avea cea mai buna performanță. Reprezentarea notată cu Bi, de la ieșirea fiecărui strat, reprezintă mulțimea funcțiilor aproximate ca fiind aproape de cele ideale prezentate anterior.[17]

Figura 3.8 Model de rețea neurală convoluțională cu n straturi (Sursa: [17])

În acest mod de învățare, rețeaua va încerca să învețe direct aceste funcții de ieșire fără vreun ajutor suplimentar, ceea ce în mediul real, va fi capabilă sa învețe maxim mulțimea Bi de funcții, mulțime ce se apropie doar de mulțimea ideală Ai. În realitate, nu va putea să se apropie nici chiar de ieșirile Bi din cauza apariției efectului de “vanishing gradient” și al modului ineficient de antrenare.

Ajutorul suplimentar asupra rețelei va fi dat de maparea conexiunilor identitate la ieșirea reziduală, adică aplicarea acestei mapări a identității ne spune că pentru un input dat, vom avea la dispoziție și un output neschimbat asupra funcției reziduale.[16]

Funcția reziduală

Funcția reziduală este reprezentată de diferența dintre intrarea si ieșirea blocului rezidual în speță. Putem spune faptul că maparea reziduală reprezintă valoarea adăugată intrării pentru a aproxima cât mai bine funcția finală Ai prezentată în Figura 3.8 a blocului. În Figura 3.9, avem o reprezentare a mapării reziduale care se comportă ca un pod între intrarea si ieșirea din blocul rezidual. În cazul Figurii 3.9, funcția de activare și weight-ul nu sunt prezente însă ele există în rețea.

Figura 3.9 Reprezentarea funcției reziduale(Sursa: [17])

Funcția ce va trebui învățată de către rețea la ieșirea din bloc, va fi notată cu H(x) pentru a corespunde cu documentația oferită de creatorii arhitecturii ResNet.

Așa cum o spun și creatorii acestei rețele, “În loc să ne așteptăm de la straturile stivuite unul sub altul să învețe cum ar trebui aproximată funcția H(x), putem lăsa straturile să aproximeze o funcție reziduală de forma F(x) = H(x) – x.”[17]

Acest citat ne indică deci, că de-a lungul procesului de antrenare, rețeaua reziduală adâncă se va concentra pe învățarea funcției reziduale F(x), deci dacă rețeaua va reuși cumva să învețe diferența dintre intrare și ieșire, atunci acuratețea de predicție poate fi îmbunătățită. Cu alte cuvinte, valoarea reziduală va trebui a fi învățată într-un mod în care să se apropie de valoarea 0, astfel încât să facă optimă maparea identitate.

În acest fel, toate straturile din rețea vor produce întotdeauna hărți optime de caracteristici, adică cea mai bună hartă de caracteristici după efectuarea operațiilor de convoluție, pooling și activare. Harta optimă de caracteristici conține toate caracteristicile pertinente care pot clasifica perfect imaginea în clasa sa de adevăr. [17]

Funcționarea mapării identității

În timpul antrenării rețelei reziduale, putem considera că există două căi prin care gradientul poate reveni la stratul de intrare cât timp se află în blocul rezidual, astfel încât să execute corect procesul de “back-propagation”. Așa cum se observă în Figura 3.9, una dintre aceste căi este maparea identitate, pe bucla din partea de sus, iar cea de-a doua cale este maparea reziduală.

În primul rând, putem enunța reprezentarea blocului rezidual notat cu F(x), ca în Figura 3.9, din punct de vedere matematic, în relația (3.3) fiind un model reprezentativ pentru acesta.

(3.3)

, unde y este ieșirea funcției, x este intrarea din blocul rezidual, iar F(x, {Wi}) reprezintă blocul în sine. În reprezentarea blocului rezidual, avem și notația Wi, acestea fiind straturile de tip weight, i reprezentând numărul de straturi dintr-un bloc rezidual.

Dacă considerăm un astfel de bloc format din 2 straturi de tip weight, relația (3.4) ne arată varianta simplificată a relației de mai sus.

(3.4)

, unde 𝛔 este funcția de activare de tip ReLU iar a doua funcție de activare neliniară este adăugată după folosirea mapării identitate, adică putem scrie H(x) = 𝛔(y). [17]

La încercarea gradientului de a trece prin a doua cale de întoarcere la stratul de intrare, acesta va întâmpina două straturi de tip weight, W1 și W2 din funcția reziduală F(x). Weight-urile sau kernel-ul (filtrul) din cele două straturi W1 și W2 sunt actualizate și valorile noului gradient sunt calculate.

Astfel, pentru a evita situația în care valorile gradientului ar trece prin procesul de “vanishing”, aceste conexiuni de tip shortcut (formate prin maparea identitate) apar în peisaj, ajutând gradientul să ajungă prin prima cale prezentată anterior la stratul de intrare, fără să întâmpine vreun strat adițional de tip weight, acest lucru fiind foarte util deoarece nu va mai exista o schimbare în

gradientul deja calculat. Abordând această metodă, blocul rezidual va fi ocolit, iar gradientul va ajunge la stratul inițial, fiind capabil să corecteze weight-urile.

Arhitectura ResNet-50

În finalul acestui capitol, voi vorbi pe scurt despre arhitectura ResNet-50, arhitectură ce urmează a fi folosită în vederea realizării lucrării de față, pentru recunoașterea mărcii și al modelului unor anumite tipuri de autoturisme.

Figura 3.10 Diagrama arhitecturii ResNet-50(Sursa: [17])

În diagrama prezentă în Figura 3.10, este prezentat modelul arhitectural ResNet-50, model ce cuprinde 4 etape. Ca intrare, imaginea poate avea înălțimea și lățimea numere divizibile cu 32, iar ca adâncimea canalului, 3. În exemplul dat, intrarea va fi de dimensiunea 224 x 224 x 3.

În continuare, fiecare arhitectură de tip ResNet, execută un strat inițial de convoluție cu kernel-ul 7×7 și un strat de tip max-pooling cu kernel de 3×3. După această introducere, prima etapă a rețelei este cuprinsă de 3 blocuri reziduale, fiecare conținând 3 straturi convoluționale având dimensiunea kernel-ului de 64, 64 și respectiv 256, convoluția având loc cu pasul 2, astfel că intrarea se va înjumătății, iar adâncimea canalului se va dubla. De altfel, cu fiecare etapă cu care rețeaua continuă antrenarea, intrarea este înjumătățită, iar adâncimea dublată.

Pentru rețele adânci ca modelul ResNet-50 și altele, fenomenul de “bottle-neck” este de altfel folosit. Pentru fiecare funcție reziduală F, avem 3 straturi cu convoluție 1×1, 3×3 și 1×1, înlănțuite ca într-o stivă. Aceste straturi de convoluție de tipul 1×1, sunt folosite pentru a reduce și implicit a restaura dimensiunile, iar straturile cu convoluție de tipul 3×3, sunt utilizate ca bottle-neck cu intrarea și ieșirea de dimensiuni mai mici. [17]

Partea terminală a rețelei dispune de un strat de tip Average Pooling, urmat de un strat de tip fully-connected având 1000 de neuroni (pentru exemplul utilizării clasei ImageNet [17]).

Clasificarea fine-grained

Variabilitatea ridicată în cadrul unei clasei și modificările aspectului global al obiectelor din aceeași categorie au fost provocările principale ale sarcinilor de recunoaștere de bază. În mod ideal, un model de categorie ar trebui să poată reprezenta toate obiectele din categorie și să fie suficient de flexibil pentru a se încadra în variabilitatea obiectelor din cadrul clasei.

Recent, conform aplicațiilor din viața reală, categoriile subordonate ce se ocupă cu identificarea diferitelor specii de păsări, flori, autoturisme, etc. au devenit un subiect foarte important pentru cercetare în domeniul clasificării imaginilor. Această problemă, cu adresare directă asupra variabilității din interiorul unei clase se numește și fine-grained visual classification și își propune încercarea de a clasifica obiecte din aceeași categorie cu doar câteva diferențe fine intre ele. În esență principala diferență între clasificarea de bază și cea fine-grained este reprezentată de către nivelul de diferențe dintre categorii. Distingerea unei păsări de un avion este o sarcină relativ simplă, ținând cont de multitudinea de indicii vizuale ce vin în ajutor, însă nu este cazul și când ne referim la diferențierea dintre două modele de autoturisme care diferă doar prin forma farurilor sau forma plafonului.

Principala provocare a algoritmului de clasificare fine-grained este reținerea detaliilor discriminative care definesc clasa țintă din imagine și eliminarea detaliilor nesemnificative. O altă provocare este descoperirea și localizarea părților ce conțin detalii discriminative în cadrul obiectelor ce urmează a fi clasificate.

O foarte interesantă categorie de imagini cu o considerabilă variabilitate intra-clasă cât și foarte multe similarități inter-clasă este reprezentată de domeniul autoturismelor. Autoturismele sunt acum indispensabile din viața modernă, ceea ce a dus la reprezentarea unei probleme în ceea ce ține de clasificarea acestora deoarece este o sarcină de recunoaștere fine-grained ce joacă un rol important în foarte multe potențiale aplicații.

Detecția de vehicule reprezintă baza pentru problemele de clasificare a vehiculelor. Detecția de vehicule confirmă prezența unui vehicul într-o imagine și extrage regiunea de interes pentru a elimina scena de fundal. În unele cazuri, este mai eficientă extragerea și utilizarea asupra clasificatorului a unei părți specifice vehiculului și nu o imagine întreagă cu acesta.

Performanța clasificării este sporită atunci când scena de fundal și elementele nedorite în cadrul vehiculului sunt eliminate.[4]

Utilitatea sistemelor de recunoaștere a autoturismelor

Problema clasificării automate de tip vision-based ce ține de marcă și model este o sarcină importantă pentru aplicațiile de tipul sistemelor de transport inteligente și a celor de supraveghere automată a autoturismelor ce poate fi considerată a fi o provocare de clasificare multi-clasă de tip fine-grained în care o clasă este definită de un producător de autoturisme, de model și de anul fabricației. Majoritatea lucrărilor din domeniu abordează mai întâi detecția autoturismelor ce va produce o regiune de interes conținând partea din față sau din spate a autoturismelor segmentate din fundal. În consecință, sistemele de clasificare a autoturismelor vor lucra pe caracteristicile extrase din aceste regiuni de interes.

Autoturismele oferă un număr mare de proprietăți unice comparativ cu alte obiecte. Acestea ne oferă o mare diversitate și un set de provocări care facilitează cercetarea clasificării imaginilor de tipul fine-grained.[4]

Provocările detecției de autoturisme

Există astfel două mari categorii de provocări în recunoașterea mărcii și a modelului unui autoturism, multiplicitatea și ambiguitatea.

Problemele legate de multiplicitate se referă la același model de autoturism având forme diferite de-a lungul anilor de fabricație. În majoritatea timpului forma autoturismului este remodelată de-a lungul timpului pentru a îndeplini condițiile pieței și tehnologia în vigoare.

Problemele legate de ambiguitate pot fi clasificate în două tipuri: similaritățile inter-clasă și variabilitatea intra-clasă. Acestea se pot exprima prin întâlnirea autoturismelor aparținând strict de producători diferiți și având forme sau aparență similară, ca un exemplu fiind două autoturisme ce au mărci diferite și au fața sau spatele asemănătoare.

Pentru oameni, recunoașterea mărcii și a modelului unui autoturism poate fi o sarcină foarte ușoară, mai ales pentru pasionații în domeniu. În principiu autoturismele pot fi identificate de către oameni cu ușurință prin aspecte ce țin de logo-ul companiei, de ornamente sau de literele ce sunt definite pe spatele autoturismului. Chiar și așa ținând cont că majoritatea autoturismelor au forme globale similare, regiuni netexturate și aparențele lor sunt de cele mai multe ori dominate de reflexia mediului înconjurător sau de linii strălucitoare, aceasta a reprezentat întotdeauna o sarcină grea pentru un calculator.

Metodele tradiționale pentru clasificarea imaginilor au nevoie de descriptori efectivi și de algoritmi de machine-learning pentru a obține niște nivele de acuratețe bună. Acești descriptori sunt folosiți pentru a reprezenta un obiect cu caracteristici specifice iar un clasificator învață eticheta imaginii pe baza acestor caracteristici.

Pentru sarcinile de recunoaștere de tipul fine-grained, provocarea este în descoperirea și localizarea regiunilor ce conțin aceste detalii discriminative. A fost dovedit pe baza cercetării cognitive că recunoașterea la nivelul de bază este concentrată pe compararea formelor obiectelor și părților acestora, iar recunoașterea de nivel subordonat este bazată pe compararea detaliilor de aparență a acestor părți specifice ale obiectelor. Oricum, dacă știm locația acestor regiuni discriminative, sarcina de recunoaștere devine mult mai ușoară.[4]

Capitolul 4.Recunoașterea unui autoturism în funcție de marcă și model

După cum a fost prezentat și în capitolul anterior, în cadrul punctului 3.8, sistemele de recunoaștere a autoturismelor în funcție de marcă și model sunt foarte căutate în perioada actuală, ele fiind utilizate în vederea formării unor sisteme inteligente de supraveghere automată a autoturismelor, cum ar fi parcările, punctele de frontieră automatizate, autostrăzile și chiar traficul efectuat în marile orașe.

Lucrarea de față are ca scop realizarea unui sistem de detecție automată a autoturismelor prezente în imagini, aceste imagini putând fi captate de către utilizatorul prototipului hardware creat în vederea testării funcționalității corecte a algoritmului de recunoaștere.

Ulterior, pentru proiectele viitoare ce se doresc a activa tot în domeniul recunoașterii de autoturisme, conform mărcii și a modelului din care acestea fac parte, algoritmul poate fi implementat cu ușurință pe orice sistem hardware având ca distribuție orice sistem de operare și care este capabil să ruleze Python 3.7, împreună cu pachetul de librării PyTorch.

În Figura 4.1 este detaliată o diagramă a procesului de realizare al algoritmului final, implementat pe modulul hardware, după testare, fiind gata de clasificarea unor noi imagini.

Figura 4.1 Procesul de realizare al algoritmului

Implementarea software a algoritmului, cât și cea a interfeței grafice a fost realizată în limbajul Python. Python este considerat a fi un limbaj de programare de nivel înalt, dinamic, ce expune o sintaxă foarte ușoară a codului. Sintaxa permite implementări echivalente cu alte limbaje în mai puține linii de cod, astfel, fiind utilizat și pentru realizarea de aplicații, cât și pentru automatizări.

Python este un limbaj interpretat, nu compilat, ce presupune ca programele să fie oarecum transformate într-un limbaj intermediar, fapt ce ajută drept avantaj la portarea aceluiași cod-base pe diferite sisteme, având diferite configurații. Depanarea codurilor scrise în Python este foarte simplă, iar acest limbaj de programare oferă foarte multe librării open-source ce pot fi folosite în diferite domenii de aplicabilitate, pornind de la operații matematice avansate, până la reprezentarea grafică a unor modele și accesarea arhitecturilor de rețele neurale și alte componente ale domeniului Machine Learning.

Etapele de realizare ale proiectului constau în 4 părți esențiale pentru siguranța funcționării corecte a ansamblului, și anume:

Alegerea tehnologiei și realizarea algoritmului de detecție a autoturismelor după marcă și model.

Asamblarea și punerea în funcțiune a componentelor utilizate pentru partea hardware a prototipului pe care urmează să fie încărcat algoritmul de detecție.

Realizarea interfeței grafice destinată utilizării algoritmului de către utilizatori.

Testarea fiecărei etape de funcționare pentru a ne asigura că totul a fost corect implementat.

4.1 Etapele realizării algoritmului de detecție

În primă fază, pentru realizarea algoritmului de detecție, mă voi folosi de o rețea neurală, implementată cu ajutorul librăriei de modele și surse pentru antrenarea rețelelor de tip deep-learning, PyTorch, librărie scrisă în limbajul de programare Python, cu versiunea aferentă folosită de Python 3.7 .

Pentru această abordare, voi avea nevoie de un set de date cu autoturisme, folosit pentru antrenarea rețelei, acesta conținând diferite modele ale diferiților producători din piață, modele ce s-au remarcat de-a lungul anilor trecuți, cât și modele de actualitate. Unul dintre cele mai cunoscute seturi de date, reprezentând autoturisme, este Stanford Cars Dataset, care conține 16,185 imagini, implicând în cadrul acestora, 196 de clase de autoturisme, imaginile pe fiecare clasă fiind împărțite în proporție de 50% pentru antrenare, respectiv testare, acesta fiind motivul pentru care am ales să o folosesc în lucrarea de față, o reprezentare a acesteia fiind prezentată în Figura 4.2 . [18]

Figura 4.2 Exemple ale setului de date Stanford Cars Dataset (Sursa: [18])

În cadrul imaginilor prezente în setul de date pe care urmează să fie efectuată antrenarea rețelei, vor exista transformări pe fiecare imagine în parte, de la redimensionare, la rotații random, normalizare și, ulterior, alegerea unui batch-size, de 8, în cazul de față, datorită memoriei procesorului grafic utilizat în vederea antrenării, memorie relativ redusă. Batch-size-ul reprezintă numărul de sample-uri din setul de antrenare, ori de test, pe care se va lucra, înainte de actualizarea parametrilor de către optimizatorul ce urmează a fi prezentat în continuare.

În ceea ce ține de alegerea tipului de rețea și modului de antrenare asupra acesteia, am ținut cont de faptul că recunoașterea autoturismelor după marcă și model este o problemă de tipul fine-grained, cu asemănări foarte puțin specifice de la o clasă la alta, astfel, având nevoie de o rețea cu o adâncime considerabilă pentru a obține un minim al acurateții de cel puțin 95% și nedorind dispariția gradienților pe parcursul antrenării, am ales să utilizez o arhitectură de rețea neurală de tip rezidual.

Proiectul final a fost împărțit în două părți, astfel având ocazia să experimentez două arhitecturi diferite ca adâncime, amândouă folosind același principiu de funcționare, pe seturi de date diferite.

Inițial, am ales rețeaua reziduală ResNet50, arhitectură prezentată în cadrul subcapitolului 3.6.4, modelul acesteia fiind deja definit în librăria PyTorch și având acces la el cu ușurință. Pentru această rețea, am folosit modelul pre-antrenat, utilizând la antrenare conceptul de Transfer Learning. Pre-antrenarea setului de date și ponderile obținute în urma acestui proces s-au realizat cu ajutorul setului de date ImageNet [19], ce conține aproximativ 16 milioane de imagini, aparținând diferitelor clase de obiecte, fiind capabil de recunoaștere a mai mult de 1000 de clase distincte.

În cadrul conceptului de Transfer Learning, având modelul rețelei ResNet50 pre-antrenat pe un set de date (ImageNet), vom antrena din nou, același model, pe un nou set de date, în cazul lucrării de față, acesta fiind reprezentată de Stanford Cars Dataset.

Domeniul Transfer Learning-ului conține mai multe abordări, cea utilizată în acest algoritm, fiind cea de Fine Tuning, proces ce pornește de la o rețea antrenată, bazată pe o anumită sarcină, făcând-o pe aceasta să performeze pentru o altă sarcină dorită.

În cazul de față, pentru rețeaua ResNet50 pre-antrenată, ultimul strat de tip Fully-Connected Layer, are în componența sa, 1000 de neuroni, aceștia fiind destinați recunoașterii celor 1000 de clase oferite de setul de date ImageNet. Acest strat va fi înlocuit, fiind necesar un nou strat, care să clasifice doar cele 3 clase diferite de autoturisme alese de mine, clase prezente în setul de date cu care se va face antrenarea.

Pentru această primă parte a proiectului am ales 3 clase din cele 196 prezente în setul de date Stanford Cars Dataset, pentru ușurința în timpii de antrenare și pentru ca algoritmul să poată fi încărcat și rulat în timpi de procesare scurți pentru o demonstrație rapidă.

Noul strat de ieșire va fi atașat modelului ce va urma antrenarea, actualizând în continuare ponderile existente pentru toate straturile de la antrenarea precedentă, pentru a se potrivi în recunoașterea claselor existente în noul set de date, acest proces făcându-se utilizând SGD [20], iar rata de învățare fiind redusă, pentru ca ponderile să nu își piardă semnificația pentru rețea, deoarece în cazul în care rata ar fi accelerată, am putea întâmpina rezultate slabe, datorită pașilor prea mari în optimizarea realizată de SGD.

Stochastic Gradient Descent este cel mai folosit algoritm de optimizare folosit pentru minimizarea funcției cost a unui algoritm de recunoaștere, optimizarea fiind realizată prin procesul de back-propagation. Diferența dintre algoritmul inițial format, Gradient Descent, din care SGD se moștenește ca principiu, este faptul că folosind Gradient Descent, se va trece prin absolut toate sample-urile din setul de antrenare pentru a realiza actualizarea unui singur parametru pe fiecare iterație, în timp ce folosind SGD, se folosește un singur sample din setul de antrenare pentru a actualiza parametrul.

Utilizarea SGD a devenit foarte folosită deoarece oferă rapiditate în cazul optimizării și minimizării funcției cost în cadrul algoritmilor de clasificare, în timp ce, pentru un set de date considerabil, utilizând GD, se poate ajunge la o funcție cost mai mică, însă într-un timp mult mai lung, astfel SGD ajungând să conveargă mult mai rapid în comparație cu GD, iar aproximarea făcută cu SGD fiind de obicei o valoare îndeajuns de mică și astfel, optimă.

Ecuația prin care se face actualizarea parametrilor în cadrul rețelei neurale, utilizând SGD, în faza de back-propagation pentru a calcula gradientul, este:

(4.1)

, unde , reprezintă faza de back-propagation, iar:

θ, este parametrul ce se dorește a fi modificat, în cazul nostru ponderea

este rata de învățare

∇, este gradientul

J, este funcția cost, parametru căreia i se atribuie ponderea, intrarea și clasa de apartenență

Ultimul termen, format din gradient (actualizarea parametrului) și funcția cost, se poate transpune pentru ușurința în citire și înțelegere a procesului, în următoarea ecuație:

(4.2)

Astfel, se observă că termenul este transformat într-o derivată parțială în funcție de parametrul ce se dorește a fi actualizat, derivată a funcției cost. Această operație, ulterior, va fi efectuată până când va converge, adică se va găsi un minim local, așa cum se observă în .

Cu fiecare iterație, respectiv fiecare finalizare a unei epoci, algoritmul SGD va selecta un subset din setul de antrenare, pentru care va executa optimizarea, în vederea găsirii minimului local.

Figura 4.3 Găsirea minimului local al funcției cost, w fiind ponderea (Sursa: [21])

În cadrul optimizării folosind algoritmul SGD, se va folosi și optimizarea utilizând așa numitul momentum, parametru temporal ce va ajuta la ajungerea spre un minim local, mult mai rapid, acesta fiind adăugat la ecuația precedentă de optimizare în timpul fazei de back-propagation, ca în relația (4.3) .

(4.3)

În practică, utilizarea acestui momentum accelerează găsirea minimului local, înmulțind valoarea γ a parametrului momentum cu valoarea actualizată precedent a parametrului θ.

În continuare, pentru procesul de antrenare, am enunțat anterior, că rata de învățare va fi aleasă de o valoare relativ mică, de ordinul 10-3, iar ținând cont că, în performanța unui model, se pot detecta platouri, în care procesul de Fine Tuning este temporar stopat, deoarece ponderile nu mai sunt actualizate în vederea clasificării dorite, a apărut conceptul de Learning Rate Scheduler, concept ce se va apela în momentul apariției acestor platouri, în cazul de față, folosind Learning Rate Scheduler-ul inclus în librăria PyTorch, ReduceLROnPlateau.

Asupra acestui tip de Scheduler, va trebui specificat, un anumit parametru, numit patience, parametru ce indică algoritmului, câte epoci să aștepte până la următorul trigger în care se va face reglarea de rată de învățare. Un alt parametru membru al acestui algoritm este și pragul, numit threshold, rolul acestuia fiind de a măsura noul optim, pentru ca algoritmul să nu fie focusat pe schimbări nesemnificative. În ultimul rând, va fi folosit si parametrul mode, cu valoarea ‘max’, parametru ce va reduce rata de învățare în momentul în care cantitatea monitorizată se oprește din creștere. [22]

Funcția cost folosită în vederea măsurării performanțelor de clasificare a modelului este Cross Entropy Loss, aceasta măsurând performanța modelului al cărui output va fi o probabilitate situată între 0 și 1. Cu cât valoarea probabilității predicției diverge de la clasa de apartenență, cu atât funcția cost crește. [23]

Figura 4.4 Graficul funcției cost Entropie-Încrucișată

pentru o valoare de adevăr = 1 (Sursa: [23])

În graficul prezent mai sus, se observă că cu cât valoarea probabilității tinde spre 1, cu atât funcția cost scade.

(4.4)

În relația (4.4), y reprezintă indicatorul binar (0 sau 1), dacă label-ul clasei c este clasificarea corectă pentru observația o, parametrul p fiind probabilitatea ca observația o să aparțină clasei c, în consecință, pentru un număr de clase M > 2, se va calcula separat pierderea pentru fiecare clasă, după care rezultatul va fi însumat. [23]

Conform informațiilor prezente mai sus, a fost realizat algoritmul ce va fi încărcat pe modulul hardware ce urmează a fi folosit în vederea demonstrației funcționării prototipului.

În cea de-a doua parte a proiectului, am realizat o antrenare similară cu cea pentru testarea prototipului, însă folosind o arhitectură de rețea neurală reziduală de tip ResNet34, antrenare bazată pe toate cele 196 de clase prezente în setul de date Stanford Cars Dataset.

În Figura 4.5 se poate observa o comparație la nivel de arhitectură, între o rețea neurală convoluțională de 34 straturi și cea de tipul ResNet34.

Pentru cele 196 de clase, am ales tot o rețea reziduală, cu mai puține straturi, deoarece am avut ocazia să compar nivelele de performanță între cele 2 și pentru că procesorul grafic al sistemului de pe care a fost inițiată antrenarea, nu mi-a permis rularea unei rețele de tip ResNet50 pentru un număr de 196 clase, chiar și cu un batch-size asupra imaginilor de intrare de mai puțin de 8 (similar cu antrenarea pe 3 clase, tot cu un batch-size de 8), iar o antrenare pe baza procesorului, ar fi avut timpi de antrenare foarte lungi și ineficienți, astfel reușind să inițiez antrenarea pe cele 196 de clase cu un batch-size de 16, cu timpi de antrenare relativ scurți.

La fel și pentru această parte, au existat transformări asupra imaginilor de intrare, iar parametrii utilizați ulterior, au fost similari cu cei folosiți la antrenarea modelului folosit pentru prototipul hardware pe care a fost încărcat algoritmul de clasificare.

Figura 4.5 Arhitectura unei rețele neurale cu 34 straturi (stânga)

și arhitectura unei rețele neurale reziduale de tip ResNet34 (dreapta) (Sursa: [24])

4.2 Asamblarea și punerea în funcțiune a modulului hardware

Pentru partea hardware, dorind să formez prin combinarea mai multor componente electronice, împreună cu o placă de dezvoltare capabilă să suporte un nivel de calcul relativ mare, un modul prototip pe care să încarc algoritmul de clasificare și împreună cu o interfață grafică creată ulterior, să fie un modul complet ce permite unui utilizator să capteze poze și să poată avea rezultate promițătoare rapide, am ales să utilizez o placă de dezvoltare Raspberry Pi 4B, împreună cu un ecran LCD Touchscreen de 3.5inch pentru a afișa interfața grafică cu rezultatele, și un modul de cameră oferit tot de Raspberry, ce ne va permite să captăm imaginile ce urmează a fi clasificate.

Raspberry Pi este o serie compactă într-o singură placă, de mini-calculator, fabricată în Regatul Unit de către Raspberry Pi Foundation. Această serie de plăcuțe de dezvoltare, a fost concepută în scopuri didactice, pentru a ajuta elevii și studenții să dezvolte diferite proiecte de tipul DIY sau teme abordate de diferiți profesori. Pe aceste plăcuțe se pot utiliza diverse sisteme de operare, de la versiuni dedicate de Windows 10, până la sisteme de operare proprii sau Linux.

Plăcuța folosită în lucrarea de față, este o plăcuță Raspberry Pi, ce are în componență un proces Arm Cortex-A72, ce operează la frecvența de lucru de 1.5 GHz cu 64 biți, o interfață Wi-Fi, Bluetooth, interfață Ethernet, porturi USB și alimentare pe bază de adaptor Type-C, împreună cu posibilitatea de a conecta monitoare externe prin micro-HDMI. De asemenea, plăcuța ne oferă și 40 de pini GPIO.

Am folosit sistemul de operare Raspbian, ce reprezintă o distribuție de Linux, modificare a interfeței bine cunoscute de Debian, acesta fiind încărcat pe un card de memorie atașat plăcuței de dezvoltare.

Odată instalată distribuția de Linux pe cardul de memorie, ecranul LCD de 3.5inch atașat la pinii GPIO ai plăcuței, și modulul de cameră conectat în portul dedicat de pe Raspberry, am putut asambla totul în carcasa dedicată acestui proiect, carcasă printată 3D, așa cum se observă în Figura 4.9.

Figura 4.6 Modulul hardware asamblat

În continuare, pentru configurarea sistemului de operare și instalarea pachetelor necesare utilizării camerei și a ecranului LCD, plăcuța a fost alimentată cu un alimentator cu tensiunea de intrare de +5V, curent de încărcare de 3A și intrare de tip Type-C.

După încercarea forțată de a conecta plăcuța la rețeaua Wi-Fi fără acces la un monitor, m-am conectat printr-o conexiune SSH la sistemul plăcuței, am instalat driver-ul pentru cameră, monitor și ulterior pachetele necesare accesării versiunii de Python 3.7, implicit a librăriilor necesare testării modelului ce urmează a fi încărcat pe plăcuță.

În Figura 4.7 se poate observa varianta finală a prototipului funcțional, cu toate pachetele necesare utilizării accesoriilor atașate și a versiunii de Python 3.7, alături de librăriile aferente.

Figura 4.7 Modulul hardware funcțional

În continuare, va fi prezentată operațiunea de realizare a interfeței grafice destinată utilizatorilor ce pot folosi prototipul în vederea clasificării autoturismelor dorite.

4.3 Interfața aplicației

Interfața grafică destinată utilizatorului a fost realizată cu ajutorul librăriei standard inclusă în versiunea 3.7 a limbajului Python, numită Tkinter.

Toate fișierele vor fi prezente pe Desktop-ul sistemului de operare de pe Raspberry, utilizatorul unui astfel de sistem final, neavând posibilitatea de navigație prin funcțiile vizuale ale linx-ului ca interfață grafică, ci doar în aplicația interfațată oferită pe sistem, după executarea procesului de boot. De asemenea va fi deschisă o consolă în background-ul interfeței grafice care este rulată odată cu procesul de boot al kernel-ului instalat pe Raspberry, pentru a ține evidența unui log, în cazul în care apare o eroare în sistem, aceasta să fie detectată și rezolvată cu ușurință. În cazul în care, modelul este îmbunătățit pe viitor, cu ajutorul algoritmului de antrenare, acesta va fi salvat și transferat în folderul corespondent fișierelor pentru model, iar algoritmul de testare de pe plăcuță va știi automat să îl încarce pe acesta, singura necesitate fiind încărcarea noului model și al claselor corespondente pentru predicția corectă, prin conexiune SSH către sistemul de Raspberry.

Ca ansamblu de fișiere, în Figura 4.8 sunt detaliate fișierele ce se vor afla pe Desktop-ul Raspberry-ului, fișiere ce sunt necesare utilizării algoritmului de clasificare.

Figura 4.8 Ansamblul de fișiere încărcat pe Raspberry

În cele ce urmează se va descrie funcționalitatea interfeței prezente în Figura 4.9 .

Figura 4.9 Interfața grafică destinată utilizatorului, înaintea clasificării

Interfața grafică va fi una simplă și foarte intuitivă pentru utilizator, astfel încât să nu apară probleme în momentul în care un utilizator neexperimentat ar dori să folosească aplicația.

În partea de sus, vor exista 2 butoane, iar în partea de jos, 2 cadrane, după cum urmează:

Butonul START – buton destinat începerii algoritmului; în momentul apăsării acestui buton, pe ecran va apărea camera, după care există un timp destinat focalizării de 5 secunde, urmând ca poza să fie captată și transmisă spre clasificare;

Butonul OPREȘTE – buton destinat închiderii aplicației și implicit sistemului de operare al plăcuței;

Cadranul din STÂNGA – cadran destinat apariției imaginii captate spre clasificare; în momentul inițial, va exista o poză de bun venit (Figura 4.9), iar după prima clasificare, va apărea poza captată de către utilizator (Figura 4.10);

Cadranul din DREAPTA – cadran destinat apariției modelului prezis de către algoritm, precizia cu care acesta a clasificat autoturismul și ora la care s-a efectuat predicția;

Figura 4.10 Interfața grafică destinată utilizatorului, după clasificare

După clasificare, oricând dorește administratorul sistemului, toate clasificările realizate anterior, vor fi scrise automat de către algoritmul utilizat pentru clasificare, într-un fișier ce poate fi accesat prin conexiune SSH la sistemul Raspberry.

Capitolul 5.Experimente și rezultate

În cele ce urmează voi detalia în mod specific, tehnica prin care au fost antrenate cele două rețele, ResNet34, pentru cele 169 clase, respectiv ResNet50, pentru cele 3 clase, urmând să specific și rezultatele obținute în urma acestora, și potențialul fiecăreia de clasificare.

Principiul de Transfer Learning a fost adoptat din perspectiva folosită de majoritatea pasionaților ce activează în domeniul Machine Learning, fie ca hobby ori ca task-uri importante, acela că antrenarea unei rețele “from scratch”, adică de la început din toate punctele de vedere, este în primul rând foarte costisitoare, din pricina necesității unui set de date foarte specific și numeros ca volum de date, iar în al doilea rând, din cauza timpului foarte mare de antrenare, respectiv necesitatea unui sistem de calcul foarte puternic, cu un procesor grafic de ultimă generație. Antrenarea celor două modele a fost realizată utilizând metoda de implementare a Transfer Learning-ului oferită de librăria PyTorch. [25]

Inițial, în stadiul incipient al proiectului, am avut ocazia de a testa manifestarea unei antrenări “from scratch”, tot pe o rețea neurală reziduală, de tip ResNet50 și ResNet152, pe un set de date exclusiv concentrat pe diverse modele de autoturisme, numit „VMMR db”, utilizând un server performant din cadrul companiei în care lucrez. În urma procesului foarte lung de antrenare, aceasta pornind de la o nouă inițializare, am avut surprinderea neplăcută de a obține în urma a 50 de epoci, o rată de acuratețe de aproximativ 50%, acest motiv datorându-se antrenării excesive pe setul de date neavând îndeajuns de multe fotografii pentru fiecare model, ajungând în situația creării procesului de over-fitting, proces ce implică învățarea perfectă a setului de test implicat în procesul de antrenare, și incapabilitatea clasificării altor modele.

Ulterior, pentru cele două modele ce au fost antrenate am urmat următorii pași:

Definirea seturilor de date, respectiv antrenare și testare

Aplicarea asupra acestor seturi de date, a transformărilor de redimensionare, rotație și normalizări

Alegerea batch-size-ului de valoare 8, pentru antrenarea rețelei ResNet50, respectiv de valoare 16 pentru antrenarea rețelei ResNet34

Definirea funcțiilor reprezentative pentru antrenarea și testarea modelului, funcții ce vor calcula 3 parametrii esențiali pentru a vizualiza performanțele, respectiv “costul antrenării”, “acuratețea antrenării” și “acuratețea testării”

Definirea modelelor ResNet50, respectiv ResNet34, pre-antrenate pe setul de date ImageNet

Înlocuirea ultimului strat al celor două rețele, în stratul liniar cu același număr de caracteristici de intrare (2048 pentru ResNet50 implicând 3 clase, respectiv, 512 pentru ResNet34 implicând 196 clase), dar doar 3, respectiv 196, ieșiri, pentru clasificarea claselor necesare

Definirea funcției cost folosite, de tipul Entropie-Încrucișată, a optimizatorului SGD specificat anterior și al programatorului de rată de învățare, respectiv ReduceLROnPlateau

Antrenarea celor 2 modele pe 10 epoci (inclusiv testarea pe setul de test, după fiecare epocă)

În cadrul Anexei conținând codul utilizat pentru antrenarea celor 2 rețele, se poate vizualiza în comentariile adăugate la fiecare pas, explicația următoarelor secvențe de cod ce urmează a fi executate, conform pașilor expuși mai sus.

În urma antrenării celor 10 epoci, pe ambele modele, am obținut niște rezultate promițătoare pe care le voi comenta în continuare:

Figura 5.1 Rezultatele obținute în urma antrenării și trecerea lor pe un grafic

pentru ResNet50 – 3 Clase (stânga) și ResNet34 – 196 Clase (dreapta)

După cum se observă în Figura 5.1, cel mai important parametru măsurat care ne interesează în vederea clasificării corecte în proporție de peste 95%, este “Acuratețea testării”, ce ne arată proporția ratei predicției pe autoturismele aparținând setului de testare.

Pentru rețeaua ResNet50, antrenată pentru cele 3 clase, acestea fiind Audi TT, Bentley Continental și Mercedes C Class, după doar 4 epoci, modelul a fost stabilizat la peste 90% acuratețe de clasificare, urmând să nu mai scadă sub această valoare. După 5 epoci, modelul a ajuns la o acuratețe de 97%, urmând ca în finalul celei de-a 10-a epoci, să ramână o acuratețe de 95%.

În ceea ce ține de funcția cost, aceasta scade la o valoare de sub 0.2, tot după 4 epoci, rămânând constantă spre o valoare de sub 0.1, după 6 epoci.

La fel și în cazul graficului reprezentând “Acuratețea antrenării”, după doar 4 epoci, modelul este stabilizat la o valoare de peste 90%.

Pentru cel de-al doilea model, ce a implicat antrenarea rețelei ResNet34, conținând 196 clase, precizia în clasificare a fost mult mai slabă, trecând de pragul de 90% abia după 7 epoci, finalizând antrenarea la un prag al acurateții pe setul de test, de doar 93%.

În cazul acestui model, funcția cost a rămas stabilizată spre o valoare de 0.2, după efectuarea celor 10 epoci, iar pe “Acuratețea antrenării”, performanța a fost tot destul de lentă, trecând de pragul de 90% abia după 8 epoci.

După finalizarea celor 10 epoci pentru ambele modele, am realizat o testare pe imagini necunoscute pentru rețea, pentru a vedea o oarecare precizie în clasificarea modelelor de autoturisme cunoscute sau necunoscute de către modele, obținând astfel următoarele rezultate:

Pentru modelul ResNet50, bazat pe clasificarea celor 3 clase menționate mai sus, am efectuat testări atât pe interfața grafică atașată modulului hardware, cât și în terminalul Python utilizat în cadrul antrenării rețelei.

Figura 5.2 Clasificare obținută în urma utilizării algoritmului din terminalul Python (partea de sus) și rezultatele obținute utilizând prototipul hardware (partea de jos)

În medie, aproximativ toate clasificările efectuate cu modulul hardware, au trecut de o precizie de 95%, astfel obținând rezultatele așteptate, însă, pentru anumite imagini capturate dintr-o poziție mai ciudată, ori dacă poza nu este focalizată corect, algoritmul va prezice corect autoturismul din fotografie, însă nu cu o precizie ce va trece de 95%.

În urma testării cu ajutorul prototipului creat, am obținut rezultatele scrise în fișierul istoric.txt, așa cum a fost menționat anterior, ca în Figura 5.3 .

Figura 5.3 Fișierul istoric.txt unde vor fi scrise rezultatele anterioare

Fotografiile captate de către sistem vor avea un nume random, format din cifre, fotografiile fiind prezente în folderul destinat lor.

Pentru modelul ResNet34, antrenat pe cele 196 clase, am făcut o testare pe diverse modele aparținând sau nu, de cele 196 clase pe care a fost antrenat modelul.

Figura 5.4 Clasificare obținută în urma utilizării algoritmului din terminalul Python pentru rețeaua ResNet34 cu 196 clase

După cum se poate observa în Figura 5.4, în urma realizării celor 4 clasificări, alături de altele captate ulterior, ce nu sunt prezente aici, pot afirma următoarele:

Pentru autoturismele care nu aparțin de cele 196 clase incluse în setul de date, modelul va aproxima o anumită valoare, cea mai apropiată de aspectul autoturismului prezent în imagine, în cadrul imaginii din dreapta jos, observând faptul că un Ford Ranger, este aproximat cu 63% ca fiind un GMC Canyon, deoarece amândouă au tipologia unui autoturism de tip Pick-up Truck.

În ceea ce ține de modelele aparținând producătorilor ce diferențiază relativ mult autoturismele de la un model la altul, în cazul de față, BMW și Mercedes, algoritmul va avea o acuratețe bună de recunoaștere, fără a încurca modelele între ele, chiar și cu o antrenare efectuată cu un set de date de volum redus pe fiecare clasă.

Pentru producătorul AUDI, în poza din stânga jos, se observă faptul că clasificarea a fost făcută corect, însă cu o precizie de doar 74.69%, acest lucru fiind datorat similarităților izbitoare intra-clasă care exista în cadrul multor modele de vehicule produse de AUDI, alături de specificația că în cadrul setului de date folosit, pozele reprezentând autoturismele de tipul celui din imagine, erau într-un număr restrâns, astfel putând afirma că cu cât setul de date este mai bogat în poze ce prezintă modelul respectiv de autoturism dorit spre clasificare, cu atât rezultatele vor fi mai bune.

În final, rezultatele reale ce au reieșit din testarea celor două modele pe 100 de autoturisme diferite (fiecare testare incluzând aproximativ 3-4 clase diferite, cunoscute de către algoritm), comparativ cu rezultatele obținute în urma antrenării, sunt următoarele:

Pentru modelul ResNet50, antrenat pe cele 3 clase:

Media valorii reale a testării – 92%

Valoarea maximă obținută în urma antrenării – 97%

Pentru modelul ResNet34, antrenat pe cele 196 clase:

Media valorii reale a testării – 84%

Valoarea maximă obținută în urma antrenării – 93%

După cum a fost specificat anterior, cea mai mare valoare a acurateții în clasificare au avut-o clasele implicând modele de autoturisme ce diferă în detalii semnificativ de la un model la altul, iar rezultatele ar fi crescut semnificativ, dacă ar fi fost utilizat un set de date de volum mai mare (în cazul acesta distribuția fiind egală între toate clasele).

Concluzii

Concluzii generale

Obiectivul principal al proiectului de licență a fost susținut de realizarea unui algoritm de clasificare a autoturismelor după marcă și model, din fotografii, urmând ca acesta să fie încărcat pe un modul hardware cu o interfață “user-friendly” care să permită unui utilizator, să capteze fotografii în timp real, rezultatul fiind afișat ulterior, pe un ecran LCD, alături de predicția modelului și rata de acuratețe cu care algoritmul a făcut clasificarea.

Inițial, am plecat de la premisa specificată în introducere și ulterior în începutul Capitolului 4, aceea că acest tip de aplicație ar putea facilita multe domenii de aplicabilitate în ceea ce ține de recunoașterea autoturismelor, cum ar fi sistemele inteligente de monitorizare a traficului, parcări și treceri de frontieră automatizate și ar putea aduce un plus, domeniului de marketing, analiștii având la dispoziție avantajul de a cunoaște pe zone geografice, cumpărătorii modelelor lor.

Limitările prezente în acest proiect, au fost în principal date de rezoluția slabă pe care o oferă modulul de captare a imaginilor oferită de Raspberry, împreună cu puterea slabă de procesare grafică ce ar fi putut fi utilizată pentru o antrenare mai puternică, cu un model mai complex și de altfel, mai rapidă a celor 2 modele.

Antrenarea celor 2 rețele folosind modelul ResNet50, respectiv ResNet34, au satisfăcut clasificările dorite, însă sub anumite condiții, performanțele nu se vor ridica peste pragul de 95%, acest lucru fiind datorat imaginilor de proastă calitate, ori modelului conținând un număr relativ mare de clase, iar parte din aceste clase, fiind și modele ale aceluiași producător, ce prezintă similarități izbitoare intra-clasă, nefiind antrenat folosind ResNet50, din pricina procesorului grafic de slabă calitate și efectuării a doar 10 epoci, dorind să obțin într-un timp cât mai scurt, performanțe trecute de 90%, însă, pentru o antrenare ulterioară, cu un set de date mai bogat ca volum de imagini cuprinzând aceleași clase de autoturisme, rezultatele pot fi mult mai satisfăcătoare în cazul tuturor claselor prezente în setul de date, cu condiția păstrării proporției de imagini aparținând de fiecare clasă.

În concluzie, această lucrare a prezentat o serie de soluții fiabile, atât ca timp, cât și ca performanțe, pentru a construi un sistem integru capabil să recunoască marca și modelul unui autoturism prezent într-o fotografie.

Contribuții personale

În cadrul lucrării de licență, am obținut rezultate promițătoare conform obiectivelor inițiale, având următoarele contribuții personale:

Găsirea unui set de date specific pentru domeniul de aplicabilitate a autoturismelor

Implementarea unui algoritm pentru antrenarea rețelelor ResNet50, respectiv ResNet34, utilizând tehnica de Transfer Learning

Încercarea implementării unei rețele de tip ResNet50 sau ResNet152, “from scratch”, ajungând la niște rezultate nu atât de satisfăcătoare

Implementarea unui algoritm pentru testarea modelelor antrenate pe cele 3 clase, respectiv 196 clase

Realizarea și asamblarea modulului hardware, împreună cu interfața grafică oferită utilizatorului și a algoritmului de captare al imaginilor

Integrarea algoritmului de clasificare, cu partea hardware și punerea în funcțiune a modelului respectiv pentru a oferi utilizatorului un modul final

Analiza rezultatelor și depistarea eventualelor surse de clasificare slabă

Dezvoltări viitoare

Ținând cont de interesul și implicarea mea în domeniul inteligenței artificiale și al învățării profunde, mi-am propus ca pe viitor, să abordez ideea de implementare a acestor tipuri de algoritmi de clasificare bazați pe recunoașterea diferitelor modele de autoturisme, spre o arie mai diversificată, integrând algoritmi de recunoaștere nu doar pentru autoturisme, ci mai specific, spre recunoașterea tuturor obiectelor dintr-un flux video, astfel implicând, de la oameni, la obiectele de pe trotuar, benzile de circulație, tipurile de autoturisme parcate și recunoașterea semnelor de circulație, fiind capabil să dezvolt un algoritm ce oferă utilitate absolut necesară autoturismelor electrice de tip self-driving.

Tot în cadrul acestei abordări, voi avea în vedere și integrarea unei interfețe grafice pentru utilizatorii acestor autoturisme, astfel încât aceștia să fie notificați prin metode sonore sau vizuale, utilizând ecranele de bord ale respectivelor vehicule, atunci când apare o problemă și este necesară intervenția acestora pentru evitarea oricărui pericol.

Bibliografie

[1] Maria Petrou și Costas Petrou, Image Processing: The Fundamentals, Second Edition , John Wiley & Sons, Ltd, 2010, pp. 1-22, pp 293, pp 295, pp 395.

[2] Why Superior Network Performance Matters, “A focus on efficiency”, 2013.

[3] YouTube, “Youtube press statistics”, https://youtube.com/yt/press/statistics.html/, 2015, accesat la data: 09.02.2020

[4] Tafazzoli, Faezeh, "Vehicle make and model recognition for intelligent transportation monitoring and surveillance.", https://doi.org/10.18297/etd/2630 ,2017, pp 1-5, pp 11-12 accesat la data: 13.01.2020.

[5] Li Deng, Dong Yu, “Deep learning: methods and applications”, Foundations and Trends in Signal Processing, vol 7, no. 3-4, pp. 197-387, 2014.

[6] GitHub, “CS231n Convolutional Neural Networks for Visual Recognition”, http://cs231n.github.io/neural-networks-1/#nn, accesat la data: 23.12.2019.

[7] Ovidiu Grigore, Curs Rețele neuronal și sisteme fuzzy ”http://www.ai.pub.ro/content/RNSF.htm”, accesat la data: 11.02.2020

[8] Victor Neagoe, Curs Recunoașterea formelor de inteligență artificială, 2014-2015

[9] Yoshua Bengio, Nicolas Boulanger-Lewandowski, and Razvan Pascanu. Advances in Optimizing Recurrent Networks. 2012.

[10] ”Negative Gradient Algorithm”, https://towardsdatascience.com/gradient-descent- algorithm-and-its-variants-10f652806a3, accesat la data: 02.03.2020

[11] CS231n: Convolutional Neural Networks for Visual Recognition, http://cs231n.stanford.edu/2018/, accesat la data: 22.03.2020

[12] P. J. Burt, “Fast filter transforms for image processing,” Compur. Graph. Image Processing, vol. 16, pp. 20-51, 1981.

[13] Machine learning in computer vision, Fei-Fei Li, http://www.cs.princeton.edu/courses/archive/spr07/cos424/lectures/li-guest-lecture.pdf, accesat la data: 22.03.2020

[14] Prof. Dr. Ing. Ovidiu Grigore, „Note de curs RNSF,” 2019. http://ai.pub.ro/, accesat la data: 14.03.2020

[15] R. Nash și K. O'Shea, „An introduction to convolutional neural networks,” arXiv preprint arXiv:1511.08458, 2015.

[16] “PyTorch ResNet: Building, Training and Scaling Residual Networks on PyTorch”, https://missinglink.ai/guides/pytorch/pytorch-resnet-building-training-scaling-residual-networks-pytorch/, accesat la data: 08.04.2020

[17] Utkarsh Gupta, “Detailed Guide to Understand and Implement ResNets”, https://cv-tricks.com/keras/understand-implement-resnets/, accesat la data: 08.04.2020

[18] Jonathan Krause, Michael Stark, Jia Deng, Li Fei-Fei, “3D Object Representations for Fine-Grained Categorization”, 4th IEEE Workshop on 3D Representation and Recognition, at ICCV 2013 (3dRR-13). Sydney, Australia. Dec. 8, 2013

[19] Stanford Vision Lab, Stanford University, “Image Net Dataset”, http://www.image-net.org/, accesat la data: 09.06.2020

[20] Guest Blogger, Deep Learning Part 2, https://blog.revolutionanalytics.com/2016/08/deep-learning-part-2.html/, accesat la data: 09.06.2020

[21] Casper Hansen, Optimizers Explained, https://mlfromscratch.com/optimizers-explained/#/, accesat la data: 09.06.2020

[22] Torch Contributors, TORCH.OPTIM, https://pytorch.org/docs/stable/optim.html#torch.optim.lr_scheduler.ReduceLROnPlateau, accesat la data: 09.06.2020

[23] Loss Functions, https://ml-cheatsheet.readthedocs.io/en/latest/loss_functions.html#cross-entropy, accesat la data: 09.06.2020

[24] Christopher Thomas BSc Hons, U-Nets with ResNet Encoders and cross connections, https://mc.ai/u-nets-with-resnet-encoders-and-cross-connections/, accesat la data: 09.06.2020

[25] Sasank Chilamkurthy, TRANSFER LEARNING FOR COMPUTER VISION TUTORIAL, https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html/, accesat la data: 09.06.2020

Anexe

Anexa 1: Cod sursă pentru antrenarea, testarea și printarea rezultatelor în grafice, pentru rețeaua ResNet50 cu 3 clase

#Se importa librariile necesare

import matplotlib.pyplot as plt

import numpy as np

import torch

import torch.nn as nn

import torch.optim as optim

import torchvision

import torchvision.models as models

import torchvision.transforms as transforms

import time

import os

import PIL.Image as Image

from IPython.display import display

#Pentru antrenare, se foloseste ori driver-ul CUDA, daca exista, altfel se realizeaza cu CPU

#Motivul acestei secvente este ca am incercat antrenarea si pe sisteme fara placi dedicate Nvidia care suportau CUDA)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

print(device)

print(torch.cuda.get_device_name(device))

#Definim directoarele de lucru

#Acestea sunt directoarele locale unde s-a realizat antrenarea finala

dataset_dir = "/home/cristi/Desktop/Test3ClaseSoft/setDate/"

main_dir = "/home/cristi/Desktop/Test3ClaseSoft/"

#Definirea transformarilor ce se vor efectua pe imaginile de antrenare, respectiv de test

transformari_antrenare = transforms.Compose([transforms.Resize((400, 400)),

transforms.RandomHorizontalFlip(),

transforms.RandomRotation(15),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

transformari_test = transforms.Compose([transforms.Resize((400, 400)),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

#Definirea seturilor de date si aplicarea transformarilor definite mai sus

#Formarea celor 2 dataloadere folosind libraria Torch

#Pentru setul de antrenare, se va face shuffle dupa fiecare epoch, batch-ul fiind ales la 8

#Pentru setul de test, nu se va face shuffle, batch-ul fiind ales la 8

#Num-workers a fost ales 2, pentru executarea a 2 subprocese in incarcarea datelor

#Batch-ul a fost ales 8 pentru ambele, numar relativ mic datorat memoriei insuficiente pe GPU

dataset = torchvision.datasets.ImageFolder(root=dataset_dir+"train", transform = transformari_antrenare)

trainloader = torch.utils.data.DataLoader(dataset, batch_size = 8, shuffle=True, num_workers = 2)

dataset2 = torchvision.datasets.ImageFolder(root=dataset_dir+"test", transform = transformari_test)

testloader = torch.utils.data.DataLoader(dataset2, batch_size = 8, shuffle=False, num_workers = 2)

#Definirea unei functii reprezentative pentru antrenarea modelului

def train_model(model, criterion, optimizer, scheduler, n_epochs = 10):

#Definim vectorii pentru stocarea de train_loss, train_accuracy, test_accuracy

losses = []

accuracies = []

test_accuracies = []

#Setam modelul in modul de antrenare

model.train()

for epoch in range(n_epochs):

#Variabile pentru loss, correct si timpul parcurs

since = time.time()

running_loss = 0.0

running_correct = 0.0

for i, data in enumerate(trainloader, 0):

#Intrarile si etichetele le parsam catre CUDA

inputs, labels = data

inputs = inputs.to(device)

labels = labels.to(device)

#Setam gradientii zero inaintea proceselor de antrenare

#Daca nu am seta gradientii 0, cand incepe antrenarea,

#acestia pot arata spre o directie gresita fata de gasirea minimului

optimizer.zero_grad()

# Procesul de feed forward, back propagation si optimizare

outputs = model(inputs)

_, predicted = torch.max(outputs.data, 1)

loss = criterion(outputs, labels)

loss.backward()

optimizer.step()

# Calcularea loss-ului si accuracy-ului

running_loss += loss.item()

running_correct += (labels==predicted).sum().item()

Durata unei epoci, loss-ul si accuracy-ul

epoch_duration = time.time()-since

epoch_loss = running_loss/len(trainloader)

epoch_acc = 100/8*running_correct/len(trainloader)

print("Epoca %s, durata: %d s, loss: %.4f, accuracy: %.4f" % (epoch+1, epoch_duration, epoch_loss, epoch_acc))

#Popularea vectorului de loss si accuracy

losses.append(epoch_loss)

accuracies.append(epoch_acc)

#Schimbarea modelului pe modul de evaluare,

#pentru efectuarea testarii pe setul de test

model.eval()

test_acc = eval_model(model)

test_accuracies.append(test_acc)

#Dupa validare se trece din nou modelul in modul de antrenare

model.train()

scheduler.step(test_acc)

since = time.time()

print('Proces de antrenare terminat.')

return model, losses, accuracies, test_accuracies

#Definirea unei functii reprezentative pentru testarea pe setul de test

#Se realizeaza operatii similare cu cele prezentate mai sus

def eval_model(model):

correct = 0.0

total = 0.0

with torch.no_grad():

for i, data in enumerate(testloader, 0):

images, labels = data

images = images.to(device)

labels = labels.to(device)

outputs = model_ft(images)

_, predicted = torch.max(outputs.data, 1)

total += labels.size(0)

correct += (predicted == labels).sum().item()

test_acc = 100.0 * correct / total

print('Acuratetea pe imaginile de test: %d %%' % (test_acc))

return test_acc

#Definirea modelului

#Resnet50 preantrenat pe setul de date ImageNet

model_ft = models.resnet50(pretrained=True)

#Preluarea numarului de intrari pentru fiecare sample in stratul FC

num_ftrs = model_ft.fc.in_features

#Inlocuim ultimul strat al retelei(fostul strat = Softmax 1000 neuroni)

#Cu un strat liniar, cu acelasi numar de intrari, si 3 iesiri, pentru cele 3 clase

model_ft.fc = nn.Linear(num_ftrs, 3)

#Trimitem modelul in CUDA

model_ft = model_ft.to(device)

#Definirea criterion-ului si optimizer-ului

criterion = nn.CrossEntropyLoss()

optimizer = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

lrscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', patience=3, threshold = 0.9)

#Antrenarea Modelului

model_ft, training_losses, training_accs, test_accs = train_model(model_ft, criterion, optimizer, lrscheduler, n_epochs=10)

#Printarea prin grafice a rezultatelor in urma antrenarii retelei

fig, axs = plt.subplots(3, gridspec_kw={'hspace': 0.2, 'wspace': 1}, figsize=(5,12))

axs[0].plot(training_losses)

axs[0].set_title("Training Loss")

axs[1].plot(training_accs)

axs[1].set_title("Training Accuracy")

axs[2].plot(test_accs)

axs[2].set_title("Test Accuracy")

#Procesul de testare

#Aflarea claselor, trecerea modelului in modul de evaluare, transformarea imaginii dorite spre testare, parsare in model

#Dupa evaluarea de catre model, v_acuratete va fi vectorul de precizie

#Din v_acuratete extragem maximul si il printam alaturi de imaginea dorita spre testare

def find_classes(dir):

classes = os.listdir(dir)

classes.sort()

class_to_idx = {classes[i]: i for i in range(len(classes))}

return classes, class_to_idx

classes, c_to_idx = find_classes(dataset_dir+"train")

model_ft.eval()

loader = transforms.Compose([transforms.Resize((400, 400)),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

image = Image.open(main_dir + "test_audi.jpg")

image = loader(image).float()

image = torch.autograd.Variable(image, requires_grad=True)

image = image.unsqueeze(0)

image = image.cuda()

output = model_ft(image)

v_acuratete = torch.nn.functional.softmax(output.data, dim = 1)

confidenta, predictie = torch.max(output.data, 1)

display(Image.open(main_dir + "test_audi.jpg"))

print("Predictia: ",classes[predictie.item()], "– Acuratete: ", round(torch.max(v_acuratete).item(),4) * 100, "%")

#Salvam modelul dupa antrenare

torch.save(model_ft, main_dir + "modelSalvat")

Anexa 2: Cod sursă pentru antrenarea, testarea și printarea rezultatelor în grafice, pentru rețeaua ResNet34 cu 196 clase

#Se importa librariile necesare

import matplotlib.pyplot as plt

import numpy as np

import torch

import torch.nn as nn

import torch.optim as optim

import torchvision

import torchvision.models as models

import torchvision.transforms as transforms

import time

import os

import PIL.Image as Image

from IPython.display import display

#Pentru antrenare, se foloseste ori driver-ul CUDA, daca exista, altfel se realizeaza cu CPU

#Motivul acestei secvente este ca am incercat antrenarea si pe sisteme fara placi dedicate Nvidia care suportau CUDA)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

print(device)

print(torch.cuda.get_device_name(device))

#Definim directoarele de lucru

#Acestea sunt directoarele locale unde s-a realizat antrenarea finala

dataset_dir = "/home/cristi/Desktop/Test196ClaseSoft/setDate/"

main_dir = "/home/cristi/Desktop/Test196ClaseSoft/"

#Definirea transformarilor ce se vor efectua pe imaginile de antrenare, respectiv de test

train_tfms = transforms.Compose([transforms.Resize((400, 400)),

transforms.RandomHorizontalFlip(),

transforms.RandomRotation(15),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

test_tfms = transforms.Compose([transforms.Resize((400, 400)),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

#Definirea seturilor de date si aplicarea transformarilor definite mai sus

#Formarea celor 2 dataloadere folosind libraria Torch

#Pentru setul de antrenare, se va face shuffle dupa fiecare epoch, batch-ul fiind ales la 16

#Pentru setul de test, nu se va face shuffle, batch-ul fiind ales la 16

#Num-workers a fost ales 2, pentru executarea a 2 subprocese in incarcarea datelor

#Batch-ul a fost ales 16 pentru ambele, numar ales relativ ok pentru puterea GPU-ului folosit, si retelei Resnet34

dataset = torchvision.datasets.ImageFolder(root=dataset_dir+"train", transform = train_tfms)

trainloader = torch.utils.data.DataLoader(dataset, batch_size = 16, shuffle=True, num_workers = 2)

dataset2 = torchvision.datasets.ImageFolder(root=dataset_dir+"test", transform = test_tfms)

testloader = torch.utils.data.DataLoader(dataset2, batch_size = 16, shuffle=False, num_workers = 2)

#Definirea unei functii reprezentative pentru antrenarea modelului

def train_model(model, criterion, optimizer, scheduler, n_epochs = 10):

#Definim vectorii pentru stocarea de train_loss, train_accuracy, test_accuracy

losses = []

accuracies = []

test_accuracies = []

#Setam modelul in modul de antrenare

model.train()

for epoch in range(n_epochs):

#Variabile pentru loss, correct si timpul parcurs

since = time.time()

running_loss = 0.0

running_correct = 0.0

for i, data in enumerate(trainloader, 0):

#Intrarile si etichetele le parsam catre CUDA

inputs, labels = data

inputs = inputs.to(device)

labels = labels.to(device)

#Setam gradientii zero inaintea proceselor de antrenare

#Daca nu am seta gradientii 0, cand incepe antrenarea,

#acestia pot arata spre o directie gresita fata de gasirea minimului

optimizer.zero_grad()

#Procesul de feed forward, back propagation si optimizare

outputs = model(inputs)

_, predicted = torch.max(outputs.data, 1)

loss = criterion(outputs, labels)

loss.backward()

optimizer.step()

# Calcularea loss-ului si accuracy-ului

running_loss += loss.item()

running_correct += (labels==predicted).sum().item()

#Durata unei epoci, loss-ul si accuracy-ul

epoch_duration = time.time()-since

epoch_loss = running_loss/len(trainloader)

epoch_acc = 100/16*running_correct/len(trainloader)

print("Epoca %s, durata: %d s, loss: %.4f, accuracy: %.4f" % (epoch+1, epoch_duration, epoch_loss, epoch_acc))

#Popularea vectorului de loss si accuracy

losses.append(epoch_loss)

accuracies.append(epoch_acc)

#Schimbarea modelului pe modul de evaluare,

#pentru efectuarea testarii pe setul de test

model.eval()

test_acc = eval_model(model)

test_accuracies.append(test_acc)

#Dupa validare se trece din nou modelul in modul de antrenare

model.train()

scheduler.step(test_acc)

since = time.time()

print('Proces de antrenare terminat.')

return model, losses, accuracies, test_accuracies

#Definirea unei functii reprezentative pentru testarea pe setul de test

#Se realizeaza operatii similare cu cele prezentate mai sus

def eval_model(model):

correct = 0.0

total = 0.0

with torch.no_grad():

for i, data in enumerate(testloader, 0):

images, labels = data

#images = images.to(device).half() # uncomment for half precision model

images = images.to(device)

labels = labels.to(device)

outputs = model_ft(images)

_, predicted = torch.max(outputs.data, 1)

total += labels.size(0)

correct += (predicted == labels).sum().item()

test_acc = 100.0 * correct / total

print('Accuracy of the network on the test images: %d %%' % (

test_acc))

return test_acc

#Definirea modelului

#Resnet34 preantrenat pe setul de date ImageNet

model_ft = models.resnet34(pretrained=True)

#Preluarea numarului de intrari pentru fiecare sample in stratul FC

num_ftrs = model_ft.fc.in_features

#Inlocuim ultimul strat al retelei(fostul strat = Softmax 1000 neuroni)

#Cu un strat liniar, cu acelasi numar de intrari, si 196 iesiri, pentru cele 196 clase

model_ft.fc = nn.Linear(num_ftrs, 196)

#Trimitem modelul in CUDA

model_ft = model_ft.to(device)

#Definirea criterion-ului si optimizer-ului

criterion = nn.CrossEntropyLoss()

optimizer = optim.SGD(model_ft.parameters(), lr=0.01, momentum=0.9)

lrscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', patience=3, threshold = 0.9)

#Antrenarea Modelului

model_ft, training_losses, training_accs, test_accs = train_model(model_ft, criterion, optimizer, lrscheduler, n_epochs=10)

#Printarea prin grafice a rezultatelor in urma antrenarii retelei

fig, axs = plt.subplots(3, gridspec_kw={'hspace': 0.2, 'wspace': 1}, figsize=(5,12))

axs[0].plot(training_losses)

axs[0].set_title("Training Loss")

axs[1].plot(training_accs)

axs[1].set_title("Training Accuracy")

axs[2].plot(test_accs)

axs[2].set_title("Test Accuracy")

#Procesul de testare

#Aflarea claselor, trecerea modelului in modul de evaluare, transformarea imaginii dorite spre testare, parsare in model

#Dupa evaluarea de catre model, v_acuratete va fi vectorul de precizie

#Din v_acuratete extragem maximul si il printam alaturi de imaginea dorita spre testare

def find_classes(dir):

classes = os.listdir(dir)

classes.sort()

class_to_idx = {classes[i]: i for i in range(len(classes))}

return classes, class_to_idx

classes, c_to_idx = find_classes(dataset_dir+"train")

model_ft.eval()

loader = transforms.Compose([transforms.Resize((400, 400)),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

image = Image.open(main_dir + "test_mercedes.jpg")

image = loader(image).float()

image = torch.autograd.Variable(image, requires_grad=True)

image = image.unsqueeze(0)

image = image.cuda()

output = model_ft(image)

v_acuratete = torch.nn.functional.softmax(output.data, dim = 1)

confidenta, predictie = torch.max(output.data, 1)

display(Image.open(main_dir + "test_mercedes.jpg"))

print("Predictia: ",classes[predictie.item()], "– Acuratete: ", round(torch.max(v_acuratete).item(),4) * 100, "%")

#Save the model to the specified path

torch.save(model_ft, main_dir + "modelSalvat")

Anexa 3: Cod sursă pentru testarea în terminal a modelului ResNet34 antrenat pe 196 clase

#Pentru a putea incarca modelul antrenat

import matplotlib.pyplot as plt

import numpy as np

import torch

import torch.nn as nn

import torch.optim as optim

import torchvision

import torchvision.models as models

import torchvision.transforms as transforms

import time

import PIL.Image as Image

from IPython.display import display

#Pentru executarea de comenzi in bash

import os

#Definim directoarele de lucru

main_dir = "/Users/cristi/Desktop/Licenta/Algoritmi utilizati/Antrenare/Test196ClaseSoft/"

dataset_dir = "/Users/cristi/Desktop/Licenta/Algoritmi utilizati/Antrenare/Test196ClaseSoft/setDate/"

#Incarcam modelul din calea unde a fost salvat dupa antrenare

model_ft = torch.load(main_dir + "modelSalvat", 'cpu')

model_ft.eval()

#Procesul de testare

#Aflarea claselor, trecerea modelului in modul de evaluare, transformarea imaginii dorite spre testare, parsare in model

#Dupa evaluarea de catre model, v_acuratete va fi vectorul de precizie

#Din v_acuratete extragem maximul si il printam alaturi de imaginea dorita spre testare

def find_classes(dir):

classes = os.listdir(dir)

classes.sort()

class_to_idx = {classes[i]: i for i in range(len(classes))}

return classes, class_to_idx

classes, c_to_idx = find_classes(dataset_dir+"train")

model_ft.eval()

loader = transforms.Compose([transforms.Resize((400, 400)),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

image = Image.open(main_dir + "test_audi3.jpg")

image = loader(image).float()

image = torch.autograd.Variable(image, requires_grad=True)

image = image.unsqueeze(0)

output = model_ft(image)

v_acuratete = torch.nn.functional.softmax(output.data, dim = 1)

confidenta, predictie = torch.max(output.data, 1)

display(Image.open(main_dir + "test_audi3.jpg"))

print("Predictia: ",classes[predictie.item()], "– Acuratete: ", round(torch.max(v_acuratete).item(),4) * 100, "%")

Anexa 4: Cod sursă pentru realizarea interfeței grafice, captarea imaginilor și integrarea testării algoritmului ResNet50 cu 3 clase pe modulul hardware

#Pentru crearea de interfete grafice

import tkinter as tk

from tkinter import *

from tkinter import font as tkFont

from PIL import ImageTk, Image

#Pentru executia de comenzi in bash

import subprocess

#Pentru timp

from datetime import datetime

#Pentru executarea de comenzi in bash

import os

#Pentru a genera random numere

from random import seed

from random import randint

#Pentru camera de la RaspBerry Pi 4

from picamera import PiCamera

from time import sleep

#Pentru a putea incarca modelul antrenat

import matplotlib.pyplot as plt

import numpy as np

import torch

import torch.nn as nn

import torch.optim as optim

import torchvision

import torchvision.models as models

import torchvision.transforms as transforms

import time

import PIL.Image as Image

from IPython.display import display

class program_principal:

def __init__(self):

self.window = tk.Tk()

self.window.attributes('-fullscreen', True)

self.fullScreenState = False

self.window.bind("<F11>", self.toggleFullScreen)

self.window.bind("<Escape>", self.quitFullScreen)

#Incarcam imaginea pe care dorim sa o avem de BUN VENIT

img_redim = Image.open('/home/pi/Desktop/source.png')

latime, inaltime = img_redim.size

#Redimensionam imaginea si vom avea in img_redim imaginea redimensionata

redim = (800,500)

img_redim = img_redim.resize(redim)

#Salvam intr-un fisier imaginea redimensionata

img_redim.save("/home/pi/Desktop/source_redim.png", "PNG")

#Partea de afisare a pozei de BUN VENIT

text1 = tk.Text(self.window, height=40, width=100)

photo = tk.PhotoImage(file='/home/pi/Desktop/source_redim.png')

text1.image_create(tk.END, image=photo)

#Partea de predictie (text initial)

text2 = tk.Text(self.window, height=40, width=65)

text2.tag_configure('big', font=('Verdana', 38, 'bold'))

text2.tag_configure('smaller', font=('Verdana', 30, 'bold'))

text2.insert(tk.END,'\nPredictia:\n', 'big')

text2.insert(tk.END, 'N/A\n', 'smaller')

text2.insert(tk.END,'Precizie:\n', 'big')

text2.insert(tk.END, 'N/A\n', 'smaller')

text2.insert(tk.END, 'Ora:\n', 'big')

text2.insert(tk.END, 'N/A', 'smaller')

helv = tkFont.Font(family='Helvetica', size=17, weight='bold')

#Buton Start

button_start = Button(self.window, text = "START", font = helv, command = lambda : self.startProg(text1,text2), height=4, width=250)

button_start.pack(side=tk.TOP)

#Buton ShutDown

button_shutdown = Button(self.window, text = "OPRESTE", font = helv, command = self.stopProg, height=4, width=250)

button_shutdown.pack(side=tk.TOP)

#Ancoram partea de imagine si partea de predictie in fereasta, sub butoane, pe stanga, una langa alta

text1.pack(side=tk.LEFT)

text2.pack(side=tk.LEFT)

#Afisarea interfetei

self.window.mainloop()

#Functie pentru pornirea programului

@staticmethod

def startProg(textX, textY):

#Se capteaza poza si se salveaza sub un nume random

nume_poza = randint(0,1000000)

camera = PiCamera()

camera.start_preview()

sleep(5)

camera.capture("/home/pi/Desktop/poze_efectuate/" + str(nume_poza) + ".png")

camera.stop_preview()

camera.close()

#Preluam ora la care se face testarea

now = datetime.now()

current_time = now.strftime("%H:%M:%S")

#Incarcam modelul din calea unde a fost salvat dupa antrenare

model_x = torch.load("/home/pi/Desktop/fisiere_model/" + "modelSalvat", 'cpu')

model_x.eval()

#Preluam label-urile claselor

def find_classes(dir):

classes = os.listdir(dir)

classes.sort()

class_to_idx = {classes[i]: i for i in range(len(classes))}

return classes, class_to_idx

classes, c_to_idx = find_classes("/home/pi/Desktop/clase/")

#Prelucram imaginea pentru testare, ca sa fie in parametrii imaginilor care au fost antrenate si testate

loader = transforms.Compose([transforms.Resize((400, 400)),

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

image = Image.open("/home/pi/Desktop/poze_efectuate/" + str(nume_poza) + ".png").convert('RGB')

image = loader(image).float()

image = torch.autograd.Variable(image, requires_grad=True)

image = image.unsqueeze(0)

output = model_x(image)

v_acuratete = torch.nn.functional.softmax(output.data, dim = 1)

confidenta, predictie = torch.max(output.data, 1)

#Luam predictia, o scriem in fisier

fisier = open("/home/pi/Desktop/istoric.txt", "a")

fisier.write("Predictia este: " + str(classes[predictie.item()]) + ", pe imaginea: " + str(nume_poza) + ".jpg, cu o incredere de: " + str(round(torch.max(v_acuratete).item(),4) * 100) + "%, testare realizata la ora: " + str(current_time) + ".\n");

fisier.close()

#Incarcam imaginea pe care dorim sa o avem pusa pentru predictie

img_redim = Image.open("/home/pi/Desktop/poze_efectuate/" + str(nume_poza) + ".png")

latime, inaltime = img_redim.size

#Redimensionam imaginea si vom avea in img_redimX imaginea redimensionata

redim = (800,500)

img_redim = img_redim.resize(redim)

#Salvam intr-un fisier imaginea redimensionata

img_redim.save("/home/pi/Desktop/poze_efectuate_redimensionate/" + str(nume_poza) + ".png", "PNG")

#Partea de afisare a pozei de predictie

textX.delete('1.0', END)

photo = tk.PhotoImage(file="/home/pi/Desktop/poze_efectuate_redimensionate/" + str(nume_poza) + ".png")

#Reference keep

label = Label(image=photo)

label.image = photo

#End Reference keep

textX.image_create(tk.END, image=photo)

#Scriem predictia in partea dreapta

textY.delete('1.0', END)

textY.tag_configure('big', font=('Verdana', 38, 'bold'))

textY.tag_configure('smaller', font=('Verdana', 30, 'bold'))

textY.insert(tk.END,'\nPredictia:\n', 'big')

textY.insert(tk.END, str(classes[predictie.item()]) + '\n', 'smaller')

textY.insert(tk.END,'Precizie:\n', 'big')

textY.insert(tk.END, str(round(torch.max(v_acuratete).item(),4) * 100) + ' %\n', 'smaller')

textY.insert(tk.END, 'Ora:\n', 'big')

textY.insert(tk.END, str(current_time), 'smaller')

#Functie pentru oprirea sistemului

@staticmethod

def stopProg():

subprocess.run(["sudo", "shutdown", "-h", "now"])

#Cele 2 functii destinate debugging-ului

def toggleFullScreen(self, event):

self.fullScreenState = not self.fullScreenState

self.window.attributes("-fullscreen", self.fullScreenState)

def quitFullScreen(self, event):

self.fullScreenState = False

self.window.attributes("-fullscreen", self.fullScreenState)

#Pornirea aplicatiei

if __name__ == '__main__':

app = program_principal()

Similar Posts