Автор статьи: Рустем Галиев
IBM Senior DevOps Engineer & Integration Architect. Официальный DevOps ментор и коуч в IBM
Прежде чем начать, введем словарь терминов.
Deep Learning (глубокое обучение) - совокупность методов машинного обучения основанных на построение нейронных сетей различной сложности и большой глубины (более 2-х слоев).
Dense - обозначение полносвязного слоя в нейронной сети.
Epoch - количество повторений циклов обучения для всей выборки данных.
До сих пор мы работали со слоем Dense для классификации изображений. Но на практике перед использованием плотного слоя мы используем пару специальных слоев — слой свертки и слой максимального объединения (или слой среднего объединения, хотя все новые модели используют слои максимального объединения). Обычно перед использованием плотных слоев можно увидеть множество пар слоев свертки и слоев с максимальным объединением.
Когда за многими парами этих слоев следует плоский слой, а затем несколько плотных слоев, это обычно называют сверточной нейронной сетью (CNN). Сверточные нейронные сети — это своего рода нейронная сеть с прямой связью, искусственные нейроны которой могут реагировать на часть окружающих ячеек в диапазоне покрытия, чего плотный слой не может достичь сам по себе.
Посмотрите на изображение CNN:
Мы воспроизведем архитектуру сверточной нейронной сети LeNet-5, предложенную Яном Лекуном в 1998 году. С тех пор у нас были гораздо более крупные и разнообразные типы сверточных нейронных сетей, но давайте реализуем эту простую архитектуру и сравним ее точность с нашей более ранней простой плотной нейронной сетью.
Мы создаем новый файл, в котором будет находиться наш код:
touch step1.py
Открываем только что созданный файл step1.py.
Начнем с импорта.
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from tensorflow.keras.datasets import mnist
Давайте теперь загрузим данные из MNIST и проверим формы изображения и метки.
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print(train_images.shape)
print(train_labels.shape)
print(test_images.shape)
print(test_labels.shape)
Выполним код
python step1.py
Мы видим, что в обучающем наборе 60 000 изображений, а в тестовом наборе — 10 000 изображений. Но есть 2 проблемы:
Во-первых, слой свертки ожидает изображения в форме (размер, строка, ширина, фильтры), а наш — просто (размер, строка, ширина). Поскольку изображения черно-белые, они представляют собой всего 1 фильтр (в отличие от изображений RGB, которые имеют 3 фильтра), но нам все еще нужно преобразовать его в форму, которую можно обработать, увеличив форму изображения на одно измерение.
Во-вторых, LeNet-5 ожидает изображения размером 32x32, как в оригинальной статье, а наши изображения имеют размер 28x28. Мы могли бы создать модель с входным размером 28x28, но давайте придерживаться исходной модели.
Мы можем адаптироваться к этому двумя способами: либо преобразовать изображение в целевой размер с помощью методов, включающих растяжение изображения, либо путем заполнения изображений. Заполнение относится к простому добавлению дополнительных строк и столбцов, которые не имеют значения, с использованием значения 0. Мы будем использовать последний метод.
Нам также придется нормализовать значения пикселей.
Нормализация данных
Необходимой практикой является нормализация наших значений пикселей путем деления на 255, чтобы получить диапазон 0-1. Это помогает нашему оптимизатору быстрее добиться минимизации.
Открываем тот же файл для продолжения: step1.py.
print("Few pixel values BEFORE normalization: \n", train_images[0,20:26,20:26])
train_images = train_images / 255.0
test_images = test_images / 255.0
print("\nFew pixel values AFTER normalization: \n", train_images[0,20:26,20:26])
Давайте запустим скрипт, чтобы просмотреть некоторые значения пикселей:
python step1.py
Обратите внимание, что значения пикселей изменились с диапазона 0–255 до 0–1. Этот простой Feature Scaling — не единственный метод нормализации, но этот простой способ деления на 255 очень распространен для пикселей изображения.
Расширение измерения
Как упоминалось в предыдущем шаге, нам нужно изменить матрицу наших изображений на (размер, 28, 28, 1) вместо (размер, 28, 28). Для этого воспользуемся функцией np.expand_dims()
.
train_images = np.expand_dims(train_images,-1)
test_images = np.expand_dims(test_images,-1)
Заполнение данных
LeNet-5 ожидает, что размеры нашего изображения будут 32x32. Чтобы решить эту проблему, мы дополним наши изображения 28x28 нулями, чтобы получить размер 32x32, т. е. добавим четыре ряда нулей (два над изображением и два под ним) и четыре столбца нулей (два слева от изображения и два справа от изображения).
train_images = np.pad(train_images, ((0,0),(2,2),(2,2),(0,0)), 'constant')
test_images = np.pad(test_images, ((0,0),(2,2),(2,2),(0,0)), 'constant')
Когда наш код и данные готовы, давайте перейдем к созданию и компиляции нашей модели.
Создание модели
«5» в LeNet-5 соответствует 5 слоям, а точнее 5 обучаемым слоям (у слоев Max Pooling и Flatten нет обучаемых параметров). Следующие слои:
Уровень свертки 1 + средний. Уровень пула 1
Уровень свертки 2 + средний. Уровень 2 пула
Flatten слой + Dense слой 1
Dense слой 2
Dense слой 3 (выходной слой)
Откройте тот же файл, чтобы продолжить: step1.py.
Для этой модели мы будем использовать несколько слоев. После его создания просмотрите model.summart()
, чтобы просмотреть количество параметров.
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(filters=6, kernel_size=(3, 3), activation='relu', input_shape=(32,32,1)),
tf.keras.layers.AveragePooling2D(),
tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3), activation='relu'),
tf.keras.layers.AveragePooling2D(),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(units=120, activation='relu'),
tf.keras.layers.Dense(units=84, activation='relu'),
tf.keras.layers.Dense(units=10, activation='softmax')
])
print(model.summary())
В слое Conv2D есть 3 важных аргумента:
Filters: это размерность выходного пространства. На изображении CNN на шаге 1 обратите внимание, что исходное изображение имеет размер фильтра, равный трем, затем уменьшается по длине и ширине, но его ширина увеличивается. Это увеличение ширины связано с обычной практикой увеличения фильтра. Каждый фильтр фиксирует некоторые ключевые особенности изображения, такие как горизонтальные и вертикальные линии.
kernel_size: указывает высоту и ширину окна свертки; каждое окно выполняет несколько вычислений над изображением. Обычно он принимает пару нечетных целых чисел.
strides: определяет шаг окна свертки. Значение по умолчанию — (1,1).
Давайте выполним код, чтобы увидеть сводку модели.
python step1.py
Модель имеет чуть более 81к параметров по сравнению с 200к в прошлом посте! Но достигнет ли он более высокого уровня точности? Давай выясним.
Давайте скомпилируем модель с оптимизатором, потерями и метриками.
Следующее, что нужно сделать, теперь, когда модель определена, — это построить ее. Мы делаем это, компилируя его с оптимизатором и функцией потерь.
model.compile(optimizer = tf.keras.optimizers.Adam(),
loss = 'sparse_categorical_crossentropy',
metrics=['accuracy'])
Теперь перейдем к фитингу и оценке модели.
Мы обучаем модель, вызывая метод fit()
Пришло время обучить модель. Мы будем использовать аргумент batch_size
, что означает, что мы будем подгонять 16 изображений за раз, так как не хотим переполнять нашу память. Обучение может занять некоторое время.
Открываем тот же .py файл: step1.py
model.fit(train_images, train_labels, epochs=5, batch_size=1024, validation_data=(test_images,test_labels))
Позже вы должны попытаться посмотреть, сможете ли вы улучшить производительность, изменив определенные гиперпараметры, такие как количество эпох или оптимизатор и т. д.
Давайте построим значения точности и потерь.
plt.plot(model.history.history['accuracy'],label='Train Accuracy')
plt.plot(model.history.history['val_accuracy'],label='Test Accuracy')
plt.legend()
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.savefig('accuracy_plot.png')
plt.close()
plt.plot(model.history.history['loss'],label='Train Loss')
plt.plot(model.history.history['val_loss'],label='Test Loss')
plt.legend()
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.savefig('loss_plot.png')
Чтобы оценить модель, мы должны знать, как она работает с невидимыми данными. Вот почему у нас есть тестовые изображения. Мы будем использовать model.evaluate()
и передавать ей тестовые изображения и метки:
model.evaluate(test_images, test_labels)
Давайте выполним код для просмотра вывода и графиков (подгонка может занять некоторое время).
python step1.py
Щелкните по accuracy_plot.png
, чтобы визуализировать график точности. Как и ожидалось, точность как тренировочного, так и тестового набора увеличивается.
Нажмите loss_plot.png, чтобы визуализировать график потерь. Как и ожидалось, потери как тренировочного, так и тестового набора уменьшаются.
Полный код:
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print(train_images.shape)
print(train_labels.shape)
print(test_images.shape)
print(test_labels.shape)
print("Few pixel values BEFORE normalization: \n", train_images[0,20:26,20:26])
train_images = train_images / 255.0
test_images = test_images / 255.0
print("\nFew pixel values AFTER normalization: \n", train_images[0,20:26,20:26])
train_images = np.expand_dims(train_images,-1)
test_images = np.expand_dims(test_images,-1)
train_images = np.pad(train_images, ((0,0),(2,2),(2,2),(0,0)), 'constant')
test_images = np.pad(test_images, ((0,0),(2,2),(2,2),(0,0)), 'constant')
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(filters=6, kernel_size=(3, 3), activation='relu', input_shape=(32,32,1)),
tf.keras.layers.AveragePooling2D(),
tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3), activation='relu'),
tf.keras.layers.AveragePooling2D(),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(units=120, activation='relu'),
tf.keras.layers.Dense(units=84, activation='relu'),
tf.keras.layers.Dense(units=10, activation='softmax')
])
print(model.summary())
model.compile(optimizer = tf.keras.optimizers.Adam(),
loss = 'sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=1024, validation_data=(test_images,test_labels))
plt.plot(model.history.history['accuracy'],label='Train Accuracy')
plt.plot(model.history.history['val_accuracy'],label='Test Accuracy')
plt.legend()
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.savefig('accuracy_plot.png')
plt.close()
plt.plot(model.history.history['loss'],label='Train Loss')
plt.plot(model.history.history['val_loss'],label='Test Loss')
plt.legend()
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.savefig('loss_plot.png')
model.evaluate(test_images, test_labels)
Я получил точность обучения 0,9642 (96,42%) и точность теста 0,9577 (95,77%)! Вы можете ожидать чего-то подобного (точное значение будет отличаться для разных прогонов).
Причина, по которой он различается для разных прогонов, заключается в том, что веса слоев инициализируются случайным образом. В любом случае, это значительное улучшение по сравнению с нашей чистой моделью Dense, и мы даже сократили количество параметров для изучения!
В заключение статьи приглашаю всех желающих на открытое занятие «Kornia - убийца OpenCV?», на котором участники узнают:
Почему Kornia применяется в обучении нейронных сетей и PyTorch, а OpenCV - нет.
За счет чего Kornia работает в разы быстрее, чем OpenCV.
Какие продвинутые функции потерь и алгоритмы для моделей Компьютерного Зрения предоставляет Kornia.
Как написать алгоритм, которые автоматически сшивает несколько фотографий в панорамный снимок.
Почему Kornia – это лучший инструмент для задач, связанных с геометрией изображений.
Регистрация на мероприятие открыта по ссылке.