Keras и сверточные нейронные сети.
Задание выполнил(а): Подчезерцев Алексей
RANDOM_SEED = 42
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__)
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
В этом задании нужно будет исследовать, как выбор функции инициализации весов влияет на обучение CNN.
Продолжим работать с датасетом CIFAR-10.
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)
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 балла) Добавьте в архитектуру модели инициализацию весов для тех слоев, где она необходима.
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.05RandomUniform — веса генерируются равномерно из отрезка [-0.05, 0.05]glorot_normal — Xavier initializer из лекцийlecun_uniformДобавьте в список losses значения функции потерь для каждой функции инициализации, их можно достать из History
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'])
Задание 1.3 (1 балла). Постройте графики зависимости функций потерь от номера итерации, подпишите их. Прокомментируйте результат.
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 немного лучше.
В этом задании мы модифицируем нейросеть с семинара, чтобы она достигала большего значения accuracy и научимся сохранять веса модели в файл во время обучения. Можно использовать только те же слои, которые использовались на семинаре: Conv2D, MaxPooling2D, LeakyReLU, Dropout, Flatten, Dense.
Задание 2.1 (4 балла). Подберите архитектуру модели так, чтобы значение accuracy на тестовой выборке было не менее 85.
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
s = reset_tf_session()
model = make_model()
model.summary()
Задание 2.2 (2 балла). Реализуйте колбэк, который сохраняет модель в .hdf5 файл и печатает имя файла, в который была сохранена модель. Используйте функцию model_save. Строка с именем файла имеет вид <name>_{0:02d}.hdf5, отформатируйте ее так, чтобы в имени строки фигурировал номер эпохи.
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 будет загружать модель из файла.
def load_from_file(model_filename, last_epoch):
return keras.models.load_model(model_filename.format(last_epoch))
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
)
Необходимый результат был достигнут на 11 эпохе, далее модель продолжала обучаться и улучшать качество.
По данным экспериментов, предшествующих получению данного результата, хорошо себя показала группа слоев из двух Conv2D с LeakyReLU после каждого из них с последующим MaxPooling2D и Dropout. Две таких группы было мало для получения хорошего качества, а четыре - уже много. Наибольшее влияние производил параметр количества фильтров в слое Conv2D.