In [0]:
# Import potrebných balíčkov
import os
import glob
import pickle
import numpy as np

import keras.backend as K
from keras.models import Model
from keras.regularizers import l2
from keras.layers import GlobalAveragePooling2D
from keras.layers import Flatten, Dense, Input, Dropout
from keras.preprocessing.image import ImageDataGenerator
from keras.applications.resnet50 import ResNet50
from keras.callbacks import EarlyStopping
from keras.optimizers import Adam
In [3]:
# Uistíme sa, že máme všetky potrebné dáta
!mkdir -p data/food5
!wget -nc -O data/food5.zip https://www.dropbox.com/s/bjz07tl5vimzset/food5.zip?dl=1
!unzip -oq -d data/food5 data/food5.zip
--2019-08-31 05:22:57--  https://www.dropbox.com/s/bjz07tl5vimzset/food5.zip?dl=1
Resolving www.dropbox.com (www.dropbox.com)... 162.125.82.1, 2620:100:6032:1::a27d:5201
Connecting to www.dropbox.com (www.dropbox.com)|162.125.82.1|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: /s/dl/bjz07tl5vimzset/food5.zip [following]
--2019-08-31 05:22:58--  https://www.dropbox.com/s/dl/bjz07tl5vimzset/food5.zip
Reusing existing connection to www.dropbox.com:443.
HTTP request sent, awaiting response... 302 Found
Location: https://ucfa1e165858aad8545cffb2987f.dl.dropboxusercontent.com/cd/0/get/AnrQ7DYNYs7rzNS2-hu6s9jg2GgTmd-bgaYVMuNgnwmw5FzIJD8S57yM_qDvyMZnnBy2v_5mylPWUheVUiuxvqjla6-GAX3-w0bzQiISxcIeZMTk91rycuJDVlAO1Q4hNd4/file?dl=1# [following]
--2019-08-31 05:22:58--  https://ucfa1e165858aad8545cffb2987f.dl.dropboxusercontent.com/cd/0/get/AnrQ7DYNYs7rzNS2-hu6s9jg2GgTmd-bgaYVMuNgnwmw5FzIJD8S57yM_qDvyMZnnBy2v_5mylPWUheVUiuxvqjla6-GAX3-w0bzQiISxcIeZMTk91rycuJDVlAO1Q4hNd4/file?dl=1
Resolving ucfa1e165858aad8545cffb2987f.dl.dropboxusercontent.com (ucfa1e165858aad8545cffb2987f.dl.dropboxusercontent.com)... 162.125.82.6, 2620:100:6032:6::a27d:5206
Connecting to ucfa1e165858aad8545cffb2987f.dl.dropboxusercontent.com (ucfa1e165858aad8545cffb2987f.dl.dropboxusercontent.com)|162.125.82.6|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 29710009 (28M) [application/binary]
Saving to: ‘data/food5.zip’

data/food5.zip      100%[===================>]  28.33M  13.6MB/s    in 2.1s    

2019-08-31 05:23:01 (13.6 MB/s) - ‘data/food5.zip’ saved [29710009/29710009]

In [0]:
# Pomocný kód
def preproc_data(X, Y, preproc_resnet, cache_filepath):
    """
    Predspracuje pôvodné obrazové dáta pomocou niekoľkých prvých vrstiev
    konvolučnej siete preproc_resnet. Výsledky predspracovania sa cache-ujú
    v súbore cache_filepath.
    """
    if os.path.exists(cache_filepath):
        preprocX, preprocY = pickle.load(open(cache_filepath, "rb"))
    else:
        preprocX = preproc_resnet.predict(X)
        preprocY = Y
        pickle.dump((preprocX, preprocY), open(cache_filepath, "wb"))
    
    return preprocX, preprocY

def make_datagen(use_augmentation):
    """
    Vytvorí generátor na základné predspracovanie obrazových dát. Ak je
    use_augmentation True, použije sa aj zväčšovanie dátovej množiny.
    """
    if use_augmentation:
        datagen = ImageDataGenerator(
            rotation_range=30.,
            shear_range=0.2,
            zoom_range=0.2,
            horizontal_flip=True)
    else:
        datagen = ImageDataGenerator()
    
    return datagen
  
def make_generator(dirname, datagen, batch_size=None, resize_pics=(224, 224),
                   shuffle=True, one_hot=False, splitVersion=1):
    class_mode = 'categorical' if one_hot else 'sparse'
    
    if batch_size is None:
        batch_size = len(glob.glob(os.path.join(dirname, '*/*.jpg')))
    
    generator = datagen.flow_from_directory(
        dirname,
        target_size=resize_pics,
        batch_size=batch_size,
        shuffle=shuffle,
        class_mode=class_mode
    )
        
    return generator

Transfer učenie

Transfer učenie budeme realizovať na dátovej množine Food 5: zmenšenej verzii dátovej množiny Food 11.

Transfer učenie je veľmi užitočná technika. Za normálnych okolností si hlboké učenie vyžaduje obrovské množstvo dát. Ak ho chceme aplikovať na malú dátovú množinu, typicky sa nám nepodarí dosiahnuť, aby hlboká sieť dobre zovšeobecňovala. Problém súvisí s tým, že malá dátová množina nedokáže väčšinou dostatočne vystihnúť všetky možné variácie vzoriek, s ktorými sa je možné stretnúť. Povedzme v prípade rozpoznávania obrazu môže existovať v podstate nekonečný počet variácií fotografie psa: líšiť sa môžu prostredím, osvetlením, plemenom psa, uhlom, v ktorom je odfotografovaný a pod. Malá dátová množina potom s vysokou pravdepodobnosťou nepokryje dostatočne tento široký priestor.

Jedným z riešení, ktoré umožňujú hlboké učenie predsa len aplikovať aj na pomerne malé dátové množiny, je transfer učenie. Ide o techniku, kde sa sieť najprv predtrénuje na veľkej, všeobecnejšej dátovej množine (v prípade spracovania obrazu to býva dátová množina ImageNet) – tam sa sieť naučí napríklad o tom, ako vyzerá prirodzený obraz a ako ho treba predspracovať. Následne sa už existujúca sieť pretrénuje na konkrétnu cieľovú úlohu.

Rámcový postup

Rámcový postup transfer učenia pre spracovanie obrazu:

  • Predtrénovať sieť na dátovej množine ImageNet.

  • Z pôvodnej siete zmazať niekoľko posledných vrstiev a nahradiť ich novými. Nová výstupná vrstva bude už mať toľko výstupov, koľko je tried v novej dátovej množine.

  • Váhy predtrénovaných vrstiev sa zafixujú. Na novej dátovej množine sa trénujú najprv len nové vrstvy.

  • Keď sa nové vrstvy natrénovali, odomkneme aj váhy predtrénovaných vrstiev a doladíme váhy celej siete. Použijeme omnoho nižšiu rýchlosť učenia – jednak preto, aby sme váhy priveľkými krokmi nerozladili, ale aj preto, že pri ladení všetkých váh sa už sieť veľmi ľahko preučí.

Príprava dátovej množiny

Pripravíme dáta. Najprv ich stiahneme a roztriedime, ak sa tak ešte nestalo.

Vytvoríme generátory a načítame pomocou nich na jeden raz všetky tréningové, testovacie a validačné dáta. Naša dátová množina je malá, takže sa zmestí do pamäte aj celá naraz.

In [6]:
train_generator = make_generator("data/food5/training", make_datagen(use_augmentation=False))
valid_generator = make_generator("data/food5/validation", make_datagen(use_augmentation=False))
test_generator = make_generator("data/food5/testing", make_datagen(use_augmentation=False))

X_train, Y_train = train_generator.next()
X_valid, Y_valid = valid_generator.next()
X_test, Y_test = test_generator.next()
Found 200 images belonging to 5 classes.
Found 200 images belonging to 5 classes.
Found 200 images belonging to 5 classes.

Načítanie predtrénovanej siete a tvorba nových vrstiev

Balíček keras poskytuje niekoľko populárnych architektúr, ktoré sú už predtrénované na dátovej množine ImageNet. Jednu takú sieť si vytvoríme, pričom pomocou argumentu include_top=False rovno povieme, že nemáme záujem o posledné vrstvy. Špecifikovať budeme aj vstupný tenzor: v našom prípade budú mať vstupné obrázky rozmer 224 x 224:

In [7]:
input_tensor=Input(shape=(224, 224) + (3,))
preproc_resnet = ResNet50(include_top=False,
                          weights='imagenet',
                          input_tensor=input_tensor) 
WARNING: Logging before flag parsing goes to stderr.
W0831 05:25:15.308028 140478834280320 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:66: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead.

W0831 05:25:15.356223 140478834280320 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:541: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

W0831 05:25:15.368756 140478834280320 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4479: The name tf.truncated_normal is deprecated. Please use tf.random.truncated_normal instead.

W0831 05:25:15.414107 140478834280320 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:190: The name tf.get_default_session is deprecated. Please use tf.compat.v1.get_default_session instead.

W0831 05:25:15.415148 140478834280320 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:197: The name tf.ConfigProto is deprecated. Please use tf.compat.v1.ConfigProto instead.

W0831 05:25:18.936615 140478834280320 deprecation_wrapper.py:119] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:2041: The name tf.nn.fused_batch_norm is deprecated. Please use tf.compat.v1.nn.fused_batch_norm instead.

W0831 05:25:19.031920 140478834280320 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.

/usr/local/lib/python3.6/dist-packages/keras_applications/resnet50.py:265: UserWarning: The output shape of `ResNet50(include_top=False)` has been changed since Keras 2.2.0.
  warnings.warn('The output shape of `ResNet50(include_top=False)` '
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5
94658560/94653016 [==============================] - 8s 0us/step

Teraz máme dve možnosti:

  • Spojíme našu predtrénovanú sieť s novovytvorenými vrstvami, zafixujeme váhy predtrénovanej siete a výsledok budeme trénovať ako celok.

  • Alebo pôvodnú sieť použijeme zatiaľ len na predspracovanie všetkých dát a nové vrstvy budeme trénovať samostatne.

Druhá možnosť bude podstatne menej výpočtovo náročná – na druhej strane nám neumožní používať priebežné zväčšovanie dátovej množiny (augmentation). Napriek tomu si teraz zvolíme túto druhú možnosť.

Predtrénovanú sieť teda použijeme na predspracovanie:

In [0]:
preprocX_train, preprocY_train = preproc_data(X_train, Y_train, preproc_resnet,
                                              "preproc_train.pkl")
preprocX_valid, preprocY_valid = preproc_data(X_valid, Y_valid, preproc_resnet,
                                              "preproc_valid.pkl")
preprocX_test, preprocY_test = preproc_data(X_test, Y_test, preproc_resnet,
                                              "preproc_test.pkl")

Vytvoríme si nové koncové vrstvy ako osobitnú sieť. Kvôli silnejšej regularizácii môžeme použiť dropout a pod.:

In [9]:
y = x = Input(shape=preproc_resnet.output_shape[1:])

y = GlobalAveragePooling2D()(y)
y = Dropout(0.5)(y)
y = Dense(train_generator.num_classes, activation='softmax',
          kernel_regularizer=l2(1e-2))(y)

model = Model(x, y)
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
W0831 05:25:52.489485 140478834280320 deprecation.py:506] From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3733: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
W0831 05:25:52.524840 140478834280320 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.


Úloha 1: Tréning nových vrstiev

Ďalej pokračujte tréningom novovytvorených vrstiev. Použite skoré ukončenie učenia (pomocou validačných dát).

Poznámka: Model trénujeme na predspracovaných dátach preprocX_train a preprocY_train.


In [0]:

Otestujeme úspešnosť nových vrstiev

In [11]:
_, val_acc = model.evaluate(preprocX_valid, preprocY_valid, verbose=0)
print("Správnosť na validačných dátach:  {}".format(val_acc))
Správnosť na validačných dátach:  0.705
In [12]:
_, test_acc = model.evaluate(preprocX_test, preprocY_test, verbose=0)
#print("Správnosť na testovacích dátach:  {}".format(test_acc))
Správnosť na testovacích dátach:  0.73

Doladenie váh celej siete

Vytvoríme si generátor tréningových dát, ktorý bude aplikovať techniky zväčšovania dátovej množiny (data augmentation). Znamená to, že tréningové obrázky vždy mierne upraví (napr. posunutí, zväčšením, otočením a pod.). Vo výsledku sa zvýši rôznorodosť dát a nikdy sa dvakrát netrénuje presne s tým istým obrázkom.

In [14]:
aug_train_generator = make_generator("data/food5/training",
                        make_datagen(use_augmentation=True), batch_size=64)
Found 200 images belonging to 5 classes.

Vytvoríme kompletný model, v ktorom už budeme trénovať aj konvolučné vrstvy. Budeme voliť nižšiu rýchlosť učenia:

In [0]:
model_full = Model(preproc_resnet.input, model(preproc_resnet.output))

model_full.compile(
    loss='sparse_categorical_crossentropy',
    optimizer=Adam(lr=1e-5),
    metrics=['accuracy']
)

Model trénujeme pomocou funkcie fit_generator, ktorá tréningové dáta berie z vyššie vytvoreného generátora:

In [19]:
model_full.fit_generator(
    aug_train_generator,
    steps_per_epoch=int(np.ceil(aug_train_generator.n /
                                aug_train_generator.batch_size)),
    epochs=50,
    validation_data=(X_valid, Y_valid),
    callbacks=[EarlyStopping()]
)
Epoch 1/50
4/4 [==============================] - 20s 5s/step - loss: 1.1114 - acc: 0.6646 - val_loss: 1.3380 - val_acc: 0.5700
Epoch 2/50
4/4 [==============================] - 7s 2s/step - loss: 1.1049 - acc: 0.5936 - val_loss: 1.2867 - val_acc: 0.5750
Epoch 3/50
4/4 [==============================] - 7s 2s/step - loss: 0.9548 - acc: 0.7046 - val_loss: 1.2320 - val_acc: 0.5900
Epoch 4/50
4/4 [==============================] - 7s 2s/step - loss: 0.9840 - acc: 0.6567 - val_loss: 1.1760 - val_acc: 0.6200
Epoch 5/50
4/4 [==============================] - 7s 2s/step - loss: 0.8394 - acc: 0.6992 - val_loss: 1.1229 - val_acc: 0.6500
Epoch 6/50
4/4 [==============================] - 7s 2s/step - loss: 0.8940 - acc: 0.6650 - val_loss: 1.0672 - val_acc: 0.6650
Epoch 7/50
4/4 [==============================] - 7s 2s/step - loss: 0.7962 - acc: 0.7595 - val_loss: 1.0248 - val_acc: 0.6750
Epoch 8/50
4/4 [==============================] - 7s 2s/step - loss: 0.6601 - acc: 0.8008 - val_loss: 0.9880 - val_acc: 0.7050
Epoch 9/50
4/4 [==============================] - 7s 2s/step - loss: 0.6849 - acc: 0.7871 - val_loss: 0.9518 - val_acc: 0.7150
Epoch 10/50
4/4 [==============================] - 7s 2s/step - loss: 0.5712 - acc: 0.8433 - val_loss: 0.9277 - val_acc: 0.7250
Epoch 11/50
4/4 [==============================] - 7s 2s/step - loss: 0.6156 - acc: 0.7983 - val_loss: 0.9060 - val_acc: 0.7300
Epoch 12/50
4/4 [==============================] - 7s 2s/step - loss: 0.5029 - acc: 0.8886 - val_loss: 0.8913 - val_acc: 0.7300
Epoch 13/50
4/4 [==============================] - 7s 2s/step - loss: 0.4875 - acc: 0.8845 - val_loss: 0.8748 - val_acc: 0.7350
Epoch 14/50
4/4 [==============================] - 7s 2s/step - loss: 0.5474 - acc: 0.8011 - val_loss: 0.8625 - val_acc: 0.7400
Epoch 15/50
4/4 [==============================] - 7s 2s/step - loss: 0.4430 - acc: 0.8956 - val_loss: 0.8486 - val_acc: 0.7550
Epoch 16/50
4/4 [==============================] - 7s 2s/step - loss: 0.4169 - acc: 0.8956 - val_loss: 0.8371 - val_acc: 0.7550
Epoch 17/50
4/4 [==============================] - 7s 2s/step - loss: 0.5748 - acc: 0.7875 - val_loss: 0.8303 - val_acc: 0.7550
Epoch 18/50
4/4 [==============================] - 7s 2s/step - loss: 0.3738 - acc: 0.9080 - val_loss: 0.8220 - val_acc: 0.7600
Epoch 19/50
4/4 [==============================] - 7s 2s/step - loss: 0.3752 - acc: 0.9204 - val_loss: 0.8165 - val_acc: 0.7600
Epoch 20/50
4/4 [==============================] - 7s 2s/step - loss: 0.3827 - acc: 0.9204 - val_loss: 0.8122 - val_acc: 0.7650
Epoch 21/50
4/4 [==============================] - 7s 2s/step - loss: 0.3598 - acc: 0.8820 - val_loss: 0.8081 - val_acc: 0.7600
Epoch 22/50
4/4 [==============================] - 7s 2s/step - loss: 0.3595 - acc: 0.9340 - val_loss: 0.8038 - val_acc: 0.7600
Epoch 23/50
4/4 [==============================] - 7s 2s/step - loss: 0.2736 - acc: 0.9711 - val_loss: 0.7974 - val_acc: 0.7600
Epoch 24/50
4/4 [==============================] - 7s 2s/step - loss: 0.3116 - acc: 0.9422 - val_loss: 0.7908 - val_acc: 0.7550
Epoch 25/50
4/4 [==============================] - 7s 2s/step - loss: 0.3393 - acc: 0.9369 - val_loss: 0.7895 - val_acc: 0.7600
Epoch 26/50
4/4 [==============================] - 7s 2s/step - loss: 0.3710 - acc: 0.9233 - val_loss: 0.7850 - val_acc: 0.7600
Epoch 27/50
4/4 [==============================] - 7s 2s/step - loss: 0.2985 - acc: 0.9711 - val_loss: 0.7798 - val_acc: 0.7550
Epoch 28/50
4/4 [==============================] - 7s 2s/step - loss: 0.2833 - acc: 0.9711 - val_loss: 0.7741 - val_acc: 0.7600
Epoch 29/50
4/4 [==============================] - 7s 2s/step - loss: 0.2635 - acc: 0.9752 - val_loss: 0.7695 - val_acc: 0.7650
Epoch 30/50
4/4 [==============================] - 7s 2s/step - loss: 0.2897 - acc: 0.9575 - val_loss: 0.7647 - val_acc: 0.7600
Epoch 31/50
4/4 [==============================] - 7s 2s/step - loss: 0.2750 - acc: 0.9492 - val_loss: 0.7593 - val_acc: 0.7700
Epoch 32/50
4/4 [==============================] - 7s 2s/step - loss: 0.3186 - acc: 0.9315 - val_loss: 0.7553 - val_acc: 0.7700
Epoch 33/50
4/4 [==============================] - 7s 2s/step - loss: 0.3007 - acc: 0.9629 - val_loss: 0.7526 - val_acc: 0.7650
Epoch 34/50
4/4 [==============================] - 7s 2s/step - loss: 0.3443 - acc: 0.9191 - val_loss: 0.7518 - val_acc: 0.7650
Epoch 35/50
4/4 [==============================] - 7s 2s/step - loss: 0.4964 - acc: 0.9274 - val_loss: 0.7507 - val_acc: 0.7650
Epoch 36/50
4/4 [==============================] - 7s 2s/step - loss: 0.2374 - acc: 0.9711 - val_loss: 0.7469 - val_acc: 0.7650
Epoch 37/50
4/4 [==============================] - 7s 2s/step - loss: 0.2785 - acc: 0.9315 - val_loss: 0.7423 - val_acc: 0.7700
Epoch 38/50
4/4 [==============================] - 7s 2s/step - loss: 0.2285 - acc: 0.9959 - val_loss: 0.7403 - val_acc: 0.7800
Epoch 39/50
4/4 [==============================] - 7s 2s/step - loss: 0.2793 - acc: 0.9274 - val_loss: 0.7391 - val_acc: 0.7800
Epoch 40/50
4/4 [==============================] - 7s 2s/step - loss: 0.2474 - acc: 0.9835 - val_loss: 0.7359 - val_acc: 0.7800
Epoch 41/50
4/4 [==============================] - 7s 2s/step - loss: 0.2484 - acc: 0.9616 - val_loss: 0.7319 - val_acc: 0.7950
Epoch 42/50
4/4 [==============================] - 7s 2s/step - loss: 0.3532 - acc: 0.9398 - val_loss: 0.7293 - val_acc: 0.7900
Epoch 43/50
4/4 [==============================] - 7s 2s/step - loss: 0.1648 - acc: 0.9959 - val_loss: 0.7240 - val_acc: 0.8000
Epoch 44/50
4/4 [==============================] - 7s 2s/step - loss: 0.3660 - acc: 0.9096 - val_loss: 0.7219 - val_acc: 0.8050
Epoch 45/50
4/4 [==============================] - 7s 2s/step - loss: 0.2283 - acc: 0.9398 - val_loss: 0.7211 - val_acc: 0.7950
Epoch 46/50
4/4 [==============================] - 7s 2s/step - loss: 0.2168 - acc: 0.9575 - val_loss: 0.7217 - val_acc: 0.7950
Out[19]:
<keras.callbacks.History at 0x7fc31dbfdb00>

Testovanie modelu

Schopnosť zovšeobecňovať teraz overíme na testovacích dátach.

In [20]:
_, test_acc = model_full.evaluate(X_test, Y_test)
print("\nSprávnosť na testovacích dátach: {}".format(test_acc))
200/200 [==============================] - 2s 9ms/step

Správnosť na testovacích dátach: 0.83
In [0]: