Компьютерное зрение с MXNet и Gluon (построчное объяснение)

Часть 1: Создание нейронной сети с использованием высокоуровневых API.

Вступление

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

Цель этой статьи - дать вам представление о том, что обычно происходит внутри модели компьютерного зрения. Мы будем создавать сверточную нейронную сеть (CNN) для распознавания одежды. Да, вы правильно поняли, мы больше не распознаем рукописные цифры (MNIST) и не обнаруживаем собак. Теперь мы узнаем одежду.

Но почему?

По информации Заландо:

Фактически, MNIST часто является первой попыткой исследователей набора данных. «Если он не работает с MNIST, он вообще не работает», - сказали они. «Что ж, если он работает с MNIST, он все равно может не работать с другими».

и снова по Заландо:

MNIST - это слишком просто. Сверточные сети могут достигать 99,7% по MNIST. Классические алгоритмы машинного обучения также могут легко достичь 97%.

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

Импортировать библиотеки

Следующие библиотеки должны быть импортированы для построения нашей модели. Если вы следовали части 1 этой статьи, вам не нужно устанавливать никаких дополнительных пакетов. Или же прочтите статью, чтобы узнать, какие 2 пакета вам следует скачать! (да, все, что вам нужно, это 2 пакета для создания собственной нейронной сети)

from mxnet import nd,autograd,gluon,init,metric
from mxnet.gluon import nn
from mxnet.gluon.data.vision import datasets, transforms
import matplotlib.pyplot as plt
from time import time

Скачать данные

Gluon имеет собственные библиотеки данных. Вы можете скачать Fashion MNIST с помощью одной строчки кода.

Поскольку мы будем оценивать нашу модель позже, мы также загрузим набор данных проверки, установив параметр train в False.

train_data = datasets.FashionMNIST(train=True)
valid_data = datasets.FashionMNIST(train=False)
X_train,y_train = train_data[0]
print(X_train.shape, y_train.dtype)
print(len(train_data))

Затем мы можем распечатать форму и тип данных нашего первого изображения:

  • Форма: (28,28,1). Высота и ширина изображения 28, и это черно-белое изображение, так как глубина только 1.
  • dtype: int32. Изображение представлено 32-битными целыми числами.

Наконец, мы наблюдаем, что у нас есть 60 000 изображений в наших обучающих данных.

Преобразовать данные

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

  • transforms.Compose позволяет нам объединить несколько функций преобразования в одну функцию. Обратите внимание, что порядок преобразования имеет значение.
  • transforms.ToTensor() изменяет ваше изображение с HWC на ​​CHW формат и тип данных с 32-битного целого на 32-битное с плавающей запятой.
  • transforns.Normalize() нормализует изображение в соответствии с двумя предоставленными параметрами - средним значением, стандартным отклонением.
transformer = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(0.13,0.31)
])
train_data = train_data.transform_first(transformer)

Загрузить данные в пакеты

Отправлять изображения по одному в нейронную сеть - плохая идея. На подпитку вашей сети уйдет огромное количество времени. Следовательно, мы создаем загрузчик данных для загрузки данных в пакеты по 256 (вы можете поэкспериментировать и изменить этот номер пакета).

Для создания нашего DataLoader необходимо 4 параметра:

  • train_data: данные о ваших тренировках (длина 60 000)
  • batch_size: 256 изображений будут загружаться в вашу нейронную сеть каждую волну
  • shuffle: перемешайте данные в случайном порядке, чтобы уменьшить ложную корреляцию между данными.
  • num_workers: Количество ядер для обучения. вы можете проверить количество доступных ядер. Разные компьютеры будут возвращать разные результаты. В моем случае я установил 4.
# check number of CPUs avaiable
from multiprocessing import cpu_count
print(cpu_count())
batch_size = 256
train_data_batch = gluon.data.DataLoader(train_data,
                                         batch_size = batch_size,
                                         shuffle=True,
                                         num_workers=4)
valid_data = gluon.data.DataLoader(valid_data.transform_first(transformer),
                                   batch_size=batch_size)

Определение вашей модели

Будем копировать модель LeNet-5.

Сначала мы начинаем с чистого листа: nn.Sequential()

Затем мы добавляем слои и слои скрытых слоев в нашу модель в соответствии с архитектурой LeNet-5.

  • nn.Conv2D: сверточный слой для извлечения функций из изображения.
  • nn.MaxPool2D: Уровень объединения для уменьшения количества обучаемых параметров.
  • nn.Flatten: сведите наши данные к одному измерению, чтобы подготовить их для полностью связанного слоя.
  • nn.Dense: полностью связанный слой
  • network.initialize(init=init.Xavier()): Выполнить инициализацию весов Ксавье. Инициализатор Xavier помогает поддерживать примерно одинаковый масштаб градиентов на всех слоях.
network = nn.Sequential()
with network.name_scope():
    network.add(
        nn.Conv2D(channels=6, kernel_size=5, activation='relu'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Conv2D(channels=16, kernel_size=3, activation='relu'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Flatten(),
        nn.Dense(120, activation='relu'),
        nn.Dense(84, activation='relu'),
        nn.Dense(10)
        )
network.initialize(init=init.Xavier())

Функция потерь

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

В задаче полиномиальной классификации мы обычно будем использовать функцию потеря кросс-энтропии. Функция потери кросс-энтропии обычно используется в паре с softmax для сжатия значений между (0,1).

softmax_cross_entropy = gluon.loss.SoftmaxCrossEntropyLoss()

Метрика оценки

С нашей функцией потерь нам нужен способ определить, насколько хороша наша модель. Мы определяем это с помощью показателей. Есть несколько показателей, таких как F1, Напоминание, Точность, Точность и т. Д. Наиболее часто используемый показатель - это точность.

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

train_acc = metric.Accuracy()

Оптимизатор

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

  • network.collect_params() : мы можем получить текущие веса нашей модели. Нашему тренеру требуются текущие веса, чтобы их можно было обновить.
  • 'sgd': Выбираем оптимизатор стохастического градиентного спуска. Посетите исходную документацию, чтобы узнать о типах доступных оптимизаторов.
  • learning_rate: Чем ниже скорость обучения, тем лучше мы можем достичь минимума / максимума. Однако это также приводит к увеличению времени обучения модели.
trainer = gluon.Trainer(network.collect_params(),
                       'sgd', {'learning_rate':0.1})

Цикл обучения

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

  1. for epoch in range(10): Мы будем обучать нашу модель в общей сложности 10 эпох / раз.
  2. train_loss = 0 Для каждой итерации мы инстанцируем наши потери равными 0.
  3. tic = time() Мы записываем текущее время, чтобы отслеживать, сколько времени требуется нашей модели на обучение.
  4. for data, label in train_data_batch: Теперь мы перебираем пакеты наших данных
  5. with autogra.record(): Записываем происходящее в нашей модели
  6. output=network(data) Передавая data в нашу network, которая является нашей моделью, мы получаем прогноз output
  7. loss=softmax_cross_entropy(output,label) Используя ранее определенную функцию потерь, мы можем вычислить наши потери, передав прогнозируемые выходные данные и наземные истинные выходные данные.
  8. loss.backward() Убытки передаются через CNN.
  9. trainer.step(batch_siz) После обратного распространения убытков мы делаем шаг вперед, чтобы обновить наши веса.
  10. train_loss += loss.mean().asscalar() Потери в обучении обновляются с учетом потерь, понесенных этим пакетом данных.
  11. train_acc.update(label,output) Точность обучения обновляется с учетом потерь, понесенных этим пакетом данных.
  12. В ведомости печати указаны потери, точность и производительность для каждой эпохи. Обычно мы распечатываем это, чтобы мы могли наблюдать и убедиться, что модель развивается.
  13. network.save_parameters("trained_net.params") Этот шаг не является обязательным. Он сохраняет обновленные веса после того, как вы обучили свою модель в файле. Веса можно загрузить в любое время в другую среду.
for epoch in range(10):
    train_loss = 0
    tic = time()
    for data, label in train_data_batch:
        with autograd.record():
            output=network(data)
            loss=softmax_cross_entropy(output,label)
        loss.backward()
        
        trainer.step(batch_size)
        
        train_loss += loss.mean().asscalar()
        train_acc.update(label,output)
        
    print("Epoch[%d] Loss: %.3f Acc: .%3f Perf: %.1f img/sec"%(
        epoch,train_loss/len(train_data_batch),
        train_acc.get()[1],
        len(train_data)/(time()-tic)
    ))
network.save_parameters("trained_net.params")

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

Оценка модели

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

Объяснение:

  1. network.load_parameters("trained_net.params") загружает веса модели, которые мы обучили ранее.
  2. Точно так же, как мы передавали пакеты обучающих данных для обучения нашей модели, теперь мы передаем пакеты данных проверки для нашей модели для оценки при обновлении valid_acc
  3. Чтобы получить окончательную точность, распечатайте его, используя valid_acc.get()[1]. Мне удалось получить точность проверки 0,9025. Вы должны получить другой номер. Это означает, что наша модель предсказывает правильный результат в 90% случаев!
network.load_parameters("trained_net.params")
valid_acc = metric.Accuracy()
for data,label in valid_data:
    output = network(data)
    valid_acc.update(label,output)
print(valid_acc.get()[1])

Заключение

Поздравляем с завершением этой статьи! К настоящему времени вы должны быть в состоянии создать и спроектировать свою собственную нейронную сеть с нуля. Есть много других библиотек глубокого обучения, таких как Tensorflow, Keras, Pytorch и Scikit-learn, которые также могут достичь тех же результатов. Выбирайте то, что вам знакомо. Различные пакеты по-прежнему должны давать аналогичные результаты.

Я рекомендую вам ознакомиться с различными доступными моделями и архитектурами и поэкспериментировать с различными данными, которые вы можете найти в Интернете.