This shows you the differences between two versions of the page.
sd-ca:teme:tema3-2018 [2018/05/08 23:28] andrei.medar [Punctaj] |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Tema 3 - Optical Character Recognition pe cifre scrise de mână ===== | ||
- | ** Responsabili: ** | ||
- | *[[luca.istrate@mines-paristech.fr | Luca Istrate]] | ||
- | *[[andre_medar@yahoo.com | Andrei Medar]] | ||
- | |||
- | |||
- | ** Data publicării: 7 mai ** | ||
- | |||
- | ** Deadline: 20 mai, ora 23:55 ** | ||
- | |||
- | ===== Obiective ===== | ||
- | |||
- | În urma realizării acestei teme: | ||
- | * veți învăța cum să adaptați structuri de date cunoscute la cerințe mai complicate | ||
- | * veți exersa lucrul colaborativ în echipe de câte doi | ||
- | * veți învăța ceva despre cum funcționează anumiți algoritmi de inteligență artificială | ||
- | |||
- | ===== 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 28x28. 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: | ||
- | |||
- | {{https://wikimedia.org/api/rest_v1/media/math/render/svg/70b8bc9f2666c42790b5aa20b13a55bdc503dbb7?.png?}} | ||
- | |||
- | 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$. | ||
- | |||
- | <note warning> | ||
- | **Atenție!** | ||
- | |||
- | În cazul în care $ pi = 0 $, nu se adaugă nimic la suma ($ \log_2 pi $ fiind nedefinit). | ||
- | </note> | ||
- | |||
- | |||
- | |||
- | == Information Gain == | ||
- | Information Gain este o metrică definită astfel: | ||
- | |||
- | {{https://wikimedia.org/api/rest_v1/media/math/render/svg/264134245f420035af0c4c96bf1c66b9b106ff20?.png?}} | ||
- | |||
- | {{https://wikimedia.org/api/rest_v1/media/math/render/svg/cedf76a71c803381440fd6cfd190b2311d0a116c?.png?}} | ||
- | |||
- | 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: | ||
- | |||
- | * $n_{stanga}$ = numărul de samples din copilul din stânga | ||
- | * $n_{dreapta}$ = numărul de samples din copilul dreapta | ||
- | * $n$ este numărul total de samples. Astfel $n = n_{stânga} + n_{dreapta}$ | ||
- | |||
- | |||
- | |||
- | <note> | ||
- | Pentru mai multe detalii, vedeți | ||
- | [[https://en.wikipedia.org/wiki/Decision_tree_learning#Information_gain | aici]]. | ||
- | </note> | ||
- | |||
- | ==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. | ||
- | |||
- | <note warning> | ||
- | **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. | ||
- | </note> | ||
- | |||
- | |||
- | |||
- | === 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. | ||
- | |||
- | <note> | ||
- | 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. | ||
- | </note> | ||
- | |||
- | === 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) | ||
- | |||
- | |||
- | * ar trebui să lucreze cu variabilele is_leaf și result din Node | ||
- | * dacă is_single_class este false, trebuie să găsiți clasa majoritară din setul de date primit. Dacă două clase apar la fel de des, o veți lua pe prima | ||
- | |||
- | bool same_class(const vector<vector<int>> &samples) | ||
- | |||
- | * întoarce true dacă toate sample-urile din samples au aceeași clasă. Altfel, întoarceți false | ||
- | |||
- | |||
- | * Exemplu: samples = {{1, 0, 0, 10....}, {1, 2, 5, 9....}, {1, 4, 99, 7...}}; ''%%same_class(samples)%%'' va întoarce true; | ||
- | * samples = {{1, 0, 0, 10...}, {0, 9, 7, 111...}}; ''%%same_class(samples)%%'' va întoarce false; | ||
- | |||
- | |||
- | float get_entropy_by_indexes(const vector<vector<int>> &samples, const vector<int> &index) | ||
- | |||
- | * calculează entropia subsetului din samples format considerând doar sample-urile de forma samples[i], cu i element din index | ||
- | |||
- | vector<int> compute_unique(const vector<vector<int>> &samples, const int col) | ||
- | |||
- | * întoarce un vector care conține valorile unice care apar în matricea samples pe coloana col | ||
- | |||
- | * Exemplu: samples = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, col = 1; ''%%compute_unique(samples, col)%%'' va întoarce vectorul | ||
- | * {2, 6, 10} (nu contează ordinea) | ||
- | |||
- | pair<vector<int>, vector<int>> get_split_as_indexes(const vector<vector<int>> &samples, const int split_index, const int split_value) | ||
- | |||
- | * în funcție de split_index și split_value, întoarce doi vectori care conțin indecși în vectorul samples, astfel încât: | ||
- | * oricare ''%%samples[i]%%'' are ''%%samples[i][split_index] <= split_value%%'', i aparținând primului rezultat returnat | ||
- | * oricare ''%%samples[j]%%'' are ''%%samples[j][split_index > split_value%%'', j aparținând celui de-al doilea rezultat returnat | ||
- | * exemplu: pentru samples = {{1, 2, 3}, {5, 6, 7}, {0, 9, 7}}, split_index = 0, split_value = 2 | ||
- | * ''%%get_split_as_indexes(samples, split_index, split_value)%%'' va întoarce perechea formată din {0, 2} si {1} | ||
- | |||
- | vector<int> random_dimensions(const int size) | ||
- | |||
- | * selectează în mod aleator sqrt(size), rotunjit la întreg prin lipsa) dimensiuni. | ||
- | * dimensiunile trebuie să fie unice și să nu existe dimensiunea 0 (pentru că ar reprezenta răspunsul) | ||
- | * exemplu: random_dimensions(5) -> {1, 3} | ||
- | * explicație: | ||
- | * floor(sqrt(5)) = 2, selectăm în mod aleator două dimensiuni din mulțimea {1, 2, 3, 4} | ||
- | |||
- | int predict(const vector<int> &image) const | ||
- | |||
- | * navigați arborele de decizie până ajungeți la o frunză și returnați valoarea din frunză | ||
- | * Exemplu: Pentru arborele: | ||
- | |||
- | (1, 10) | ||
- | / \ | ||
- | (10) (5) | ||
- | |||
- | * unde nodul(1, 10) este nod de decizie iar (10) și (5) noduri frunză. 1 reprezintă indexul de split, 10 reprezintă valoarea de split. | ||
- | * dacă avem image = {1, 15, 20, 7, 19} predict(image) va întoarce 5. ''%%image[split_index]%%'' $=$ 15, $15 \gt 10$, deci mergem în dreapta. Nodul din dreapta este nod frunză și are rezultatul 5. | ||
- | |||
- | |||
- | int predict(const vector<int> &image) (din RandomForest) | ||
- | |||
- | * Pentru fiecare Decision Tree din Forest, află răspunsul prezis și în final, întoarce cel mai des întâlnit răspuns. | ||
- | |||
- | vector<vector<int>> get_random_samples(const vector<vector<int>>& samples, int num_to_return) | ||
- | |||
- | * pentru matricea samples, întoarce o submatrice cu num_to_return linii selectate random | ||
- | |||
- | pair<int, int> find_best_split(const vector<vector<int>> &samples, const vector<int> &dimensions) | ||
- | |||
- | * bazat pe dimensions, găsiți split-ul care maximizează information gain și întoarceți o pereche de forma (split_index, split_value) | ||
- | * dacă nu se găsește niciun split bun, atunci nodul trebuie să fie frunză. Recomandăm să întoarceți ceva care nu are sens, cum ar fi (-1, -1). | ||
- | |||
- | void train(const vector<vector<int>> &samples) | ||
- | |||
- | * folosindu-vă de funcțiile descrise mai sus, implementați algoritmul de învățare pentru decision tree | ||
- | * pseudocod: | ||
- | * verificați dacă ați ajuns la un nod care trebuie să fie frunză | ||
- | * căutați cea mai bună valoare de split (nu uitați să folosiți random_dimensions | ||
- | * dacă nu a fost găsit niciun split bun, faceți frunza | ||
- | * dacă ați găsit un split, salvați split_index și split_value în nodul curent, creați copii pe baza split-ului și mergeți recursiv | ||
- | |||
- | |||
- | <note> | ||
- | **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ă. | ||
- | </note> | ||
- | |||
- | ===== 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 ===== | ||
- | Scheletul si checkerul vor fi postate in cursul saptamanii acesteia. | ||
- | ===== Reguli pentru trimitere ===== | ||
- | |||
- | Temele vor trebui trimise pe [[https://elf.cs.pub.ro/vmchecker/ui/#SD|vmchecker]]. | ||
- | **Atenție!** Temele trebuie trimise în secțiunea **Structuri de Date (CA)**. | ||
- | |||
- | Arhiva trebuie să conțină: | ||
- | * sursele voastre (randomForest.cpp, decisionTree.cpp si alte surse, pe care le scrieti voi) | ||
- | * fisier **README** care să conțină detalii despre implementarea temei si despre modul in care ati impartit sarcinile. Va recomandam sa folositi un sistem de versionare al surselor (git). **Atentie,** nu folositi repository-uri publice de git. | ||
- | * încercați să evitați folosirea altor fișiere sursa. Nu aveți voie să modificați Makefile-ul. Dacă totuși simțiți nevoia să modularizați mai mult, puteți să folosiți fisiere .h în care să scrieți și implementările | ||
- | |||
- | ===== Punctaj ===== | ||
- | |||
- | - **50p** get_split_as_indexes, same_class, random_dimensions, get_random_samples, compute_unique(cate **10p** fiecare) | ||
- | - **30p** Acuratetea algoritmului: > 85% -> **30p**, > 55% -> **20p**, > 25% -> **10p** | ||
- | - **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) | ||
- | - **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. | ||
- | - O temă care obține **0p** pe vmchecker este punctată cu **0**. | ||
- | - O tema care nu compilează va fi punctată cu **0**. | ||
- | |||
- | <note warning> | ||
- | **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. | ||
- | </note> | ||
- | |||
- | ===== FAQ ===== | ||
- | |||
- | * **Q**: Putem folosi STL? \\ | ||
- | * **A**: Da, puteți să folosiți STL. |