Это руководство знакомит вас с глубоким обучением на Python: научитесь предварительно обрабатывать данные, моделировать, оценивать и оптимизировать нейронные сети на основе известных данных «MNIST».

MNIST («Модифицированный Национальный институт стандартов и технологий») с момента его выпуска в 1999 году, этот классический набор данных рукописных изображений служил основой для алгоритмов классификации тестов. По мере появления новых методов машинного обучения MNIST остается надежным ресурсом как для исследователей, так и для учащихся.

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

Более подробную информацию о наборе данных, включая опробованные на нем алгоритмы и уровни их успеха, можно найти на http://yann.lecun.com/exdb/mnist/index.html. Набор данных доступен по лицензии Creative Commons Attribution-Share Alike 3.0.

В качестве альтернативы вы также можете загрузить набор данных с сайта kaggle https://www.kaggle.com/c/digit-recognizer. Я получил общедоступную оценку 0,99628, используя методику ниже (10% лучших в kaggle).

Глубокое обучение

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

Алгоритмы глубокого обучения состоят из связанных слоев.

- Первый слой называется входным.
- Последний слой называется выходным.
- Все промежуточные слои называются скрытыми слоями.

Слово «глубокий» означает, что сеть объединяет нейроны более чем в два слоя.

Распознаватель цифр

Это руководство познакомит вас с:

  • Набор данных Fashion MNIST, содержащий 70 000 изображений в оттенках серого в 10 категориях. На изображениях показаны отдельные предметы одежды в низком разрешении (28 на 28 пикселей).
  • Как использовать Python и его библиотеки для понимания, изучения и визуализации ваших данных
  • Как предварительно обработать ваши данные
  • Как создать многослойный перцептрон для задач классификации с помощью последовательной модели Кераса
  • Как скомпилировать и подогнать данные к этим моделям
  • Как использовать вашу модель для прогнозирования целевых значений и как проверить модели, которые вы построили

Загрузка библиотек

import pandas as pd
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.optimizers import Adam,RMSprop
from keras.layers.normalization import BatchNormalization
from keras.utils import np_utils,plot_model
from keras.preprocessing.image import ImageDataGenerator
from IPython.display import Image
from keras.layers import Conv2D, MaxPooling2D
from keras.callbacks import ReduceLROnPlateau,ModelCheckpoint,EarlyStopping
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix,accuracy_score
from keras.datasets import mnist

Здесь 42000 изображений используются для обучения сети, а 28000 изображений используются в качестве заключительного теста, для которого вы можете предсказать класс этих 28000 изображений. Чтобы оценить, насколько точно сеть научилась классифицировать эти изображения, вам также понадобится набор для проверки. Вы можете получить доступ к Fashion MNIST прямо из TensorFlow. Импортируйте и загрузите данные Fashion MNIST, как показано ниже:

train_mnist = pd.read_csv(‘../input/digit-recognizer/train.csv’)
test_mnist = pd.read_csv(‘../input/digit-recognizer/test.csv’)

Глядя на форму обучающих данных ниже, она содержит 42000 изображений, каждое из которых представлено как 28 x 28 пикселей.

print(train_mnist.shape)
print(train_mnist.head(1))
(42000, 785)
   label  pixel0  pixel1  pixel2  pixel3  pixel4  pixel5  pixel6  pixel7  \
0      1       0       0       0       0       0       0       0       0   

   pixel8    ...     pixel774  pixel775  pixel776  pixel777  pixel778  \
0       0    ...            0         0         0         0         0   

   pixel779  pixel780  pixel781  pixel782  pixel783  
0         0         0         0         0         0  

[1 rows x 785 columns]

Наконец, присвоение данных и целевых меток переменным X и y.

X = train_mnist.drop(“label”,axis=1)
y = train_mnist[‘label’]
X.shape
(42000, 784)

Каждая метка представляет собой целое число от 0 до 9:

np.unique(y)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int64)

Предварительная обработка данных

Перед обучением сети данные необходимо предварительно обработать:

plot_image = X.values.reshape(X.shape[0], 28, 28)
plt.figure()
plt.imshow(plot_image[3])
plt.colorbar()
plt.grid(False)
plt.show()

Масштабируйте эти значения в диапазоне от 0 до 1, прежде чем передавать их в модель нейронной сети. Для этого разделите значения на 255. Важно, чтобы обучающий набор и набор тестирования были предварительно обработаны одинаково:

X = X / 255.0
test_mnist = test_mnist / 255.0

А пока импортируйте train_test_split из sklearn.model_selection в вышеуказанный раздел библиотеки.

X = pd.DataFrame(X)
y = pd.DataFrame(y)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.15,random_state=0)

Чтобы передать изображения в сверточную нейронную сеть, вы должны преобразовать фрейм данных в четыре измерения. Это можно сделать с помощью метода numpy reshape. В конце концов, Керасу нужно дополнительное измерение для каналов. Если бы это были изображения RGB, было бы 3 канала, но поскольку MNIST имеет шкалу серого, он использует только один.

X_train = X_train.values.reshape(X_train.shape[0],28,28,1)
X_test = X_test.values.reshape(X_test.shape[0],28,28,1)
test_mnist = test_mnist.values.reshape(test_mnist.shape[0],28,28,1)

Обучите модель

Keras предлагает два разных способа определения сети. Здесь я определю последовательный API, в котором вы просто добавляете по одному слою за раз, начиная с ввода. Проблема с последовательным API заключается в том, что он не позволяет моделям иметь несколько входов или выходов, которые необходимы для некоторых проблем. Чтобы создать сверточную нейронную сеть, вам нужно всего лишь создать объект Sequential и использовать функцию добавления для добавления слоев.
Первый слой - это слой Conv2D. Это слои свертки, которые будут работать с нашими входными изображениями, которые рассматриваются как 2-мерные матрицы.

64 в первом слое - это количество узлов в слое. Это число может быть увеличено или уменьшено в зависимости от размера набора данных. В данном случае 64 работают хорошо, так что я пока останусь на этом.

  • Размер ядра - это размер матрицы фильтра для нашей свертки. Таким образом, размер ядра 3 означает, что у вас будет матрица фильтров 3x3.
  • Активация - это функция активации для слоя. Функция активации, которую я буду использовать для первого уровня, - это ReLU, или выпрямленная линейная активация. Доказано, что эта функция активации хорошо работает в нейронных сетях.
  • Первый слой также принимает форму ввода. Это форма каждого входного изображения, 28,28,1, как было показано ранее, где 1 означает, что изображения в оттенках серого.

Добавление пикселей к краю изображения называется заполнением.

В Keras это указывается с помощью аргумента «padding» на уровне Conv2D, который имеет значение по умолчанию «valid» (без заполнения). Это означает, что фильтр применяется только к допустимым путям ввода. Значение «padding» для «same» вычисляет и добавляет отступы, необходимые для входного изображения (или карты функций), чтобы гарантировать, что выходные данные имеют ту же форму, что и входные.

Maxpooling: для каждой из областей, представленных фильтром, мы возьмем max этой области и создадим новую выходную матрицу, где каждый элемент - это максимум региона в исходном вводе.

Чтобы повысить стабильность нейронной сети, пакетная нормализация нормализует выходные данные предыдущего слоя активации путем вычитания среднего значения пакета и деления на стандартное отклонение пакета.

Вы можете добавить больше слоев в сеть, чтобы увидеть, как работает модель. Ниже добавлены три слоя. Между слоями Conv2D и плотным слоем есть слой «Flatten».

Flatten служит связующим звеном между сверточным и плотным слоями.
«Плотный» - это тип слоя, который мы будем использовать для нашего выходного слоя. Плотный - это стандартный тип слоя, который во многих случаях используется для нейронных сетей. У вас должно быть 10 узлов на выходном слое, по одному для каждого возможного результата (0–9).

Термин «выпадение» относится к выпадающим единицам (скрытым и видимым) в нейронной сети. - Dropout: простой способ предотвратить переоснащение нейронных сетей.

model = Sequential()
model.add(Conv2D(64, (3, 3), input_shape=(28,28,1),padding=”SAME”))
model.add(BatchNormalization(axis=-1))
model.add(Activation(‘relu’))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(128,(3, 3),padding=”SAME”))
model.add(BatchNormalization(axis=-1))
model.add(Activation(‘relu’))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(192,(3, 3),padding=”SAME”))
model.add(Activation(‘relu’))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten())
# Fully connected layer
model.add(Dense(256))
model.add(BatchNormalization())
model.add(Activation(‘relu’))
model.add(Dropout(0.3))
model.add(Dense(10))
model.add(Activation(‘softmax’))
model.summary()

Глядя на краткое изложение модели ниже:

output_channels * (input_channels * window_size + 1) == number_parameters(param)
64 * (1 * (3*3) + 1)  == 640
True

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

В первом слое у нас есть 64 узла с формой каждого входного слоя 28x28. После использования «max_pooling» форма уменьшается до 14x14, поскольку мы использовали maxpooling 2x2.

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 28, 28, 64)        640       
_________________________________________________________________
batch_normalization_1 (Batch (None, 28, 28, 64)        256       
_________________________________________________________________
activation_1 (Activation)    (None, 28, 28, 64)        0         
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 14, 14, 128)       73856     
_________________________________________________________________
batch_normalization_2 (Batch (None, 14, 14, 128)       512       
_________________________________________________________________
activation_2 (Activation)    (None, 14, 14, 128)       0         
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 7, 7, 192)         221376    
_________________________________________________________________
activation_3 (Activation)    (None, 7, 7, 192)         0         
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 3, 3, 192)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1728)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 256)               442624    
_________________________________________________________________
batch_normalization_3 (Batch (None, 256)               1024      
_________________________________________________________________
activation_4 (Activation)    (None, 256)               0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 10)                2570      
_________________________________________________________________
activation_5 (Activation)    (None, 10)                0         
=================================================================
Total params: 742,858
Trainable params: 741,962
Non-trainable params: 896

Вы также можете определить параметр обратных вызовов с помощью learning_rate_reduction, best_model и early_stopping. Перед началом тренировочного процесса я определю несколько гиперпараметров ниже.

  • EarlyStopping: используется для остановки тренировки, когда отслеживаемое количество перестало улучшаться.
  • монитор: количество, которое необходимо контролировать. В данном случае это val_loss. Тренировка остановится автоматически, если потери перестанут улучшаться.
  • Min_delta: минимальное изменение отслеживаемого количества, которое квалифицируется как улучшение, то есть абсолютное изменение меньше min_delta не будет считаться улучшением.
  • терпение: количество эпох, в которых наблюдаемое количество не улучшилось, после чего обучение будет остановлено.
  • restore_best_weights: восстанавливать ли веса модели из эпохи с лучшим значением наблюдаемой величины. Если False, используются веса модели, полученные на последнем этапе обучения.

На обучение моделей глубокого обучения могут уйти часы, дни или даже недели. Если запуск неожиданно остановится, вы можете потерять много работы. Это подход, при котором снимок состояния системы делается в случае сбоя системы. Если есть проблема, не все потеряно. Контрольную точку можно использовать напрямую или в качестве отправной точки для нового прогона, продолжая с того места, где он остановился.
При обучении моделей глубокого обучения контрольной точкой являются веса модели. Эти веса можно использовать для прогнозирования как есть или использовать в качестве основы для непрерывного обучения.

Здесь model_weights.h5 - это файл веса.

Learning_rate_reduction: Модели часто выигрывают от снижения скорости обучения в 2–10 раз, когда обучение останавливается. Этот обратный вызов отслеживает количество, и если не наблюдается улучшения для «терпеливого» количества эпох, скорость обучения снижается.

  • Фактор: коэффициент, на который будет снижена скорость обучения. new_lr = lr * коэффициент
  • min_lr: нижняя граница скорости обучения.
early_stopping = EarlyStopping(monitor=’val_loss’, min_delta=1e-10, patience=10,restore_best_weights=True)
best_model = ModelCheckpoint(‘model_weights.h5’, monitor=’val_acc’, verbose=2, save_best_only=True, mode=’max’)
learning_rate_reduction = ReduceLROnPlateau(monitor=’val_acc’,patience=3, verbose=2,factor=0.5,min_lr=0.00001)

Составление модели

Далее вам необходимо скомпилировать модель. Компиляция модели принимает три параметра: оптимизатор, потери и метрики.

  • Оптимизатор контролирует скорость обучения. Мы будем использовать «адам» в качестве оптимизатора. Адам, как правило, хороший оптимизатор для многих случаев. Оптимизатор адама регулирует скорость обучения на протяжении всего обучения.
  • Скорость обучения определяет, насколько быстро рассчитываются оптимальные веса для модели. Меньшая скорость обучения может привести к более точным весам (до определенного момента), но время, необходимое для вычисления весов, будет больше.
  • Я буду использовать sparse_categorical_crossentropy для функции потерь. Это наиболее распространенный выбор для классификации. Более низкий балл означает, что модель работает лучше.

Когда у нас есть проблема классификации с одной меткой и несколькими классами, метки являются взаимоисключающими для всех данных, что означает, что каждая запись данных может принадлежать только одному классу. Затем мы можем представить y_true с помощью однократных встраиваний.

Например, y_true с 3 образцами, каждый из которых принадлежит классам 2, 0 и 2.

y_true = [[0, 0, 1],
[1, 0, 0],
[0, 0, 1]]

Станет:

y_true_one_hot = [2, 0, 2]

model.compile(optimizer=’adam’,
loss=’sparse_categorical_crossentropy’,
metrics=[‘accuracy’])

ImageDataGenerator и расширение данных

Увеличение данных используется для расширения набора обучающих данных с целью повышения производительности и способности модели к обобщению. Расширение данных изображений поддерживается в библиотеке глубокого обучения Keras через класс ImageDataGenerator.

Класс Keras ImageDataGenerator на самом деле работает следующим образом:

  • Принятие пакета изображений, используемых для обучения
  • Взять этот пакет и применить серию случайных преобразований к каждому изображению в пакете (включая случайное вращение, изменение размера, сдвиг и т. Д.).
  • Замена исходной партии новой, случайно преобразованной партией.
  • Обучение CNN на этом случайно преобразованном пакете (т.е. сами исходные данные не используются для обучения)

ImageDataGenerator принимает исходные данные, случайным образом преобразует их и возвращает только новые преобразованные данные.

Увеличение данных включает в себя широкий спектр методов, используемых для создания «новых» обучающих выборок из исходных, путем применения случайных колебаний и возмущений (но в то же время гарантируя, что метки классов данных не изменяются).

Наша цель при применении увеличения данных - повысить обобщаемость модели.

gen = ImageDataGenerator(
 featurewise_center=False, 
 samplewise_center=False, 
 featurewise_std_normalization=False, 
 samplewise_std_normalization=False, 
 rotation_range=10, 
 zoom_range = 0.1,
 width_shift_range=0.1, 
 height_shift_range=0.1, 
 horizontal_flip=False,
 vertical_flip=False)
gen.fit(X_train)

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

Keras API поддерживает это, указывая аргумент «validation_data» для функции model.fit () при обучении модели, которая, в свою очередь, возвращает объект, описывающий производительность модели для выбранных потерь и показателей в каждую эпоху обучения.

h = model.fit_generator(
 gen.flow(X_train, y_train, batch_size=64),
 validation_data=(X_test, y_test),
 steps_per_epoch=len(X_train) // 64,
 epochs=50, verbose=1,
 callbacks=[learning_rate_reduction,best_model,early_stopping]
 )
Epoch 1/50
557/557 [==============================] - 23s 41ms/step - loss: 0.1681 - acc: 0.9500 - val_loss: 0.0710 - val_acc: 0.9792

Epoch 00001: val_acc improved from -inf to 0.97921, saving model to mnist_weights.h5
Epoch 2/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0719 - acc: 0.9778 - val_loss: 0.0591 - val_acc: 0.9811

Epoch 00002: val_acc improved from 0.97921 to 0.98111, saving model to mnist_weights.h5
Epoch 3/50
557/557 [==============================] - 18s 33ms/step - loss: 0.0584 - acc: 0.9823 - val_loss: 0.0860 - val_acc: 0.9748

Epoch 00003: val_acc did not improve from 0.98111
Epoch 4/50
557/557 [==============================] - 18s 33ms/step - loss: 0.0475 - acc: 0.9852 - val_loss: 0.0507 - val_acc: 0.9856

Epoch 00004: val_acc improved from 0.98111 to 0.98556, saving model to mnist_weights.h5
Epoch 5/50
557/557 [==============================] - 18s 33ms/step - loss: 0.0455 - acc: 0.9859 - val_loss: 0.0507 - val_acc: 0.9848

Epoch 00005: val_acc did not improve from 0.98556
Epoch 6/50
557/557 [==============================] - 18s 33ms/step - loss: 0.0390 - acc: 0.9871 - val_loss: 0.0534 - val_acc: 0.9859

Epoch 00006: val_acc improved from 0.98556 to 0.98587, saving model to mnist_weights.h5
Epoch 7/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0396 - acc: 0.9874 - val_loss: 0.0415 - val_acc: 0.9862

Epoch 00007: val_acc improved from 0.98587 to 0.98619, saving model to mnist_weights.h5
Epoch 8/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0374 - acc: 0.9888 - val_loss: 0.0563 - val_acc: 0.9824

Epoch 00008: val_acc did not improve from 0.98619
Epoch 9/50
557/557 [==============================] - 18s 33ms/step - loss: 0.0328 - acc: 0.9896 - val_loss: 0.0315 - val_acc: 0.9908

Epoch 00009: val_acc improved from 0.98619 to 0.99079, saving model to mnist_weights.h5
Epoch 10/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0350 - acc: 0.9886 - val_loss: 0.0267 - val_acc: 0.9916

Epoch 00010: val_acc improved from 0.99079 to 0.99159, saving model to mnist_weights.h5
Epoch 11/50
557/557 [==============================] - 19s 34ms/step - loss: 0.0295 - acc: 0.9903 - val_loss: 0.0271 - val_acc: 0.9933

Epoch 00011: val_acc improved from 0.99159 to 0.99333, saving model to mnist_weights.h5
Epoch 12/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0276 - acc: 0.9912 - val_loss: 0.0310 - val_acc: 0.9910

Epoch 00012: val_acc did not improve from 0.99333
Epoch 13/50
557/557 [==============================] - 18s 31ms/step - loss: 0.0276 - acc: 0.9909 - val_loss: 0.0309 - val_acc: 0.9908

Epoch 00013: val_acc did not improve from 0.99333
Epoch 14/50
557/557 [==============================] - 17s 31ms/step - loss: 0.0264 - acc: 0.9919 - val_loss: 0.0337 - val_acc: 0.9898

Epoch 00014: ReduceLROnPlateau reducing learning rate to 0.0005000000237487257.

Epoch 00014: val_acc did not improve from 0.99333
Epoch 15/50
557/557 [==============================] - 18s 31ms/step - loss: 0.0184 - acc: 0.9942 - val_loss: 0.0201 - val_acc: 0.9948

Epoch 00015: val_acc improved from 0.99333 to 0.99476, saving model to mnist_weights.h5
Epoch 16/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0180 - acc: 0.9944 - val_loss: 0.0213 - val_acc: 0.9935

Epoch 00016: val_acc did not improve from 0.99476
Epoch 17/50
557/557 [==============================] - 18s 32ms/step - loss: 0.0167 - acc: 0.9948 - val_loss: 0.0220 - val_acc: 0.9935

Epoch 00017: val_acc did not improve from 0.99476
Epoch 18/50
557/557 [==============================] - 17s 31ms/step - loss: 0.0169 - acc: 0.9946 - val_loss: 0.0203 - val_acc: 0.9940

Epoch 00018: ReduceLROnPlateau reducing learning rate to 0.0002500000118743628.

Epoch 00018: val_acc did not improve from 0.99476
Epoch 19/50
557/557 [==============================] - 17s 31ms/step - loss: 0.0130 - acc: 0.9958 - val_loss: 0.0160 - val_acc: 0.9951

Epoch 00019: val_acc improved from 0.99476 to 0.99508, saving model to mnist_weights.h5
Epoch 20/50
557/557 [==============================] - 17s 31ms/step - loss: 0.0128 - acc: 0.9962 - val_loss: 0.0182 - val_acc: 0.9949

Epoch 00020: val_acc did not improve from 0.99508
Epoch 21/50
557/557 [==============================] - 17s 31ms/step - loss: 0.0124 - acc: 0.9963 - val_loss: 0.0158 - val_acc: 0.9954

Epoch 00021: val_acc improved from 0.99508 to 0.99540, saving model to mnist_weights.h5
Epoch 22/50
211/557 [==========>...................] - ETA: 10s - loss: 0.0112 - acc: 0.9964

Функция прогнозирования выдаст массив с 10 числами. Эти числа представляют собой вероятности того, что входное изображение представляет каждую цифру (0–9). Индекс массива с наибольшим номером представляет прогноз модели. Сумма каждого массива равна 1 (поскольку каждое число является вероятностью).

y_pred = model.predict(X_test)
y_pred = np.argmax(y_pred,axis = 1)
accuracy_score(y_test,y_pred)
0.9961904761904762
pd.DataFrame(h.history).plot()

conf_mat = confusion_matrix(y_test,y_pred)
f,ax = plt.subplots(figsize=(7, 7))
sns.heatmap(conf_mat, cmap=’Blues’,annot=True, linewidths=.5, fmt= ‘.1f’,ax=ax)