Реализация сверточной нейронной сети при помощи Keras

Keras — это простая в использовании, но мощная библиотека глубокого обучения для Python. В этом посте мы построим простую сверточную нейронную сеть (CNN) и обучим ее решению реальной задачи с помощью Keras.

Этот пост предназначен для начинающих пользователей Keras, но предполагает наличие базовых знаний о CNN.

Нужен только код? Полный исходный код находится в конце.

 Хотите скачать книги по Python в 2 клика? Тогда вам в наш телеграм канал PythonBooks 

Проблема: классификация цифр в MNIST

Мы будем решать классическую вводную задачу компьютерного зрения: классификация рукописных цифр по MNIST. Все просто: необходимо классифицировать изображение как цифру.

Примеры изображений из набора данных MNIST
Примеры изображений из набора данных MNIST

Каждое изображение в наборе данных MNIST имеет размер 28×28 и содержит центрированную полутоновую цифру. Наша CNN принимает изображение и выдает один из 10 возможных классов (по одному на каждую цифру).

1. Настройка

Я предполагаю, что у вас уже есть базовая установка Python (скорее всего, так и есть). Давайте сначала загрузим некоторые пакеты, которые нам понадобятся:

$ pip install tensorflow numpy mnist

Примечание. Нам не нужно устанавливать пакет keras, поскольку теперь он поставляется вместе с TensorFlow в качестве официального API высокого уровня! Теперь рекомендуется использовать Keras из TensorFlow, а не отдельный пакет keras.

Теперь вы можете импортировать эти пакеты и работать с набором данных MNIST:

import numpy as np
import mnist
from tensorflow import keras

# The first time you run this might be a bit slow, since the
# mnist package has to download and cache the data.
train_images = mnist.train_images()
train_labels = mnist.train_labels()

print(train_images.shape) # (60000, 28, 28)
print(train_labels.shape) # (60000,)

2. Подготовка данных

Перед началом работы мы нормализуем значения пикселей изображения с [0, 255] до [-0,5, 0,5], чтобы облегчить обучение нашей сети (использование меньших, центрированных значений обычно приводит к лучшим результатам). Мы также изменим форму каждого изображения с (28, 28) на (28, 28, 1), поскольку Keras требует третьего измерения.

import numpy as np
import mnist

train_images = mnist.train_images()
train_labels = mnist.train_labels()
test_images = mnist.test_images()
test_labels = mnist.test_labels()

# Normalize the images.
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5

# Reshape the images.
train_images = np.expand_dims(train_images, axis=3)
test_images = np.expand_dims(test_images, axis=3)

print(train_images.shape) # (60000, 28, 28, 1)
print(test_images.shape)  # (10000, 28, 28, 1)

Мы готовы приступить к созданию нашей CNN!

3. Построение модели

Любая модель Keras строится либо с помощью класса Sequential, который представляет собой линейный стек слоев, либо с помощью функционального класса Model, который является более настраиваемым. Мы будем использовать более простую модель Sequential, поскольку наша CNN будет представлять собой линейный стек слоев.

Начнем с инициализации модели Sequential:

from tensorflow.keras.models import Sequential

# WIP
model = Sequential([
  # layers...
])

Конструктор Sequential принимает массив Layers Keras. Для нашей CNN мы будем использовать 3 типа слоев: Convolutional, Max Pooling и Softmax.

from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten

num_filters = 8
filter_size = 3
pool_size = 2

model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(10, activation='softmax'),
])
  • num_filters, filter_size и pool_size — это необъяснимые переменные, задающие гиперпараметры для нашей CNN.
  • Первый слой в любой Sequential модели должен задавать форму входа input_shape, поэтому мы делаем это на Conv2D. После задания формы входа Keras автоматически определяет формы входов для последующих слоев.
  • Выходной слой Softmax имеет 10 узлов, по одному на каждый класс.

4. Компиляция модели

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

  • Оптимизатор. Мы остановимся на довольно хорошем оптимизаторе по умолчанию: градиентном оптимизаторе Adam. В Keras есть множество других оптимизаторов, которые также можно рассмотреть.
  • Функция потерь. Поскольку мы используем выходной слой Softmax, мы будем использовать потери от перекрестной энтропии. Keras различает binary_crossentropy (2 класса) и categorical_crossentropy (>2 классов), поэтому мы будем использовать последнюю. См. все потери Keras.
  • Список метрик. Поскольку это задача классификации, мы просто попросим Keras вывести метрику точности.

Вот как выглядит эта компиляция:

model.compile(
  'adam',
  loss='categorical_crossentropy',
  metrics=['accuracy'],
)

Вперед!

5. Обучение модели

Обучение модели в Keras буквально сводится к вызову функции fit() и указанию некоторых параметров. Возможных параметров очень много, но мы ограничимся только этими:

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

Есть один нюанс: Keras ожидает, что обучающие цели будут 10-мерными векторами, поскольку в нашем выходном слое Softmax 10 узлов. Сейчас наши массивы train_labels и test_labels содержат одиночные целые числа, представляющие класс для каждого изображения:

import mnist

train_labels = mnist.train_labels()
print(train_labels[0]) # 5

Удобно, что в Keras есть метод-утилита, который решает именно эту проблему: to_categorical. Он превращает наш массив целых чисел класса в массив однокатегорийных векторов. Например, 2 превратится в [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (он индексирован нулями).

Вот как это выглядит:

from tensorflow.keras.utils import to_categorical

model.fit(
  train_images,
  to_categorical(train_labels),
  epochs=3,
  validation_data=(test_images, to_categorical(test_labels)),
)

Теперь мы можем собрать все вместе, чтобы обучить нашу сеть:

import numpy as np
import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten
from tensorflow.keras.utils import to_categorical

train_images = mnist.train_images()
train_labels = mnist.train_labels()
test_images = mnist.test_images()
test_labels = mnist.test_labels()

# Normalize the images.
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5

# Reshape the images.
train_images = np.expand_dims(train_images, axis=3)
test_images = np.expand_dims(test_images, axis=3)

num_filters = 8
filter_size = 3
pool_size = 2

# Build the model.
model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(10, activation='softmax'),
])

# Compile the model.
model.compile(
  'adam',
  loss='categorical_crossentropy',
  metrics=['accuracy'],
)

# Train the model.
model.fit(
  train_images,
  to_categorical(train_labels),
  epochs=3,
  validation_data=(test_images, to_categorical(test_labels)),
)

Выполнение этого кода на полном наборе данных MNIST дает следующие результаты:

Epoch 1
loss: 0.2433 - acc: 0.9276 - val_loss: 0.1176 - val_acc: 0.9634
Epoch 2
loss: 0.1184 - acc: 0.9648 - val_loss: 0.0936 - val_acc: 0.9721
Epoch 3
loss: 0.0930 - acc: 0.9721 - val_loss: 0.0778 - val_acc: 0.9744

С помощью этой простой CNN мы достигаем 97,4% точности тестирования!

6. Использование модели

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

model.save_weights('cnn.h5')

Теперь мы можем в любой момент перезагрузить обученную модель, перестроив ее и загрузив сохраненные веса:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten

num_filters = 8
filter_size = 3
pool_size = 2

# Build the model.
model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(10, activation='softmax'),
])

# Load the model's saved weights.
model.load_weights('cnn.h5')

Использовать обученную модель для прогнозирования очень просто: мы передаем массив входных данных в функцию predict(), а она возвращает массив выходных данных. Следует помнить, что на выходе нашей сети 10 вероятностей (из-за softmax), поэтому мы будем использовать np.argmax(), чтобы превратить их в реальные цифры.

# Predict on the first 5 test images.
predictions = model.predict(test_images[:5])

# Print our model's predictions.
print(np.argmax(predictions, axis=1)) # [7, 2, 1, 0, 4]

# Check our predictions against the ground truths.
print(test_labels[:5]) # [7, 2, 1, 0, 4]

7. Расширения

Мы можем еще много чего сделать для экспериментов и улучшения нашей сети — в этом официальном примере Keras MNIST CNN достигла точности 99 тестов после 15 эпох. Давайте рассмотрим, какие модификации можно внести в нашу CNN.

Глубина сети

Что произойдет, если мы добавим или удалим конволюционные слои? Как это повлияет на обучение и/или конечную производительность модели?

model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  Conv2D(num_filters, filter_size),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(10, activation='softmax'),
])

Dropout

А что если попробовать добавить слои Dropout, которые обычно используются для предотвращения перебора?

from tensorflow.keras.layers import Dropout

model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  MaxPooling2D(pool_size=pool_size),
  Dropout(0.5),
  Flatten(),
  Dense(10, activation='softmax'),
])

Полносвязные слои

Что если добавить полностью связанные слои между конволюционными выходами и конечным слоем Softmax? Это обычно делается в CNN, используемых для компьютерного зрения.

from tensorflow.keras.layers import Dense

model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(64, activation='relu'),
  Dense(10, activation='softmax'),
])

Параметры конволюции

А что если поиграть с параметрами Conv2D? Например:

# These can be changed, too!
num_filters = 8
filter_size = 3

model = Sequential([
  # See https://keras.io/layers/convolutional/#conv2d for more info.
  Conv2D(
    num_filters,
    filter_size,
    input_shape=(28, 28, 1),
    strides=2,
    padding='same',
    activation='relu',
  ),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(10, activation='softmax'),
])

Заключение

Вы реализовали свою первую CNN с помощью Keras! Мы достигли точности тестирования 97,4% с нашей простой начальной сетью. Ниже я снова приведу полный исходный код для ознакомления.

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

Спасибо за чтение! Полный исходный код приведен ниже.

Полный код

# The full CNN code!
####################
import numpy as np
import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten
from tensorflow.keras.utils import to_categorical

train_images = mnist.train_images()
train_labels = mnist.train_labels()
test_images = mnist.test_images()
test_labels = mnist.test_labels()

# Normalize the images.
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5

# Reshape the images.
train_images = np.expand_dims(train_images, axis=3)
test_images = np.expand_dims(test_images, axis=3)

num_filters = 8
filter_size = 3
pool_size = 2

# Build the model.
model = Sequential([
  Conv2D(num_filters, filter_size, input_shape=(28, 28, 1)),
  MaxPooling2D(pool_size=pool_size),
  Flatten(),
  Dense(10, activation='softmax'),
])

# Compile the model.
model.compile(
  'adam',
  loss='categorical_crossentropy',
  metrics=['accuracy'],
)

# Train the model.
model.fit(
  train_images,
  to_categorical(train_labels),
  epochs=3,
  validation_data=(test_images, to_categorical(test_labels)),
)

# Save the model to disk.
model.save_weights('cnn.h5')

# Load the model from disk later using:
# model.load_weights('cnn.h5')

# Predict on the first 5 test images.
predictions = model.predict(test_images[:5])

# Print our model's predictions.
print(np.argmax(predictions, axis=1)) # [7, 2, 1, 0, 4]

# Check our predictions against the ground truths.
print(test_labels[:5]) # [7, 2, 1, 0, 4]

Перевод статьи «Keras for Beginners: Implementing a Convolutional Neural Network».