In [0]:
# Import potrebných balíčkov
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import accuracy_score, precision_score, recall_score
from IPython.display import Image

import keras.backend as K
from keras.models import Model
from keras.callbacks import EarlyStopping
from keras.datasets import mnist
from keras.layers import (Conv2D, MaxPooling2D, Flatten, Dense,
                          Dropout, Activation, Input)
from keras.utils import plot_model

Klasifikácia MNIST číslic

Tento príklad bude ilustrovať, ako sa dá zostrojiť jednoduchá konvolučná sieť na klasifikáciu obrazu na dátovej množine MNIST obsahujúcej rukou písané číslice.

Načítanie dátovej množiny

Začneme načítaním dátovej množiny MNIST. Tento krok bude veľmi jednoduchý, keďže keras má presne na tento účel vstavanú hotovú funkciu. Dátová množina bude už rozdelená na tréningovú a testovaciu časť.

In [3]:
(X_train_raw, Y_train_raw), (X_test_raw, Y_test_raw)\
    = mnist.load_data()
Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 2s 0us/step

Zobrazme teraz niekoľko náhodne zvolených vzoriek z dátovej množiny.

In [4]:
num_rows = 4; num_cols = 4
fig, axes = plt.subplots(num_rows, num_cols)

for row in axes:
  for ax in row:
    ax.imshow(X_train_raw[np.random.randint(0,
                len(X_train_raw)-1)])
    ax.set_xticks([])
    ax.set_yticks([])

Teraz budeme dáta konvertovať na 32-bitové float-y a preškálujeme do rozsahu [0, 1] (t.j. delíme 255):

In [0]:
X_train = X_train_raw.astype('float32') / 255
X_test = X_test_raw.astype('float32') / 255

Tiež pridáme osobitný rozmer pre farebné kanály: napriek tomu, že máme len jeden.

In [0]:
X_train = X_train.reshape(X_train.shape + (1,))
X_test = X_test.reshape(X_test.shape + (1,))

Na výstupnú triedu aplikujemem kódovanie 1 z n.

In [0]:
encoder = OneHotEncoder(categories='auto', sparse=False)
Y_train = encoder.fit_transform(Y_train_raw.reshape((-1, 1)))
Y_test = encoder.transform(Y_test_raw.reshape((-1, 1)))

Skontrolujeme tvar dátovej množiny, aby sme zistili, aký rozmer má mať vstup a výstup neurónovej siete.

In [8]:
X_train.shape
Out[8]:
(60000, 28, 28, 1)
In [9]:
Y_train.shape
Out[9]:
(60000, 10)

Zostavenie konvolučnej siete

Ako zvyčajne, sieť začneme zostavovať od vstupnej vrstvy. Naše obrázky sú rozmeru 28 x 28 a sú čierno-biele, čo znamená, že pracujeme len s jedným farebným kanálom.

In [0]:
y = x = Input(shape=(28, 28, 1))

Ďalej budeme na vstup aplikovať zopár konvolučných a združovacích vrstiev. Budeme ich reťaziť dovtedy, kým rozmer výstup nebude rozumne malý.

Bude tiež treba špecifikovať počet filtrov (buď vyskúšame nejakú ľubovoľnú hodnotu a dúfame, že bude fungovať alebo môžeme použiť niektorú metódu na ladenie hyperparametrov, napr. bayesovskú optimalizáciu).

Treba tiež špecifikovať veľkosť konvolučného jadra. Jadro veľkosti 3 x 3 väčšinou funguje dobre. Padding nastavíme na 'valid' (podrobnejšie vysvetlenie nájdete v slajdoch).

In [11]:
y = Conv2D(filters=32,
           kernel_size=(3, 3),
           padding='valid')(y)
y = Activation('relu')(y)
y = MaxPooling2D(pool_size=(2, 2))(y)
W0830 12:59:29.671447 140176817092480 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4432: The name tf.random_uniform is deprecated. Please use tf.random.uniform instead.

W0830 12:59:29.788244 140176817092480 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4267: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.

Teraz znovu skontrolujeme rozmer výstupu, aby sme zistili, aký veľký je.

In [12]:
shape = K.int_shape(y)
size = np.product(shape[1:])
print("Shape: {}. Total size: {}".format(shape, size))
Shape: (None, 13, 13, 32). Total size: 5408

To je stále dosť veľký výstup. Pridajme ešte jednu vrstvu.

In [0]:
y = Conv2D(filters=16,
           kernel_size=(3, 3),
           padding='valid')(y)
y = Activation('relu')(y)
y = MaxPooling2D(pool_size=(2, 2))(y)
In [14]:
shape = K.int_shape(y)
size = np.product(shape[1:])
print("Shape: {}. Total size: {}".format(shape, size))
Shape: (None, 5, 5, 16). Total size: 400

To už je rozumnejšia veľkosť. Teraz zalomme operáciou Flatten príslušnú maticu do vektora a aplikujme naň ešte niekoľko úplne prepojených vrstiev.

In [0]:
y = Flatten()(y)

y = Dense(128)(y)
y = Activation('relu')(y)

Nasleduje už len výstupná vrstva, ktorá má toľko neurónov, koľko je tried, a aktivačnú funkciu softmax.

In [0]:
num_classes = 10
y = Dense(num_classes)(y)
y = Activation('softmax')(y)

Vytvoríme a skompilujeme model.

In [17]:
model = Model(x, y)
model.compile(loss='categorical_crossentropy',
              optimizer='adam', metrics=['accuracy'])
W0830 12:59:41.395330 140176817092480 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/optimizers.py:793: The name tf.train.Optimizer is deprecated. Please use tf.compat.v1.train.Optimizer instead.

W0830 12:59:41.417116 140176817092480 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3576: The name tf.log is deprecated. Please use tf.math.log instead.

S použitím pomocnej funkcie plot_model si môžeme vizualizovať architektúru vytvorenej siete:

In [18]:
plot_model(model, "model.png", )
Image(retina=True, filename='model.png')
Out[18]:

Učenie

In [19]:
model.fit(X_train, Y_train, epochs=5, batch_size=128)
W0830 12:59:46.486816 140176817092480 deprecation.py:323] From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_grad.py:1250: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
W0830 12:59:46.545788 140176817092480 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:1033: The name tf.assign_add is deprecated. Please use tf.compat.v1.assign_add instead.

Epoch 1/5
60000/60000 [==============================] - 9s 156us/step - loss: 0.2687 - acc: 0.9224
Epoch 2/5
60000/60000 [==============================] - 2s 39us/step - loss: 0.0734 - acc: 0.9779
Epoch 3/5
60000/60000 [==============================] - 2s 38us/step - loss: 0.0516 - acc: 0.9842
Epoch 4/5
60000/60000 [==============================] - 2s 38us/step - loss: 0.0403 - acc: 0.9875
Epoch 5/5
60000/60000 [==============================] - 2s 39us/step - loss: 0.0327 - acc: 0.9898
Out[19]:
<keras.callbacks.History at 0x7f7d0eb433c8>

Testovanie

Teraz model otestujeme na testovacích dátach.

In [0]:
y_test_raw = model.predict(X_test)
y_test = encoder.inverse_transform(y_test_raw)
In [21]:
cm = pd.crosstab(Y_test_raw.reshape(-1),
                 y_test.reshape(-1),
                 rownames=['actual'],
                 colnames=['predicted'])
sns.heatmap(cm.values, annot=True, cmap='coolwarm')
plt.xlabel("predicted")
plt.ylabel("actual")
Out[21]:
Text(33.0, 0.5, 'actual')
In [27]:
print("Správnosť: {}.".format(accuracy_score(
    Y_test_raw, y_test
)))

print("Presnosť: {}.".format(precision_score(
    Y_test_raw, y_test, average='macro'
)))

print("Úplnosť: {}.".format(recall_score(
    Y_test_raw, y_test, average='macro'
)))
Správnosť: 0.9841.
Presnosť: 0.9841646342428889.
Úplnosť: 0.9842970752210652.

Úloha 1: Lepšia regularizácia

Náš klasifikátor ako tak funguje, ale výsledky nie sú príliš pôsobivé: mohol by zovšeobecňovať aj lepšie.

  • Pokúste sa pridať dropout vrstvu a/alebo dávkovú normalizáciu za každý konvolučno-aktivačno-združovací blok.
  • Tiež sa pokúste zvýšiť počet epoch učenia a prípadne použiť skoré ukončenie učenia pomocou validačných dát (napr. pomocou argumentu validation_split funkcie fit).

In [0]: