Алгоритм

Алгоритм нахождения кластеров:

  1. Выбор количества кластеров. Вы должны изначально знать сколько кластеров должно получиться. Это и есть \(k\).
  2. Случайным образом инициализируются \(k\) точек. Эти точки будут центроидами кластеров.
  3. Каждое наблюдение присваивается к кластеру, к которому оно ближе всего.
  4. Центроиды пересчитываются, их координаты равны средним значениям наблюдений, которые были получены на предыдущем этапе.
  5. Пункты 3 и 4 повторяются до тех пор пока центроиды не стабилизируются, то есть наблюдения не будут перетекать из одного кластера в другой.

Давайте посмотрим как работает кластеризация на очень простом примере.

## Parsed with column specification:
## cols(
##   x1 = col_double(),
##   x2 = col_double()
## )
## # A tibble: 6 x 2
##       x1     x2
##    <dbl>  <dbl>
## 1 -9.21   0.205
## 2 -0.384 -0.137
## 3  0.464 -1.61 
## 4 -7.61   2.73 
## 5 -8.42   2.25 
## 6 -6.80   1.85

Видно, что здесь есть 2 кластера. Давай попробуем построить модель.

## # A tibble: 6 x 3
##       x1     x2 cluster
##    <dbl>  <dbl> <fct>  
## 1 -9.21   0.205 2      
## 2 -0.384 -0.137 1      
## 3  0.464 -1.61  1      
## 4 -7.61   2.73  2      
## 5 -8.42   2.25  2      
## 6 -6.80   1.85  2

Выбор k

Посмотрев на график из предыдущего примера мы сразу поняли, что у нас есть 2 кластера. Это было легко, так как мы смотрели на график в двумерном пространстве. На график в трехмерном пространстве мы тоже можем посмотреть. Но как понять сколько кластеров, если количество переменных в нашем датасете больше 3?

Elbow method (метод согнутого плеча)

Чтобы понять как работает этот метод, давайте сначала определим total within-cluster sum of squares. Это метрика, которая показывает насколько далеко находятся наблюдения от центроидов своего кластера.

\[ TotalWithinSS = \sum_{i=1}^k\sum_{x \in C_i}d(x, \bar x_{C_i})^2 \]

Первая сумма отвечает за каждый кластер (\(i\)). Вторая сумма перебирает все точки \(x\), которые находятся в \(i\) кластере. В итоге суммируется расстояние от точки до центроида ее кластера и возводится в квадрат. В построенной моделе уже есть эта метрика.

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

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

Само значение этой метрики ничего нам не дает. Но это значение можно сравнивать с другими значениями. Давайте построим несколько графиков с разным количеством кластеров.

Чтобы построить графики рядом друг с другом воспользуемся пакетом ggpubr, в котором находится функция ggarange

Видно, что когда кластер 1, то метрика очень большая, так как дистанция между центров кластера и всеми точками очень велико. Важно заметить, что изменение метрики при переходе от 1 кластера ко 2 велико по сравнению с изменением при переходе от 2 к 3 или от 3 к 4. Давайте построим 10 моделей и изобразим эту динамику на графике.

На этом графике прекрасно видно, что кластеров должно быть 2, так как при увеличении количества кластеров значение метрики не так сильно падает. Это в свою очередь означает, что добавление новых кластеров избыточно.

Этот график достаточно прост. Изменение метрики при переходе от 2 до 3 кластеров может быть больше. Здесь уже вам нужно выбирать некоторый порог, который будет сигнализировать достаточно ли этого изменени, чтобы увеличить количество кластеров.

Silhouette method (метод “силуэта”)

Другим способом определения количества кластеров является Silhouette method. Этот метод предполагает расчет так называемой ширины “силуэта”. Это метрика расчитывается для каждого наблюдения и состоит из двух частей:

  1. Within Cluster Distance – расстояние внутри кластера. Для \(i\)-ого наблюдения \(C(i)\).
  2. Closest Neighbor Distance – расстояние до ближайшего соседнего кластера. Для \(i\)-ого наблюдения \(N(i)\).

Предположим мы решили выбрать 3 кластера. Давайте посмотрим на отдельном наблюдении на каждую составляющую метрики.

Для этого наблюдения Within Cluster Distance равна средней дистанции до каждой точки из 1 кластера.

## # A tibble: 1 x 1
##       C
##   <dbl>
## 1  3.35

Видно, что ближайший к нашему наблюдению является второй кластер. Поэтому для этого наблюдения Closest Neighbor Distance равна средней дистанции до каждой точки из 2 кластера.

## # A tibble: 1 x 1
##       N
##   <dbl>
## 1  3.95

Используя 2 эти состовляющие мы можем посчитать ширину “силуэта”.

В нашем случае она равна:

## [1] 0.15

Данная метрика может принимать значения от -1 до 1.

  • Значение близкое к 1 предполагает, что это наблюдение хорошо “вписывается” в текущий кластер.
  • Значение близкое к 0 предполагает, что это наблюдение находится на границе между двумя кластерами и может принадлежать одному из них.
  • Значение близкое к -1 предполагает, что это наблюдение лучше подходит другому кластеру.

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

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

Из объекта мы можем получить значение метрики.

##    cluster neighbor sil_width
## 55       1        3 0.4475706
## 86       1        3 0.4334942
## 82       1        3 0.4298834
## 31       1        3 0.4281010
## 37       1        3 0.4274173
## 34       1        3 0.4257712
## [1] 0.5453131

Можно нарисовать график.

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

И по этой метрике мы видим, что 2 кластера это оптимальный вариант, так как именно модель с 2 кластерами имеет наивысшее значение.

Методы снижения размерности (PCA, t-SNE)

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

Одним из таких методов является метод главных компонент PCA – principal component analysis. Это линейный метод, который создает новые переменные (компоненты).Первая компонента, которая получается с помощью этого метода является самой информативной. При этом каждая следующая компонента добавляет информацию, но уже не в таком размере. Мы не будем изучать, что за магия стоит за этим алгоритмом, а просто посмотрим как с ним работать в R.

Чтобы использовать PCA нам понадобится встроенная функция prcomp. В качестве датасета возьмем iris [ссылка]. Этот датасет обычно используется для задач классификации, так мы знаем метку цветка для каждого наблюдения. Но нам он подойдет для оценки того, как справится PCA с этим датасетом.

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

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

Другие методы

Можно также почитать про другие методы и пакеты по [ссылке] во вкладке “Дополнение: NbClust”.

MNIST

## Parsed with column specification:
## cols(
##   .default = col_double()
## )
## See spec(...) for full column specifications.
## # A tibble: 10 x 10
##       X1    X2    X3    X4    X5    X6    X7    X8    X9   X10
##    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1     7     0     0     0     0     0     0     0     0     0
##  2     2     0     0     0     0     0     0     0     0     0
##  3     1     0     0     0     0     0     0     0     0     0
##  4     0     0     0     0     0     0     0     0     0     0
##  5     4     0     0     0     0     0     0     0     0     0
##  6     1     0     0     0     0     0     0     0     0     0
##  7     4     0     0     0     0     0     0     0     0     0
##  8     9     0     0     0     0     0     0     0     0     0
##  9     5     0     0     0     0     0     0     0     0     0
## 10     9     0     0     0     0     0     0     0     0     0

## sigma summary: Min. : 0.376511303164114 |1st Qu. : 0.587868440336179 |Median : 0.645481705644739 |Mean : 0.639623828351072 |3rd Qu. : 0.693785408493937 |Max. : 0.855727968124747 |
## Epoch: Iteration #100 error is: 19.3209974108791
## Epoch: Iteration #200 error is: 1.4167339594037
## Epoch: Iteration #300 error is: 1.19620197103667
## Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
## Using compatibility `.name_repair`.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.