Глибоке навчання для новачків: тонке налаштування нейронної мережі

Введення
Представляємо третю (і останню) статтю у серії, задумана, щоб допомогти швидко розібратися в технології глибокого навчання; ми будемо рухатися від базових принципів до нетривіальних особливостей з метою отримати гідну продуктивність на двох наборах даних: MNIST (класифікація рукописних цифр) і CIFAR-10 (класифікація невеликих зображень з десяти класів: літак, автомобіль, птиця, кішка, олень, собака, жаба, кінь, корабель і вантажівка).


Минулого разу ми розглянули модель сверточной нейронної мережі і показали, як за участю простого, але ефективного методу регуляризації під назвою dropout можна швидко досягти точності 78.6%, використовуючи фреймворк для побудови мереж глибокого навчання Keras.

Тепер ви володієте базовими навичками, необхідними для застосування глибокого навчання до більшості цікавих завдань (виняток становить завдання обробки нелінійних часових рядів, розгляд якої виходить за рамки цього керівництва і для вирішення якої зазвичай предпочительней рекуррентые нейронні мережі (RNN). Завершальна частина цього посібника буде містити те, що дуже важливо, але часто упускається у подібних статтях — прийоми і хитрощі тонкої настройки моделі, щоб навчити узагальнювати її краще, ніж та базова модель, з якої ви почали.

Ця частина керівництва передбачає знайомство з першою і другий статтями циклу.

Налаштування гиперпараметров і базова модель
Звичайно процес розробки нейронної мережі починається з розробки якої-небудь простої мережі, або безпосередньо застосовуючи ті архітектури, які вже успішно застосовувалися для розв'язання подібних задач, або використовуючи ті гиперпараметры, які раніше вже давали непогані результати. В кінцевому підсумку, сподіваємося, ми досягнемо такого рівня продуктивності, який послужить гарною відправною точкою, після якої ми можемо спробувати поізменять всі зафіксовані параметри і отримати з мережі максимальну продуктивність. Цей процес звичайно називають налаштуванням гиперпараметров, тому що він включає зміну компонентів мережі, які повинні бути встановлені до початку навчання.

Хоча описаний тут метод може дати більш відчутні переваги на CIFAR-10, із-за відносної складності швидкого створення прототипу на ньому в відсутність графічного процесора ми зосередимося на поліпшення його продуктивності на MNIST. Звичайно, якщо засоби дозволяють, я закликаю вас випробувати подібні методи на CIFAR і своїми очима побачити, наскільки вони виграють у порівнянні зі стандартним підходом CNN.

Відправною крапкою для нас буде служити вихідна CNN, представлена нижче. Якщо якісь фрагменти коду здаються вам незрозумілими, пропоную ознайомитися з попередніми двома частинами цього циклу, де описано всі базові принципи.

Код базової моделі
from keras.datasets import mnist # підпрограм for fetching the MNIST dataset
from keras.models import Model # basic class for specifying and training a neural network
from keras.layers import Input, Dense, Flatten, Convolution2D, MaxPooling2D, Dropout
from keras.utils import np_utils # utilities for one hot encoding of ground truth values

batch_size = 128 # in each iteration, we consider 128 training examples at once
num_epochs = 12 # we iterate twelve times over the entire training set
kernel_size = 3 # we will use 3x3 kernels throughout
pool_size = 2 # we will use 2x2 pooling throughout
conv_depth = 32 # use 32 kernels in both convolutional layers
drop_prob_1 = 0.25 # dropout after pooling with probability 0.25
drop_prob_2 = 0.5 # dropout in the FC layer with probability 0.5
hidden_size = 128 # there will be 128 neurons in both hidden layers

num_train = 60000 # there are 60000 training examples in MNIST
num_test = 10000 # there are 10000 test examples in MNIST

height, width, depth = 28, 28, 1 # MNIST images are 28x28 and greyscale
num_classes = 10 # there are 10 classes (1 per digit)

(X_train, y_train), (X_test, y_test) = mnist.load_data() # fetch MNIST data

X_train = X_train.reshape(X_train.shape[0], depth, height, width)
X_test = X_test.reshape(X_test.shape[0], depth, height, width)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255 # Normalise data to [0, 1] range
X_test /= 255 # Normalise data to [0, 1] range

Y_train = np_utils.to_categorical(y_train, num_classes) # One-hot encode the labels
Y_test = np_utils.to_categorical(y_test, num_classes) # One-hot encode the labels

inp = Input(shape=(depth, height, width) # N. B. Keras expects channel dimension first
# Conv [32] -> Conv [32] -> Pool (with dropout on the pooling layer)
conv_1 = Convolution2D(conv_depth, kernel_size, kernel_size, border_mode='same', activation='relu')(inp)
conv_2 = Convolution2D(conv_depth, kernel_size, kernel_size, border_mode='same', activation='relu')(conv_1)
pool_1 = MaxPooling2D(pool_size=(pool_size, pool_size))(conv_2)
drop_1 = Dropout(drop_prob_1)(pool_1)
flat = Flatten()(drop_1)
hidden = Dense(hidden_size, activation='relu')(flat) # Hidden ReLU layer
drop = Dropout(drop_prob_2)(hidden)
out = Dense(num_classes, activation='softmax')(drop) # Output softmax layer

model = Model(input=inp, output=out) # To define a model, just specify its input and output layers

model.compile(loss='categorical_crossentropy', # using the cross-entropy loss function
optimizer='adam', # using the Adam optimiser
metrics=['accuracy']) # reporting the accuracy

model.fit(X_train, Y_train, # Train the model using the training set...
batch_size=batch_size, nb_epoch=num_epochs,
verbose=1, validation_split=0.1) # ...holding out 10% of the data for validation
model.evaluate(X_test, Y_test, verbose=1) # Evaluate the trained model on the test set!


Лістинг навчання
Train on 54000 samples, validate on 6000 samples
Epoch 1/12
54000/54000 [==============================] - 4s - loss: 0.3010 - acc: 0.9073 - val_loss: 0.0612 - val_acc: 0.9825
Epoch 2/12
54000/54000 [==============================] - 4s - loss: 0.1010 - acc: 0.9698 - val_loss: 0.0400 - val_acc: 0.9893
Epoch 3/12
54000/54000 [==============================] - 4s - loss: 0.0753 - acc: 0.9775 - val_loss: 0.0376 - val_acc: 0.9903
Epoch 4/12
54000/54000 [==============================] - 4s - loss: 0.0629 - acc: 0.9809 - val_loss: 0.0321 - val_acc: 0.9913
Epoch 5/12
54000/54000 [==============================] - 4s - loss: 0.0520 - acc: 0.9837 - val_loss: 0.0346 - val_acc: 0.9902
Epoch 6/12
54000/54000 [==============================] - 4s - loss: 0.0466 - acc: 0.9850 - val_loss: 0.0361 - val_acc: 0.9912
Epoch 7/12
54000/54000 [==============================] - 4s - loss: 0.0405 - acc: 0.9871 - val_loss: 0.0330 - val_acc: 0.9917
Epoch 8/12
54000/54000 [==============================] - 4s - loss: 0.0386 - acc: 0.9879 - val_loss: 0.0326 - val_acc: 0.9908
Epoch 9/12
54000/54000 [==============================] - 4s - loss: 0.0349 - acc: 0.9894 - val_loss: 0.0369 - val_acc: 0.9908
Epoch 10/12
54000/54000 [==============================] - 4s - loss: 0.0315 - acc: 0.9901 - val_loss: 0.0277 - val_acc: 0.9923
Epoch 11/12
54000/54000 [==============================] - 4s - loss: 0.0287 - acc: 0.9906 - val_loss: 0.0346 - val_acc: 0.9922
Epoch 12/12
54000/54000 [==============================] - 4s - loss: 0.0273 - acc: 0.9909 - val_loss: 0.0264 - val_acc: 0.9930
9888/10000 [============================>.] - ETA: 0s

[0.026324689089493085, 0.99119999999999997]


Як видно, наша модель досягає точності 99.12% на тестовій множині. Це трохи краще, ніж результати MLP, розглянутої в першій частині, але нам ще є куди рости!

В даному посібнику ми поділимося способами поліпшення таких «базових» нейронних мереж (не відступаючи від архітектури CNN), а потім оцінимо приріст продуктивності, який ми отримаємо.

L_2-регуляризация
В попередній статті ми розповідали, що однією з основних проблем машинного навчання є проблема перенавчання (overfitting), коли модель в гонитві за мінімізацією витрат на навчання втрачає здатність до узагальнення.


Як уже говорилося, існує простий спосіб тримати перенавчання під контролем — метод dropout.

Але є й інші регуляризаторы, які можна застосувати до нашої мережі. Можливо, самий популярний з них — L_2-регуляризация (також звана скороченням ваг, англ. weight decay), яка використовує більш прямий підхід до регуляризації, ніж dropout. Зазвичай першопричиною перенавчання є складність моделі (в сенсі кількості її параметрів), занадто висока для розв'язуваної задачі і наявного навчальної множини. У певному сенсі, завдання регуляризатора — знизити складність моделі, зберігши кількість її параметрів. L_2-регуляризация виконується за допомогою накладання штрафів (penalising) на ваги з найбільшими значеннями, мінімізуючи їх L_2-норму з використанням параметра λ — коефіцієнт регуляризації, що виражає перевагу мінімізації норми щодо мінімізації втрат на навчальній множині. Тобто, для кожного ваги ω ми додаємо до цільової функції \mathcal{L}(\vec{\hat{y}},\vec{y})доданок {\lambda\over 2} ||\vec{w}||^2 = {\lambda\over 2} \sum_{i=1}^Ww_i^2(множник ½ використовується для того, щоб градієнт цього доданку по параметру ω дорівнював λω, а не 2λω — для зручності застосування методу зворотного поширення помилки).

Зверніть увагу, що вкрай важливо правильно вибрати λ. Якщо коефіцієнт дуже малий, то ефект від регуляризації буде мізерний, якщо ж занадто великий — модель обнулить всі ваги. Тут ми візьмемо λ = 0.0001; щоб додати цей метод регуляризації в нашу модель, нам знадобиться ще один імпорт, після чого достатньо лише додати параметр
W_regularizer
до кожного шару, де ми хочемо застосовувати регуляризацию.

from keras.regularizers import l2 # L2-regularisation
# ...
l2_lambda = 0.0001
# ...
# This is how to add L2-regularisation to any Keras layer with weights (e.g. Convolution2D/Dense)
conv_1 = Convolution2D(conv_depth, kernel_size, kernel_size, border_mode='same', W_regularizer=l2(l2_lambda), activation='relu')(inp)

Ініціалізація мережі
Один з моментів, який ми випустили з уваги у попередній статті — принцип вибору початкових значень ваг для шарів, що складають модель. Очевидно, що це питання дуже важливе: установка всіх ваг в 0 буде серйозною перешкодою для навчання, так як ні один з ваг спочатку не буде активним. Присвоювати ваг значення з інтервалу ±1 — теж зазвичай не кращий варіант — насправді, іноді (в залежності від завдання і складності моделі) від правильної ініціалізації моделі може залежати, вона досягне найвищої продуктивності або взагалі не буде сходитися. Навіть якщо завдання не передбачає такої крайності, вдало вибраний спосіб ініціалізації ваг може значно впливати на здатність моделі до навчання, так як він встановлювати параметри моделі з урахуванням функції втрат.

Тут я наведу два найбільш цікавих методу.

Метод ініціалізації Завьера (Xavier) (іноді — метод Glorot'а). Основна ідея цього методу — спростити проходження сигналу через шар під час як прямого, так і зворотного поширення помилки для лінійної функції активації (цей метод також добре працює для сигмоидной функції, так як ділянка, де вона ненасыщена, також має лінійний характер). При обчисленні ваг цей метод спирається на імовірнісний розподіл (рівномірне або нормальний) з дисперсією, рівною \mathrm{Var}(W) = {2 \over{n_{in} + n_{out}}}, де n_{in}n_{out}— кількості нейронів в попередньому і подальшому шарах відповідно.

Метод ініціалізації Ге (He) — це варіація методу Завьера, що більше підходить функції активації ReLU, що компенсує той факт, що ця функція повертає нуль для половини області визначення. А саме, у цьому випадку \mathrm{Var}(W) = {2 \over{n_{in}}}

Щоб отримати необхідну дисперсію для ініціалізації Завьера, розглянемо, що відбувається з дисперсією вихідних значень лінійного нейрона (без складовою зсуву), припускаючи, що ваги і вхідні значення не корелюють і мають нульове матожидание:

\mathrm{Var}(\sum_{i=1}^{n_{in}}w_ix_i) = \sum_{i=1}^{n_{in}}\mathrm{Var}(w_ix_i) = \sum_{i=1}^{n_{in}}\mathrm{Var}(W)\mathrm{Var}(X) = n_{in}\mathrm{Var}(W)\mathrm{Var}(X)

З цього випливає, що, щоб зберегти дисперсію вхідних даних після проходження через шар, необхідно, щоб дисперсія була \mathrm{Var}(W) = {1 \over{n_{in}}}. Ми можемо застосувати цей аргумент при зворотному поширення помилки, щоб отримати \mathrm{Var}(W) = {1 \over{n_{out}}}. Так як зазвичай ми не можемо задовольнити обом цим вимогам, ми вибираємо дисперсію ваг як їх середнє: \mathrm{Var}(W) = {2 \over{n_{in} + n_{out}}}, що на практиці, як правило, працює чудово.

Два цих методу підійдуть для більшості прикладів, з якими ви зіткнетеся (хоча дослідження також заслуговує метод ортогональною ініціалізації (orthogonal initialization), особливо стосовно до рекуррентным мереж). Вказати спосіб ініціалізації для шару не складно: вам всього лише треба вказати параметр
init
, як описано нижче. Ми будемо використовувати рівномірну ініціалізацію Ге (
he_uniform
) для всіх верств ReLU і рівномірну ініціалізацію Завьера (
glorot_uniform
) для вихідного softmax шару (так як по суті він являє собою узагальнення логістичної функції на численні подібні дані).

# Add He initialisation to a layer
conv_1 = Convolution2D(conv_depth, kernel_size, kernel_size, border_mode='same', init='he_uniform', W_regularizer=l2(l2_lambda), activation='relu')(inp)
# Add Xavier initialisation to a layer
out = Dense(num_classes, init='glorot_uniform', W_regularizer=l2(l2_lambda), activation='softmax')(drop)

Батч-нормалізація (batch normalization)
Батч-нормалізація — метод прискорення глибокого навчання, запропонований Ioffe і Szegedy на початку 2015 року, вже процитований на arXiv 560 разів! Метод вирішує таку проблему, що перешкоджає ефективному навчання нейронних мереж: у міру поширення сигналу по мережі, навіть якщо ми нормалізували його на вході, пройшовши через внутрішні шари, він може сильно спотворитися як за матожиднию, так і по дисперсії (дане явище називається внутрішнім ковариационным зрушенням), що загрожує серйозними невідповідностями між градієнтами на різних рівнях. Тому нам доводиться використовувати більш сильні регуляризаторы, сповільнюючи тим самим темп навчання.

Батч-нормалізація -пропонує досить просте рішення цієї проблеми: нормалізувати вхідні дані таким чином, щоб отримати нульове матожидание і одиничну дисперсію. Нормалізація виконується перед входом в кожен шар. Це означає, що під час навчання ми нормалізуємо
batch_size
прикладів, а під час тестування ми нормалізуємо статистику, отриману на основі навчальної множини, так як побачити заздалегідь тестові дані ми не можемо. А саме, ми обчислюємо очікування і дисперсію для певного батча (пакета) \mathcal{B}=x_1, ..., x_mнаступним чином:

\mu_{\mathcal{B}}={1\over m}\sum_{i=1}^mx_i
\sigma^2_{\mathcal{B}}={1\over m}\sum_{i=1}^m(x_i - \mu_{\mathcal{B}})^2
З допомогою цих статистичних характеристик ми перетворимо функцію активації таким чином, щоб вона мала нульове матожидание і одиничну дисперсію на всьому батче:

\hat{x_i} = {x_i-\mu_{\mathcal{B}}\over\sqrt{\sigma_{\mathcal{B}}^2+\epsilon}}
де ε >0 — параметр, що захищає нас від ділення на 0 (у випадку, якщо середньоквадратичне відхилення батча дуже мало або навіть дорівнює нулю). Нарешті, щоб отримати остаточну функцію активації y, нам треба переконатися, що під час нормалізації ми не втратили здатності до узагальнення, і так як до вихідним даним ми застосували операції масштабування і зсуву, ми можемо дозволити довільні масштабування і панорамування нормалізованих значень, отримавши остаточну функцію активації:

y_i = \gamma\hat{x_i} + \beta
Де β і γ — параметри батч-нормалізації, яким можна навчити (їх можна оптимізувати методом градієнтного спуску на навчальних даних). Це узагальнення також означає, що батч-нормалізацію може бути корисно застосовувати безпосередньо до вхідних даних нейронної мережі.

Цей метод у застосуванні до глибоких згортковим мереж майже завжди успішно досягає своєї мети — прискорити навчання. Більш того, він може служити відмінним регуляризатором, дозволяючи не так обачно вибирати темп навчання, потужність L_2-регуляризатора і dropout (іноді необхідність у них зовсім відпадає). Регуляризация тут — наслідок того факту, що результат роботи мережі для певного прикладу більше не детерміновано (він залежить від усього батча, в рамках якого цей результат отримано), що спрощує узагальнення.

І нарешті, хоча автори методу рекомендують застосовувати батч-нормалізацію функції активації нейрона, останні дослідження показують, що якщо не допомогти, то, принаймні, так само вигідно використовувати її після активації, що ми і зробимо в рамках цього керівництва.

У Keras додати батч-нормалізацію до вашої мережі дуже просто: за неї відповідає шар
BatchNormalization
, якому ми передамо кілька параметрів, найважливіший з яких —
axis
(вздовж осі який даних будуть обчислювати статистичні характеристики). Зокрема, під час роботи зі згортковими шарами, нам краще нормалізувати вздовж окремих каналів, отже, вибираємо
axis=1
.

from keras.layers.normalization import BatchNormalization # batch normalisation
# ...
inp_norm = BatchNormalization(axis=1)(inp) # apply BN to the input (N. B. need to rename here)
# conv_1 = Convolution2D(...)(inp_norm)
conv_1 = BatchNormalization(axis=1)(conv_1) # apply BN to the first conv layer

Розширення навчальної множини (data augmentation)
У той час як описані вище методи стосувалися в основному тонкого налаштування самої моделі, буває корисно вивчити варіанти налаштування даних, особливо коли мова йде про задачах розпізнавання зображень.

Уявімо, що ми навчили нейронну мережу розпізнавати рукописні цифри, які були приблизно одного розміру і були акуратно вирівняні. Тепер уявімо, що станеться, якщо хтось дасть цій мережі на тестування злегка зсунуті цифри різного розміру і нахил — тоді її впевненість у правильному класі різко впаде. В ідеалі добре б вміти так навчати мережу, щоб вона залишалася стійкою до подібним спотворень, але наша модель може навчатися тільки на основі тих зразків, які ми їй надали, при тому що вона проводить в деякому роді статистичний аналіз навчальної множини і екстраполює його.

На щастя, для цієї проблеми існує рішення, просте, але ефективне, особливо на завданнях по розпізнаванню зображень: штучно розширте навчальні дані спотвореними версіями під час навчання! Це означає наступне: перед тим, як подати приклад на вхід моделі, ми застосуємо до нього всі трансформації, які вважатимемо за потрібне, а потім дозволимо мережі безпосередньо спостерігати, який ефект має застосування їх до даних і навчаючи її «добре поводитися» і на цих прикладах. Наприклад, ось кілька прикладів зрушених, масштабованих, деформованих, нахилених цифр з набору MNIST.



Keras надає чудовий інтерфейс для розширення навчальної множини — клас
ImageDataGenerator
. Ми ініціалізуємо клас, повідомляючи йому, які види трансформації ми хочемо застосовувати до зображень, а потім проганяємо навчальні дані через генератор, викликаючи метод
fit
, а потім метод
flow
, отримуючи безперервно розширюється ітератор з тим батчам, які ми поповнюємо. Є навіть спеціальний метод
model.fit_generator
, який проведе навчання нашої моделі, використовую цей ітератор, що істотно спрощує код. Існує невеликий недолік: так ми втрачаємо параметр
validation_split
, а значить, нам доведеться відокремлювати валидационное підмножину самим, але це займе всього чотири строчки коду.

Тут ми будемо використовувати випадкові горизонтальні і вертикальні зрушення.
ImageDataGenerator
також надає нам можливість виконувати випадкові повороти, масштабування, деформацію і дзеркальне відображення. Всі ці трансформації також варто спробувати, крім хіба що дзеркального відображення, так як в реальному житті ми навряд чи зустрінемо розгорнуті таким чином рукописні цифри.

from keras.preprocessing.image import ImageDataGenerator # data augmentation
# ... after model.compile(...)
# Explicitly split the training and validation sets
X_val = X_train[54000:]
Y_val = Y_train[54000:]
X_train = X_train[:54000]
Y_train = Y_train[:54000]

datagen = ImageDataGenerator(
width_shift_range=0.1, # randomly shift images horizontally (fraction of total width)
height_shift_range=0.1) # randomly shift images vertically (fraction of total height)
datagen.fit(X_train)

# fit the model on the batches generated by datagen.flow()---most parameters similar to model.fit
model.fit_generator(datagen.flow(X_train, Y_train,
batch_size=batch_size),
samples_per_epoch=X_train.shape[0],
nb_epoch=num_epochs,
validation_data=(X_val, Y_val),
verbose=1)

Ансамблі (ensembles)
Одна цікава особливість нейронних мереж, яку можна помітити, коли вони використовуються для розподілу даних на більш ніж два класи — це те, що при різних початкових умовах навчання їм легше дається розподіл по класам, в той час як інші приводять у замішання. На прикладі MNIST можна виявити, що окремо взята нейронна мережа чудово вміє відрізняти від трійки п'ятірок, але не вчиться правильно відокремлювати від одиниці сімок, в той час як з ділячи з іншою мережею навпаки.

З цим невідповідністю можна боротися за допомогою методу статистичних ансамблів — місце однією мережі побудуйте кілька її копій з різними початковими значеннями і обчисліть середній їх результат на одних і тих же вхідних даних. Тут ми будемо будувати три окремих моделі. Відмінності між ними можна легко представити у вигляді діаграми, побудованої також в Keras.

Базова мережа

Ансамбль

І знову Keras дозволяє здійснити задумане, додавши мінімальна кількість коду — обернем метод побудови складових частин моделі в цикл, об'єднуючи їх результати в останньому шарі
merge
.

from keras.layers import merge # for merging predictions in an ensemble
# ...
ens_models = 3 # we will train three separate models on the data
# ...
inp_norm = BatchNormalization(axis=1)(inp) # Apply BN to the input (N. B. need to rename here)

outs = [] # the list of ensemble outputs
for i in range(ens_models):
# conv_1 = Convolution2D(...)(inp_norm)
# ...
outs.append(Dense(num_classes, init='glorot_uniform', W_regularizer=l2(l2_lambda), activation='softmax')(drop)) # Output softmax layer

out = merge(outs, mode='ave') # average the predictions to obtain the final output

Рання зупинка (early stopping)
Опишу тут ще один метод в якості введення в більш широку область оптимізації гиперпараметров. Поки що ми використовували валидационное безліч даних виключно для контролю за ходом навчання, що, без сумніву, не раціонально (так як ці дані не використовуються в конструктивних цілях). Насправді ж валидационное безліч може служити базою для оцінки гиперпараметров мережі (таких, як глибина, кількість нейронів/ядер, параметри регуляризації і т. д.). Уявіть, що мережа проганяється з різними комбінаціями гиперпараметров, а потім рішення приймається на основі їх продуктивності на валидационном множині. Зверніть увагу, що ми не повинні знати нічого про тестовому безлічі до того як остаточно визначимося з гиперпараметрами, так як інакше ознаки тестової множини мимоволі увіллються в процес навчання. Цей принцип також відомий як золоте правило машинного навчання, і порушувалося у багатьох ранніх підходах.

Можливо, найпростіший спосіб використання валидационного безлічі — налаштування кількості "епох" (циклів) за допомогою процедури, відомої як рання зупинка — просто зупиніть процес навчання, якщо за заданий кількість епох (параметр patience) втрати не починають зменшуватися. Так як наш набір даних відносно невеликий і швидко насичується, ми встановимо patience рівним п'яти епох, а максимальна кількість епох збільшимо до 50 (навряд чи це число коли-небудь буде досягнуто).

Механізм ранньої зупинки реалізований в Keras допомогою класу функцій зворотного виклику
EarlyStopping
. Функції зворотного виклику викликаються після кожної епохи навчання за допомогою параметра
callbacks
, переданого методам
fit
або
fit_generator
. Як завжди, все дуже компактно: наша програма збільшується лише на один рядок коду.

from keras.callbacks import EarlyStopping
# ...
num_epochs = 50 # we iterate at most fifty times over the entire training set
# ...
# fit the model on the batches generated by datagen.flow()---most parameters similar to model.fit
model.fit_generator(datagen.flow(X_train, Y_train,
batch_size=batch_size),
samples_per_epoch=X_train.shape[0],
nb_epoch=num_epochs,
validation_data=(X_val, Y_val),
verbose=1,
callbacks=[EarlyStopping(monitor='val_loss', patience=5)]) # adding early stopping

Просто покажіть мені код
Після застосування шести вищеописаних прийомів оптимізації, код нашої нейронної мережі буде виглядати наступним чином.

Код
from keras.datasets import mnist # підпрограм for fetching the MNIST dataset
from keras.models import Model # basic class for specifying and training a neural network
from keras.layers import Input, Dense, Flatten, Convolution2D, MaxPooling2D, Dropout, merge
from keras.utils import np_utils # utilities for one hot encoding of ground truth values
from keras.regularizers import l2 # L2-regularisation
from keras.layers.normalization import BatchNormalization # batch normalisation
from keras.preprocessing.image import ImageDataGenerator # data augmentation
from keras.callbacks import EarlyStopping # early stopping

batch_size = 128 # in each iteration, we consider 128 training examples at once
num_epochs = 50 # we iterate at most fifty times over the entire training set
kernel_size = 3 # we will use 3x3 kernels throughout
pool_size = 2 # we will use 2x2 pooling throughout
conv_depth = 32 # use 32 kernels in both convolutional layers
drop_prob_1 = 0.25 # dropout after pooling with probability 0.25
drop_prob_2 = 0.5 # dropout in the FC layer with probability 0.5
hidden_size = 128 # there will be 128 neurons in both hidden layers
l2_lambda = 0.0001 # use 0.0001 as a L2-factor regularisation
ens_models = 3 # we will train three separate models on the data

num_train = 60000 # there are 60000 training examples in MNIST
num_test = 10000 # there are 10000 test examples in MNIST

height, width, depth = 28, 28, 1 # MNIST images are 28x28 and greyscale
num_classes = 10 # there are 10 classes (1 per digit)

(X_train, y_train), (X_test, y_test) = mnist.load_data() # fetch MNIST data

X_train = X_train.reshape(X_train.shape[0], depth, height, width)
X_test = X_test.reshape(X_test.shape[0], depth, height, width)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')

Y_train = np_utils.to_categorical(y_train, num_classes) # One-hot encode the labels
Y_test = np_utils.to_categorical(y_test, num_classes) # One-hot encode the labels

# Explicitly split the training and validation sets
X_val = X_train[54000:]
Y_val = Y_train[54000:]
X_train = X_train[:54000]
Y_train = Y_train[:54000]

inp = Input(shape=(depth, height, width) # N. B. Keras expects channel dimension first
inp_norm = BatchNormalization(axis=1)(inp) # Apply BN to the input (N. B. need to rename here)

outs = [] # the list of ensemble outputs
for i in range(ens_models):
# Conv [32] -> Conv [32] -> Pool (with dropout on the pooling layer), applying BN in between
conv_1 = Convolution2D(conv_depth, kernel_size, kernel_size, border_mode='same', init='he_uniform', W_regularizer=l2(l2_lambda), activation='relu')(inp_norm)
conv_1 = BatchNormalization(axis=1)(conv_1)
conv_2 = Convolution2D(conv_depth, kernel_size, kernel_size, border_mode='same', init='he_uniform', W_regularizer=l2(l2_lambda), activation='relu')(conv_1)
conv_2 = BatchNormalization(axis=1)(conv_2)
pool_1 = MaxPooling2D(pool_size=(pool_size, pool_size))(conv_2)
drop_1 = Dropout(drop_prob_1)(pool_1)
flat = Flatten()(drop_1)
hidden = Dense(hidden_size, init='he_uniform', W_regularizer=l2(l2_lambda), activation='relu')(flat) # Hidden ReLU layer
hidden = BatchNormalization(axis=1)(hidden)
drop = Dropout(drop_prob_2)(hidden)
outs.append(Dense(num_classes, init='glorot_uniform', W_regularizer=l2(l2_lambda), activation='softmax')(drop)) # Output softmax layer

out = merge(outs, mode='ave') # average the predictions to obtain the final output

model = Model(input=inp, output=out) # To define a model, just specify its input and output layers

model.compile(loss='categorical_crossentropy', # using the cross-entropy loss function
optimizer='adam', # using the Adam optimiser
metrics=['accuracy']) # reporting the accuracy

datagen = ImageDataGenerator(
width_shift_range=0.1, # randomly shift images horizontally (fraction of total width)
height_shift_range=0.1) # randomly shift images vertically (fraction of total height)
datagen.fit(X_train)

# fit the model on the batches generated by datagen.flow()---most parameters similar to model.fit
model.fit_generator(datagen.flow(X_train, Y_train,
batch_size=batch_size),
samples_per_epoch=X_train.shape[0],
nb_epoch=num_epochs,
validation_data=(X_val, Y_val),
verbose=1,
callbacks=[EarlyStopping(monitor='val_loss', patience=5)]) # adding early stopping

model.evaluate(X_test, Y_test, verbose=1) # Evaluate the trained model on the test set!


Лістинг навчання
Epoch 1/50
54000/54000 [==============================] - 30s - loss: 0.3487 - acc: 0.9031 - val_loss: 0.0579 - val_acc: 0.9863
Epoch 2/50
54000/54000 [==============================] - 30s - loss: 0.1441 - acc: 0.9634 - val_loss: 0.0424 - val_acc: 0.9890
Epoch 3/50
54000/54000 [==============================] - 30s - loss: 0.1126 - acc: 0.9716 - val_loss: 0.0405 - val_acc: 0.9887
Epoch 4/50
54000/54000 [==============================] - 30s - loss: 0.0929 - acc: 0.9757 - val_loss: 0.0390 - val_acc: 0.9890
Epoch 5/50
54000/54000 [==============================] - 30s - loss: 0.0829 - acc: 0.9788 - val_loss: 0.0329 - val_acc: 0.9920
Epoch 6/50
54000/54000 [==============================] - 30s - loss: 0.0760 - acc: 0.9807 - val_loss: 0.0315 - val_acc: 0.9917
Epoch 7/50
54000/54000 [==============================] - 30s - loss: 0.0740 - acc: 0.9824 - val_loss: 0.0310 - val_acc: 0.9917
Epoch 8/50
54000/54000 [==============================] - 30s - loss: 0.0679 - acc: 0.9826 - val_loss: 0.0297 - val_acc: 0.9927
Epoch 9/50
54000/54000 [==============================] - 30s - loss: 0.0663 - acc: 0.9834 - val_loss: 0.0300 - val_acc: 0.9908
Epoch 10/50
54000/54000 [==============================] - 30s - loss: 0.0658 - acc: 0.9833 - val_loss: 0.0281 - val_acc: 0.9923
Epoch 11/50
54000/54000 [==============================] - 30s - loss: 0.0600 - acc: 0.9844 - val_loss: 0.0272 - val_acc: 0.9930
Epoch 12/50
54000/54000 [==============================] - 30s - loss: 0.0563 - acc: 0.9857 - val_loss: 0.0250 - val_acc: 0.9923
Epoch 13/50
54000/54000 [==============================] - 30s - loss: 0.0530 - acc: 0.9862 - val_loss: 0.0266 - val_acc: 0.9925
Epoch 14/50
54000/54000 [==============================] - 31s - loss: 0.0517 - acc: 0.9865 - val_loss: 0.0263 - val_acc: 0.9923
Epoch 15/50
54000/54000 [==============================] - 30s - loss: 0.0510 - acc: 0.9867 - val_loss: 0.0261 - val_acc: 0.9940
Epoch 16/50
54000/54000 [==============================] - 30s - loss: 0.0501 - acc: 0.9871 - val_loss: 0.0238 - val_acc: 0.9937
Epoch 17/50
54000/54000 [==============================] - 30s - loss: 0.0495 - acc: 0.9870 - val_loss: 0.0246 - val_acc: 0.9923
Epoch 18/50
54000/54000 [==============================] - 31s - loss: 0.0463 - acc: 0.9877 - val_loss: 0.0271 - val_acc: 0.9933
Epoch 19/50
54000/54000 [==============================] - 30s - loss: 0.0472 - acc: 0.9877 - val_loss: 0.0239 - val_acc: 0.9935
Epoch 20/50
54000/54000 [==============================] - 30s - loss: 0.0446 - acc: 0.9885 - val_loss: 0.0226 - val_acc: 0.9942
Epoch 21/50
54000/54000 [==============================] - 30s - loss: 0.0435 - acc: 0.9890 - val_loss: 0.0218 - val_acc: 0.9947
Epoch 22/50
54000/54000 [==============================] - 30s - loss: 0.0432 - acc: 0.9889 - val_loss: 0.0244 - val_acc: 0.9928
Epoch 23/50
54000/54000 [==============================] - 30s - loss: 0.0419 - acc: 0.9893 - val_loss: 0.0245 - val_acc: 0.9943
Epoch 24/50
54000/54000 [==============================] - 30s - loss: 0.0423 - acc: 0.9890 - val_loss: 0.0231 - val_acc: 0.9933
Epoch 25/50
54000/54000 [==============================] - 30s - loss: 0.0400 - acc: 0.9894 - val_loss: 0.0213 - val_acc: 0.9938
Epoch 26/50
54000/54000 [==============================] - 30s - loss: 0.0384 - acc: 0.9899 - val_loss: 0.0226 - val_acc: 0.9943
Epoch 27/50
54000/54000 [==============================] - 30s - loss: 0.0398 - acc: 0.9899 - val_loss: 0.0217 - val_acc: 0.9945
Epoch 28/50
54000/54000 [==============================] - 30s - loss: 0.0383 - acc: 0.9902 - val_loss: 0.0223 - val_acc: 0.9940
Epoch 29/50
54000/54000 [==============================] - 31s - loss: 0.0382 - acc: 0.9898 - val_loss: 0.0229 - val_acc: 0.9942
Epoch 30/50
54000/54000 [==============================] - 31s - loss: 0.0379 - acc: 0.9900 - val_loss: 0.0225 - val_acc: 0.9950
Epoch 31/50
54000/54000 [==============================] - 30s - loss: 0.0359 - acc: 0.9906 - val_loss: 0.0228 - val_acc: 0.9943
10000/10000 [==============================] - 2s 





[0.017431972888592554, 0.99470000000000003]

Наша модель після оновлення досягає точності 99.47% на тестовому наборі даних, а це значний приріст порівняно з початковою продуктивністю 99.12%. Звичайно, для такого маленького і відносно простого набору даних, як MNIST, вигода не здається настільки значною. Застосувавши ті ж прийоми до задачі розпізнавання CIFAR-10, при наявності необхідних ресурсів, ви зможете отримати більш відчутну перевагу.

Пропоную вам далі попрацювати з цією моделлю: зокрема, спробуйте використовувати валидационные дані не тільки для прийому ранньої зупинки, але ще й для оцінки розмірності та кількості ядер, розмірів прихованих шарів, стратегій оптимізації, функцій активації, кількості мереж в ансамблі, та порівняйте результати з кращими з кращих (на момент написання цього поста краща модель досягала точності 99.79% на MNIST).

Висновок
У даній статті ми розглянули шість прийомів для тонкого налаштування нейронних мереж, описаних у попередніх постах:

L_2-регуляриация
Ініціалізація
Батч-нормалізація
Розширення навчальної множини
Метод ансамблів
Рання зупинка

І успішно застосували їх до глибокої сверточной мережі, побудованої в Keras, що дозволило досягти значного збільшення точності на MNIST і зайняло менше 90 рядків коду.

Це була завершальна стаття циклу. Прочитати попередні дві частини можна тут і тут.

Сподіваюся, отримані вами знання стануть тим стимулом, який у поєднанні з необхідними ресурсами дозволить вам стати наикрутейшими інженерами мереж глибокого навчання.

Спасибі!

О, а приходьте до нас працювати? :)wunderfund.io — молодий фонд, який займається високочастотної алготорговлей. Високочастотна торгівля — це безперервне змагання кращих програмістів і математиків всього світу. Приєднавшись до нас, ви станете частиною цієї захоплюючої сутички.

Ми пропонуємо цікаві і складні завдання з аналізу даних і low latency розробки для захоплених дослідників і програмістів. Гнучкий графік і ніякої бюрократії, швидко приймаються рішення і втілюються в життя.

Приєднуйтесь до нашої команди: wunderfund.io
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.