machine learning
February 7, 2020

99% точность в классификации цифр на Keras

В этой статье я покажу, как разработать классификатор глубокого обучения с использованием библиотеки Keras, чтобы достичь точности 99% в датасете цифр MNIST. Мы разработаем сверточную нейронную сеть (CNN) для задачи классификации.

Прежде чем мы начнем

Нам понадобится:

  • Python 3
  • Библиотека Keras с установленным в ней бэкендом в виде TensorFlow
  • Matplotlib для графиков
  • Numpy

По сути это минимальный набор для того, чтобы решать подобную задачу. Тем не менее, существуют так же библиотеки Theano, Caffe, PyTorch, sklearn... Все они достаточно неплохи и решают поставленные задачи очень хорошо.

Почему Keras

Я решил выбрать Keras в качестве библиотеки для Deep Learning, поскольку она лучше всего подходит для новичков. Keras – это минималистичная Python библиотека, которая может запускаться поверх TensorFlow, Theano и других. Keras поддерживает широкий спектр слоев нейронных сетей таких, как сверточные, рекуррентные или плотные.

Конечно Keras не настолько функционален, как сам TensorFlow и дает меньше опций для управления сетевым соединением. Если вы собираетесь создать какую-то специализированную модель глубокого обучения, это может стать серьезным ограничением.

Что такое Deep Learning

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

Deep Learning сейчас активно используется в Computer Vision (компьютерное зрение или CV). По сути, сейчас мы можем классифицировать изображения, находить на них объекты и даже помечать эти объекты подписями.

Упс, что-то пошло не так...

Что такое сверточная сеть (CNN)

Сверточная нейронная сеть – это сеть с множеством слоев, предполагающая что входные данные это какое-либо изображение. Эти слои позволяют объединять значения расположенных рядом пикселей и выделять более обобщённые признаки изображения.

Для этого используют ядро (kernel) (3х3, 5х5, 7х7 пикселей и т.п.), которым последовательно водят по картинке. Каждый элемент такого ядра имеет свой весовой коэффициент, умножаемый на значение того пикселя изображения, на который в данный момент наложен элемент ядра. Затем полученные для всего окна числа складываются, и эта взвешенная сумма даёт значение очередного признака.

Свёртка это не единственный способ получения обобщённой характеристики группы пикселей. Самый простой способ – это выбрать один пиксель по заданному правилу, например максимальный. Именно это и делает слой MaxPooling.

Датасет MNIST

Датасет MNIST – это набор изображений рукописных цифр, имеющий 60000 изображений для тренировки и 10000 тестовых изображений, каждое из которых представляет собой картинку в градациях серого размером 28 x 28.

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

Загрузка и предобработка данных

Библиотека Keras предоставляет датасет этих цифр в своем модуле keras.datasets. Это очень удобно, и позволяет сократить время предобработки данных в разы.

Загрузим данные из датасета, и отобразим 14 рандомных цифр оттуда с помощью matplotlib:

from keras.datasets import mnist
from numpy import random
import matplotlib.pyplot as plt

# загружаем данные и делим их на тренировочные и валидационные
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# фиксируем seed для повторяемости результатов
random.seed(123)

# берем 14 случайных изображений
N = random.randint(0, X_train.shape[0], 14)
digits = X_train[N]
labels = y_train[N]

# код для отрисовки
f, ax = plt.subplots(
    2, 7, figsize=(12,5),
    gridspec_kw={'wspace':0.03, 'hspace':0.01}, 
    squeeze=True
)    

for r in range(2):
    for c in range(7):
        index = r * 7 + c
        ax[r,c].axis("off")
        ax[r,c].imshow(digits[index], cmap='gray')
        ax[r,c].set_title('No. {0}'.format(labels[index]))
        
plt.show()
plt.close()

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

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

  • Преобразование изображений в тензор формы (num_samples, image_height, image_width, num_channels) - для наших изображений в градациях серого 28x28 это будет (num_samples, 28, 28, 1), где num_samples = 60 000 для тренировочного набора данных и num_samples = 10 000 для тестового набор данных.
  • One-hot-encoding - Keras предоставляет функцию to_categorical() в своем модуле утилит, который мы будем использовать.

Следующий код иллюстрирует процесс предобработки данных:

from keras.utils import to_categorical

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

#one-hot encode
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

One-hot-encoding работает следующим образом: например число 3 становится массивом [0 0 0 1 0 0 0 0 0 0], а 5 [0 0 0 0 0 1 0 0 0 0].

Модель CNN

Keras – это очень универсальная, но в то же время простая в изучении и понимании библиотека глубокого обучения, которая может работать поверх нескольких других сред глубокого обучения – она поддерживает Tensorflow, Theano и Microsoft CNTK, обычно по умолчанию используется Tensorflow.

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

Keras можно использовать как с процессором, так и с видеокартой. В этом примере я использую Keras, сконфигурированный с Tensorflow на компьютере с процессором - для простой модели, такой как MNIST, достаточно только процессора.

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

Мы создадим CNN со следующей архитектурой, используя Sequential:

  • 3 Conv2D слоя с 32, 64 и 64 фильтрами каждый, с использованием функции активации relu, kernel_size = 3
  • За каждым слоем Conv2D сразу следует слой MaxPooling2D с pool_size = 2
  • Наконец, наш выходной слой - это Dense слой с 10 узлами (соответствует 10 выходным классам) и функцией активации softmax - мы используем softmax для многоклассовой классификации
  • Мы компилируем модель с функцией потерь categorical_crossentropy и оптимизатором adam
model = Sequential()

# добавляем слои
model.add(Conv2D(32, kernel_size=3, activation='relu', input_shape=(28,28,1)))
model.add(MaxPooling2D(pool_size=2)) 
model.add(Conv2D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(64, kernel_size=3, activation='relu'))
model.add(Flatten())
model.add(Dense(10, activation='softmax'))

# компилируем модель, используем метрику accuracy
model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

# выводим информацию о нашей модели
print(model.summary())

Далее обучаем модель на тренировочном наборе (X_train, y_train), используя (X_test, y_test) для валидации. Обучение проводится в течение 10 эпох с batch_size = 32 (по умолчанию).

Вот код для запуска цикла обучения.

results = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10)

Отлично! Мы близки к 99 % точности.

Epoch 8/10
60000/60000 [==============================] - 32s 526us/step - loss: 0.0269 - accuracy: 0.9919 - val_loss: 0.0665 - val_accuracy: 0.9847
Epoch 9/10
60000/60000 [==============================] - 32s 533us/step - loss: 0.0266 - accuracy: 0.9920 - val_loss: 0.0550 - val_accuracy: 0.9868
Epoch 10/10
60000/60000 [==============================] - 32s 533us/step - loss: 0.0243 - accuracy: 0.9931 - val_loss: 0.0676 - val_accuracy: 0.9846

Чтобы оценить, как работала наша модель, давайте построим графики results.history ['accuracy'] и results.history ['val_accuracy'].

plt.plot(results.history['accuracy'], label='train')
plt.plot(results.history['val_accuracy'], label='test')
plt.legend()
plt.show()

Графики позволяют предположить, что эта модель испытывает небольшое переобучение после ~4 эпох. Также на графиках точности мы видим, что точность обучения возрастает до 100%, в то время как точность проверки выравнивается примерно через 4 эпохи - при значении около 99%.

Это типичное поведение переобученной модели. В целях предотвращения переобучения можно добавить Dropout, но об этом уже в следующий раз!

Весь код доступен по ссылке:https://github.com/Dartrisen/keras-mnist/blob/master/cnn.py


Материал подготовлен образовательной организацией Python Academy.