Za chwilę zagłębimy się w zagadnienia teoretyczne związane z konwolucyjnymi sieciami neuronowymi. Dlaczego sieci te tak dobrze sprawdzają się podczas przetwarzania obrazu? Zacznijmy od przyjrzenia się prostemu przykładowi sieci konwolucyjnej. Sieć ta służy do klasyfikacji zbioru cyfr MNIST. Problemem tym zajmowaliśmy się już wcześniej w rozdziale 2. — zastosowana wówczas sieć o gęstych połączeniach uzyskała dokładność testową na poziomie 97,8%. Mimo że sieć konwolucyjna, którą przedstawię za chwilę, będzie bardzo prosta, to uzyska o wiele lepszy wynik od modelu z rozdziału 2.
Poniższy kod przedstawia wygląd podstawowej sieci konwolucyjnej. Jest ona stosem warstw layer_conv_2d i layer_max_pooling_2d. Za chwilę opiszę szczegółowo działanie tych warstw.
Sieć neuronowa przyjmuje tensor o kształcie określanym przez wysokość obrazu, jego szerokość i kanały obrazu — nie wliczamy tutaj wymiaru próbki. W związku z tym sieć ma przetwarzać obiekty wejściowe o rozmiarze (28, 28, 1) — taki jest właśnie format obrazów wchodzących w skład zbioru MNIST. Dlatego do pierwszej warstwy sieci przekazujemy argument input_shape=(28, 28, 1).
r
r library(keras) model <- keras_model_sequential() %>% layer_conv_2d(filters = 32, kernel_size = c(3, 3), activation = , input_shape = c(28, 28, 1)) %>% layer_max_pooling_2d(pool_size = c(2, 2)) %>% layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = ) %>% layer_max_pooling_2d(pool_size = c(2, 2)) %>% layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = )
Using TensorFlow backend.
Wyświetlmy aktualną architekturę sieci:
r
r summary(model)
Model
_________________________________________________________________________________________________________
Layer (type) Output Shape Param #
=========================================================================================================
conv2d_1 (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D) (None, 13, 13, 32) 0
_________________________________________________________________________________________________________
conv2d_2 (Conv2D) (None, 11, 11, 64) 18496
_________________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D) (None, 5, 5, 64) 0
_________________________________________________________________________________________________________
conv2d_3 (Conv2D) (None, 3, 3, 64) 36928
=========================================================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
_________________________________________________________________________________________________________
Na wyjściu każdej warstwy layer_conv_2d i layer_max_pooling_2d pojawia się trójwymiarowy tensor o kształcie (wysokość, szerokość, kanały). Wraz z zagłębianiem się przez Ciebie w sieć wysokość i szerokość mają tendencję do przyjmowania mniejszych wartości. Liczbę kanałów określa pierwszy argument przekazany do warstw layer_conv_2d (możemy mieć np. 32 lub 64 kanały).
Kolejnym krokiem jest przekazanie ostatniego tensora wyjściowego o kształcie (3, 3, 64) do klasyfikatora w postaci sieci gęstej (ten typ sieci opisywałem wcześniej) — stosu warstw dense. Klasyfikatory te przetwarzają jednowymiarowe wektory, a nasze dane mają postać trójwymiarowego tensora. W związku z tym musimy spłaszczyć nasze dane wyjściowe, a następnie dodać kilka górnych warstw dense:
r
r model <- model %>% layer_flatten() %>% layer_dense(units = 64, activation = ) %>% layer_dense(units = 10, activation = )
Klasyfikacja ma na celu dokonanie przyporządkowania do jednego z dziesięciu zbiorów, a więc będziemy korzystać z ostatniej warstwy generującej 10 wartości wyjściowych i użyjemy funkcji aktywacji softmax. Oto aktualna charakterystyka architektury sieci:
r
r summary(model)
Model
_________________________________________________________________________________________________________
Layer (type) Output Shape Param #
=========================================================================================================
conv2d_1 (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D) (None, 13, 13, 32) 0
_________________________________________________________________________________________________________
conv2d_2 (Conv2D) (None, 11, 11, 64) 18496
_________________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D) (None, 5, 5, 64) 0
_________________________________________________________________________________________________________
conv2d_3 (Conv2D) (None, 3, 3, 64) 36928
_________________________________________________________________________________________________________
flatten_1 (Flatten) (None, 576) 0
_________________________________________________________________________________________________________
dense_1 (Dense) (None, 64) 36928
_________________________________________________________________________________________________________
dense_2 (Dense) (None, 10) 650
=========================================================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________________________________________________
Jak widać, tensory przed skierowaniem do dwóch warstw Dense miały początkowo kształt (3, 3, 64) i zostały spłaszczone do wektorów o kształcie (576).
Spróbujmy wytrenować sieć konwolucyjną na zbiorze MNIST. Możemy w tym celu ponownie użyć dużej części kodu przedstawionego wcześniej w rozdziale 2.
r
r mnist <- dataset_mnist() c(c(train_images, train_labels), c(test_images, test_labels)) %<-% mnist train_images <- array_reshape(train_images, c(60000, 28, 28, 1)) train_images <- train_images / 255 test_images <- array_reshape(test_images, c(10000, 28, 28, 1)) test_images <- test_images / 255 train_labels <- to_categorical(train_labels) test_labels <- to_categorical(test_labels) model %>% compile( optimizer = , loss = _crossentropy, metrics = c() )
model %>% fit( train_images, train_labels, epochs = 5, batch_size=64 )
Sprawdźmy działanie modelu na testowym zbiorze danych:
r
r results <- model %>% evaluate(test_images, test_labels)
r
r results
$loss
[1] 0.03213284
$acc
[1] 0.9901
Gęsta sieć zastosowana w celu rozwiązania tego problemu w rozdziale 2. charakteryzowała się testową dokładnością na poziomie 97,8%, a podstawowa sieć konwolucyjna uzyskała wynik 99,3%. Względna wartość błędu została zmniejszona o 68%. Nieźle!