Мы действительно знаем все о K-средних?

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

Оглавление

K-означает

Алгоритм направлен на разделение точек данных на k наборов (т. Е. кластеры) путем минимизации отклонений набора. Последнее просто означает минимизацию расстояния от центроида (центра кластера).

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

from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# Generate data
centers = [[1, 1], [-1, -1], [1, -1]]
n_clusters = len(centers)
X, y_true = make_blobs(n_samples=3000, centers=centers, cluster_std=0.7)
# K-means
kmeans = KMeans(n_clusters=n_clusters)
kmeans.fit(X)
y_kmeans = kmeans.predict(X)
centers = kmeans.cluster_centers_
# Weighted K-means
weights = list(map(lambda x: x*10, y_true))
kmeans = KMeans(n_clusters=n_clusters)
kmeans.fit(X, sample_weight=weights)
y_kmeans_w = kmeans.predict(X, sample_weight=weights)
centers_w = kmeans.cluster_centers_
# Plotting
fig, ax = plt.subplots(1, 3, figsize=(20,7))
colors = ['#d35400', '#34495e', '#2980b9']
versions = ['Original Data', 'K-means', 'Weighted K-means']
y = [y_true, y_kmeans, y_kmeans_w]
centers_ = [centers, centers_w]
for i in range(3):
    ax[i].scatter(X[:, 0], X[:, 1], s=50, c=list(map(lambda x: colors[x], y[i])), alpha=0.5)
    elif i>0:
        ax[i].scatter(centers_[i-1][:, 0], centers_[i-1][:, 1], marker='+', c='black', s=200)
    ax[i].set_title(versions[i])
    ax[i].set_xticks([])
    ax[i].set_yticks([])
plt.show()

Для целей этого раздела мы случайным образом сгенерировали набор данных из 3 кластеров, в общей сложности 3000 точек данных. Веса присваиваются следующим образом w_i = true_cluster_index * 10. Очевидно, кластеры и соответствующие им центроиды очень сильно зависят от результирующего вектора весов. Следует очень осторожно подходить к тому, когда и как использовать взвешенные K-средние, поскольку это может иметь большое влияние на конечные центры кластеров.

MiniBatch K-средства

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

from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import time
# Generate data
centers = [[1, 1], [-1, -1], [1, -1]]
n_clusters = len(centers)
X, y_true = make_blobs(n_samples=3000, centers=centers, cluster_std=0.7)
# K-means
kmeans = KMeans(n_clusters=n_clusters)
t0 = time.time()
k_means.fit(X)
t_batch = time.time() - t0
y_kmeans = kmeans.predict(X)
centers = kmeans.cluster_centers_
mbk = MiniBatchKMeans(n_clusters=n_clusters, batch_size=45,
                      max_no_improvement=10, verbose=0)
t0 = time.time()
mbk.fit(X)
t_mini_batch = time.time() - t0
y_kmeans_batch = mbk.predict(X)
centers_b = mbk.cluster_centers_
# Plotting
fig, ax = plt.subplots(1, 2, figsize=(15,7))
colors = ['#d35400', '#34495e', '#2980b9']
versions = ['K-means', 'MiniBatch K-means']
y = [y_kmeans, y_kmeans_batch]
t = [t_batch, t_mini_batch]
centers_ = [centers, centers_b]
for i in range(2):
    ax[i].scatter(X[:, 0], X[:, 1], s=50, c=list(map(lambda x: colors[x], y[i])), alpha=0.5)
    ax[i].scatter(centers_[i][:, 0], centers_[i][:, 1], marker='+', c='black', s=200)
    ax[i].set_title(versions[i] + f' | train time: {np.round(t[i], 2)}fs')
    ax[i].set_xticks([])
    ax[i].set_yticks([])
plt.show()

Нечеткие c-средства

Изначально K-means - это жесткая кластеризация (т.е. кластеры не перекрываются). Что, если образцы принадлежат нескольким кластерам? Затем нам нужны алгоритмы, которые выполняют мягкую кластеризацию (т.е. каждый образец имеет вероятность принадлежности к кластеру).

Процесс аналогичен K-средним, но центроид кластера вычисляется как среднее всех точек, взвешенных по их вероятности принадлежности к кластеру i.

import skfuzzy as fuzz
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# Generate data
centers = [[1, 1], [-1, -1], [1, -1]]
n_clusters = len(centers)
X, y_true = make_blobs(n_samples=3000, centers=centers, cluster_std=0.7)
# Fuzzy c-means
cntr, u, u0, d, jm, p, fpc = fuzz.cluster.cmeans(X.T, n_clusters, 2, error=0.005, maxiter=1000)
# Plotting
colors = ['#d35400', '#34495e', '#2980b9']
fig, ax = plt.subplots(1, 2, figsize=(15,7))
ax[0].scatter(X[:, 0], X[:, 1], s=50, c=list(map(lambda x: colors[x], y_true)), alpha=0.5)
ax[0].set_title('Original Data')
ax[0].set_xticks([])
ax[0].set_yticks([])
cluster_membership = np.argmax(u, axis=0)
for j in range(n_clusters):
    cX = X[cluster_membership == j]
    ax[1].scatter(cX[:, 0], cX[:, 1], s=50, c=colors[j], alpha=0.5)
    
for pt in cntr:
    ax[1].scatter(pt[0], pt[1], marker='+', c='black', s=200)
ax[1].set_title(f'Fuzzy c-means | FPC {np.round(fpc, 2)}')
ax[1].set_xticks([])
ax[1].set_yticks([])

FPC (коэффициент нечеткого разделения) - это показатель производительности, который находится в диапазоне от 0 до 1, где 1 является лучшим. В нашем случае мы достигли FPC 0,68. В нашей задаче каждая точка принадлежит одному кластеру, поэтому np.argmax(u, axis=0), однако, можно дополнительно исследовать вероятности членства для каждого кластера. Например, мы могли бы определить порог и найти, какие образцы принадлежат нескольким кластерам или одному кластеру после определения порога.

K-medoids

Передайте привет кузену K-means, K-medoids. Возможно, вы уже догадались, что между двумя алгоритмами не так уж много различий, и вы правы. Основные отличия заключаются в следующем:

  1. Центры (medoids) теперь являются фактическими точками данных из нашего набора данных - что не является обязательным для K-средних.
    большая интерпретируемость центры кластеров, чем в K-средних
  2. Вместо евклидова расстояния K-medoids сводит к минимуму сумму мер попарного несходства.
    ›более устойчив к выбросам и шуму

В следующем сценарии мы исследуем результаты K-medoids с использованием 3 мер попарного несходства - Manhattan, Cosine и Euclidean - и K-средних.

from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn_extra.cluster import KMedoids
import matplotlib.pyplot as plt
# Generate data
centers=[[1,1], [-1,-1], [1,-1]]
n_clusters = len(centers)
X, y_true = make_blobs(n_samples=3000, centers=centers, cluster_std=0.7)
# K-medoids models
selected_models = [(KMedoids(metric="manhattan", n_clusters=n_clusters), "KMedoids (manhattan)"),
                   (KMedoids(metric="euclidean", n_clusters=n_clusters), "KMedoids (euclidean)"),
                   (KMedoids(metric="cosine", n_clusters=n_clusters), "KMedoids (cosine)"),
                   (KMeans(n_clusters=n_clusters), 'KMeans')]
colors=['#d35400', '#34495e', '#2980b9']
fig, ax = plt.subplots(2, 2, figsize=(15, 15))
perm = list(set(itertools.permutations([0,1,1,0], 2)))
for s in range(4):
    i, j = perm[s]
    model, name = selected_models[s]
    model.fit(X)
    y_hat = model.predict(X)
    centers_hat = model.cluster_centers_
    inertia = model.inertia_
    ax[i, j].scatter(X[:,0], X[:, 1], s=50, c=list(map(lambda x: colors[x], y_hat)), alpha=0.5)
    ax[i, j].scatter(centers_hat[:, 0], centers_hat[:, 1], marker='+', c='black', s=200)
    ax[i, j].scatter(np.array(centers)[:, 0], np.array(centers)[:, 1], marker='o', c='black', s=200, alpha=0.6)
    ax[i, j].set_title(name)
    ax[i, j].set_xticks([])
    ax[i, j].set_yticks([])
plt.show()

На каждом графике центры кластеров изображены крестиком, а истинные центры представлены кружком. Учитывая эту информацию, сравнивая все 4, мы видим, что косинус работает хуже всех остальных. K-medoids с использованием евклидова расстояния дает очень похожие результаты с K-средними, что и ожидалось. Наконец, манхэттенское расстояние дает очень похожие результаты с евклидовым расстоянием.

K-means - это относительно простой алгоритм, но при этом очень мощный. Благодаря простой и быстрой реализации он по праву стал одним из самых популярных алгоритмов для задач кластеризации.

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