Preporučujem da pogledate sledeće snimke:
https://www.youtube.com/watch?v=KuXjwB4LzSA
https://www.youtube.com/watch?v=YRhxdVk_sIs
Delovi materijala su preuzeti iz skripte "Mašinsko učenje" profesora Mladena Nikolića
Na filtriranim slikama u nižim slojevima mreže se prepoznaju jednostavniji oblici, dok se u višim slojevima mogu razaznati i neke složenije konture koje sa nalaze na polaznoj slici.
U prethodnim razmatranjima nije diskutovano da li filter deluje samo nad tačno jednim kanalom prethodnog sloja ili nad više. U praksi se implementiraju višekanalne operacije konvolucije koje omogućavaju da filter deluje nad više kanala iz prethodnog sloja odjednom. Ovo je ilustrovano sledećom slikom.
Sledeći sloj koji smo pomenuli je sloj agregacije.
Sloj agregacije ukrupnjuje informacije koje dobija iz prethodnog sloja, obično tako što računa neku jednostavnu funkciju agregacije susednih jedinica prethodnog sloja, poput maksimuma ili proseka. Ukoliko agregira, na primer, $3 \times 3$ piksela, onda je broj izlaza ovog sloja 9 puta manji od broja izlaza prethodnog. Maksimum je najpopularniji izbor funkcije agregacije. Ukoliko $3 \times 3$ jedinice prethodnog sloja predstavljaju ulaz u jednu jedinicu sloja agregacije i ako ona računa njihov maksimum, dolazi do zanemarivanje informacije o tome gde je precizno neko svojstvo pronađeno, ali se ne gubi informacija da je pronađeno. Ovakva vrsta zanemarivanja informacije često ne šteti cilju koji treba postići. Na primer, ako su na slici pronađeni oko, uvo, usta i nos, informacija o tačnoj poziciji najverovatnije nije bitna za odlučivanje da li se na slici nalazi lice. Naime, u realnosti, šanse da slika koja sadrži navedene elemente ne predstavlja lice su izuzetno male. Ipak, ukoliko je potrebno napraviti mrežu koja igra igru u kojoj su pozicije objekata na ekranu bitne, nije poželjno koristiti agregaciju.
Uloga agregacije je smanjenje broja računskih operacija u višim slojevima, a i smanjenje broja parametara (koji može biti ogroman) u potpuno povezanoj mreži koja sledi za slojevima konvolucije i agregacije. Sve to rezultuje smanjenjem računske zahtevnosti pri optimizaciji i smanjenjem fleksibilnosti modela.
Broj slojeva i dimenzije filtera su takođe hiperparametri.
Kao što smo rekli, na izlaze poslednjeg od tih slojeva se obično nadovezuje potpuno povezana mreža, koja uči nad atributima koje prethodni slojevi konstruišu. To je prikazano na sledećoj slici.
Demonstriraćemo upotrebu konvolutivnih neuronskih mreža na bazi ručno pisanih brojeva MNIST. MNIST sadrži crno-bele slike dimenzije 28x28 piksela na kojima se nalazi neka od cifara od 0 do 9.
Sledeći kod je većinski preuzet od asistenata Anđelke Zečević i Milana Čugurovića sa časova vežbi iz Mašinskog učenja.
Prvo ćemo učitati sve neophodne biblioteke.
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, Dropout, Flatten, Conv2D, MaxPooling2D
from tensorflow.keras.losses import CategoricalCrossentropy
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import utils
from tensorflow.keras import backend as K
np.random.seed(7)
Učitavanje podataka
(X_train, y_train), (X_test, y_test) = mnist.load_data()
Dimenzije slike ćemo sačuvati u okviru promenljivih img_rows i img_cols.
img_rows = 28
img_cols = 28
Prilikom obrade slika ulazi u mrežu treba da budu 4D tenzori. Uzimaju se u obzir širina i visina slike, broj kanala (jedan za monohromatske ili tri za RGB slike) i broj instanci. Stoga je učitane podatke potrebno transformisati na ovaj oblik.
Postoje dve konvencije zapisa slika. Channels last zapis je oblika (samples, height, width, color_depth), a chanell first oblika (samples, color_depth, height, width). Keras biblioteka (i TensorFlow) podrazumevano koriste Channel last zapis. Tako se, na primer, paketić veličine 128 koji sadrži crno-bele slike dimenzija 256x256 opisuje tenzorom dimenzija (128, 256, 256, 1), a recimo, paketić veličine 128 slika koje su u RGB modu tenzorom dimenzija (128, 256, 256, 3). Sledeća linija koda proverava koji zapis koristi Keras biblioteka.
K.image_data_format()
'channels_last'
input_shape = (img_rows, img_cols, 1)
X_train = X_train.reshape(X_train.shape[0], img_rows, img_cols, 1)
X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 1)
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
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.
Za ovu transformaciju iskoristićemo funkciju to_categorical paketa keras.utils.
number_of_classes = 10
y_train = utils.to_categorical(y_train, number_of_classes)
y_test = utils.to_categorical(y_test, number_of_classes)
Naš model će se sastojati iz više konvolutivnih i agregacionih slojeva.
Konvolutivni slojevi se na nivou biblioteke kreiraju korišćenjem funkcije Conv2D. Prvo se navodi broj filtera (parametar filters, obično neimenovan), zatim veličine kernela (kernel_size parametar), veličina pomeraja (parametar strides) i uokvirenje (padding parametar).
Agregacioni slojevi (MaxPooling2D i AvgPooling2D) vrše redukciju slojeva svođenjem blokova zadatih večina na njihove maksimalne ili prosečne vrednosti. Veličina bloka zadaje se parametrom pool_size.
Prilikom treniranja koristićemo i Dropout tehniku regularizacije. Ova tehnika regularizacije podrazumeva isključivanje nasumično odabranih neurona, i na taj način se omogućava drugačiji protok podataka kroz mrežu i smanjuje zavisnost izlaza od ulaza. Da bi se primenila ova tehnika regularizacije, dovoljno je mreži dodati Dropout sloj. Njegov parametar je verovatnoća isključivanja pojedinačnih neurona.
Sloj ispravljanja, sloj Flatten, se korist za transformisanje matrica vrednosti u vektore i obično se koristi kao priprema za prelazak sa konvolutivnog dela na dalji gusti deo mreže.
model = Sequential([
Input(shape=input_shape),
Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), padding='same',
activation='relu'),
Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same',
activation='relu'),
MaxPooling2D(pool_size=(2, 2)),
Dropout(0.25),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5),
Dense(number_of_classes, activation='softmax')
])
Prikaz modela i uvid u broj parametara koje mreža treba da nauči možemo da dobijemo pozivom funkcije summary.
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 28, 28, 32) 320
conv2d_1 (Conv2D) (None, 28, 28, 64) 18496
max_pooling2d (MaxPooling2D (None, 14, 14, 64) 0
)
dropout (Dropout) (None, 14, 14, 64) 0
flatten (Flatten) (None, 12544) 0
dense (Dense) (None, 128) 1605760
dropout_1 (Dropout) (None, 128) 0
dense_1 (Dense) (None, 10) 1290
=================================================================
Total params: 1,625,866
Trainable params: 1,625,866
Non-trainable params: 0
_________________________________________________________________
Funkcija gubitka koju mreža minimizuje će biti kategorička unakrsna entropija. Za treniranje mreže ćemo koristiti optimizator Adam sa podešenim korakom učenja.
model.compile(loss=CategoricalCrossentropy(), optimizer=Adam(learning_rate=0.001),
metrics=['accuracy'])
Mrežu ćemo trenirati koristeći paketiće veličine 128 u 12 epoha. Treniranje mreže će malo duže trajati.
batch_size = 128
epochs = 12
history = model.fit(X_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_split=0.2)
Epoch 1/12
2023-03-26 18:54:15.225324: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz
375/375 [==============================] - 29s 77ms/step - loss: 0.2821 - accuracy: 0.9136 - val_loss: 0.0641 - val_accuracy: 0.9818 Epoch 2/12 375/375 [==============================] - 29s 78ms/step - loss: 0.0968 - accuracy: 0.9720 - val_loss: 0.0503 - val_accuracy: 0.9855 Epoch 3/12 375/375 [==============================] - 30s 79ms/step - loss: 0.0691 - accuracy: 0.9796 - val_loss: 0.0449 - val_accuracy: 0.9871 Epoch 4/12 375/375 [==============================] - 30s 81ms/step - loss: 0.0582 - accuracy: 0.9819 - val_loss: 0.0408 - val_accuracy: 0.9886 Epoch 5/12 375/375 [==============================] - 30s 81ms/step - loss: 0.0472 - accuracy: 0.9856 - val_loss: 0.0450 - val_accuracy: 0.9885 Epoch 6/12 375/375 [==============================] - 31s 81ms/step - loss: 0.0424 - accuracy: 0.9857 - val_loss: 0.0445 - val_accuracy: 0.9867 Epoch 7/12 375/375 [==============================] - 31s 83ms/step - loss: 0.0390 - accuracy: 0.9872 - val_loss: 0.0405 - val_accuracy: 0.9892 Epoch 8/12 375/375 [==============================] - 31s 81ms/step - loss: 0.0326 - accuracy: 0.9897 - val_loss: 0.0437 - val_accuracy: 0.9893 Epoch 9/12 375/375 [==============================] - 31s 82ms/step - loss: 0.0320 - accuracy: 0.9897 - val_loss: 0.0412 - val_accuracy: 0.9895 Epoch 10/12 375/375 [==============================] - 31s 84ms/step - loss: 0.0287 - accuracy: 0.9904 - val_loss: 0.0397 - val_accuracy: 0.9901 Epoch 11/12 375/375 [==============================] - 31s 83ms/step - loss: 0.0270 - accuracy: 0.9910 - val_loss: 0.0418 - val_accuracy: 0.9909 Epoch 12/12 375/375 [==============================] - 31s 83ms/step - loss: 0.0224 - accuracy: 0.9925 - val_loss: 0.0372 - val_accuracy: 0.9902
Možemo i nacrtati grafike funkcije gubitka i tačnosti u toku treniranja.
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.title('Loss')
plt.plot(np.arange(0, epochs), history.history['loss'], label='train')
plt.plot(np.arange(0, epochs), history.history['val_loss'], label='val')
plt.legend(loc='best')
plt.subplot(1, 2, 2)
plt.title('Accuracy')
plt.plot(np.arange(0, epochs), history.history['accuracy'], label='train')
plt.plot(np.arange(0, epochs), history.history['val_accuracy'], label='val')
plt.legend(loc='best')
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 [==============================] - 2s 6ms/step - loss: 0.0309 - accuracy: 0.9911 Test loss: 0.030902909114956856 Test accuracy: 0.991100013256073
Na bazi MNIST napraviti model pomoću potpuno povezane neuronske mreže. Uporediti dobijene rezultate sa rezultatima koje smo dobili koristeći konvolutivnu mrežu.