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

В этой статье вы узнаете:

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

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

Подача вперед

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

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

Прежде чем перейти к формулам для прямой подачи нейронной сети, давайте рассмотрим некоторые обозначения:

  • Xᵢ = i-й вход в узел
  • wᵢ = i-й вес, поступающий в узел
  • b = смещение для этого узла
  • z = вывод узла до применения функции активации
  • a = вывод узла после применения функции активации

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

Верхний узел имеет 3 входа (значения x), что означает, что он также имеет три веса. Вы можете представить, что этот узел является частью некоторого скрытого слоя с предыдущим скрытым слоем, содержащим 3 узла. Помните, что узел связан со всеми узлами, предшествующими ему, поэтому у нас есть 3 входа. Каждый ввод имеет соответствующий вес, который будет использоваться для вычисления значения z, первого вычисления в узле. Приведенную ниже формулу можно использовать для вычисления значения z узла, показанного выше:

Таким образом, чтобы вычислить значение z, каждый ввод умножается на соответствующий вес. Все эти значения суммируются, а затем добавляются к смещению. После вычисления значения z к сумме применяется функция активации, что дает нам окончательный результат узла. Некоторые функции активации, обычно используемые в узлах скрытого слоя, — это ReLU, Tanh или Sigmoid, в то время как некоторые функции активации, обычно используемые в выходном слое, — это Sigmoid или Softmax. Я буду использовать ReLU для примеров в этой статье, так как его легко отличить. Ниже показано, как функция ReLU выглядит на графике:

Функция ReLU принимает максимальное значение между 0 и значением z, как указано ниже:

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

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

Для повышения скорости вычислений скалярные произведения используются для расчета z для всего слоя, а не для одного узла, в соответствии с приведенной ниже формулой:

Таким образом, мы можем вычислить значения z для слоя из скалярного произведения входных данных и транспонированных весов (верхний индекс T означает транспонирование значений) плюс смещение для каждого узел. Затем к этим значениям z можно применить функцию ReLU. Ниже приведен пример кода на Python с использованием numpy:

import numpy as np
# In this example, the previous layer has 4 nodes which is
# why the input has 4 values.
# The layer we are calculating has 3 layers but each node in
# this layer has to take in 4 inputs. This is why the
# weights array is a 3x4 array (3 nodes, 4 inputs)
# There are 3 biases, 1 for each node in this layer.
inputs = np.array([1,2,3,4])
weights = np.array([[1, 0.5, -0.5, 1],
                    [0.5, -1, 1, -0.5],
                    [0, 1, -1, 0.5]])
biases = np.array([10,9,8])
# Calculate the z value
z = np.dot(inputs, weights.T) + biases
# Calculate the activation value
a = np.maximum(0, z)

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

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

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

Функция потерь может быть любой функцией, которую вы хотите, но подходящая функция потерь будет возвращать низкое значение (тот, который ближе к 0), чем ближе нейронная сеть к достижению вашей цели, и высокое значение, если сеть была далеко. от достижения желаемой цели. Примечание. для большинства функций потерь наименьшее значение потерь равно 0, а наибольшее значение потерь равно -inf или inf. Например, предположим, что я хочу классифицировать изображение как кошку или собаку. Эта задача представляет собой бинарную задачу, в которой мы можем представить 1 как кошку, а 0 как собаку. Итак, нам нужна нейронная сеть с одним выходным узлом, который дает нам значение от 0 до 1. Если выходное значение больше или равно 0,5, мы можем сказать, что сеть подумала, что изображение было кошкой, и если выходное значение значение меньше 0,5, мы можем сказать, что сеть подумала, что изображение было собакой.

Чтобы найти потери в этой сети, мы отправим выходные данные (от 0 до 1) через функцию потерь BCE (Binary Cross Entropy), которая показана ниже:

Во-первых, давайте рассмотрим нотацию для этой функции:

  • N = количество обучающих примеров.
  • yᵢ = Настоящая метка
  • ŷᵢ = предсказанная метка (предсказанная сетью)

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

Этот член умножает истинную метку на логарифм предсказанной метки. Итак, допустим, мы хотели предсказать, что изображение было кошкой. Тогда yᵢ будет равно 1, так как мы представляем кошку как 1 в примере, описанном выше. Таким образом, если наша сеть со 100% уверенностью предсказала, что на изображении изображена кошка, то ŷᵢ также будет равна 1. Поскольку логарифм 1 равен 0, этот термин станет равным 0.

Если наша сеть предсказала, что изображение было котом со значением 0,75, то ŷᵢ равно 0,75. Логарифм 0,75 составляет около -0,28, поэтому этот член становится -0,28.

Если бы наша сеть предсказала, что изображение было собакой с выводом 0, то ŷᵢ равно 0. Логарифм 0 — это отрицательная бесконечность, поэтому этот термин становится отрицательной бесконечностью.

Обратите внимание, что этот член становится более отрицательным по мере того, как прогнозируемое значение, ŷᵢ, смещается дальше от истинной метки 1. Таким образом, этот член уменьшает потери в зависимости от того, насколько далеко прогноз от фактического. ценить.

Если yᵢ равно 0, то есть мы хотим, чтобы сеть предсказывала, что изображение является собакой, тогда этот термин всегда равен 0.

Следующий термин выглядит следующим образом:

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

Если yᵢ равно 0, то этот термин будет следовать тому, как работал предыдущий термин, когда yᵢ был равен 1, но в противоположном направлении. Таким образом, если ŷᵢ равно 0, то значение журнала равно 0, поскольку журнал 1-0 равен 0.

Если ŷᵢ равно 0,75, значение журнала равно log(0,25), что составляет около -1,38.

Если ŷᵢ равно 1, значение журнала равно log(0), что равно отрицательной бесконечности.

Как видите, когда истинная метка yᵢ равна 0, член уменьшается по мере удаления прогнозируемого значения от 0.

При объединении двух терминов, когда истинная метка (yᵢ) равна 1, активен первый термин (yᵢ ∙ log(ŷᵢ). В этом случае по мере того, как ŷᵢ приближается к 0 (ближе к неправильному ярлыку), сумма двух членов приближается к отрицательной бесконечности. Когда истинная метка (yᵢ) равна 0, активен второй член ((1-yᵢ) ∙ log(1-ŷᵢ)) . В этом случае, когда ŷᵢ приближается к 1 (близко к неправильной метке), сумма двух членов также приближается к отрицательной бесконечности. Эти термины могут принимать следующие значения: [-∞, 0], где 0 означает ŷᵢ=yᵢ, а -∞ означает ŷᵢ=(1 -yᵢ).

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

Окончательный термин выглядит следующим образом:

При обучении нейронной сети вы не хотите давать сети один пример и заставлять ее учиться на этом одном примере. Вы хотите, чтобы сеть училась на нескольких примерах. Вот почему существует суммирование от i до N, где i начинается с 0-го примера и заканчивается последним NN).

Помните, что эта функция потерь может находиться в диапазоне от [-∞, 0], где -∞ означает, что сеть работает как можно хуже, а 0 означает, что сеть работает как можно лучше. Проблема в том, что отрицательная бесконечность очень неинтуитивна, поскольку более отрицательные значения, кажется, указывают на то, что сеть работает хорошо, хотя на самом деле она работает ужасно. Помните, что 0 — это «низкое» значение в функции потерь, а -∞ и ∞ — «высокие» значения в функции потерь. Таким образом, отрицательный знак меняет диапазон на [0, ∞], что делает его более интуитивным. После применения отрицательного знака сеть работает хуже, когда потери больше положительного значения, и действительно хорошо работают, когда потери близки к 0. Отрицательный знак не делает ничего важного для обучения нейронной сети, но делает функция потерь стала намного более интуитивной и простой в отладке.

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

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

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

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

Начнем с визуализации базовой нейронной сети с двумя входами и одним выходом:

Диаграмма выше может выглядеть немного странно, но это потому, что вычисления z и a разбиты на разные узлы. Для простоты потеря равна значению z, но применяется та же математика. Подача вперед уже рассчитана, где потери составляют 4.

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

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

И снова это 1. Поскольку функция потерь равна a, частная производная функции потерь по a равна 1. В отличие от предыдущей производной функции потеря, это значение важно, даже если оно всего 1. Представьте себе производную потери BCE по отношению к ŷᵢ. Это значение не просто 1 и влияет на все остальные производные.

Теперь давайте вычислим производную функции активации, которая является ReLU. Производная ReLU очень проста, так как она равна 0, когда z‹0, и 1, когда z›0. Когда z равно 0, производная не определена, поэтому, как правило, мы говорим, что производная в этой точке равна 0. Ниже представлен обновленный график с производной убытка относительно z значение:

Поскольку значение z равно 4, что больше 0, производная от значения z равна 1. Но это всего лишь производная от z значение с точки зрения функции активации. Нам нужна производная от значения z по отношению к функции Loss. Для этого воспользуемся цепным правилом. Все, что нам нужно сделать для цепного правила, — это умножить ранее рассчитанные производные на новые, чтобы получить производную потерь по отношению к значению z. Конечное значение по-прежнему равно 1, но это потому, что производная функции активации равна 1.

Теперь давайте вычислим производную весов и смещений. Напомним, что формула значения z в этом примере выглядит следующим образом:

Ниже приведены производные для всех значений этой функции:

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

Давайте обновим диаграмму этими новыми производными:

Теперь, когда у нас есть производные весов и смещений, мы можем обновить веса и смещения. Поскольку мы хотим минимизировать потери, мы вычитаем значения весов и смещений по их производным. На практике производные весов и смещений умножаются на константу с именем α (альфа), которая используется для более стабильного обновления. α обычно находится в диапазоне от некоторого значения, превышающего 0, до 1. Таким образом, мы уменьшаем количество обновлений весов и смещений. В этом случае давайте использовать α = 0,1. Ниже приведены формулы, используемые для обновления каждого параметра:

Итак, давайте обновим диаграмму новыми весами и смещениями:

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

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

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

Ниже приведен код Python, который обновляет сеть 100 раз. Обратите внимание, как потери уменьшаются и приближаются к 0, что является наименьшим возможным значением потерь.

import numpy as np

# The parameters
x1 = 2
x2 = -4
w1 = 0.5
w2 = -0.5
b = 1
alpha = 0.1
# Loop 100 times which is the numebr of times
# an update will happen to the network
for i in range(0, 100):
    # Forward feed
    z = x1*w1 + x2*w2 + b
    a = max(0, z)
    Loss = a
    print(f"Loss at step #{i+1} : {Loss}")
    
    # Backpropagation
    dLoss = 1
    da = 1
    dz = (1 if a >= 0.5 else 0)
    dLoss_dz = dz*da
    dx1 = w1
    dw1 = x1
    dx2 = w2
    dw2 = x2
    db = 1

    dLoss_dx1 = dx1*dz*da
    dLoss_dw1 = dw1*dz*da
    dLoss_dx2 = dx2*dz*da
    dLoss_dw2 = dw2*dz*da
    dLoss_db = db*dz*da

    # Update the parameters
    w1 = w1 - alpha*w1
    w2 = w2 - alpha*w2
    b = b - alpha*b

Это основная идея нейронной сети (в частности, многослойного восприятия (MLP)). Существует много других типов нейронных сетей, таких как сверточная нейронная сеть (CNN), которая работает с изображениями, или рекуррентная нейронная сеть (RNN), которая работает с текстом, но модель, которую мы только что рассмотрели, является основой почти всех других моделей нейронных сетей. .

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