PCA: как использовать на практике и что под капотом?

May 06 2023
PCA (анализ главных компонент) — один из самых простых методов уменьшения размерности. Может быть очень удобно в тех случаях, когда многомерные данные необходимо визуально оценить в меньшем пространстве, чем исходное измерение данных.
Фото BoliviaInteligenteна Unsplash

PCA (анализ главных компонент) — один из самых простых методов уменьшения размерности. Может быть очень удобно в тех случаях, когда многомерные данные необходимо визуально оценить в меньшем пространстве, чем исходное измерение данных. Или, например, когда вы хотите быстро уменьшить количество предикторов, подаваемых в какой-то ML-алгоритм, но таким образом, чтобы оставшиеся предикторы объясняли наибольшую долю дисперсии (читай, сохраняли наибольший объем информации) исходного набор данных.

Эта статья разделена на 2 раздела. В первом разделе будет продемонстрировано, как можно применять PCA с помощью python и scikit-learn к искусственному набору данных. Во втором разделе будет показано, как шаг за шагом, используя только карандаш и лист бумаги, сделать то же самое, что и аналитические структуры.

Раздел 1 — прикладное использование PCA

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

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

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

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

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

Рисунок 1 — диаграмма разброса second_from_login и ratio_balances

Что касается переменных, описывающих количество просмотренных страниц в приложении и количество часов, прошедших с момента регистрации клиента, то мы будем рассматривать их как непрерывные переменные. Например, если количество просмотренных страниц равно 2,134, это означает, что третья страница была прокручена только на 13,4%. Для этих переменных сделаем такое же допущение, что их значения должны различаться между классом мошенников и обычными клиентами:

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

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

Рисунок 2 — первые 5 строк синтетических данных

Отлично, у нас есть набор данных со 104 различными непрерывными переменными и 1 меткой. Теперь мы хотим отобразить их в меньшем пространстве. Например, по прямой или по плоскости, чтобы отразить как можно больше полезной информации. С помощью python и scikit-learn это легко. Все, что для этого нужно сделать, — это создать объект класса PCA, указав количество главных компонент, по которым будет сжат исходный набор данных. То есть, если мы укажем 1 компонент, то все 104 переменные будут сжаты таким образом, чтобы отображались на 1 оси, сохраняя максимально возможную информацию; если укажем 2, то по 2 осям и так далее. Как создаются компоненты и как именно сжимается пространство, будет объяснено во втором разделе со всей математикой, стоящей за этим.

Рисунок 3 — Отражение X на 1 основном компоненте

Рисунок 4 — X отражается на 2 основных компонентах

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

Теперь давайте выясним, какой процент дисперсии объясняется с помощью двух основных компонентов:

Рисунок 5 — объясненная дисперсия

Атрибут объясненная_вариантность_отношение_ для созданного объекта класса PCA возвращает процент всей объясненной дисперсии данных, где порядковый номер элемента списка является номером основного компонента.

Рисунок 6 — сумма объясненной дисперсии

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

Нужно ли вам ограничиваться 1–3 основными компонентами при использовании PCA? Конечно, нет. 1–3 компонента удобно визуализировать, чтобы понять какие-то базовые закономерности, если они есть, в ваших данных. Однако визуальный анализ — далеко не единственное применение PCA. Вы можете сжать пространство таким образом, чтобы сохранить как можно больше объяснимой дисперсии при минимальном количестве ваших функций. В этом случае вы уменьшите время обучения ваших алгоритмов.

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

Теперь мы будем обучать 2 алгоритма RandomForest без настройки гиперпараметров. Первый на исходном пространстве данных, второй на пространстве, сжатом с помощью PCA. Оценим время обучения, а также итоговое качество:

Рисунок 7 — Вывод полных данных РЧ

Рисунок 8 — Вывод сжатых данных RF

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

Раздел 2 — создание PCA с нуля

В этом разделе мы построим PCA шаг за шагом, используя математику, лежащую в основе этого метода. Настоятельно советую вам взять лист бумаги и ручку, потому что решая все самостоятельно, вы сможете намного лучше погрузиться в логику, которая есть в этом методе. Я также рекомендую посмотреть это видео от 3Blue1Brown, чтобы понять интуицию метода.

В качестве набора данных будем использовать двумерную матрицу 10x2, состоящую из 2-х векторов x и y:

Отразим этот набор данных на плоскости:

Рисунок 9 — x и y на плоскости

Обратите внимание на синий крест. Это точка, координаты которой являются средним значением x и y соответственно.

Шаг 1 — центрирование данных

Для центрирования набора данных нужно сделать так, чтобы среднее значение вектора x и y стало равным 0, для этого из каждого x[i] нужно вычесть среднее x, из каждого y[i] нужно вычесть среднее у:

Рисунок 10 — исходные и центрированные данные

Обратите внимание на синий крест. При центрировании он перемещается на место оранжевого креста и становится в начало системы координат (0; 0). Соответствующий сдвиг делает каждая синяя точка, занимающая место оранжевой.

Рисунок 11 — от исходных до центрированных данных

Шаг 2 — рассчитать ковариационную матрицу

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

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

Ковариация рассчитывается по следующей формуле, где n — длина соответствующего столбца:

Формула 1 — ковариация

Но так как мы уже центрировали данные, а средние x и y равны 0, то формула упрощается до вида:

Формула 2 - ковариация по центрированным данным

А сама ковариационная матрица будет выглядеть так:

Формула 3 — ковариационная матрица

Используя python, вычислить ковариационную матрицу очень просто, достаточно вызвать одну функцию:

Рисунок 12 — рассчитанная ковариационная матрица с помощью python

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

Уравнение 1

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

Шаг 3 — вычислить собственные значения ковариационной матрицы

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

На канале 3Blue1Brown есть отличное объяснение этой концепции. Здесь я постараюсь кратко объяснить суть на небольшом примере:

Рисунок 13 — отражение параллелограмма

У нас есть исходная фигура ABCD, которую мы зеркально отражаем по оси Y. В результате цифра занимает место AB*C*D*. Как видите, вектор x остался на той же оси, только изменил направление (стал x*). Следовательно, этот вектор является собственным вектором, и собственное значение этого вектора будет равно -1.

Теперь давайте посмотрим на вектор y и его зеркальное отражение — y*. Очевидно, что они лежат на совершенно разных осях. Следовательно, вектор y не является собственным вектором этого линейного преобразования.

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

Формула 4 — формула собственного вектора и собственного значения

где A — матрица линейного преобразования, вектор x — собственный вектор этой матрицы, lambda — собственное значение собственного вектора x.

В PCA матрица A — это ковариационная матрица cov , которую мы определили на шаге 2, поэтому наша формула будет следующей:

Формула 5— формула собственного вектора и собственного значения (2)

Решая это уравнение, находим собственные значения ковариационной матрицы:

Формула 6— характеристическое уравнение

В этом случае либо вектор x равен 0 (что для нас не интересно, так как мы ищем ненулевые векторы), либо матрица (cov-lambda*I) = 0 (и этот случай как раз тот, что нам нужно). Матрица может быть равна нулю, когда ее определитель равен 0. А определитель матрицы равен 0, когда в матрице пропорциональные или нулевые строки или столбцы, что как раз и свидетельствует о сжатии исходного пространства. Таким образом:

Уравнение 2

Решая это квадратное уравнение, находим его корни:

лямбда1 = 1,3

лямбда2 = 3,3

Давайте проверим расчеты с помощью numpy:

Рисунок 14 — собственные значения, вычисленные с помощью numpy

Отлично, мы нашли собственные значения примененного линейного преобразования! Теперь на их основе найдем сами собственные векторы.

Шаг 4 — найти собственные векторы ковариационной матрицы

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

Для лямбда1:

Уравнение 3

Допустим, x1 = 1, тогда:

Собственный вектор 1

Для лямбда2 считается аналогично. В ходе расчетов получаем, что второй собственный вектор будет равен:

Собственный вектор 2

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

Формула 7 — норма вектора

Подставляя в формулу координаты каждого собственного вектора, получаем, что норма первого вектора = 1,31, норма второго вектора 1,55. Разделив каждый собственный вектор на его норму, получим, что нормированные координаты первого вектора равны:

Собственный вектор 1 (нормализованный)

И нормированные координаты второго вектора:

Собственный вектор 2 (нормированный)

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

Матрица собственных векторов

Давайте убедимся, что наши расчеты верны, используя numpy:

Рисунок 15 — собственные векторы, рассчитанные numpy

С поправкой на ошибки округления, это именно те векторы, которые мы рассчитали!

Пусть вас не смущает тот факт, что наши векторы кажутся умноженными на -1, потому что это точно такие же векторы, так как они лежат на одной прямой. Для тех, кто хочет более подробных объяснений, на stackoverflow есть много топиков: link1 , link2 , link3 .

Шаг 5 — трансформируем исходное пространство

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

Рисунок 16 — PCA X своими руками

Убедитесь, что наши расчеты верны, используя класс PCA из sklearn:

Рисунок 17 — PCA X со sklearn

Учитывая ошибку округления, мы получили точно такую ​​же матрицу, которую возвращает sklearn PCA.

Подведение итогов

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