Многомерные методы анализа данных – методы работы с данными, в которых много колонок. Мы уже сталкивались с некоторыми многомерными методами, например, с множественной линейной регрессией (Глава 21.5). Поэтому вы знаете, что многомерность создает новые проблемы. Например, при множественных корреляциях или попарных сравнениях возникает проблема множественных сравнений, а при использовании множественной регрессии лишние предикторы могут ловить только шум и приводить к переобучению (если говорить в терминах машинного обучения). Короче говоря, больше - не значит лучше. Нужно четко понимать, зачем мы используем данные и что пытаемся измерить.
Однако в некоторых случаях мы в принципе не можем ничего интересного сделать с маленьким набором переменных. Много ли мы можем измерить личностным тестом с одним единственным вопросом? Можем ли мы точно оценить уровень интеллекта по успешности выполнения одного единственного задания? Очевидно, что нет. Более того, даже концепция интеллекта в современном его представлении появилась во многом благодаря разработке многомерных методов анализа! Ну или наоборот: исследования интеллекта подстегнули развитие многомерных методов.
24.1 Уменьшение размерности
Представьте многомерное пространство, где каждая колонка — это отдельная ось, а каждая строка задает координаты одной точки в этом пространстве. Мы получим многомерную диаграмму рассеяния.
Многомерную диаграмму рассеяния, к сожалению, нельзя нарисовать, поэтому нарисуем несколько двухмерных диаграмм рассеяния для отображения сочетания всех колонок со всеми набора данных penguins.
library(tidyverse)
Warning: package 'dplyr' was built under R version 4.2.3
Warning: package 'stringr' was built under R version 4.2.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.4.4 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.0
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Даже представить как устроены многомерные данные очень трудно, а ведь мы отобразили только четыре числовых колонки! Понять связи между отдельными переменными мы можем, если посмотрим на каждую диаграмму рассеяния по отдельности, но и то если их не слишком много. Но связи в многомерных данных могут быть гораздо более сложными и на простых диаграммах рассеяния их иногда нельзя заметить. Это как смотреть на тени, поворачивая какой-нибудь предмет различными сторонами.
Уменьшение размерности позволяет вытащить из данных самую мякотку, уместив исходные данные в небольшое количество переменных. Остальное из данных удаляется.
Осторожно: уменьшение размерности приводит к потере данных
Уменьшение размерности – не “бесплатная” операция. Уменьшение размерности всегда приводит к потере части данных и может значительно их изменить.
Данные сниженной размерности гораздо проще визуализировать. Например, снизив размерность данных до двух шкал, можно просто нарисовать диаграмму рассеяния. Анализируя связь полученных шкал с изначальными шкалами и взаимное расположение точек в новых шкалах, можно многое понять об исследуемых данных.
Однако снижение размерности используется не только для эксплораторного анализа, но и для снижения количества фичей в машинном обучения, для удаления шума из данных, для более экономного хранения данных и многих других задач.
24.2 Анализ главных компонент (Principal component analysis)
Анализ главных компонент(АГК; Principal component analysis) – это, пожалуй, самый известный метод уменьшения размерности. АГК просто поворачивает систему координат многомерного пространства, которое задано имеющимися числовыми колонками. Координатная система поворачивается таким образом, чтобы первые оси объясняли как можно больший разброс данных, а последние - как можно меньший. Тогда мы могли бы отбросить последние оси и не очень-то многое потерять в данных. Для двух осей это выглядит вот так:
Первая ось должна минимизировать красные расстояния. Вторая ось будет просто перпендикулярна первой оси.
Для продвинутых: какая математика стоит за АГК?
Для начала мы посчитаем корреляционную матрицу (как вариант – ковариационную матрицу, если мы не хотим делать z-преобразование переменных
А вот коэффициент множителя длины нового вектора - это собственное значение.
eigens$values
[1] 2.7537551 0.7725168 0.3652359 0.1084922
В контексте АГК, собственные вектора - это новые оси (т.е. те самые новые компоненты), а собственные значения - это размер объясняемой дисперсии с помощью новых осей. Чтобы перейти от дисперсии к стандартному отклонению, нам нужно взять корень из собственных значений:
sqrt(eigens$values)
[1] 1.6594442 0.8789293 0.6043475 0.3293816
Собственные вектора, ранжированные по их собственным значениям от большего к меньшему, — это и есть главные компоненты в искомом порядке.
Именно таким образом считается АГК в функции princomp().
Функция prcomp() идет другим путем, используя сингулярное разложение матрицы (singular value decomposition) исходных данных, это дает чуть более точные результаты.
Последний этап – поворот исходных данных в новом пространстве с помощью матричного перемножения изначальных данных (\(z\)-трансформированных или нет).
Итак, для начала нам нужно центрировать и нормировать данные - вычесть среднее и поделить на стандартное отклонение, т.е. посчитать \(z\)-оценки (см. Глава 12.5.3.1). Это нужно для того, чтобы сделать все шкалы равноценными. Это особенно важно делать когда разные шкалы используют несопоставимые единицы измерения. Скажем, одна колонка - это масса человека в килограммах, а другая - рост в метрах. Если применять АГК на этих данных, то ничего хорошего не выйдет: вклад роста будет слишком маленьким. А вот если мы сделаем \(z\)-преобразование, то приведем и вес, и рост к “общему знаменателю”.
В базовом R уже есть инструменты для АГК princomp() и prcomp(), считают они немного по-разному. Возьмем более рекомендуемый вариант, prcomp(). Эта функция умеет самостоятельно поводить \(z\)-преобразования, для чего нужно поставить center = TRUE и scale. = TRUE.
Уже много раз встречавшаяся нам функция summary(), примененная на результат проведения АГК, выдаст информацию о полученных компонентах. Наибольший интерес представляют строчки “Proportion of Variance” (доля дисперсии, объясненная компонентой) и “Cumulative Proportion” (накопленная доля дисперсии для компоненты данной и всех предыдущих компонент).
summary(penguins_prcomp)
Importance of components:
PC1 PC2 PC3 PC4
Standard deviation 1.6594 0.8789 0.60435 0.32938
Proportion of Variance 0.6884 0.1931 0.09131 0.02712
Cumulative Proportion 0.6884 0.8816 0.97288 1.00000
Функция plot() повзоляет визуализировать соотношение разных компонент.
plot(penguins_prcomp)
Как мы видим, первый компонент объясняет большую часть дисперсии, второй и третий компоненты заметно меньше, последний – совсем немного, скорее всего, этот компонент репрезентирует некоторый шум в данных.
Теперь мы можем визуализировать первые два компонента. Это можно сделать с помощью базовых инструментов R.
В дополнение к диаграмме рассеяния можно нарисовать график переменных функцией fviz_pca_var(). По осям \(x\) и \(y\) будут отображены первая и вторая компоненты соответственно. Как и для функции fviz_pca_ind(), можно выбрать и другие компоненты с помощью параметра axes =.
fviz_pca_var(penguins_prcomp)
Вместо отдельных точек мы видим здесь стрелочки, которые представляют собой изначальные шкалы. Чем ближе эти стрелочки к осям \(x\) и \(y\), тем больше коэффициент корреляции между исходной шкалой и выбранной компонентой.
Если коэффициент корреляции отрицательный, то стрелочка исходной шкалы направлена в противоположную сторону от оси (влево или вниз).
Наконец, есть так называемый биплот (biplot), в котором объединены оба графика: и индивидуальные точки, и стрелочки с изначальными шкалами!
Еще один способ нарисовать биплот – с помощью пакета {ggfortify}. В этом пакете есть функция autoplot(), которая автоматически рисует что-то в зависимости от класса объекта на входе. Если это объект класса prcomp, то мы получим тот самый биплот.
library(ggfortify)autoplot(penguins_prcomp, data = penguins, colour ="species")
24.3 tSNE
t-Distributed Stochastic Neighbor Embedding (t-SNE) - это еще один метод снижения размерности, который используется, в основном, для визуализации многомерных данных со сложными нелинейными связями между переменными в пространстве меньшей размерности (двумерном или трехмерном).
Идея t-SNE состоит в том, чтобы расположить точки данных на такой картинке таким образом, чтобы близкие объекты оказались близко друг к другу, а далекие объекты - далеко друг от друга. Для этого используется математический подход, основанный на вероятностях и расстояниях между объектами.
Для примера возьмем знаменитый набор данных MNIST (“Modified National Institute of Standards and Technology”). MNIST – это своего рода iris или penguins в мире глубокого обучения: самый базовый учебный набор данных, на котором учатся писать искусственные нейросети. Этот набор данных очень большой, поэтому мы будем работать с его уменьшенной версией:
Rows: 6000 Columns: 785
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (785): label, 1x1, 1x2, 1x3, 1x4, 1x5, 1x6, 1x7, 1x8, 1x9, 1x10, 1x11, 1...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
MNIST содержит большое количество написанных от руки цифр, каждый столбец – это яркость отдельного пикселя.
Особенность этого набора данных в том, что стандартными линейными методами снижения размерности такими как АГК здесь не обойтись: нужно “поймать” сложные взаимоотношения между различными пикселями, которые создают различные палки, кружочки и закорючки.
Давайте посмотрим, как справится в этой ситуации АГК:
names(pca_df) <-c("x", "y", "label")pca_df %>%ggplot() +geom_text(aes(x = x, y = y, label = label, colour =as.factor(label))) +guides(colour ="none") +theme_minimal()
Какое-то разделение произошло, но большая часть цифр находится в общей куче.
Теперь попробуем посмотреть, что сделает с теми же данными t-SNE. Для этого воспользуемся пакетом {Rtsne}.
install.packages("Rtsne")
t-SNE работает итеративно, перемещая точки на картинке, чтобы улучшить соответствие между исходными данными и их представлением на картинке. t-SNE – довольно ресурсоемкий алгоритм (особенно по сравнению с АГК), поэтому в функции Rtsne() есть множество настроек для контроля скорости ее работы. Например, можно выбрать максимальное количество итераций с помощью параметра max_iter =. Но самый главный параметр – dims =, в котором нужно задать количество измерений, которое мы хотим получить. По умолчанию dims = 2, но можно поставить, например, dims = 3, чтобы получить точки в трехмерном пространстве.
Как видите, t-SNE гораздо лучше справился с разделением цифр на группы: схожие цифры находятся рядом, непохожие – далеко. На получившемся графике заметно, что цифры 4 и 9 плохо разделись, но это довольно закономерно: эти цифры похожи по написанию.
Важно отметить, что t-SNE не сохраняет все абсолютные расстояния между объектами, поэтому важно рассматривать результат в контексте относительных расстояний. Также стоит помнить, что t-SNE не является методом для точного измерения расстояний или сравнения объектов, а скорее для визуального представления структуры данных.