W3docs

K-means

Изучите K-Means кластеризацию в Python с scikit-learn: алгоритм, масштабирование признаков, выбор K и оценка силуэта.

K-Means — это алгоритм машинного обучения без учителя, который разбивает набор данных на K непересекающихся кластеров, минимизируя суммарное квадратичное расстояние между каждой точкой данных и центроидом назначенного ей кластера. На этой странице объясняется, как работает алгоритм, как реализовать его в Python с помощью scikit-learn, как выбрать правильное число кластеров и каких ошибок следует избегать.

Что такое K-Means кластеризация?

K-Means группирует точки данных так, чтобы точки внутри одного кластера были максимально похожи друг на друга, а точки из разных кластеров — максимально различались. «Похожесть» измеряется евклидовым расстоянием — длиной прямой линии между двумя точками в пространстве признаков.

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

Распространённые примеры применения в реальном мире:

  • Сегментация клиентов — группировка клиентов по привычкам трат и демографическим данным.
  • Сжатие изображений — уменьшение числа цветов путём замены каждого пикселя цветом ближайшего центроида.
  • Кластеризация документов — группировка новостных статей или тикетов поддержки по теме.
  • Обнаружение аномалий — точки, далёкие от любого центроида, могут быть выбросами.

Как работает алгоритм

K-Means чередует два шага до тех пор, пока назначение кластеров не перестанет изменяться (сходимость):

  1. Инициализация — выбор K начальных центроидов (начальных точек). Стратегия k-means++ по умолчанию разносит их далеко друг от друга, чтобы снизить вероятность плохой инициализации.
  2. Назначение — каждая точка данных назначается ближайшему центроиду, образуя K кластеров.
  3. Обновление — пересчёт каждого центроида как среднего всех точек, назначенных ему.
  4. Повторение шагов 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.371

K=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 к новому набору данных:

  1. Удалите или ограничьте выбросы — экстремальные значения искажают центроиды.
  2. Закодируйте категориальные переменные — K-Means требует числового ввода.
  3. Масштабируйте признаки с помощью StandardScaler (или MinMaxScaler, если распределение признаков ограничено).
  4. Используйте график локтя, чтобы сузить кандидатные значения K.
  5. Используйте оценку силуэта, чтобы количественно сравнить конкретные значения K.
  6. Задайте n_init=10 и init='k-means++' для надёжной инициализации.
  7. Проверяйте размеры кластеров с помощью np.bincount(labels) — очень неравные размеры могут указывать на плохое K или загрязнение выбросами.
  8. Визуализируйте с помощью диаграммы рассеяния (или проекции PCA для многомерных данных).

Связанные главы

Was this page helpful?