О чем этот пост?

Мы реализуем искусственную нейронную сеть (ИНС) с использованием NumPy на Python. Это не обязательно теоретическое введение в ИНС или алгоритм градиентного спуска. В этой статье основное внимание уделяется объяснениям нижнего уровня кода и самого алгоритма.

Что необходимо сделать перед прочтением этой статьи?

Вы должны уметь читать, писать и понимать код Python. Справочная информация о NumPy будет полезна, или вы можете посмотреть документацию, если не знаете, что делает функция.

Теоретические знания ИНС на высоком уровне облегчат понимание кода.

Почему мы должны знать, как реализовать ИНС, если PyTorch и TensorFlow уже существуют?

Для многих приложений мы можем использовать очень глубокие нейронные сети, которые представляют собой объединение слоев свертки, LSTM, FCN и т. Д. Работа таких архитектур, как сиамские нейронные сети, маскированная RCNN и т. Д., Не является полностью интуитивно понятной. Есть множество функций потерь, оптимизаторов и показателей. Новички могут создать конвейер (используя PyTorch или TensorFlow), но им будет сложно правильно обучить модель без переобучения.

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

Теоретические основы

Прямое распространение

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

Если мы повторим этот процесс для всех нейронов в слое, мы получим новый вектор значений, который является выходом текущего слоя и используется как вход для следующего слоя. Начиная с входного вектора, если мы повторим этот процесс для всех слоев, мы получим окончательные выходные значения ИНС.

Это (вкратце) прямое распространение.

Обратное распространение

Целью обратного распространения является обновление весов и смещений (те же веса, которые используются для вычисления «z» во время прямого пропуска). Для каждого веса каждого нейрона мы вычисляем «dw», частную производную функции потерь по весу. Затем мы обновляем вес в направлении отрицательного градиента (в основном градиентный спуск). Точно так же мы обновляем условия смещения. Частные производные вычисляются с использованием графа вычислений.

Пожалуйста, обратитесь к уравнениям на рисунках 1 и 2 для реализации ИНС.

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

Прежде чем мы действительно приступим к созданию ИНС, нам нужно импортировать необходимые библиотеки. NumPy поможет нам с линейной алгеброй. Мы будем тестировать нашу ИНС на знаменитом наборе данных Iris. В наборе данных есть 4 характеристики цветка ириса, и каждый набор характеристик соответствует одному из 3 видов цветов ириса.

Мы загружаем набор данных в строку 6. Характеристики сохраняются в переменной «X» в строке 14, а их метки сохраняются в «Y» в строке 13.

Исходные метки хранятся в одномерном массиве типа [1,2,1,0,2,1,… ..]. Мы используем One-Hot Encoding для этого iris.target (строки с 10 по 13).

Наконец, мы разделили набор данных на X_train и X_test.

Создание класса для ИНС

Не беспокойтесь о размере кода. По большей части это просто комментарии. Во-первых, функция __init__ просто создает все основные атрибуты, необходимые для хранения данных кеша (строки 23, 24) и свойств ИНС.

Создание архитектуры ИНС

Функция add_layer (строка 26) аналогична функции Add () в Keras и создает матрицу весов для i-го слоя. Обратите внимание, что в строках 40 и 42 мы создаем матрицу с дополнительным столбцом. Это потому, что я решил включить член смещения в саму матрицу весов, чтобы все веса и смещения можно было обновлять одновременно. Мы инициализируем эти значения случайным образом (хотя есть и другие способы инициализации). Мы добавляем веса и активацию к атрибутам экземпляра, а также добавляем пустые векторы в качестве кеша к переменным кеша (строки с 45 по 46).

Функция сборки (строка 50) завершает архитектуру ИНС. Он делает это, добавляя последний слой с необходимым количеством выходных нейронов (строки с 69 по 71). Он также определяет активацию последнего слоя и общую функцию потерь, которую мы хотим использовать (строки 72, 73).

Служебные функции

Это вспомогательные функции, упрощающие код

Начнем с активации и активации функций. Для этой реализации я выбрал 2 простые функции активации. Это сигмовидная и ReLU.

Функция __activate (строка 98) принимает входной вектор и применяет нелинейную функцию активации (поэлементно). Мы можем передать наш выбор активации в качестве аргумента. Это полезно на этапе прямого распространения.

Метод __derivative (строка 113) используется для нахождения производной активированного вектора для конкретной функции активации. Поскольку мы перебираем все слои во время обратного распространения, мы будем использовать это один раз для каждого слоя для вычисления «dz». (См. Рисунок 2)

Теперь поговорим о функциях потерь. Я выбрал функции потерь Среднеквадратичная ошибка и Кросс-энтропия.

Метод __loss (строка 128) принимает прогнозируемые и фактические выходные значения вместе с выбором функции потерь. Затем он возвращает не только убыток, но и производную по выходу ИНС. Это упрощает отслеживание потерь и прямое использование этой функции для обратного распространения.

Методы __mse (строка 211) и __logloss (строка 222) возвращают среднеквадратичную ошибку и потерю кросс-энтропии соответственно.

Прямое и обратное распространение

Теперь мы подошли к той части, где мы все это собрали. Настоятельно рекомендуется хорошо понимать уравнения на рисунках 1 и 2.

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

Метод __forward (строка 75) просто выполняет прямое распространение с учетом входных данных. Мы инициализируем «a» входными значениями. Затем мы итеративно вычисляем «z», а затем «a» (строки 90, 91) по всем уровням сети. Для члена смещения к матрице весов был добавлен дополнительный столбец (строка 42). Умножение одной матрицы вычислит «z». Таким образом, мы добавляем вектор единиц к «a», потому что член смещения добавляется напрямую (т. Е. Он умножается на 1, а затем складывается). Если мы выполняем прямое распространение в целях обучения, нам необходимо кэшировать промежуточные значения «a» и «z» (строки 92–94). Обратите внимание, что в строке 86 и 95 мы меняем местами оси (это то же самое, что и транспонирование), чтобы сохранить тот факт, что каждый обучающий пример представлен как строка во время ввода и вывода и как столбец для вычислений.

Метод __update (строка 147) выполняет шаг прямого распространения, используя указанную выше функцию. Он начинается с вычисления «da» с использованием метода __loss (строка 162). Строка 165 добавляет дополнительную строку, которую мы игнорируем в строке 167 в соединении «da [: - 1]». Это гарантирует правильность формы задействованных матриц и векторов. Для всех слоев метод итеративно вычисляет «da», «dz» и «dw» (строки с 167 по 169). Наконец, мы обновляем веса (и смещения) в направлении отрицательного градиента с определенной скоростью обучения (строка 170).

На этом завершается этап градиентного спуска ИНС.

Метод поезда (строка 174) принимает аргументы и несколько раз вызывает метод __update. Здесь указаны 2 типа оптимизатора. Первый - «пакетный», где «dw» усредняется по всем обучающим примерам. Во-вторых, это «sgd», который представляет собой стохастический градиентный спуск, который многократно вызывает метод __update для каждого обучающего примера.

В зависимости от ситуации может использоваться соответствующий оптимизатор.

Наконец, метод тестирования используется для прогнозирования значений входных данных на этапе развертывания. Здесь снова вызывается метод __forward (строка 200) (строка 209).

Пора протестировать конвейер !!

Создаем экземпляр объекта ИНС (строка 1). Архитектура простая. Добавлен единственный скрытый слой с активацией сигмоида (строка 2). Модель собирается с финальной активацией как сигмовидная.

Модель обучена для 1000 эпох со скоростью обучения 0,01. Мы используем «sgd» в качестве оптимизатора и «кросс-энтропию» в качестве функции потерь. В данном случае это сработало.

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

Трубопровод

Комментарии

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

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

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

Заключение

Спасибо, что прочитали эту статью. Дайте мне знать, как вы относитесь к объяснению в ответах ниже.

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

Буду очень признателен, если вы предложите какие-нибудь хорошие темы для будущих статей. Они могут быть более интересными или ориентированными на приложения, например, обучение с подкреплением, GAN, робототехника и т. Д., Вместо вводной темы, такой как ИНС.