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

Но что, черт возьми, означают эти два слова?

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

Что, черт возьми, такое нейронная сеть

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

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

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

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

Функция активация - это функция, которая сопоставляет произвольный вывод логит-функции с любым конкретным диапазоном значений. Обычно он используется для добавления нелинейности нашей модели. Это позволяет сети более сложным образом комбинировать входные данные и, в свою очередь, расширять возможности функций, которые они могут моделировать. Примеры наиболее часто используемых функций активации: sigmoid, softmax, ReLU, tanh и т. Д.

Структура сети

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

Типичная нейронная сеть состоит из 3-х типов слоев:

  1. Входной уровень: указанные точки данных передаются в этот слой. Может быть только 1 входной слой. Количество нейронов в этом слое равно количеству входов.
  2. Скрытые слои. Это основа всей сети. Это слои, которые пытаются найти шаблоны во входных данных, чтобы получить нужные нам выходы. В сети может быть любое количество скрытых слоев.
  3. Выходной уровень: этот уровень дает нам прогнозы сети, т. е. выходы, которые, по мнению сети, должны быть правильными с учетом ее текущих параметров (веса и смещения каждого нейрона). Количество нейронов в этом слое равно количеству значений, которые нам нужно предсказать. Поскольку наша задача - классифицировать цифры MNIST, у нас будет 10 нейронов, так как есть 10 цифр, для которых нужно вычислять прогнозы.

Поэтому базовый сетевой уровень можно определить как:

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

В нашей реализации мы будем использовать два слоя:

  1. Плотный слой - где каждый нейрон в слое связан с каждым нейроном в слое сразу после него.
  2. Слой активации ReLU - слой, который находится поверх плотного слоя, который применяет функцию активации ReLU к выходам плотного слоя. Я мог бы использовать наиболее распространенную функцию сигмоида, но иногда я стараюсь быть резким, поэтому я выберу функцию ReLU.

Эти 2 слоя можно определить как:

Обучение сети

Итак, теперь, когда мы определили архитектуру сети, как вообще ее обучать? Да, используя параметры, т. Е. веса и смещения.

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

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

Чтобы оптимизировать нашу сеть, нам нужна такая функция, поэтому мы определяем функцию потерь - та, которую мы используем, называется log softmax cross-entropy loss (снова остроумная). .

Давайте разберем это слово за словом:

  1. Софтмакс

Функция Softmax принимает N-мерный вектор действительных чисел и преобразует его в вектор действительных чисел в диапазоне (0,1), который в сумме дает 1. Таким образом, она выводит распределение вероятностей, которое делает его пригодным для вероятностной интерпретации при классификации. задания.

2. Потеря перекрестной энтропии

Кросс-энтропия указывает расстояние между тем, каким, по мнению модели, должно быть выходное распределение, и тем, каким является исходное распределение.

Напишем это в коде:

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

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

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

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

Запуск кода

Весь код с графиком точности приведен ниже.

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