K-means
Изучите K-Means кластеризацию в Python с scikit-learn: алгоритм, масштабирование признаков, выбор K и оценка силуэта.
K-Means — это алгоритм машинного обучения без учителя, который разбивает набор данных на K непересекающихся кластеров, минимизируя суммарное квадратичное расстояние между каждой точкой данных и центроидом назначенного ей кластера. На этой странице объясняется, как работает алгоритм, как реализовать его в Python с помощью scikit-learn, как выбрать правильное число кластеров и каких ошибок следует избегать.
Что такое K-Means кластеризация?
K-Means группирует точки данных так, чтобы точки внутри одного кластера были максимально похожи друг на друга, а точки из разных кластеров — максимально различались. «Похожесть» измеряется евклидовым расстоянием — длиной прямой линии между двумя точками в пространстве признаков.
Поскольку K-Means работает с сырыми значениями расстояний, он является алгоритмом без учителя: он обнаруживает структуру в неразмеченных данных без необходимости в целевой переменной.
Распространённые примеры применения в реальном мире:
- Сегментация клиентов — группировка клиентов по привычкам трат и демографическим данным.
- Сжатие изображений — уменьшение числа цветов путём замены каждого пикселя цветом ближайшего центроида.
- Кластеризация документов — группировка новостных статей или тикетов поддержки по теме.
- Обнаружение аномалий — точки, далёкие от любого центроида, могут быть выбросами.
Как работает алгоритм
K-Means чередует два шага до тех пор, пока назначение кластеров не перестанет изменяться (сходимость):
- Инициализация — выбор K начальных центроидов (начальных точек). Стратегия
k-means++по умолчанию разносит их далеко друг от друга, чтобы снизить вероятность плохой инициализации. - Назначение — каждая точка данных назначается ближайшему центроиду, образуя K кластеров.
- Обновление — пересчёт каждого центроида как среднего всех точек, назначенных ему.
- Повторение шагов 2–3 до тех пор, пока ни одна точка не сменит кластер или не будет достигнуто максимальное число итераций.
Минимизируемая величина называется инерцией (также обозначается WCSS — внутрикластерная сумма квадратов):
inertia = sum over all points of (distance from point to its centroid)²Меньшая инерция означает более плотные и компактные кластеры.
Инициализация k-means++
Значение по умолчанию init='k-means++' (умолчание scikit-learn) выбирает первый центроид случайно, а затем каждый последующий — с вероятностью, пропорциональной квадрату расстояния до ближайшего уже выбранного центроида. Это разносит начальные центроиды в стороны и, как правило, позволяет находить лучшие кластеры быстрее, чем случайная инициализация.
Масштабирование признаков перед кластеризацией
K-Means полностью основан на евклидовом расстоянии. Если один признак измеряется в тысячах (например, годовой доход), а другой — в единицах (например, рейтинг от 1 до 5), признак с большими значениями будет доминировать в вычислении расстояния, а признак с меньшими значениями фактически игнорируется. Всегда масштабируйте признаки заранее.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)StandardScaler центрирует каждый признак до среднего 0 и стандартного отклонения 1. Смотрите главу о масштабировании для альтернатив, таких как MinMaxScaler.
Реализация K-Means с помощью scikit-learn
Следующий пример генерирует синтетический набор данных, масштабирует его, обучает K-Means и изучает результаты.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import numpy as np
# Generate 300 points in 3 natural clusters
X, y_true = make_blobs(n_samples=300, centers=3, random_state=42)
# Scale the features — always required before K-Means
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Fit K-Means with 3 clusters
kmeans = KMeans(n_clusters=3, init='k-means++', n_init=10, random_state=42)
kmeans.fit(X_scaled)
# Cluster assignment for every training point (0-indexed)
labels = kmeans.labels_
print('Cluster labels (first 10):', labels[:10])
# e.g. [2 2 0 1 0 1 2 2 0 1]
print('Cluster sizes:', np.bincount(labels))
# e.g. [100 100 100]
print('Inertia (WCSS):', round(kmeans.inertia_, 2))
# e.g. 18.26
print('Iterations to converge:', kmeans.n_iter_)
# e.g. 2Ключевые атрибуты после обучения:
| Атрибут | Описание |
|---|---|
labels_ | ID кластера (от 0 до K-1) для каждой обучающей точки |
cluster_centers_ | Координаты K центроидов (форма: K × n_features) |
inertia_ | Общий WCSS — чем меньше, тем лучше |
n_iter_ | Число итераций EM до сходимости |
Предсказание принадлежности к кластеру для новых данных
После обучения масштабировщика и модели на обучающих данных используйте scaler.transform() + kmeans.predict(), чтобы назначить новые точки существующим кластерам. Никогда не переобучайте масштабировщик на новых данных.
import numpy as np
# Two new, unseen points (original feature scale)
new_points = np.array([[0.5, -0.5],
[-1.0, 2.0]])
# Transform with the SAME scaler used during training
new_scaled = scaler.transform(new_points)
# Predict cluster membership
predictions = kmeans.predict(new_scaled)
print('Predicted clusters:', predictions)
# e.g. [2 0]Выбор правильного числа кластеров (K)
K-Means требует заранее указать K, и это зачастую самая сложная часть. В этом помогают две взаимодополняющие техники: метод локтя и оценка силуэта.
Метод локтя
Постройте график инерции (WCSS) в зависимости от K. По мере роста K инерция всегда уменьшается — но темп улучшения замедляется. «Локоть» — это точка, в которой добавление ещё одного кластера даёт убывающую отдачу.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
wcss = []
for k in range(1, 11):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
km.fit(X_scaled)
wcss.append(km.inertia_)
plt.plot(range(1, 11), wcss, marker='o')
plt.xlabel('Number of clusters (K)')
plt.ylabel('Inertia (WCSS)')
plt.title('Elbow method')
plt.tight_layout()
plt.show()На этом наборе данных (3 естественных кластера) инерция резко падает с K=1 до K=3, а затем выравнивается. Локоть при K=3 подтверждает истинное число кластеров.
Оценка силуэта
Оценка силуэта измеряет, насколько хорошо каждая точка соответствует своему кластеру по сравнению с ближайшим соседним кластером. Она варьируется от -1 (неверный кластер) до +1 (идеально разделённый кластер). Оценка выше 0,5 считается хорошей; выше 0,7 — отличной.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
for k in range(2, 8):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
print(f'k={k} silhouette={score:.3f}')Вывод:
k=2 silhouette=0.688
k=3 silhouette=0.848
k=4 silhouette=0.679
k=5 silhouette=0.522
k=6 silhouette=0.357
k=7 silhouette=0.371K=3 даёт наивысшую оценку силуэта (0,848), подтверждая, что три кластера лучше всего описывают эти данные. Используйте оба метода вместе — график локтя показывает, где прекращается улучшение; оценка силуэта даёт одно число для сравнения значений K.
Визуализация кластеров K-Means
Диаграммы рассеяния показывают, хорошо ли разделены кластеры. Для наборов данных с более чем двумя признаками перед построением графика сначала следует применить уменьшение размерности (например, PCA).
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
kmeans = KMeans(n_clusters=3, n_init=10, random_state=42)
labels = kmeans.fit_predict(X_scaled)
centers = kmeans.cluster_centers_
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels, cmap='viridis', s=40, alpha=0.7)
plt.scatter(centers[:, 0], centers[:, 1],
c='red', marker='X', s=200, zorder=5, label='Centroids')
plt.legend()
plt.title('K-Means clustering (k=3)')
plt.xlabel('Feature 1 (scaled)')
plt.ylabel('Feature 2 (scaled)')
plt.tight_layout()
plt.show()Каждый цвет представляет один кластер. Красные крестики обозначают центроиды — среднее положение всех точек в этом кластере.
Преимущества и ограничения
Преимущества
- Простота и скорость. K-Means имеет сложность O(n · k · i), где n — число точек, k — число кластеров, i — число итераций. Алгоритм масштабируется на миллионы точек с помощью
MiniBatchKMeans. - Легко интерпретировать. Центроиды дают конкретное резюме типичных значений каждого кластера.
- Хорошо работает, когда кластеры примерно сферические и одинакового размера.
- Не требует размеченных данных. Полностью без учителя — целевая переменная не нужна.
Ограничения
- K нужно указать заранее. Используйте метод локтя и оценку силуэта для выбора, но ни один из них не даёт однозначного ответа для неоднозначных данных.
- Чувствительность к инициализации. Плохая начальная конфигурация может привести к локальному оптимуму.
k-means++и многократные перезапуски (n_init=10) снижают этот риск. - Предполагает сферические кластеры одинакового размера. K-Means плохо справляется с вытянутыми, серповидными или сильно неравными кластерами. Используйте иерархическую кластеризацию или DBSCAN для несферических форм.
- Чувствительность к выбросам. Одна экстремальная точка может сильно сдвинуть центроид от истинного центра кластера. Удаляйте или ограничивайте выбросы перед обучением.
- Чувствительность к масштабу. Признаки с разными шкалами необходимо стандартизировать — смотрите главу о масштабировании.
Распространённые ошибки
Пропуск масштабирования признаков. Это самая распространённая ошибка. Без масштабирования признаки с большим числовым диапазоном доминируют в вычислении расстояния, а признаки с меньшим диапазоном игнорируются.
Задание n_init=1. В старых версиях scikit-learn значение по умолчанию было n_init='warn' (предупреждение, если не задано). Всегда задавайте n_init=10 (или больше), чтобы запустить K-Means с 10 различными случайными инициализациями и сохранить лучший результат.
Повторное обучение масштабировщика на новых данных. Обучите StandardScaler один раз на обучающих данных, а затем вызывайте transform() для любых новых данных. Повторный вызов fit_transform() на новых данных изменяет масштаб и делает модель несогласованной.
Отношение к ID кластеров как к значимому порядку. ID кластеров (0, 1, 2, …) — это произвольные метки, присваиваемые scikit-learn. Они не являются порядковыми — кластер 2 не «больше» или «важнее» кластера 0. Сравнивайте кластеры по значениям центроидов и числу участников.
Применение K-Means к нечисловым данным. K-Means требует числовых признаков для вычисления расстояний. Для категориальных данных сначала закодируйте их (например, с помощью one-hot encoding) и проверьте, имеет ли смысл евклидово расстояние для вашей задачи. Смотрите главу о категориальных данных.
K-Means против иерархической кластеризации
| Характеристика | K-Means | Иерархическая кластеризация |
|---|---|---|
| Число кластеров | Нужно указать K до запуска | Можно выбрать после запуска (по дендрограмме) |
| Масштабируемость | Масштабируется на миллионы строк | Память O(n²) — неприемлемо выше ~10 000 строк |
| Детерминированность | Случайный (используйте random_state для воспроизводимости) | Полностью детерминированный |
| Форма кластеров | Лучше для сферических кластеров | Гибкость с различными методами связи |
| Вывод | Плоское назначение кластеров | Дерево (дендрограмма), показывающее историю объединений |
Используйте K-Means, когда набор данных большой и у вас уже есть хорошая оценка K. Используйте иерархическую кластеризацию, когда хотите исследовать несколько значений K без переобучения или когда кластеры могут быть несферическими.
Практический чеклист
Следуйте этому чеклисту при применении K-Means к новому набору данных:
- Удалите или ограничьте выбросы — экстремальные значения искажают центроиды.
- Закодируйте категориальные переменные — K-Means требует числового ввода.
- Масштабируйте признаки с помощью
StandardScaler(илиMinMaxScaler, если распределение признаков ограничено). - Используйте график локтя, чтобы сузить кандидатные значения K.
- Используйте оценку силуэта, чтобы количественно сравнить конкретные значения K.
- Задайте
n_init=10иinit='k-means++'для надёжной инициализации. - Проверяйте размеры кластеров с помощью
np.bincount(labels)— очень неравные размеры могут указывать на плохое K или загрязнение выбросами. - Визуализируйте с помощью диаграммы рассеяния (или проекции PCA для многомерных данных).
Связанные главы
- Масштабирование — стандартизация признаков перед кластеризацией
- Иерархическая кластеризация — альтернатива, не требующая заранее знать K
- K-ближайших соседей — supervised метод, также использующий расстояние
- Дерево решений — supervised классификация без допущений о расстоянии
- Разделение на обучающую/тестовую выборку — оценка моделей машинного обучения
- Диаграмма рассеяния — визуализация кластеров с помощью Matplotlib