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

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

как цифры 5041. Давайте начнем!

Нейронные сети

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

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

Для выходного слоя логично иметь 10 нейронов, пронумерованных от 0 до 9 для представления каждой цифры. Мы обозначим желаемый результат для входных данных x как y(x), а фактический результат нашей сети — как a. И y(x), и a могут быть представлены в виде 10-мерных векторов. Например, если бы у нас было изображение 2, то y(x) было бы [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]ᵀ. Что касается a, мы принимаем нейрон с самым высоким значением активации в качестве предсказания сети.

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

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

Обучение с градиентным спуском

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

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

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

Этой конкретной функцией стоимости является квадратичная функция стоимости или среднеквадратическая ошибка (есть и другие, например функция кросс-энтропийной стоимости).

В каком смысле наша функция стоимости измеряет ошибку нашей сети? Когда выход нашей сети близок к желаемому результату, тогда ay(x)и, таким образом, C(w, b) маленький. Когда выход нашей сети очень далек, тогда y(x)a становится большим и, в свою очередь, стоимость тоже. Имея в виду эти результаты, наша цель получить хороший набор весов и смещений становится вопросом минимизации функции стоимости.

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

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

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

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

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

Чтобы добраться до дна, наш путешественник хочет двигаться в направлении с самым крутым спуском. Математически мы можем вычислить вектор градиента ∇C, который дает нам направление наискорейшего подъема, и просто двигаться в противоположном направлении. Если вы не знакомы с градиентами, то их компоненты являются частными производными функции, поэтому для нас ∇C = [∂C/∂v₁ , ∂C /v₂]ᵀ.

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

Объединив наш градиент и скорость обучения, мы можем вывести правило обновления для нашего туриста, чтобы достичь дна долины. В точке v в долине наш путешественник должен перейти к v’, что определяется как:

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

Так как же применить градиентный спуск к обучению нейронных сетей? Помните, что наша цель с нашей нейронной сетью — минимизировать ошибку, которая является функцией наших весов и предубеждений. Видите отношения еще? Наши переменные v — это веса и смещения, а наш вектор градиента ∇C имеет компоненты ∂C/∂wиC/bвместо этого.

Таким образом, наши правила обновления становятся такими:

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

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

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

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

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

Наше правило обновления для наших весов и смещений становится следующим:

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

Резюме

И это конец 2 части! Вот хорошее резюме всего, что мы обсуждали:

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

В следующей статье мы рассмотрим обратное распространение — последний ключ к полноценной нейронной сети!