Домашнее задание 3.

Keras и сверточные нейронные сети.

Задание выполнил(а): Подчезерцев Алексей

In [0]:
RANDOM_SEED = 42
In [2]:
import tensorflow as tf
import keras
from keras import backend as K
%matplotlib inline
import matplotlib.pyplot as plt
print(tf.__version__)
print(keras.__version__)

The default version of TensorFlow in Colab will soon switch to TensorFlow 2.x.
We recommend you upgrade now or ensure your notebook will continue to use TensorFlow 1.x via the %tensorflow_version 1.x magic: more info.

1.15.0
2.2.5
Using TensorFlow backend.
In [0]:
def reset_tf_session():
    curr_session = tf.get_default_session()
    if curr_session is not None:
        curr_session.close()
    K.clear_session()
    config = tf.ConfigProto()
    config.gpu_options.allow_growth = True
    s = tf.InteractiveSession(config=config)
    K.set_session(s)
    return s

Задание 1 — инициализация весов CNN (3 балла).

В этом задании нужно будет исследовать, как выбор функции инициализации весов влияет на обучение CNN.

Продолжим работать с датасетом CIFAR-10.

In [4]:
from keras.datasets import cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

NUM_CLASSES = 10
cifar10_classes = ["airplane", "automobile", "bird", "cat", "deer", 
                   "dog", "frog", "horse", "ship", "truck"]

print("Train samples:", x_train.shape, y_train.shape)
print("Test samples:", x_test.shape, y_test.shape)

# нормализуем входные данные
x_train = x_train / 255 - 0.5
x_test = x_test / 255 - 0.5

y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
Train samples: (50000, 32, 32, 3) (50000, 1)
Test samples: (10000, 32, 32, 3) (10000, 1)
In [0]:
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Activation, Dropout
from keras.layers.advanced_activations import LeakyReLU
from keras.models import load_model

Определим функцию fit_model с архитектурой архитектура CNN.

Метод model.fit возвращает объект класса keras.callbacks.History() — это колбэк, который автоматически применяется ко всем моделям и логирует много чего полезного. В частности логируются значения функции потерь на каждой итерации.

Задание 1.1 (0.5 балла) Добавьте в архитектуру модели инициализацию весов для тех слоев, где она необходима.

In [0]:
def fit_model(initializer='glorot_normal'):
    s = reset_tf_session()  
    INIT_LR = 5e-3
    BATCH_SIZE = 32
    EPOCHS = 10
   
    def lr_scheduler(epoch):
        return INIT_LR * 0.9 ** epoch  

    ### YOUR CODE HERE 
    # kernel_initializer=initializer для тех слоев, которым нужна инициализация весов
    model = Sequential()
    model.add(Conv2D(filters=16, padding='same', kernel_size=(3,3), input_shape=(32,32,3), kernel_initializer=initializer))
    model.add(LeakyReLU(0.1))
    model.add(Conv2D(filters=64, padding='same', kernel_size=(3,3), kernel_initializer=initializer))  
    model.add(LeakyReLU(0.1))
    model.add(MaxPooling2D(pool_size=(2,2), padding='same'))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(40, kernel_initializer=initializer))
    model.add(LeakyReLU(0.1))
    model.add(Dropout(0.5))    
    model.add(Dense(10, kernel_initializer=initializer))
    model.add(Activation("softmax"))


    model.compile(
        loss='categorical_crossentropy',
        optimizer=keras.optimizers.adamax(lr=INIT_LR),
        metrics=['accuracy']
    )
  
    history = model.fit(
        x_train, y_train,
        batch_size=BATCH_SIZE,
        epochs=EPOCHS,
        callbacks=[keras.callbacks.LearningRateScheduler(lr_scheduler)],                   
        shuffle=True,
        verbose=0,
        initial_epoch=0    
    )

    # Возвращаем объект класса keras.callbacks.History
    return history

Задание 1.2 (1.5 балла). Обучите модель с разными функциями инициализации весов:

  • Zeros — веса инициализируются нулями
  • Constant=0.05 — веса инициализируются константой 0.05
  • RandomUniform — веса генерируются равномерно из отрезка [-0.05, 0.05]
  • glorot_normal — Xavier initializer из лекций
  • lecun_uniform

Добавьте в список losses значения функции потерь для каждой функции инициализации, их можно достать из History

In [0]:
losses = []
for initializer in [keras.initializers.Zeros(), 
                    keras.initializers.Constant(value=0.05), 
                    keras.initializers.RandomUniform(minval=-0.05, maxval=0.05, seed=RANDOM_SEED),
                    keras.initializers.glorot_normal(seed=RANDOM_SEED),
                    keras.initializers.lecun_uniform(seed=RANDOM_SEED),
                    ]:
    history = fit_model(initializer)
    losses.append(history.history['loss'])
WARNING:tensorflow: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.

WARNING:tensorflow: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.

WARNING:tensorflow: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`.
WARNING:tensorflow: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.

WARNING:tensorflow: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.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow_core/python/ops/math_grad.py:1424: where (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
WARNING:tensorflow: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.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:1020: The name tf.assign is deprecated. Please use tf.compat.v1.assign instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3005: The name tf.Session is deprecated. Please use tf.compat.v1.Session instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:207: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:216: The name tf.is_variable_initialized is deprecated. Please use tf.compat.v1.is_variable_initialized instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:223: The name tf.variables_initializer is deprecated. Please use tf.compat.v1.variables_initializer instead.

WARNING:tensorflow: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.

WARNING:tensorflow: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.

Задание 1.3 (1 балла). Постройте графики зависимости функций потерь от номера итерации, подпишите их. Прокомментируйте результат.

In [0]:
labels = ["Zero", "Constant", "RandomUniform", "Glorot normal", "Lecun"]
plt.figure(figsize=[16, 7])
for i,v in enumerate(labels):
    plt.plot(losses[i], label=f"{v} init")
plt.title('Зависимость функции потерь от номера итерации и типа инициализации')
plt.legend(loc='best')

plt.xlabel("# итерации")
plt.ylabel("Функция потерь")
plt.show()

Инициализация 0 не позволяет обучаться слою (производная в точке зануляется).

Другие же показали уменьшение функции потерь при увеличении номера итерации. При этом чем более математически обоснован алгоритм, тем меньше потери.

Случайная инициализация весов себя показала заметно лучше, чем с одинаковыми значениями. Даже если параметры имеют одинаковые значения, они будут обучаться по-разному (т.к. веса их разные), что приведет к более быстрому и качественному решению.

Glorot normal и Lecun дали почти одинаковые результаты, но lecun немного лучше.

Задание 2 — CNN для CIFAR-10 с сохранением весов модели (7 баллов)

В этом задании мы модифицируем нейросеть с семинара, чтобы она достигала большего значения accuracy и научимся сохранять веса модели в файл во время обучения. Можно использовать только те же слои, которые использовались на семинаре: Conv2D, MaxPooling2D, LeakyReLU, Dropout, Flatten, Dense.

Задание 2.1 (4 балла). Подберите архитектуру модели так, чтобы значение accuracy на тестовой выборке было не менее 85.

In [0]:
def make_model():    
    model = Sequential()
    initializer = keras.initializers.lecun_uniform(seed=RANDOM_SEED)

    model.add(Conv2D(filters=64, padding='same', kernel_size=(3,3), input_shape=(32,32,3), kernel_initializer=initializer))
    model.add(LeakyReLU(0.1))    
    model.add(Conv2D(filters=64, padding='same', kernel_size=(3,3), kernel_initializer=initializer))  
    model.add(LeakyReLU(0.1))    
    model.add(MaxPooling2D(pool_size=(2,2), padding='same'))
    model.add(Dropout(0.3)) 

    model.add(Conv2D(filters=128, padding='same', kernel_size=(3,3), kernel_initializer=initializer))
    model.add(LeakyReLU(0.1))    
    model.add(Conv2D(filters=128, padding='same', kernel_size=(3,3), kernel_initializer=initializer))  
    model.add(LeakyReLU(0.1))    
    model.add(MaxPooling2D(pool_size=(2,2), padding='same'))
    model.add(Dropout(0.3)) 

    model.add(Conv2D(filters=256, padding='same', kernel_size=(3,3), kernel_initializer=initializer))  
    model.add(LeakyReLU(0.1))
    model.add(Conv2D(filters=256, padding='same', kernel_size=(3,3), kernel_initializer=initializer))  
    model.add(LeakyReLU(0.1))
    model.add(MaxPooling2D(pool_size=(2,2), padding='same')) 
    model.add(Dropout(0.3)) 

    model.add(Flatten())
    model.add(Dense(256, kernel_initializer=initializer))                
    model.add(LeakyReLU(0.1))
    model.add(Dropout(0.5))    
    model.add(Dense(NUM_CLASSES, kernel_initializer=initializer))             
    
    model.add(Activation("softmax"))
    return model
        
In [52]:
s = reset_tf_session()
model = make_model()
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 32, 32, 64)        1792      
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 32, 32, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 32, 32, 64)        36928     
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 32, 32, 64)        0         
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 16, 64)        0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 16, 16, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 16, 16, 128)       73856     
_________________________________________________________________
leaky_re_lu_3 (LeakyReLU)    (None, 16, 16, 128)       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 16, 16, 128)       147584    
_________________________________________________________________
leaky_re_lu_4 (LeakyReLU)    (None, 16, 16, 128)       0         
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 8, 8, 128)         0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 8, 8, 128)         0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 8, 8, 256)         295168    
_________________________________________________________________
leaky_re_lu_5 (LeakyReLU)    (None, 8, 8, 256)         0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 8, 8, 256)         590080    
_________________________________________________________________
leaky_re_lu_6 (LeakyReLU)    (None, 8, 8, 256)         0         
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 4, 4, 256)         0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 4, 4, 256)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 4096)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 256)               1048832   
_________________________________________________________________
leaky_re_lu_7 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 10)                2570      
_________________________________________________________________
activation_1 (Activation)    (None, 10)                0         
=================================================================
Total params: 2,196,810
Trainable params: 2,196,810
Non-trainable params: 0
_________________________________________________________________

Задание 2.2 (2 балла). Реализуйте колбэк, который сохраняет модель в .hdf5 файл и печатает имя файла, в который была сохранена модель. Используйте функцию model_save. Строка с именем файла имеет вид <name>_{0:02d}.hdf5, отформатируйте ее так, чтобы в имени строки фигурировал номер эпохи.

In [0]:
class ModelSaveCallback(keras.callbacks.Callback):

    def __init__(self, file_name):
        super(ModelSaveCallback, self).__init__()
        self.file_name = file_name

    def on_epoch_end(self, epoch, logs=None):
        filename = self.file_name.format(epoch)
        keras.models.save_model(self.model, filename)
        print(f"Model save into {filename}")

Задание 2.3 (1 балл). Реализуйте функцию, которая с помощью load_model будет загружать модель из файла.

In [0]:
def load_from_file(model_filename, last_epoch):
    return keras.models.load_model(model_filename.format(last_epoch))
In [55]:
INIT_LR = 5e-3
BATCH_SIZE = 32
EPOCHS = 20

model_filename = 'weights_{0:02d}.hdf5'

s = reset_tf_session()

model = make_model()

model.compile(
    loss='categorical_crossentropy',
    optimizer=keras.optimizers.adamax(lr=INIT_LR),
    metrics=['accuracy']
)

def lr_scheduler(epoch):
    return INIT_LR * 0.9 ** epoch 

# в случае, если обучение было прервано, можно загрузить модель из файла, 
# соответствующего последней эпохе, за которую есть сохраненные веса
# model = load_from_file(model_filename, 4)
  
history = model.fit(
    x_train, y_train,
    batch_size=BATCH_SIZE,
    epochs=EPOCHS,
    callbacks=[keras.callbacks.LearningRateScheduler(lr_scheduler), 
               # не забудьте передать сюда ModelSaveCallback
               ModelSaveCallback(model_filename)
              ],
    validation_data=(x_test, y_test),
    shuffle=True,
    verbose=1,
    initial_epoch=0    
)
  
Train on 50000 samples, validate on 10000 samples
Epoch 1/20
50000/50000 [==============================] - 57s 1ms/step - loss: 1.4968 - acc: 0.4547 - val_loss: 1.0953 - val_acc: 0.6173
Model save into weights_00.hdf5
Epoch 2/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.9800 - acc: 0.6594 - val_loss: 0.8715 - val_acc: 0.7007
Model save into weights_01.hdf5
Epoch 3/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.8169 - acc: 0.7202 - val_loss: 0.7571 - val_acc: 0.7471
Model save into weights_02.hdf5
Epoch 4/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.7011 - acc: 0.7598 - val_loss: 0.6584 - val_acc: 0.7822
Model save into weights_03.hdf5
Epoch 5/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.6289 - acc: 0.7873 - val_loss: 0.6344 - val_acc: 0.7899
Model save into weights_04.hdf5
Epoch 6/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.5585 - acc: 0.8105 - val_loss: 0.5489 - val_acc: 0.8192
Model save into weights_05.hdf5
Epoch 7/20
50000/50000 [==============================] - 55s 1ms/step - loss: 0.5005 - acc: 0.8300 - val_loss: 0.5546 - val_acc: 0.8330
Model save into weights_06.hdf5
Epoch 8/20
50000/50000 [==============================] - 55s 1ms/step - loss: 0.4543 - acc: 0.8452 - val_loss: 0.5105 - val_acc: 0.8365
Model save into weights_07.hdf5
Epoch 9/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.4050 - acc: 0.8594 - val_loss: 0.4810 - val_acc: 0.8463
Model save into weights_08.hdf5
Epoch 10/20
50000/50000 [==============================] - 55s 1ms/step - loss: 0.3734 - acc: 0.8702 - val_loss: 0.4687 - val_acc: 0.8487
Model save into weights_09.hdf5
Epoch 11/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.3275 - acc: 0.8862 - val_loss: 0.5229 - val_acc: 0.8558
Model save into weights_10.hdf5
Epoch 12/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.2979 - acc: 0.8981 - val_loss: 0.4711 - val_acc: 0.8523
Model save into weights_11.hdf5
Epoch 13/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.2692 - acc: 0.9060 - val_loss: 0.5421 - val_acc: 0.8570
Model save into weights_12.hdf5
Epoch 14/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.2413 - acc: 0.9161 - val_loss: 0.5097 - val_acc: 0.8639
Model save into weights_13.hdf5
Epoch 15/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.2201 - acc: 0.9231 - val_loss: 0.4797 - val_acc: 0.8650
Model save into weights_14.hdf5
Epoch 16/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.2071 - acc: 0.9279 - val_loss: 0.5495 - val_acc: 0.8652
Model save into weights_15.hdf5
Epoch 17/20
50000/50000 [==============================] - 55s 1ms/step - loss: 0.1866 - acc: 0.9337 - val_loss: 0.5316 - val_acc: 0.8657
Model save into weights_16.hdf5
Epoch 18/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.1760 - acc: 0.9383 - val_loss: 0.6182 - val_acc: 0.8702
Model save into weights_17.hdf5
Epoch 19/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.1638 - acc: 0.9425 - val_loss: 0.5146 - val_acc: 0.8751
Model save into weights_18.hdf5
Epoch 20/20
50000/50000 [==============================] - 56s 1ms/step - loss: 0.1494 - acc: 0.9470 - val_loss: 0.5648 - val_acc: 0.8737
Model save into weights_19.hdf5

Необходимый результат был достигнут на 11 эпохе, далее модель продолжала обучаться и улучшать качество.

По данным экспериментов, предшествующих получению данного результата, хорошо себя показала группа слоев из двух Conv2D с LeakyReLU после каждого из них с последующим MaxPooling2D и Dropout. Две таких группы было мало для получения хорошего качества, а четыре - уже много. Наибольшее влияние производил параметр количества фильтров в слое Conv2D.