Neprihlásený používateľ |

Špecifikácia zadania


Cieľom zadania je napísať program v jazyku C, ktorý bude klasifikovať obrázky rukou písaných číslic pomocou jednoduchej 1-vrstvovej neurónovej siete. Neurónová sieť je už vopred natrénovaná a jej váhy sú dodané spolu so zadaním. Obrázky, s ktorými budeme pracovať pochádzajú s verejne známej MNIST databázy. Okrem klasifikácie obrázkov bude výsledný program schopný vykonávať elementárne operácie v rámci neurónovej siete: zobrazovanie váh, počítanie váženého súčtu neurónu (tzv. weighted sum), počítanie hodnoty aktivačnej funkcie, počítanie funkcie Softmax, hľadanie maxima v poli hodnôt a zobrazovanie načítaných pixelov obrázku. Pri tvorbe zadania sme sa inšpirovali existujúcou prácou od Andrewa Cartera: MNIST Neural Network in C. V zadaní si precvičíte matematické výrazy, polia a prácu s projektom, ktorý sa skladá z viacerých hlavičkových a zdrojových súborov.

Úloha

Program bude ovládaný pomocou terminálu a bude vykonávať tieto operácie:

  • Režim 1: Výpis váh pre zvolený neurón.
  • Režim 2: Výpis váženého súčtu (z angl. weighted sum) pre zvolený neurón a načítaný obrázok.
  • Režim 3: Výpis hodnoty aktivačnej funkcie.
  • Režim 4: Výpis vypočítaných hodnôt funkcie Softmax.
  • Režim 5: Výpis indexu najväčšieho prvku v poli (Findmax).
  • Režim 6: Výpis načítaného obrázku číslice.
  • Režim 7: Výpis výsledku klasifikácie načítaného obrázku číslice.

Obrázky rukou písaných číslic

Obrázky, s ktorými budeme pracovať pochádzajú z databázy MNIST. Neurónová sieť, ktorú budeme používať bola natrénovaná na tejto databáze a má úspešnosť rozpoznávania približne 90 %. MNIST je rozsiahla zbierka obrázkov rukou písaných číslic. Obsahuje číslice 0-9. Jedná sa o rozsiahlu databázu: 60 000 trénovacích obrázkov a 10 000 testovacích obrázkov.

Rozmer každého obrázku je 28 x 28 pixelov. Každý obrázok je reprezentovaný odtieňmi šedej farby (tzv. grayscale), t.j. pixely sú v rozsahu $\langle0,255\rangle$.


Obrázok 1: Rozmer obrázku je 28 x 28

Obrázok budeme v zadaní reprezentovať pomocou 1D poľa (prepísané pixely po riadkoch zľava doprava). Obrázky sú uložené v TXT súboroch a načítajú sa do poľa pomocou presmerovania štandardného vstupu (stdin). Hodnoty pixelov obrázku sú normalizované z pôvodného rozsahu $\langle0,255\rangle$ do rozsahu $\langle0,1\rangle$ (je to vhodnejšia reprezentácia pre NS).


Obrázok 2: Reprezentácia obrázku pomocou 1D poľa.

Na zjednodušenie testovania budeme mať v tomto zadaní obrázky (resp. ich pixely) uložené v TXT súboroch a následne pomocou presmerovania štandardného vstupu v termináli ich vieme jednoducho načítať. Obrázok je v TXT súbore reprezentovaný ako 1 riadok s postupnosťou hodnôt pixelov oddelených 1 medzerou. Spolu má obrázok 784 pixelov.


Obrázok 3: Pixely obrázku uložené v TXT súbore (jeden dlhý riadok hodnôt).

Neurónová sieť

Na rozpoznávanie obrázkov budeme používať 1-vrstvovú neurónovú sieť. Táto vrstva bude obsahovať 10 neurónov (keďže klasifikujeme 10 rôznych druhov číslic). Vstupom do siete bude obrázok (1D pole pixelov) a výstupom bude určenie triedy (resp. menovka), do ktorej obrázok patrí, t.j. číslo 0 až 9. Všetky neuróny budú využívať aktivačnú funkciu ReLU.


Obrázok 4: Zjednodušený pohľad na neurónovú sieť na klasifikáciu obrázku.
Architektúra siete

Kompletná architektúra siete, s ktorou budeme v zadaní pracovať je znázornená na obrázku 5. Vstupný obrázok s rozmermi 28x28 je reprezentovaný 1D poľom, ktoré má 784 hodnôt pixelov (červené štvorce na obrázku). Sieť obsahuje 10 neurónov (modré kruhy na obrázku). Každý neurón je zodpovedný za klasifikáciu príslušného typu číslice. Do každého neurónu vstupujú všetky pixely obrázku, t.j. vytvoríme tak plne prepojenú sieť. Každý neurón si na základe svojich vstupov vypočíta výstupnú hodnotu, tzv. aktiváciu.

Keď získame všetkých 10 aktivácií (pole 10 hodnôt), tak ich následne pošleme do funkcie Softmax. Funkcia Softmax určí pravdepodobnosti klasifikácie jednotlivých typov číslic, t.j. vráti 10-prvkové pole, ktorého hodnoty predstavujú pravdepodobnosti. Prvok s indexom 0 v tomto poli predstavuje pravdepodobnosť, že sieť klasifikuje vstupný obrázok ako 0. Rovnaký princíp platí pre ostatné čísla v poli pravdepodobností. Výstup funkcie Softmax ďalej pošleme do funkcie Findmax, ktorá v 10-prvkovom poli nájde index maxima. Tento index určí typ číslice, ktorá je klasifikovaná s najvyššou pravdepodobnosťou. Tento index zároveň prehlásime za výsledok klasifikácie číslice.


Obrázok 5: Architektúra neurónovej siete, ktorú používame v zadaní.
Ako funguje neurón?

Neurón je základným stavebným prvkom neurónovej siete. Jeho úlohou je spracovať vstupné signály, výsledok spracovania poslať do svojej aktivačnej funkcie. Výstup z aktivačnej funkcie je konečným výstupom neurónu. Do každého neurónu vstupuje v našom prípade spolu 785 hodnôt = 784 hodnôt pixelov obrázku + 1 bias hodnota. Bias hodnota je špeciálna konštanta každého neurónu (každý neurón má práve 1 bias hodnotu), ktorá pomáha sieti lepšie predpovedať výsledky.

Každý pixel obrázku vstupujúci do neurónu je vynásobený svojou váhou. Každý neurón má svoje vlastné váhy. Vedomosti našej natrénovanej neurónovej siete sú uložené práve vo váhach a bias hodnotách. Keď poznáme hodnoty všetkých pixelov obrázku, všetky váhy neurónu a jeho bias hodnotu, vypočítame tzv. vážený súčet. Vážený súčet je vizualizovaný na obrázku 6.


Obrázok 6: Schéma znázorňujúca výpočet váženého súčtu vstupov do neurónu (tzv. weighted sum).

Výsledok váženého súčtu pošleme na vstup ReLU aktivačnej funkcie. ReLU funkcia je definovaná predpisom:

$ f(x) = \begin{cases} 0 & \text{if }x \leq 0 \\ x & \text{if }x > 0 \end{cases}\ $

Na obrázku 7 vidíme kompletnú schému činnosti neurónu.


Obrázok 7: Schéma znázorňujúca činnosť neurónu.
Váhy a bias hodnoty

Váhy a bias hodnoty sú uložené v 1D poliach v dodanom zdrojovom súbore data.c. Váhy sú uložené v poli s názvom weights a bias hodnoty sú v poli s názvom bias. Počet váh je 784 x 10 = 7840 (pre každý neurón 784 váh). Počet bias hodnôt je 10 (pre každý neurón jedna). Na obrázku 8 vidíme spôsob, akým treba sprístupňovať váhy zvoleného neurónu. V poli weights majú jednotlivé neuróny svoje váhy uložené za sebou. Prvých 784 hodnôt poľa tak predstavuje váhy neurónu s indexom 0, ďalších 784 hodnôt predstavuje váhy neurónu s indexom 1 a pod.


Obrázok 8: Sprístupňovanie váh jednotlivých neurónov v poli weights.
Funkcia Softmax

Funkcia Softmax slúži na výpočet pravdepodobnosti klasifikácie pre každý typ číslice. Máme 10 neurónov. Každý neurón si vypočíta svoju aktiváciu (t.j. výstup z aktivačnej funkcie). Získame tak pole 10 hodnôt (na obrázku 9 je označené ako z). Toto pole odovzdáme do funkcie Softmax. Tá každému prvku v poli vypočíta jeho pravdepodobnosť klasifikácie, t.j. vytvorí nové výstupné pole. Matematický predpis funkcie Softmax pre $i$-ty prvok z poľa z je uvedený nad obrázkom 9.

$softmax(\textbf{z})_{i} = \dfrac{e^{(z_{i} - max(\textbf{z}))}}{\sum_{j=0}^{K}e^{(z_{j} - max(\textbf{z}))}}$

kde $max(\textbf{z})$ je hodnota najväčšieho prvku z poľa z a $K=9$ je index posledného neurónu.


Obrázok 9: Softmax funkcia.

Projekt

V tomto zadaní budete pracovať s projektom, ktorý sa skladá z 5 súborov:

Hlavičkové súbory
  • data.h
  • functions.h
Zdrojové súbory
  • data.c
  • functions.c
  • z2.c

Nasleduje krátky popis jednotlivých súborov.

Hlavičkové súbory

  • data.h - Obsahuje potrebné makrá a deklarácie polí pre váhy a bias hodnoty.
  • functions.h - Obsahuje deklarácie pomocných funkcií:
    • ReLU funkcia
    • funkcia na načítanie hodnôt do poľa zo štandardného vstupu
    • funkcia na výpis načítaného obrázku číslice

Zdrojové súbory

  • data.c - Obsahuje definície polí pre váhy a bias hodnoty
  • functions.c - Obsahuje definície funkcií, ktoré sú zadeklarované v data.h
  • z2.c - Hlavný súbor zadania, do ktorého sú vložené všetky potrebné hlavičkové súbory. Tento súbor po vypracovaní ako jediný odovzdávate

Projektový konfiguračný súbor CMakeLists.txt pre CLion

Spolu s projektovými súbormi máte k dispozícii hlavný konfiguračný súbor CLion projektu, ktorý je správne nastavený a netreba ho meniť. V prípade, že nemáte nainštalovanú verziu programu CMake 3.27 a vyššiu, prepíšte číslo verzie na vyhovujúcu hodnotu. Po spustení CLion-u si otvorte projekt kliknutím na tlačidlo Open File or Project a zvoľte si koreňový adresár projektu (t.j. adresár, v ktorom je uložený súbor CMakeLists.txt). CLion projekt automaticky rozpozná. Projekt skompilujte. Ak všetko prebehne bez chýb, môžete pracovať na zadaní.

cmake_minimum_required(VERSION 3.27)
project(z2 C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -lm")

add_executable(z2 src/functions.c src/data.c src/z2.c)
target_include_directories(z2 PRIVATE ${PROJECT_SOURCE_DIR}/include)
Výpis 1: Obsah súboru CMakeLists.txt.

Správne vloženie hlavičkových súborov do z2.c

Zadanie je nutné implementovať v súbore z2.c. Ostatné súbory projektu sa nesmú upravovať. Do súboru z2.c je potrebné vložiť hlavičkové súbory data.h a functions.h tak, aby bol uvedený len názov súborov, t.j. cesta sa nesmie uvádzať (viď Obrázok 10).


Obrázok 10: Správne (vľavo) a nesprávne (vpravo) vloženie hlavičkových súborov.

Režimy činnosti

Budeme rozlišovať 7 režimov činnosti. Aplikácia sa rozhodne, do ktorého režimu vstúpi po načítaní prvej číselnej hodnoty zo štandardného vstupu. Platné sú len čísla 1-7. Ostatné hodnoty sa budú ignorovať.

Režim 1 - Výpis váh pre zvolený neurón

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 1 (vstup do režimu 1). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá index neurónu (platné sú čísla 0-9), pre ktorý chce vypísať váhy. Následne sa vypíšu váhy daného neurónu. Treba rešpektovať pravidlá výpisu: váhy sa vypíšu po riadkoch, v každom riadku sa vypíše 10 váh s presnosťou 2 desatinných miest (použite funkciu printf). Váhy musia byť oddelené aspoň 1 medzerou. Okrem váh sa nič iné nevypíše.


Obrázok 11: Príklad spustenia režimu 1 v termináli.


Režim 2 - Výpis váženého súčtu pre zvolený neurón a načítaný obrázok

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 2 (vstup do režimu 2). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá index neurónu (platné sú čísla 0-9), pre ktorý chce vypočítať vážený súčet. Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá obrázok. Obrázok sa zadáva vo forme postupnosti pixelov oddelených 1 medzerou. Obrázok musí obsahovať 784 pixelov. Po zadaní obrázku prebehne výpočet a vypíše sa hodnota vypočítaného váženého súčtu. Treba rešpektovať pravidlá výpisu: výsledok sa vypíše s presnosťou 2 desatinných miest (použite funkciu printf). Okrem váženého súčtu sa nič iné nevypíše.


Obrázok 12: Textový súbor, ktorý obsahuje príklad vstupných hodnôt pre režim 2. Z dôvodu jednoduchosti testovania program treba v termináli spustiť s presmerovaním štandardného vstupu z tohto súboru.



Obrázok 13: Príklad spustenia režimu 2 v termináli. Pomocou operátora < spustíme program s presmerovaným štandardným vstupom zo súboru mode2.txt.


Režim 3 - Výpis hodnoty aktivačnej funkcie

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 3 (vstup do režimu 3). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá vstupnú hodnotu pre funkciu ReLU. Následne prebehne výpočet a vypíše sa výsledok funkcie ReLU. Treba rešpektovať pravidlá výpisu: výsledok sa vypíše s presnosťou 2 desatinných miest (použite funkciu printf). Žiadny iný výpis sa neuskutoční.


Obrázok 14: Príklad spustenia režimu 3 v termináli.


Režim 4 - Výpis vypočítaných hodnôt funkcie Softmax

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 4 (vstup do režimu 4). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá postupnosť 10 hodnôt (ľubovoľné desatinné čísla). Tieto hodnoty treba načítať do poľa. Pole sa následne pošle do funkcie Softmax a vypíše sa výsledok. Výsledkom je znova pole s 10 číslami, ktoré predstavujú pravdepodobnosti. Treba rešpektovať pravidlá výpisu: výsledné pole sa vypíše tak, že jeho prvky budú oddelené 1 medzerou a každé číslo sa zobrazí s presnosťou 2 desatinných miest (použite funkciu printf). Žiadny iný výpis sa neuskutoční.


Obrázok 15: Príklad spustenia režimu 4 v termináli.


Režim 5 - Výpis indexu najväčšieho prvku v poli (Findmax)

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 5 (vstup do režimu 5). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá postupnosť 10 hodnôt (ľubovoľné desatinné čísla). Tieto hodnoty treba načítať do poľa. Pole sa následne pošle do funkcie Findmax a vypíše sa index najväčšieho prvku v poli. Ak má pole viacero rovnakých najväčších prvkov, vypíše sa index, ktorý je najbližšie k hodnote 0. Žiadny iný výpis sa neuskutoční.


Obrázok 16: Príklad spustenia režimu 5 v termináli.


Režim 6 - Výpis načítaného obrázku číslice

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 6 (vstup do režimu 6). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá obrázok. Obrázok sa zadáva vo forme postupnosti pixelov oddelených 1 medzerou. Obrázok musí obsahovať 784 pixelov. Následne sa vypíše načítaný obrázok. Na výpis je nutné použiť dodanú funkciu print_image, ktorá je deklarovaná v hlavičkovom súbore functions.h. Žiadny iný výpis sa neuskutoční.


Obrázok 17: Textový súbor, ktorý obsahuje príklad vstupných hodnôt pre režim 6. Z dôvodu jednoduchosti testovania program treba v termináli spustiť s presmerovaním štandardného vstupu z tohto súboru.



Obrázok 18: Príklad spustenia režimu 6 v termináli. Pomocou operátora < spustíme program s presmerovaným štandardným vstupom zo súboru mode6.txt.


Režim 7 - Výpis výsledku klasifikácie načítaného obrázku číslice

Spustíme skompilovaný program v termináli. Používateľ zadá číslo 7 (vstup do režimu 7). Nasleduje aspoň 1 biely znak (medzera alebo nový riadok). Potom používateľ zadá obrázok. Obrázok sa zadáva vo forme postupnosti pixelov oddelených 1 medzerou. Obrázok musí obsahovať 784 pixelov. Následne sa načítaný obrázok klasifikuje a vypíše sa jeho menovka (číslo 0-9). Žiadny iný výpis sa neuskutoční.


Obrázok 19: Textový súbor, ktorý obsahuje príklad vstupných hodnôt pre režim 7. Z dôvodu jednoduchosti testovania program treba v termináli spustiť s presmerovaním štandardného vstupu z tohto súboru.



Obrázok 20: Príklad spustenia režimu 7 v termináli. Pomocou operátora < spustíme program s presmerovaným štandardným vstupom zo súboru mode7.txt.

Ako načítať obrázok?

V tomto zadaní je potrebné načítavať obrázky zo štandardného vstupu. Existujú teda 2 možnosti ako obrázok načítať: zadaním 784 pixelov ručne na klávesnici (resp. CTRL+C, CTRL+V) a druhá možnosť je spustiť program s presmerovaným štandardným vstupom z TXT súboru. Z programátorského hľadiska sú obidve možnosti totožné.

Na načítanie obrázku (všetkých 784 pixelov) použite dodanú funkciu load_data, ktorá je deklarovaná v hlavičkovom súbore functions.h.

Ako testovať váš program?

Spolu s CLion projektom máte k dispozícii priečinok s testovacími súbormi (treba rozbaliť ZIP súbor). V tomto priečinku nájdete:

  • Podpriečinok digits-img, ktorý obsahuje obrázky číslic v PNG formáte na experimentálne účely.
  • Podpriečinok digits-txt, ktorý obsahuje obrázky číslic reprezentované pomocou TXT súborov (sú v nich hodnoty pixelov). Tieto súbory využijete v prípade, že si chcete testovať režim 2, 6 a 7, kde je jedným zo vstupov obrázok.
  • Podpriečinok python_scripts, ktorý obsahuje pomocné experimentálne Python skripty:
    • img2txt.py na konverziu obrázku číslice do TXT súboru (očakáva sa, že vstupný obrázok má rozmer 28x28 a jeho farebný režim je grayscale)
    • txt2img.py na konverziu obrázku v TXT podobe do obrazovej podoby (očakáva sa, že v TXT súbore bude 784 hodnôt pixelov v rozsahu $\langle0,1\rangle$)
  • Podpriečinok stdin, ktorý obsahuje testovacie príklady pre jednotlivé režimy činnosti.

Obrázok 21: Priečinky s testovacími súbormi

Ak si chceme napríklad otestovať režim 7 (klasifikácia obrázku), treba ísť do priečinku stdin. V ňom nájdeme podpriečinok mode07. Tu sa nachádza viacero TXT súborov, ktoré predstavujú testovacie vstupy do programu. Program môžeme spustiť napr. nasledovne:

./z2 < example_8_2.txt

V tomto prípade si otestujeme klasifikáciu obrázku, na ktorom je číslica 8.

Chybové situácie

V programe netreba ošetrovať vstupy ani žiadne iné chybové situácie. Váš odovzdaný program bude automatizovane testovaný len s platnými a zmysluplnými vstupmi.

Hodnotenie zadania

Odovzdávací systém otestuje a ohodnotí nasledovné oblasti funkcionality vášho programu. Na získanie bodov za konkrétny testovací scenár je nutné, aby testom prešli všetky testovacie prípady v danom scenári.

Testovacie scenáre
Scenár 1
Režim 1 - výpis váh
1,0 b
Scenár 2
Režim 2 - výpis váženého súčtu
2,0 b
Scenár 3
Režim 3 - výpis hodnoty ReLU funkcie
0,5 b
Scenár 4
Režim 4 - výpis hodnôt funkcie Softmax
1,5 b
Scenár 5
Režim 5 - výpis výsledku funkcie Findmax
0,5 b
Scenár 6
Režim 6 - výpis načítaného obrázku
0,5 b
Scenár 7
Režim 7 - výpis výsledku klasifikácie načítaného obrázku
4,0 b
Súčet 10 b

Video

Video ukážka spustenia programu
TODO Doplní sa neskôr.

Testovacie príklady

Táto sekcia obsahuje správne očakávané hodnoty testovacích príkladov pre jednotlivé scenáre. Vstupy pre jednotlivé testovacie príklady sa nachádzajú v priečinku stdin, ktorý je súčasťou testovacieho balíka súborov, ktorý si môžete stiahnuť.

Poznámka: ideálny spôsob testovania vášho programu je prostredníctvom presmerovania štandardného vstupu v termináli z príslušného TXT súboru, ktorý obsahuje vstupy (aby ste sa tak vyhli komplikáciám pri kopírovaní údajov a ich zadávaní pomocou klávesnice).

Zoznam testovacích scenárov:

Scenár 1 Výpis váh pre zvolený neurón | Priečinok mode01

Scenár 2 Výpis váženého súčtu pre zvolený neurón a načítaný obrázok | Priečinok mode02

Scenár 3 Výpis hodnoty aktivačnej funkcie | Priečinok mode03

Scenár 4 Výpis vypočítaných hodnôt funkcie Softmax | Priečinok mode04

Scenár 5 Výpis indexu najväčšieho prvku v poli (Findmax) | Priečinok mode05

Scenár 6 Výpis načítaného obrázku číslice | Priečinok mode06

Scenár 7 Výpis výsledku klasifikácie načítaného obrázku číslic | Priečinok mode07

Zdroje

Nasledujúce zdroje vám môžu pomôcť pri programovaní zadania. Odporúčame si tieto zdroje preštudovať. Na prístup k niektorým zdrojom potrebujete byť prihlásení vo vašom Google STU konte.

Prednášky

Jazyk C

Funkcie printf a scanf

Zdroje použité pri tvorbe zadania a prezentácie

Copyright © 2024, Pavol Marák, ÚIM FEI STU.
Vyrobené pomocou Django a Spectre.css.
Regulárne výrazy testujeme pomocou Regular Expression 101.
Videá prehrávame pomocou Plyr prehrávača.