Table of Contents

Tema 3 - Optical Character Recognition pe cifre scrise de mână

Responsabili:

Data publicării: 7 mai

Deadline: 20 mai, ora 23:55

Actualizari si modificari:

Obiective

În urma realizării acestei teme:

Intro

Optical Character Recognition este un proces prin care se transformă text scanat în text editabil. În cadrul acestei teme, veți dezvolta un program care va aplica un algoritm de Machine Learning în acest scop. Algoritmul are doua etape: Etapa de învățare, în care codul vostru va primi o serie de imagini și va trebui să învețe să le recunoască; Etapa de prezicere, unde codul vostru va primi o serie de imagini care nu au mai fost vazute și va trebui să decidă ce cifră este reprezentată de imagine.

Cerința

Va trebui să implementați o versiune puțin simplificată a algoritmului Random Forest pentru clasificare.

Reprezentarea Datelor

Fiecare cifră este reprezentată ca o imagine de 28×28. Pentru simplitate, în loc să folosim o matrice pentru a reprezenta o imagine, vom folosi un vector de lungime 784 (28 * 28). Pentru etapa de învățare, fiecare vector va mai avea o dimensiune în plus, anume: v[0] va fi cifra reprezentată de imagine. Deci, în etapa de învățare, fiecare vector va avea 785 de intrări. Pentru predicția cifrei, vectorii nu vor avea răspunsul pe prima pozitie, deci vor avea 784 de intrări.

Fiecare pixel din imagine este de fapt un întreg de la 0 la 255, imaginea fiind practic grey-scale.

Terminologie

Clasa

În cazul nostru, cifra care trebuie prezisă. Termenul de clasă vine din faptul că această problema este una de clasificare.

Sample

Un vector (ca mai sus) care, în cazul nostru, reprezintă o imagine a unei cifre.

Dimensiune

Index al vectorului (denumirea vine de la vectorii din algebră, un vector cu 3 dimensiuni fiind un vector de lungime 3).

Descrierea algoritmului

Decision Tree

Structura

Un arbore de decizie este un arbore binar (în cazul nostru, pentru că parametrii sunt variabile continue). Acesta este similar cu un arbore binar de căutare. Spre deosebire de BST, în nodurile care nu sunt frunze avem două valori, un index de split (splitIdx) și o valoare de split (splitValue). Decizia de a merge în stânga sau în dreapta este efectuată în felul următor: dacă input[splitIndex] <= splitValue , mergem în stânga, daca nu, în dreapta. Astfel, singura diferență la nivelul nodurilor care nu sunt frunze este faptul câ inputul primit la search într-un arbore de decizie este un vector, iar comparațiile în fiecare nod se fac pe dimensiunea specificată în nod.

Un nod frunză conține clasa prezisă pentru vectorul input.

Învățare

Învățarea se face prin împărțirea setului de date după o anumită regulă, în mod recursiv. Când dintr-o împărțire rezultă un set de date cu o singură clasă, atunci se creează o frunză cu clasa respectivă. Split-urile se efectuează în felul următor: pentru fiecare dimensiune (aici apare o modificare la random forest, vezi mai jos, Random subspace projection) și pentru fiecare valoare de pe dimensiunea respectivă se face un split. Pe fiecare din aceste split-uri se aplică o metrică care trebuie minimizată/maximizată (în funcție de metrică). Noi vom folosi o metrică numită Information Gain.

Entropia

Information Gain se bazează pe noțiunea de entropie din Teoria Informației. Entropia este definită astfel:

Entropia unui set de teste în cazul nostru se măsoară în felul următor: fie $pi$ ($i$ de la $0$ la $9$) $=$ numărul de teste din set care are ca rezultat numărul i împărțit la numărul de teste. $pi$ este practic probabilitatea ca un test să aibă rezultatul $i$.

Atenție!

În cazul în care $ pi = 0 $, nu se adaugă nimic la suma ($ \log_2 pi $ fiind nedefinit).

Information Gain

Information Gain este o metrică definită astfel:

Practic, pentru a calcula Information Gain pentru un anumit set de test samples și un index și o valoare de split, calculăm entropia părintelui, respectiv entropia copiiilor rezultați în urma acelui split și aplicăm formula din imagine.

În cazul nostru, suma ponderată a entropiilor copiilor este:

$$ \dfrac{n_{stanga} \cdot H(stanga) + n_{dreapta} \cdot H(dreapta)}{n} $$

Unde:

Pentru mai multe detalii, vedeți aici.

Algoritm

Se alege split-ul care maximizează Information Gain, se stochează indexul de split și valoarea de split în nodul respectiv, după care mergem recursiv pe copii. În cazul în care toate split-urile au ca rezultat un copil care nu are niciun element, atunci vom face un nod frunză care are ca valoare clasa majoritară din setul de date.

Atenție!

Dacă printre splituri există unele care au un copil fără elemente, atunci considerăm că split-ul este prost, și nu-l luăm în considerare. Un element devine frunză doar atunci când toate spliturile au un copil vid.

Bootstrap aggregation (bagging)

Random forest este un algoritm care combină mai mulți decision tree pentru a da o predicție mai bună. Primul pas pentru această combinare este antrenarea fiecărui din cei n decision trees cu seturi de date mai mici, alese în mod aleator ca subseturi din setul inițial de training. Aceste subseturi se pot intersecta între ele. Va trebui să implementați generarea acestor subseturi.

Random subspace projection (feature bagging)

Random subspace projection este o modificare adusă de Random forest la Decision trees-ii pe care îi folosește. Această modificare constă în forțarea split-urilor doar pe anumite dimensiuni, selectate aleator. Astfel, la fiecare split, se aleg mai întâi $\sqrt{num\_dimensiuni}$ dimensiuni, dupa care se încearcă split-uri si se maximizează information gain doar pe acele dimensiuni. Dacă spre exemplu, un sample are $16$ dimensiuni, la fiecare split veți considera doar $4$ dintre ele.

Ca optimizare la selectarea unui split, puteți folosi compute_unique pentru a găsi valorile unice de pe dimensiunile selectate. Astfel, în loc să verificați toate numerele de la $0$ la $255$, le veți verifica doar pe cele care apar. Acest lucru crește viteza algoritmului cu mult, având în vedere faptul că, în general, pixelii vor fi aproape albi sau aproape negri. Valorile intermediare apărând în mai puține locuri.

Schelet

Aveți de implementat funcțiile cu TODO din randomForest.cpp și decisionTree.cpp. Nu aveți voie să modificați altceva.

Note pentru implementare

  void make_leaf(const vector<vector<int>> &samples, const bool is_single_class)
  bool same_class(const vector<vector<int>> &samples) 
  float get_entropy_by_indexes(const vector<vector<int>> &samples, const vector<int> &index)    
  vector<int> compute_unique(const vector<vector<int>> &samples, const int col)
  pair<vector<int>, vector<int>> get_split_as_indexes(const vector<vector<int>> &samples, const int split_index, const int split_value)
  vector<int> random_dimensions(const int size) 
  int predict(const vector<int> &image) const
                 (1, 10)
                  /  \
               (10)  (5)
  int predict(const vector<int> &image) (din RandomForest)
  vector<vector<int>> get_random_samples(const vector<vector<int>>& samples, int num_to_return)
  pair<int, int> find_best_split(const vector<vector<int>> &samples, const vector<int> &dimensions)
  void train(const vector<vector<int>> &samples)

Hint!

Folosiți-vă cât de mult puteți de funcții care operează cu indecși. Copierea matricelor este o operație scumpă.

Date de intrare

Scheletul se ocupa de citirea datelor, deci voi nu trebuie sa implementati nimic.

Date de ieșire

Scheletul se ocupa de datele de iesire.

Schelet de cod si Checker

Resurse

Scheletul si checkerul sunt disponibile aici.

Detalii schelet

Reguli pentru trimitere

Temele vor trebui trimise pe vmchecker. Atenție! Temele trebuie trimise în secțiunea Structuri de Date (CA).

Arhiva trebuie să conțină:

Punctaj

  1. 50p get_split_as_indexes, same_class, random_dimensions, get_random_samples, compute_unique(cate 10p fiecare)
  2. 30p Acuratetea algoritmului: > 85% → 30p, > 55% → 20p, > 25% → 10p
  3. 10p README + comentarii/claritate cod (ATENȚIE! Fișierul README trebuie făcut explicit, cât să se înțeleagă ce ați făcut în sursă, dar fără comentarii inutile și detalii inutile)
  4. 10p pentru coding-style, proporțional cu punctajul obținut pe teste. De exemplu, pentru o temă care obține maxim pe teste, se pot obține 10p dacă nu aveți erori de coding style. Pentru o temă care obține 40p pe teste, se pot obține 5p dacă nu aveți erori de coding style.
  5. O temă care obține 0p pe vmchecker este punctată cu 0.
  6. O temă care nu compilează va fi punctată cu 0.

Nu copiați!

Toate soluțiile vor fi verificate folosind o unealtă de detectare a plagiatului. În cazul detectării unui astfel de caz, atât plagiatorul cât și autorul original (nu contează cine care este) vor primi punctaj 0 pe toate temele!

De aceea, vă sfătuim să nu lăsați rezolvări ale temelor pe calculatoare partajate (la laborator etc), pe mail/liste de discuții/grupuri etc.

Atentie!

Nu folositi comentarii NOLINT in cod (cu exceptia celor care sunt deja prezente in schelet). Daca aveti astfel de comentarii in plus, primiti 0p pe coding style.

FAQ