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

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

Обзор

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

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

Конструктор

Сначала давайте начнем с нашего импорта

import numpy as np
import mnist

Как я уже упоминал в обзоре, мы будем использовать набор данных MNIST для обучения нашей сети и проверки ее точности. Есть множество способов импортировать этот набор данных, но я считаю, что этот проект Хёнсока Чон наиболее удобен, поскольку мы работаем с множественными массивами.

Здесь мы определяем наши гиперпараметры:

  • eta (скорость обучения): скорость обучения указывает на величину шагов, предпринятых весами для схождения.
  • альфа (коэффициент импульса): мы будем использовать импульс, чтобы ускорить процесс обучения и повысить точность, но основным мотивом использования импульса является то, что он не позволяет нашей модели застрять на локальном минимуме, изменяя направление изменения веса. Я нашел эту статью, чтобы дать краткое объяснение импульса.
  • n_iter: просто это количество итераций, которые мы пройдем для обучения нашей модели.
  • batch_size: наконец, мы будем обучать нашу сеть пакетами, что означает, что мы будем просматривать образцы группами. Это помогает нашей модели быстрее обучаться и использовать меньше памяти.

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

Одно горячее кодирование

Сначала давайте определим функцию, которая позволит нашей модели понять результат.

Эта функция превращает наши текущие метки, которые в основном находятся в диапазоне от 0 до 9, в массив размером 10, заполненный нулями и единицей в позиции, соответствующей цифре. Так, например, метка '1' будет преобразована в [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] и ' 5 'будет [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
И так далее…

Инициализация веса и отклонений

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

Как уже упоминалось, у нас будет 2 скрытых слоя, 1 вход и 1 выход. первая матрица весов будет иметь форму (100, 784 + 1), плюс один - это наша единица смещения. Первый слой сети, то есть входной, будет иметь форму (50, 785). В однослойной сети у нас есть один набор весов, который используется для вычисления нашего вывода, здесь скрытые слои действуют как эти выходные данные для предыдущего слоя.

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

Вычисление прямого прохода

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

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

Функция прогнозирования и убытков

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

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

Расчет обратного прохода

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

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

Собираем все вместе - функция Fit ()

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

Мы выбираем размер 100 для каждого скрытого слоя, а затем инициализируем наши веса, используя функцию, которую мы определили ранее, принимая размеры наших слоев. Затем мы инициализируем предыдущие веса, которые будут использоваться для импульса. Можно вообще не использовать их или иметь альфа равную 0, если мы не хотим использовать импульс в нашей модели и по-прежнему добиваться успеха, поэтому не стесняйтесь делать это, если хотите, чтобы все было просто. Также мы определили списки train_losses и train_acc, чтобы отслеживать потери и точность.

В нашем внутреннем цикле мы будем перечислять наши X (изображения) и y (метки). Сначала мы выполняем горячую кодировку для наших этикеток. Затем сеть вычислит прямой проход, получит потери, вычислит обратный проход, чтобы получить градиенты и использовать их для обновления наших весов. После этого мы проверяем точность нашей модели для каждой эпохи, чтобы дать нам отзывы о ее производительности во время обучения.

Импорт и подготовка нашего набора данных

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

Как я уже упоминал в начале, я буду использовать этот проект для импорта набора данных в виде массива numpy. Функция prepare_data () принимает входные данные изображения (X) и метки (y) и преобразует их в пакеты по 50, поэтому вместо того, чтобы иметь массив размером (60000, 784) для изображений, мы Буду работать с (1200, 50, 784). Наконец, мы используем функцию нормализации для преобразования интенсивности пикселей в значения от 0 до 1.

Обучение нашей модели

Теперь, когда наша модель готова к работе, мы инициализируем ее как mlp, загружаем наши обучающие данные, подготавливаем ее с помощью нашей функции prepare_data и, наконец, вызываем функцию fit (), чтобы начать процессы обучения.

Я обнаружил, что модель смогла достичь уровня точности 0,95 примерно после 200 итераций (n_iter = 200). Не стесняйтесь экспериментировать с гиперпараметрами и наблюдать за изменением точности.

Заключение и заключительные мысли

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

Также не стесняйтесь обращаться ко мне в любое время, я все еще студент, и я хотел бы найти больше людей в этой увлекательной области!

использованная литература

[1] Двоичная кросс-энтропия, также известная как потеря журнала - функция стоимости, используемая в логистической регрессии. (2020, 9 ноября). Получено из Analytics Vidhya: https://www.analyticsvidhya.com/blog/2020/11/binary-cross-entropy-aka-log-loss-the-cost-function-used-in-logistic-regression/

[2] Бушаев В. (3 декабря 2017 г.). Стохастический градиентный спуск с импульсом. Получено с сайта Towards Data Science: https://towardsdatascience.com/stochastic-gradient-descent-with-momentum-a84097641a5d