От перцептрона до Адалайна

От персептрона до Аделины

Установка правильных основ

Фото: Einar Storsul на Unsplash

Введение

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

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

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

Нам понадобятся некоторые формулы. Я использовал онлайн-редактор уравнений LaTeX для разработки кода LaTeX для уравнений, а затем плагин Chrome Maths Equations Anywhere для отображения уравнения в виде изображения. Единственным недостатком этого подхода является то, что код LaTeX не сохраняется на случай, если вам нужно его отрисовать снова. Для этой цели в конце статьи я предоставляю список уравнений. Если вам незнаком LaTeX, это может иметь свою образовательную ценность. Правильное использование обозначений – часть пути в машинном обучении.

Адаптивный линейный нейронный классификатор (адалайн)

Так что же такое алгоритм адалайн? Адалайн – как перцептрон, является двоичным классификатором. Прогноз делается с использованием набора входных значений для признаков [x₁, .. , xₘ], где m – количество признаков. Входные значения умножаются на веса [w₁, .. , wₘ], а затем добавляется смещение для получения суммарного входа z = w₁x₁ + .. + wₘxₘ + b. Суммарный вход передается в линейную функцию активации σ(z), которая затем используется для предсказания с помощью ступенчатой функции, как и у перцептрона:

Основное отличие адалайна от перцептрона заключается в том, что линейная функция активации используется для обучения весов, в то время как ступенчатая функция используется только для предсказания в конце. Кажется, что это маленькая вещь, но это имеет значительное значение. Линейная функция активации дифференцируема, в то время как ступенчатая функция – нет! Порог 0,5 выше не является жестким условием. Изменяя порог, мы можем регулировать точность и полноту в соответствии с нашими потребностями, то есть исходя из того, какая цена ложных положительных и ложных отрицательных ответов (ошибок) для нас.

В случае адалайна линейная функция активации просто является идентичностью, т.е. σ(z) = z. Целевая функция (также известная как функция потерь), которую необходимо минимизировать в процессе обучения, определяется следующим образом:

где w – веса

а b – смещение. Суммирование производится по всем примерам в обучающем наборе. В некоторых реализациях функция потерь также включает коэффициент 1/2 для удобства. Это сокращается, как только мы берем градиенты функции потерь по отношению к весам и смещению и, как увидим ниже, не оказывает никакого влияния, кроме как масштабирует скорость обучения в два раза. В этой статье мы не используем коэффициент 1/2.

Для каждого примера мы вычисляем квадратную разницу между рассчитанным результатом

и фактической классификацией. Обратите внимание, что входной вектор понимается как матрица с формой (1, m), то есть, как мы увидим позже, это одна строка нашей матрицы признаков x с формой (n, m).

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

В последней строке вводится важная матричная запись. Матрица признаков x имеет форму (n, m), и мы транспонируем ее столбец j, то есть, создаем матрицу формы (1, n). Фактические классификации y – это матрица формы (n, 1). Чистый результат всех образцов z также является матрицей формы (n, 1), которая не меняется после активации, которая применяется к каждому из ее элементов. Конечным результатом вышеприведенной формулы является скаляр. Можете ли вы догадаться, как мы могли бы выразить градиенты относительно всех весов с использованием матричной записи?

где транспонированная матрица признаков имеет форму (m, n). Конечным результатом этой операции является матрица формы (m, 1). Эта нотация важна. Вместо использования циклов мы будем использовать именно эту матричную умножение с использованием numpy. В эпоху нейронных сетей и графических процессоров способность применять векторизацию – это важно!

Что насчет градиента функции потерь по смещению?

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

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

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

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

Альтернативная формулировка и решение в замкнутой форме

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

при таком подходе нетто-выход для всех образцов в обучающем наборе становится

это означает, что матрица признаков была расширена на один столбец, заполненный единицами, что приводит к форме (n, m+1). Градиент относительно совокупного весового набора становится

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

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

Реализация адалайна на Python

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

Мы реализуем адалайн с помощью класса, который предоставляет функции fit и predict в привычном стиле scikit-learn API.

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

Использование адалайна на практике

Мы делаем необходимые импорты и создаем синтетический набор данных, как в предыдущей статье о перцептроне article

что приводит к

Scatterplot of the two classes in the synthetic dataset. Image by the Author.

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

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

Это создает график сходимости

Сходимость Адалайн. Изображение автора.

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

который создает

Граница решения, подогнанная моделью адалайна. Изображение автора.

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

Мини-пакетный vs полный пакетный градиентный спуск

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

который создает

Влияние размера пакета на сходимость (с использованием скорости обучения 0,001). Изображение автора.

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

Влияние размера пакета на сходимость (с использованием скорости обучения 0,002). Изображение автора.

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

Выводы

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

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

Код LaTeX уравнений, использованных в статье

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