Numpy - это продолжение моей серии статей о нейронных сетях. Чтобы просмотреть предыдущий блог этой серии или освежить в памяти нейронные сети, нажмите здесь.

Этот пост является продолжением статьи Понимание и создание нейронных сетей с вычислительными графами с нуля.

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

Понимание «двоичной классификации» поможет нам сформулировать основные концепции, которые помогут нам понять многие из вариантов, которые мы делаем в мульти-классификации, поэтому этот пост также послужит прелюдией к «Пониманию и созданию слоя Softmax с вычислительными графиками с нуля». ».

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

Часть Ⅰ: Понимание двоичной классификации

Давайте копаться

Бинарная классификация - распространенная задача машинного обучения. Он включает в себя прогнозирование того, является ли данный пример частью того или иного класса. Этим двум классам можно произвольно присвоить либо «0», либо «1» для математического представления, но чаще всего интересующему объекту / классу присваивается «1» (положительный ярлык), а остальным - «0» (отрицательный ярлык). Например:

  • На данном изображении изображен кот (1) или нет (0)?
  • Учитывая результаты теста пациента, является ли опухоль доброкачественной (0; безвредной) или злокачественной (1; опасной) ?
  • Учитывая информацию о человеке (например, возраст, уровень образования, семейное положение и т. Д.) В качестве характеристик, предскажите, будет ли он зарабатывать менее 50 тысяч долларов (0) или более 50 тысяч долларов. (1) в год.
  • Является ли данная электронная почта спамом (1) или не спамом (0)?

Во всех приведенных выше примерах интересующему объекту / классу присвоена положительная метка (1).

В большинстве случаев будет довольно очевидно, требует ли данная проблема машинного обучения двоичной классификации или нет. Общее практическое правило заключается в том, что двоичная классификация помогает нам ответить на вопросы типа "да" (1) / нет (0).

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

Давайте расширим эту нейронную сеть, чтобы раскрыть ее сложности.

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

  • Входные данные: x₁ и x₂ - это входные узлы для двух функций, которые представляют собой пример, который мы хотим, чтобы наша нейронная сеть учиться у. Поскольку входные узлы образуют первый уровень сети, они все вместе называются «входным слоем».
  • Веса: w₁ и w₂ представляют значения веса, которые мы связываем с входными данными x₁ и x₂, соответственно . Веса контролируют влияние каждого ввода на вычисление следующего узла. Нейронная сеть «учится» этим весам, чтобы делать точные прогнозы. Первоначально веса назначаются случайным образом.
  • Линейный узел (z): узел «z» создает линейную функцию из всех входящих в него входных данных т.е. z = w₁x₁ + w₂ x₂ + b
  • Смещение: «b» представляет узел смещения. Узел смещения вставляет добавочную величину в узел линейной функции (z). Как следует из названия, смещение изменяет вывод так, чтобы он лучше согласовывался с желаемым результатом. Значение смещения инициализируется на b = 0, и также изучается на этапе обучения.
  • Сигмоидальный узел: этот σ узел, называемый сигмоидальным узлом, принимает входные данные от предыдущего линейного узла (z ) и передает его через следующую функцию активации, называемую сигмовидной функцией (из-за ее S-образной кривой), также известной как логистическая функция :

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

Линейный узел (z) в сочетании с узлом смещения (b) и узлом активации, например, сигмовидный узел (σ), образует «нейрон» в искусственной нейронной сети .

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

Использование одиночного сигмоидного / логистического нейрона в выходном слое является основой нейронной сети с бинарной классификацией. Это связано с тем, что выходные данные сигмоидной / логистической функции можно удобно интерпретировать как оценочную вероятность (p̂, произносится p-hat ), что данный ввод относится к «положительному» классу. Как? Давайте углубимся немного глубже.

Сигмоидальная функция сжимает любой входной сигнал в выходной диапазон 0 ‹σ‹ 1. Так, например, если бы мы создавали детектор на основе нейронной сети «кошка (1) против не-кошки (0)», учитывая изображения в качестве входных примеров, наш выходной слой все равно будет одиночным Сигмовидный нейрон, преобразующий все вычисления из предыдущих слоев в простой выходной диапазон 0–1.

Тогда мы можем просто интерпретировать как «Какова вероятность того, что данное входное изображение принадлежит кошке?», где «cat» - это положительный ярлык. Если p̂≈0, то очень маловероятно, что на входе изображение кошки, с другой стороны, p̂≈1, то весьма вероятно, что на входе изображение кошки. Проще говоря, показывает, насколько уверенно наша модель нейронной сети предсказывает, что входными данными является кошка, то есть положительный класс (1).

Математически это можно описать просто как условную вероятность:

Поскольку каждая архитектура нейронной сети с бинарной классификацией имеет единственный сигмовидный нейрон в выходном слое, как показано на Рис.6 выше, выходные данные Сигмоид (оценочная вероятность) зависит от выходного сигнала линейного узла (z) , связанного с нейроном. Если значение линейного узла (z) равно:

  1. Больше нуля (z ›0), тогда выходной сигнал сигмоидального узла больше 0,5 (σ (z)› 0,5 ), что можно интерпретировать как «Вероятность того, что на входном изображении изображена кошка, больше, чем 50%».
  2. Меньше нуля (z ‹0), тогда выходной сигнал сигмоидального узла меньше 0,5 (σ (z)‹ 0,5) , что можно интерпретировать как «Вероятность того, что на входном изображении изображена кошка, меньше, чем 50%».
  3. Равно нулю (z = 0), тогда выходной сигнал сигмоидального узла равен 0,5 (σ (z) = 0,5) , что означает, что «Вероятность того, что на входном изображении изображена кошка, составляет ровно 50%».

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

Приведенные выше данные представляют логический вентиль И, где выходу присваивается положительная метка (1), только когда оба входа имеют значение x₁ = 1 и x₂ = 1, всем остальным случаям присваивается отрицательный ярлык (0 ). Каждая строка данных представляет собой пример, на котором наша нейронная сеть должна учиться, а затем классифицировать. Я также нанес точки на двумерную плоскость, чтобы их было легко визуализировать (красные точки представляют собой точки, в которых класс (y) равен 0, а зеленый крест представляет собой точку, в которой класс 1). Этот набор данных также оказывается линейно разделяемым, то есть мы можем провести прямую линию, чтобы отделить положительные помеченные примеры от отрицательных.

Синяя линия, показанная выше, называемая границей решения, разделяет наши два класса. Над линией - наш положительный помеченный пример (зеленый крест), а под линией - наши отрицательные помеченные примеры (красные кресты). За кулисами эта синяя линия образована узлом z (линейная функция). Позже мы увидим, как нейронная сеть изучает эту границу принятия решения.

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

Стохастический градиентный спуск

Вычисления в нейронной сети выполняются слева направо, это называется прямым распространением. Давайте пройдемся по всем прямым вычислениям, которые наша нейронная сеть будет выполнять при наличии только первого обучающего примера x₁ = 0 и x₂ = 0. Кроме того, мы случайным образом инициализируем веса на w₁ = 0,1 и w₂ = 0,6, а смещение на b = 0.

Итак, прогноз нейронной сети составляет p̂ = 0,5. Напомним, это нейронная сеть с двоичной классификацией, представляет собой оценочную вероятность того, что входной пример с функциями x₁ = 0 & x₂ = 0 , принадлежит к положительному классу (1). Наша нейронная сеть в настоящее время считает, что существует 0,5 (или 50%) шанс, что первый обучающий пример принадлежит к положительному классу (помните из уравнения вероятности, это равно P (1∣ x₁, x₂; w, b) = p̂ = 0,5 ).

Ой! Это довольно плохо 😕, особенно потому, что метка отрицательная связана с первым примером, то есть y = 0. Предполагаемая вероятность должна быть около p̂≈0; маловероятно, что первый пример принадлежит к положительному классу, поэтому вероятность принадлежности к отрицательному классу высока (т. е. P (0∣ x₁, x₂; w, b) ≈1-p̂≈1) .

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

Двоичная функция потери кросс-энтропии

Примечание: в большинстве языков программирования «log» - это натуральный логарифм (журнал с основанием- e), обозначаемый в математике как «ln ». Для согласованности между кодом и уравнениями считайте «log» натуральным логарифмом, а не «log₁₀» (журнал с основанием 10).

Функция потерь двоичной кросс-энтропии (BCE) определяется следующим образом:

Все функции потерь по существу сообщают нам, насколько далеко наш прогнозируемый результат от желаемого, только для одного примера. Проще говоря, функция потерь вычисляет ошибку между прогнозом и фактическим значением. Имея это в виду, функция двоичной кросс-энтропии (BCE) Loss вычисляет другую потерю, когда связанная метка обучающего примера y = 1 (положительный) и разные Потери, когда метка y = 0 (отрицательная). Давайте посмотрим:

Теперь очевидно, что функция BCE Loss на Рис.12 - это просто элегантно сжатая версия кусочного уравнения .

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

Таким образом, функция BCE Loss улавливает интуицию о том, что нейронная сеть должна заплатить высокий штраф (Loss → ∞), когда оценочная вероятность относительно метки обучающего примера, совершенно неправильно. С другой стороны, потеря должна равняться нулю (Loss = 0), если оценка вероятности относительно метки обучающего примера верна. Проще говоря, потеря BCE должна равняться нулю только в двух случаях:

  1. если пример имеет положительную метку (y = 1), модель нейронной сети должна быть полностью уверена, что пример принадлежит положительному классу, т. е. p̂ = 1.
  2. если пример имеет отрицательную метку (y = 0), модель нейронной сети должна быть полностью уверена, что пример не принадлежит положительному классу, т. е. p̂ = 0.

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

Мы также можем разбить производную на кусочную функцию и визуализировать ее эффекты:

Положительная производная означает уменьшение весов, а отрицательная - увеличение весов. Чем круче наклон (градиент), тем более неверным был прогноз. Давайте уделим время , чтобы убедиться, что мы понимаем это утверждение:

  • Если градиент отрицательный, это будет означать, что мы смотрим на первую кривую потерь, где фактическая метка для примера положительная (y = 1) . Единственный способ довести потери до нуля - это двигаться в направлении, противоположном наклону (градиенту), от отрицательного к положительному. Следовательно, нам нужно увеличить веса и смещения так, чтобы z = w₁x₁ + w₂x₂ + b ›0 (вспомните рис.8) и, в свою очередь, оценочная вероятность принадлежности к положительному классу: p̂≈σ (z) ≈1.
  • Точно так же, когда градиент положительный, мы смотрим на вторую кривую потерь, где фактическая метка для примера отрицательна (y = 0). Единственный способ довести потери до нуля - это снова двигаться в направлении, противоположном наклону (градиенту), на этот раз от положительного к отрицательному. В этом случае нам потребуется уменьшить веса и смещение так, чтобы z = w₁x₁ + w₂x₂ + b ‹0 и, следовательно, оценочная вероятность принадлежащий положительному классу p̂≈σ (z) ≈0.

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

Теперь, когда мы знаем назначение функции потерь и то, как работает функция двоичной кросс-энтропии потерь, давайте вычислим потерю BCE в нашем текущем примере (x₁ = 0 и x₂ = 0), для которого наша нейронная сеть оценила вероятность принадлежности к положительному классу p̂ = 0,5. а его метка (y) - y = 0:

Убыток составляет примерно 0,693 (округлено до 3 знаков после запятой). Теперь мы можем использовать производную функции BCE Loss, чтобы проверить, нужно ли нам увеличивать или уменьшать веса и смещение, используя процесс, называемый обратное распространение; это противоположно прямому распространению, мы отслеживаем назад от вывода к вводу. Обратное распространение позволяет нам выяснить, сколько потерь несет каждая часть нейронной сети, а затем мы можем соответствующим образом скорректировать эти части нейронной сети.

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

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

Давайте шаг за шагом пройдемся по обратному распространению:

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

Теперь давайте воспользуемся производным узла сигмоида и продолжим обратное распространение градиента:

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

Наконец, мы можем обновить параметры (веса и смещение) нейронной сети, выполнив градиентный спуск.

Градиентный спуск

Градиентный спуск - это настройка параметров нейронной сети путем перемещения в отрицательном направлении градиента, то есть от наклонной области к более плоской .

Общее уравнение градиентного спуска:

Скорость обучения, α (произносится как альфа) ,, используется для управления размером шага вниз по кривой потерь (Рис. 21). Скорость обучения - это гиперпараметр нейронной сети, что означает, что она не может быть изучена посредством обратного распространения градиентов и должна быть установлена ​​создателем нейронной сети. сеть, в идеале после некоторых экспериментов. Для получения дополнительной информации о влиянии скорости обучения вы можете обратиться к моему предыдущему сообщению.

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

Мы установим скорость обучения (α) на α = 1.

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

Теперь оценочная вероятность для примера 1ˢᵗ, принадлежащего к положительному классу (), снизилась с 0,5 примерно до 0,378 (округлено до 3 dp), и, следовательно, потеря BCE также немного снизилась с 0,693 до примерно 0,475 (до 3 dp).

До сих пор мы выполняли стохастический градиентный спуск. Мы использовали только один пример (x₁ = 0 и x₂ = 0 ) из нашего набора данных логического элемента И из четырех примеров для выполнения одной обучающей итерации (каждая обучающая итерация - это прямое распространение, вычисление потерь с последующим обратным распространением и обновлением весов посредством градиентного спуска).

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

Пакетный градиентный спуск

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

Пакет - это просто вектор / матрица, полная обучающих примеров.

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

Функция стоимости двоичной кросс-энтропии

Для пакетного градиентного спуска нам необходимо настроить функцию потерь двоичной кросс-энтропии (BCE), чтобы учесть не только один пример, но и все примеры в пакете. Эта скорректированная функция потерь называется функцией Cost (также обозначается буквой J в нейронной сети. литературе и иногда также называется целевой функцией).

Вместо расчета убытка на одном примере функция Cost вычисляет средний убыток по ВСЕ примеры в партии.

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

Производная бинарной функции кросс-энтропии стоимости

В векторизованном виде наша функция BCE Cost выглядит следующим образом:

Как и ожидалось, стоимость - это просто среднее значение потерь для двух примеров, но все наши вычисления векторизованы, что позволяет нам вычислить стоимость двоичной кросс-энтропии для партии за один раз. Мы предпочитаем использовать векторизованные вычисления в нейронных сетях, поскольку компьютерное оборудование (CPU и GPU) лучше подходит для пакетных вычислений в векторизованной форме. (Примечание: если бы у нас был только один пример в пакете, стоимость BCE просто рассчитывала бы потери BCE, как в примере стохастического градиентного спуска, который мы рассмотрели ранее)

Затем давайте выведем частные производные этой векторизованной функции затрат.

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

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

Для векторизованных (пакетных) вычислений нам необходимо настроить линейный узел (z) нейронной сети так, чтобы он принимал векторизованные входные данные и использовал функцию стоимости вместо Функция потерь, тоже по той же причине.

Узел Z теперь вычисляет скалярное произведение между матрицей весов соответствующего размера (W ) и данные обучения (X). Выходные данные узла Z теперь также являются вектором / матрицей.

Теперь мы можем настроить наши данные (X, W, b & Y) для векторизованных вычислений.

Теперь мы, наконец, готовы выполнить прямое и обратное распространение с использованием Xₜᵣₐᵢₙ, Yₜᵣₐᵢₙ, W, и b.

(ПРИМЕЧАНИЕ. Все результаты ниже для краткости округлены до трех десятичных знаков)

Посредством векторизованных вычислений мы выполнили прямое распространение; вычисление всех предполагаемых вероятностей для каждого примера в партии за один раз.

Теперь мы можем рассчитать стоимость BCE на основе этих оценочных вероятностей выходных данных (P ̂). (Ниже для наглядности я выделил части функции затрат, которые вычисляют потери на положительных примерах синим, а отрицательные - красным)

Итак, Стоимость с нашими текущими весами и смещением составляет приблизительно 0,720. Наша цель сейчас - уменьшить эту стоимость с помощью обратного распространения ошибки и градиентного спуска. Давайте пройдемся по шагам обратного распространения ошибки.

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

(Для тех, кто не понимает, как вычисляются ∂Cost / ∂W и ∂Cost / ∂b на последнем шаге обратного распространения, обратитесь к моему предыдущему блогу, где я разбиваю это вычисление, более конкретно, почему производные от скалярных произведений в результате транспонированные матрицы)

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

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

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

  • ∂Cost / ∂W = [-0,002, 0,027]
  • ∂Cost / ∂b = [0,239]

Примерно после 5000 эпох (эпоха завершена, когда нейронная сеть проходит все обучающие примеры в обучающей итерации) Стоимость неуклонно снижается примерно до 0,003 , наши веса устанавливаются примерно на W = [10.678, 10.678], смещение разрешается примерно до b = [-16.186]. По кривой затрат ниже мы видим, что сеть сошлась с хорошим набором параметров (т.е. W & b):

Кривая затрат (или кривая обучения) - это динамика модели нейронной сети с течением времени. Это стоимость, отображаемая после каждых нескольких итераций обучения (или эпох). Обратите внимание, как быстро Стоимость сначала уменьшается, а затем становится асимптотической. Вспомните Рис. 21, потому что изначально величина градиента велика, но когда мы спускаемся для более плоской области около минимальной стоимости величина градиента уменьшается, и дальнейшее обучение лишь незначительно улучшает параметры нейронной сети.

После того, как нейронная сеть была обучена в течение 5000 эпох, прогнозируемые вероятности выхода () на Xₜᵣₐᵢₙ:

[[9.46258077e-08,  4.05463814e-03,  4.05463814e-03, 9.94323194e-01]]

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

  1. для x₁ = 0, x₂ = 0 прогнозируемый результат равен p̂≈ 9,46 × 10⁻ ⁸≈0,0000000946
  2. для x₁ = 0, x₂ = 1 прогнозируемый результат равен p̂≈ 4,05 × 10⁻ ³≈0,00405
  3. для x₁ = 1, x₂ = 0 прогнозируемый результат равен p̂≈ 4,05 × 10⁻ ³≈0,00405
  4. для x₁ = 1, x₂ = 1 прогнозируемый результат равен p̂≈ 9,94 × 10⁻ ¹≈0,994

Напомним, что метки: y = [0, 0, 0, 1]. Итак, только для последнего примера нейронная сеть на 99,4% уверена в своей принадлежности к положительному классу, для остальных - менее чем на 1%. Кроме того, помните уравнения вероятности из Рис.7? P (1) = p̂ и P (0) = 1-p̂, поэтому прогнозируемые вероятности () подтверждают, что наша нейронная сеть знает, что делает 👌.

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

Порог классификации

В задачах бинарной классификации обычно классифицируют все прогнозы нейронной сети по положительному классу (1), если предполагаемая вероятность (p̂ ) больше определенного порога и аналогично отрицательному классу (0), если оценочная вероятность ниже порога.

Математически это можно записать следующим образом:

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

Функция Sigmoid / Logistic предоставляет нам естественное пороговое значение. Вспомните предыдущий рисунок 8.

Итак, с естественным порогом 0,5 классы можно предсказать следующим образом:

Как мы это интерпретируем? Хорошо, если нейронная сеть составляет не менее 50% (0,5 ) уверен, что входные данные относятся к положительному классу (1), то мы отнесем его к положительному классу (ŷ = 1 ), в противном случае мы отнесем его к отрицательному классу (ŷ = 0).

Вспомните, как мы предсказали на Рис.10, что нейронная сеть может разделить два класса в наборе данных логического элемента И, нарисовав линию, разделяющую положительный класс (зеленый крест) и отрицательный класс. класс (красные кресты). Ну, положение этой линии определяется нашим пороговым значением. Посмотрим:

Вспомните, что после тренировки наши веса и смещение приблизились к значениям W = [10,678, 10,678] и b = [-16,186] соответственно. Давайте подставим их в неравенство, полученное на рис. 43, выше.

Кроме того, понимание этого неравенства дает нам уравнение линии, разделяющей наши два класса:

Это уравнение линии, отмеченной на Рис.45, образует Границу принятия решения. Граница принятия решения - это линия, по которой нейронная сеть меняет свой прогноз с положительного на отрицательный класс и наоборот. Все точки (x₁, x₂), попадающие на линию, имеют предполагаемую вероятность ровно 50%, т.е. p̂ = 0,5 , все точки над ней имеют оценочную вероятность более 50%, т. е. p̂ ›0,5, и все точки, которые опускаются ниже линии, имеют оценочную вероятность меньше чем 50%, т.е. p̂ ‹0,5.

Мы можем визуализировать границу решения, закрасив область зеленым, где нейронная сеть предсказывает положительный класс (1) и красный, где нейронная сеть предсказывает отрицательный класс (0).

В большинстве случаев мы можем установить пороговое значение 0,5 в задачах двоичной классификации. Итак, что можно сделать после такого глубокого понимания порогового значения? Должны ли мы просто установить его на 0,5 и забыть об этом? НЕТ! В некоторых случаях вы хотите, чтобы пороговое значение было высоким, например, если вы создаете модель нейронной сети для обнаружения рака, вы хотите, чтобы ваша нейронная сеть была очень надежной, возможно, не менее 95% (0,95) или даже 99% (0,99), что пациент болен раком, потому что в противном случае ему, возможно, придется пройти токсическую химиотерапию бесплатно. С другой стороны, для модели нейронной сети с детектором кошек может быть установлен низкий порог, около 0,5 или около того, потому что даже если нейронная сеть неверно классифицирует кошку, это всего лишь забавная случайность, без вреда - нет фола.

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

После обучения нейронной сети на четырех рисунках выше я построил границу решения (слева), затемненную границу решения (в центре) и кратчайшее расстояние каждой точки от границы решения (справа) с порогом классификации в диапазоне от От 0,000000001 до 0,9999

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

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

До сих пор мы многому научились, верно? 😅 По большей части мы знаем почти все о задачах двоичной классификации и о том, как их решать с помощью нейронных сетей. К сожалению, у меня плохие новости, наша функция Binary Cross-Entropy Loss имеет серьезный вычислительный недостаток, она очень нестабильна в своем текущем виде😱.

Не волнуйся! С помощью простых вычислений мы сможем решить эту задачу.

Реализация двоичной функции кросс-энтропии

Давайте еще раз посмотрим на функцию потери двоичной кросс-энтропии (BCE):

Обратите внимание на кусочное уравнение, что все характеристики функции двоичной кросс-энтропийной потери зависят от функции «log» (напомним, «log» здесь натуральный логарифм).

Построим функцию log и визуализируем ее характеристики:

Функция log в Binary Cross-Entropy Loss определяет, когда нейронная сеть платит высокий штраф (Loss → ∞ ) и когда нейронная сеть верна (Loss → 0). Домен функции log - 0 ‹x‹ ∞, а ее диапазон неограничен -∞ ‹log (x)‹ ∞ , что еще более важно, поскольку x становится все ближе и ближе к нулю (x → 0), значение log ( x) стремится к отрицательной бесконечности (log (x) → -∞). Таким образом, небольшие изменения в значениях, близких к нулю, имеют сильное влияние на результат функции двоичной кросс-энтропийной потери, кроме того, наши компьютеры могут хранить числа только с определенной точностью с плавающей запятой, и когда есть функции, стремящиеся к бесконечности, вызывают числовое переполнение (переполнение - это когда число слишком велико для хранения в памяти компьютера, а недостаточное - когда число слишком мало) в компьютерах. Оказывается, сильная сторона функции двоичной кросс-энтропии, функции log, также является ее слабостью, что делает ее нестабильной вблизи малых значений.

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

Рассмотрим следующий пример:

Точно так же при вычислении градиентов для приведенного выше примера:

Теперь посмотрим, как это исправить:

Мы успешно вывели функцию натурального логарифма (log) из опасной зоны! Диапазон «1 + e⁻» больше 1 ( т.е. 1 + e ⁻ ᶻ ›1) в результате диапазон функции« log »в потере BCE становится больше 0 ( т.е. log (1 + e⁻ ᶻ) ›0). Общая функция двоичной кросс-энтропии больше не является критически нестабильной.

На этом мы можем остановиться, но давайте сделаем еще один шаг и еще больше упростим функцию потерь:

Мы значительно упростили выражение двоичной кросс-энтропии (BCE), но с этим возникла проблема. Сможете ли вы угадать это, глядя на кривую «1 + e⁻ ᶻ» из Рис.53?

Выражение «1 + e⁻ ᶻ» стремится к бесконечности для отрицательных значений ( т.е. 1 + e⁻ ᶻ → ∞ , when z ‹0 )! Итак, к сожалению, это упрощенное выражение переполняется при обнаружении отрицательного значения. Попробуем это исправить.

Теперь с помощью этого выражения «eᶻ + 1» мы решили проблему нестабильности логарифмической функции при отрицательных значениях. К сожалению, теперь мы сталкиваемся с противоположной проблемой: новая функция двоичной кросс-энтропии потерь нестабильна для больших положительных значений 😕, потому что «eᶻ + 1» стремится к бесконечности для положительных значений ( т.е. eᶻ + 1 → ∞, когда z ›0 )!

Представим себе два экспоненциальных выражения:

Нам нужно как-то объединить эти две упрощенные функции (на Рис.54 и 56) в одну двоичную крестовину. -Энтропия (BCE), чтобы общая функция потерь была стабильной для всех значений, положительных и отрицательных.

Подтвердим, что он правильно вычисляет отрицательные и положительные значения:

Найдите минутку, чтобы понять это, и попробуйте соединить это вместе с кусочно-устойчивой функцией двоичной кросс-энтропии потерь из Рис.58.

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

Обратите внимание, что предыдущая «нестабильная» функция двоичных кросс-энтропийных потерь принимала в качестве входных данных метка (y) и вероятности от последнего сигмовидного узла (), но новый Функция стабильной двоичной кросс-энтропии потерь принимает в качестве входной метки (y) и значения из последнего линейного узла (z). То же самое и со стабильной функцией затрат.

Теперь, когда у нас есть стабильная функция BCE Loss и соответствующая ей функция BCE Cost, как нам найти стабильный градиент функции двоичной кросс-энтропии ?

Этот ответ все время был у всех на виду!

Вспомните производную функции двоичной кросс-энтропии (Рис.15):

Также помните, что во время обратного распространения эта производная течет в сигмовидный узел и умножается на локальный градиент в сигмовидном узле, который является просто производной сигмовидной функции (Рис.19.b. ):

Когда мы умножаем две производные, возникает красивая математика:

Таким образом, для вычисления производной ∂Loss / ∂z нам даже не нужно вычислять производную функции потерь или производную сигмовидного узла, вместо этого мы можем просто обойти сигмовидный узел и передайте «p̂-y» в качестве восходящего градиента к последнему линейному узлу (z) !

Эта оптимизация имеет два больших преимущества:

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

Что такое насыщающий градиент? Напомним кривую сигмовидной функции

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

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

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

Чтобы классифицировать точки данных набора данных XOR, мы будем использовать следующую архитектуру нейронной сети:

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

Давайте расширим эту двухслойную нейронную сеть, прежде чем приступить к прямому и обратному распространению:

Теперь мы готовы выполнить пакетный градиентный спуск, начиная с пересылки:

Теперь мы можем рассчитать стабильную стоимость:

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

Черт, это было много! 😅 Но теперь мы знаем все о нейронной сети двоичной классификации. Наконец, перейдем к градиентному спуску и обновим наши веса.

На этом этапе, если вы хотите самостоятельно выполнить следующую итерацию обучения и углубить свое понимание, ниже приведены приблизительные градиенты, которые вы должны получить (с округлением до 3 d.p):

Итак, после 5000 эпох Стоимость неуклонно снижается примерно до 0,0017, и мы получаем следующую кривую обучения и Граница принятия решения, когда для порогового значения классификации установлено значение 0,5 (в разделе кодирования вы можете поиграть с пороговым значением и посмотреть, как оно влияет на границу решения):

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

1- Разве это не просто логистическая регрессия?

Да, нейронная сеть всего с одним сигмовидным нейроном и без скрытых слоев, как на Рис.1, представляет собой логистическую регрессию . Нейронная сеть с одним сигмовидным нейроном / логистическая регрессия может классифицировать более простые наборы данных, которые можно разделить прямой линией (например, данные логического элемента AND). Для сложных наборов данных (таких как XOR) необходимо вручную выполнить проектирование функций, чтобы нейронная сеть / логистическая регрессия с одним сигмовидным нейроном работала. адекватно (объяснено в предыдущем посте).

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

Кроме того, функция стоимости двоичной кросс-энтропии для нейронной сети / логистической регрессии с одним сигмовидным нейроном является выпуклой (u-образной) с гарантированной точкой глобального минимума. С другой стороны, для глубокого нейронная сеть, функция стоимости двоичной кросс-энтропии не гарантирует наличие глобального минимума; Практически это не оказывает серьезного влияния на обучение глубоких нейронных сетей, и исследования показали, что это можно смягчить с помощью большего количества обучающих данных.

2- Можем ли мы использовать исходные вероятности выхода как есть?

Да, также можно использовать необработанные вероятности нейронной сети, в зависимости от типа проблемы, которую вы пытаетесь решить. Например, вы обучаете модель двоичной классификации для прогнозирования вероятности автомобильной аварии на перекрестке в день, P (авария ∣ день). Предположим, вероятность равна P (авария ∣ день) = 0,08. Итак, через год на этом перекрестке мы можем ожидать:

P (авария ∣ день) × 365 = 0,08 × 365 = 29,2 аварии

3- Как найти оптимальный порог классификации?

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

К сожалению, в реальных случаях точность сама по себе является плохой метрикой. Это особенно очевидно в случаях, когда классы перекошены в наборе данных (проще говоря, примеров одного класса больше, чем другого). Схема AND, как мы видели ранее, также страдала от этой проблемы; o только один пример положительного класса, остальная часть отрицательного класса. Если вернуться назад и посмотреть на рис.47.d. , где мы установили настолько высокий порог классификации (0,9999), что модель предсказала отрицательный класс для всех наших примеров, вы увидите, что точность модели по-прежнему 75%! Звучит вполне приемлемо, но, судя по данным, это не так.

Рассмотрим другой случай, когда вы обучаете модель обнаружения рака, но в вашем наборе данных на 1000 пациентов есть только один пример пациента с раком. Теперь, если модель всегда выводит отрицательный класс (т. Е. не рак, 0), независимо от ввода, у вас будет классификатор с 99,9% точности набора данных!

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

Точность. Сколько положительных прогнозов сделал классификатор? (Истинные положительные результаты / общее количество прогнозируемых положительных результатов)

Вспомните. Какую долю положительных примеров удалось идентифицировать классификатору? (Истинные положительные результаты / Общее количество фактических положительных результатов)

Обе эти метрики можно визуализировать с помощью матрицы 2 × 2, называемой «матрицей неточностей»:

Настройка порога классификации - это борьба между Precision и Recall. Если Точность высока (т. Е. Высокий порог классификации), Отзыв будет низким, и наоборот. Понимание компромисса между точностью и отзывом - это тема, которая выходит за рамки статьи и станет темой будущего Nothing but Numpy блог.

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

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

4- Откуда взялась эта двоичная функция потери кросс-энтропии?

5- Почему бы просто не использовать функцию среднеквадратичной ошибки (MSE), как в прошлом блоге? В конце концов, вы смогли решить те же примеры с помощью MSE .

6- Как Tensorflow и Keras реализуют двоичную классификацию и функцию двоичной кросс-энтропии (бонус)?

На этом завершается Часть Ⅰ.

Часть Ⅱ: Кодирование нейронной сети с модульной двоичной классификацией

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

Код для класса Linear Layer остается прежним.

Код для класса Sigmoid Layer также остается прежним:

Функция стоимости двоичной кросс-энтропии (BCE) (и ее варианты) являются основным новым дополнением к кодовой форме в прошлый раз.

Во-первых, давайте посмотрим на «нестабильную» функцию двоичной кросс-энтропии стоимости compute_bce_cost(Y, P_hat), которая принимает в качестве аргументов истинные метки (Y) и вероятности из последнего сигмовидного слоя (P_hat). Эта простая версия функции Cost возвращает нестабильную версию стоимости двоичной кросс-энтропии (cost) и ее производную по вероятностям (dP_hat):

Теперь давайте посмотрим на стабильную версию функции двоичной кросс-энтропии стоимости compute_stable_bce_cost(Y, Z), которая принимает в качестве аргумента истинные метки (Y) и выходные данные последнего линейного слоя (Z). Эта функция Cost возвращает стабильную версию стоимости двоичной кросс-энтропии (cost), рассчитанную TensorFlow, и производную по последнему линейному слою (dZ_last):

Наконец, давайте также посмотрим, как Keras реализует функцию стоимости двоичной кросс-энтропии. compute_keras_like_bce_cost(Y, P_hat, from_logits=Flase принимает в качестве аргументов истинные метки (Y), выходные данные последнего линейного слоя (Z) или последнего сигмовидного слоя (P_hat) в зависимости от необязательного аргумента from_logits. Если из from_logtis=Flase (по умолчанию), то все предполагают, что P_hat содержит вероятности, которые необходимо преобразовать в логиты для вычисления функции стабильной стоимости. Если из from_logtis=True, тогда все предполагают, что P_hat содержит выходные данные из линейного узла (Z), и функция стабильной стоимости может быть вычислена напрямую. Эта функция возвращает стоимость (cost) и производную по последнему линейному слою (dZ_last).

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

Мы будем использовать набор данных о цветках ириса, который оказался одним из первых наборов данных, созданных для статистического анализа. Набор данных Iris содержит 150 примеров цветков ириса, принадлежащих 3 видам - ​​Iris-setosa, Iris-versicolor и Iris-virginica. Каждый пример имеет 4 характеристики - длину лепестка, ширину лепестка, длину чашелистика и ширину чашелистника.

Для нашей первой нейронной сети двоичной классификации мы создадим однослойную нейронную сеть, как на Рис.1, чтобы различать Iris-virginica и другие, используя только длина лепестка и ширина лепестка в качестве входных характеристик. Итак, давайте построим слои нейронной сети:

Теперь мы можем перейти к обучению нашей нейронной сети:

Обратите внимание, что мы передаем производную dZ1 прямо в линейный слой Z1.backward(dZ1), минуя сигмовидный слой A1, из-за оптимизации, которую мы придумали ранее.

После выполнения цикла для 5000 эпох в записной книжке мы видим, что стоимость неуклонно снижается примерно до 0,080.

Cost at epoch#4700: 0.08127062969243247
Cost at epoch#4800: 0.08099585868475366
Cost at epoch#4900: 0.08073032792428664
Cost at epoch#4999: 0.08047611054333165

Результатом является следующая кривая обучения и граница принятия решения:

Точность нашей модели по обучающим данным составляет:

The predicted outputs of first 5 examples: 
[[ 0.  0.  1.  0.  1.]]
The predicted prbabilities of first 5 examples:
 [[ 0.012  0.022  0.542  0.     0.719]]
The accuracy of the model is: 96.0%

Ознакомьтесь с другими записными книжками в репозитории. Мы будем опираться на то, что узнали в этом блоге, в будущих блогах Nothing but NumPy, поэтому вам следует создать классы слоев (если вы этого не сделали раньше) и двоичный крест. -Энтропия Cost работает по памяти в качестве упражнения и попробуйте воссоздать пример логического элемента И из части .

На этом блог завершается🙌🎉. Спасибо, что нашли время прочитать этот пост, надеюсь, вам понравилось.

По любым вопросам обращайтесь ко мне в Twitter @RafayAK

Этот блог был бы невозможен без следующих ресурсов и людей: