Preporučujem da pogledate sledeće video-materijale:
Prvi deo
Drugi deo
Treći deo
Četvrti deo
Delovi materijala su preuzeti iz skripte "Mašinsko učenje" profesora Mladena Nikolića
Ideja neuronskih mreža delimično je inspirisana načinom funkcionisanja bioloških neurona, mada su savremene neuronske mreže pre svega matematički i računski modeli. Zbog svoje fleksibilnosti i velike izražajne moći, našle su primenu u rešavanju brojnih zadataka, kao što su prepoznavanje objekata na slikama, obrada govora, mašinsko prevođenje i igranje video igara. Ipak, njihovo obučavanje može biti računski zahtevno, naročito kod velikih modela i velikih skupova podataka.
Uprkos veoma dobrim performansama, neuronske mreže nisu najbolji izbor u svakoj situaciji. Za uspešnu primenu često je potreban dovoljno veliki skup podataka, kao i odgovarajući računski resursi. Njihova prednost posebno dolazi do izražaja kod podataka sirove reprezentacije, kao što su slike, zvuk ili drugi signali, jer mogu automatski da izdvajaju korisne i složene reprezentacije iz podataka.
U zavisnosti od arhitekture i oblasti primene, razlikuje se više tipova neuronskih mreža. Osnovnu i najjednostavniju varijantu predstavljaju potpuno povezane neuronske mreže (fully connected neural networks).
Neuron poseduje težine pridružene svojim ulazima i jedan slobodni član. Na osnovu ulaznih vrednosti najpre računa ponderisanu sumu uz dodatak slobodnog člana, a zatim se na dobijeni rezultat primenjuje aktivaciona funkcija. Rezultat aktivacione funkcije predstavlja izlaz neurona i koristi se kao ulaz neuronima narednih slojeva.
Drugim rečima, neuronska mreža u svojim skrivenim slojevima konstruiše nove atribute. Svaki naredni sloj nadograđuje reprezentacije dobijene u prethodnom sloju i na taj način gradi sve složenije atribute. Smatra se da je upravo ova sposobnost jedan od glavnih razloga za uspešnost dubokih neuronskih mreža.
Struktura mreže, odnosno raspored neurona po slojevima i izbor aktivacionih funkcija, često se određuje na osnovu iskustva i postojećih rešenja koja su dala dobre rezultate na sličnim problemima. Kada je reč o aktivacionim funkcijama, u praksi se često koriste sledeće funkcije:
Jedna od osnovnih optimizacionih metoda prvog reda za diferencijabilne funkcije jeste gradijentni spust (gradient descent). Gradijent funkcije u datoj tački pokazuje pravac njenog najbržeg rasta, pa se pri minimizaciji krećemo u pravcu negativnog gradijenta. Ako sa $\theta_k$ označimo vrednost parametara u $k$-tom koraku, tada se naredna iteracija dobija formulom
$$\theta_{k+1}=\theta_k-\alpha_k \nabla f(\theta_k),$$
gde je $\alpha_k$ korak učenja (learning rate), a $f$ funkcija gubitka.
Izbor veličine koraka ima veliki značaj. Ako je korak prevelik, algoritam može preskakati oblasti sa manjim vrednostima funkcije gubitka ili čak divergovati. Ako je korak premali, konvergencija može biti veoma spora. U praksi se zato pažljivo bira vrednost parametra learning rate, a često se koriste i adaptivne metode koje ga efektivno prilagođavaju tokom obučavanja. Kao kriterijumi zaustavljanja mogu se koristiti unapred zadat broj iteracija ili epoha, mala promena vrednosti funkcije gubitka, ili izostanak poboljšanja na validacionom skupu.
Kod obučavanja dubokih neuronskih mreža mogu se javiti problemi nestajućih i eksplodirajućih gradijenata, naročito kod dubljih arhitektura. U takvim situacijama gradijenti mogu postati suviše mali, pa se parametri gotovo više ne menjaju, ili suviše veliki, što dovodi do nestabilnog obučavanja. Zbog toga se u praksi koriste različita unapređenja osnovnog gradijentnog spusta, kao što su Momentum, RMSProp i Adam. Adam je naročito popularan jer kombinuje ideju momenta i adaptivnog skaliranja koraka za različite parametre.
Za razliku od konveksnih optimizacionih problema, funkcija gubitka kod neuronskih mreža najčešće nije konveksna, pa zato ne možemo očekivati nalaženje globalnog minimuma jednostavnim optimizacionim postupkom. Ipak, u praksi se često stiče utisak da broj lokalnih minimuma koji daju zaista loš rezultat nije veliki, dok mnoga dobijena rešenja imaju slične vrednosti funkcije gubitka i slične performanse modela. Zbog toga je u praksi često dovoljno pronaći dovoljno dobru tačku u kojoj model daje dobre performanse na podacima. Na konačan rezultat utiču inicijalizacija parametara, izbor arhitekture, optimizacioni algoritam i podešavanje hiperparametara.
Kod višeklasnih klasifikacionih problema, izlazni sloj obično sadrži onoliko neurona koliko ima kategorija. Na izlaze tih neurona pre aktivacije, odnosno na dobijene skorove, primenjuje se softmax funkcija: $$\mathrm{softmax}(x_1,\dots,x_k)=\left(\frac{e^{x_1}}{\sum_{i=1}^{k} e^{x_i}},\dots,\frac{e^{x_k}}{\sum_{i=1}^{k} e^{x_i}}\right).$$ Dobijene vrednosti interpretiraju se kao verovatnoće pripadnosti odgovarajućim kategorijama, a rezultat klasifikacije određuje se izborom kategorije sa najvećom verovatnoćom.
Shema propagacije unapred može se zapisati u obliku $$ X \rightarrow Z_1=XW_1+b_1 \rightarrow H_1=g(Z_1) \rightarrow Z_2=H_1W_2+b_2 \rightarrow H_2=g(Z_2) \rightarrow \cdots \rightarrow H_L \rightarrow \hat{y} \rightarrow \mathcal{L}, $$ gde je $X$ matrica ulaznih podataka, $W_i$ matrica težina u $i$-tom sloju, $b_i$ vektor slobodnih članova u $i$-tom sloju, $Z_i$ matrica vrednosti pre primene aktivacione funkcije, $H_i$ matrica izlaza iz $i$-tog sloja, $\hat{y}$ vektor ili matrica predikcija modela, a $\mathcal{L}$ vrednost funkcije gubitka na trening skupu.
Polazeći od izlaznog sloja i krećući se unazad ka ulaznom sloju, u svakom sloju se obavljaju sledeći koraci:
Na taj način se, sloj po sloj, efikasno izračunavaju gradijenti po svim parametrima mreže.
Sledeći kod je u velikoj meri preuzet i prilagođen na osnovu materijala sa vežbi iz Mašinskog učenja koje su držali Anđelka Zečević i Milan Čugurović.
import numpy as np
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
np.random.seed(10)
Za učitavanje MNIST skupa podataka iskoristićemo podršku biblioteke Keras, pomoću funkcije mnist.load_data().
(X_train, y_train), (X_test, y_test) = mnist.load_data()
Prva instanca učitanog skupa izgleda ovako
plt.imshow(X_train[0], cmap='gray')
plt.show()
Da bismo slike prosledili kao ulaze mreže, transformisaćemo ih u vektore dužine 28x28=784.
image_size = X_train.shape[1]
X_train = X_train.reshape(X_train.shape[0], image_size*image_size)
X_test = X_test.reshape(X_test.shape[0], image_size*image_size)
Slike ćemo pripremiti tako što ćemo ih normalizovati, tj. svesti vrednosti pojedinačnih piksela na vrednost iz intervala iz [0, 1]. Prethodno ćemo promeniti tip elemenata matrice sa uint8 na float32 kako bi mogli da sačuvamo realne vrednosti.
X_train = X_train.astype('float32')
X_train /= 255
X_test = X_test.astype('float32')
X_test /= 255
Kao što smo videli, vrednosti ciljne promenljive su brojevi od 0 od 9. Uvid u brojnost instanci po klasama možemo dobiti na osnovu sledećeg grafika.
classes = np.unique(y_train)
plt.xlabel('Klasa')
plt.ylabel('Broj instanci')
plt.title ('MNIST skup za treniranje')
plt.xticks(classes)
plt.bar(classes, np.bincount(y_train))
plt.show()
Vrednosti ciljne promenljive ćemo pripremiti tako što ćemo ih transformisati u vektore dužine 10 sa jedinicom na poziciji koja odgovara vrednosti cifre (takozvano one-hot kodiranje). Na primer, broj 3 će biti transformisan u vektor [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] sa jedinicom na poziciji tri.
number_of_classes = classes.shape[0]
y_train = keras.utils.to_categorical(y_train, number_of_classes)
y_test = keras.utils.to_categorical(y_test, number_of_classes)
Ulaz u mrežu je slika dimenzije 784 piksela.
Formiraćemo neuronsku mrežu sa dva skrivena sloja. Prvi skriveni sloj ima 128 neurona, dok drugi ima 64 neurona. U oba sloja koristi se ReLU aktivaciona funkcija.
Izlazni sloj mreže sadrži 10 neurona, pri čemu svaki neuron odgovara jednoj od cifara od 0 do 9. Nakon primene softmax funkcije, dobijene vrednosti mogu se interpretirati kao verovatnoće pripadnosti odgovarajućim klasama. Kao rezultat klasifikacije bira se cifra kojoj odgovara najveća od tih verovatnoća. Zbog toga se u izlaznom sloju koristi aktivaciona funkcija mekog maksimuma (softmax).
model = Sequential([
Input(shape=(image_size*image_size, )),
Dense(units=128, activation='relu'),
Dense(units=64, activation='relu'),
Dense(units=number_of_classes, activation='softmax')
])
Strukturu modela možemo prikazati pozivom metode summary().
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 128) 100480
dense_1 (Dense) (None, 64) 8256
dense_2 (Dense) (None, 10) 650
=================================================================
Total params: 109,386
Trainable params: 109,386
Non-trainable params: 0
_________________________________________________________________
Za optimizaciju ćemo koristiti Adamov gradijentni spust.
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
Mrežu ćemo trenirati u 30 epoha korišćenjem paketića veličine 128 instanci.
batch_size = 128
epochs = 30
history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=0)
2026-03-31 23:13:32.093613: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz
Možemo nacrtati i grafike funkcije gubitka i tačnosti u toku treniranja.
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.title('Funkcija gubitka')
plt.plot(np.arange(0, epochs), history.history['loss'], label='train')
plt.subplot(1, 2, 2)
plt.title('Tacnost')
plt.plot(np.arange(0, epochs), history.history['accuracy'])
plt.show()
Model ćemo oceniti na skupu za testiranje.
score = model.evaluate(X_test, y_test)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
313/313 [==============================] - 0s 411us/step - loss: 0.1142 - accuracy: 0.9807 Test loss: 0.11418993771076202 Test accuracy: 0.9807000160217285