Что такое кластеризация k-means и зачем она аналитику?
k-means — это способ разбить объекты (чаще всего клиентов) на k групп по похожести, когда у вас нет заранее размеченных категорий. Вы говорите алгоритму «раздели мне базу на 4 сегмента», а он сам находит 4 центра и относит каждого клиента к ближайшему. Это обучение без учителя: нет колонки «сегмент», которую можно было бы предсказывать, есть только признаки — сколько человек тратит, как часто заходит, когда покупал в последний раз.
Мне это нужно постоянно и по очень приземлённым поводам. Маркетинг просит «выделите нам группы для рассылок». Продукт хочет понять, есть ли среди пользователей несколько типовых паттернов поведения. CRM спрашивает, кого догонять скидкой, а кого не трогать, чтобы не разбудить спящего. Во всех этих случаях у меня нет готовой разметки — и k-means закрывает задачу за полчаса, если понимать, что он делает и где врёт.
Важно сразу расставить ожидания: кластеризация не даёт «правильного» ответа. Она даёт разбиение, которое зависит от ваших признаков, масштаба и выбранного k. Поэтому ценность не в самом факте «я запустил k-means», а в том, насколько осмысленными получились группы для бизнеса.
Как k-means находит группы: центроиды и итерации
Алгоритм на удивление простой, его можно объяснить на пальцах за минуту.
- Вы задаёте число кластеров
k. Алгоритм ставитkслучайных точек — это центроиды, центры будущих групп. - Каждый объект приписывается к ближайшему центроиду (обычно по евклидову расстоянию — по прямой в пространстве признаков).
- Центроид пересчитывается: он переезжает в среднее арифметическое всех точек своей группы. Отсюда и название — means, средние.
- Шаги 2 и 3 повторяются. Точки переприписываются, центры снова сдвигаются — и так до тех пор, пока центроиды почти перестанут двигаться.
Вот и весь цикл: приписали — усреднили — приписали — усреднили. Алгоритм минимизирует суммарный разброс внутри кластеров (в sklearn эта величина называется inertia — сумма квадратов расстояний от точек до их центров).
Две вещи, о которых полезно помнить. Первая: из-за случайного старта результат может немного плавать от запуска к запуску, поэтому sklearn по умолчанию перезапускает алгоритм несколько раз (n_init) и берёт лучший вариант. Вторая: k — это ваш ввод, а не вывод. Алгоритм никогда не скажет «на самом деле у вас 3 группы, а не 5». Сколько попросили — столько и нарежет, даже если это бессмысленно.
Зачем масштабировать признаки перед кластеризацией?
Потому что k-means считает расстояния, а расстояния зависят от единиц измерения. Это самая частая и самая обидная ошибка новичков.
Представьте два признака клиента: сумма покупок (от 0 до 500 000 рублей) и число заказов (от 1 до 30). Расстояние между двумя людьми будет почти целиком определяться рублями — потому что разница в 50 000 рублей численно чудовищно больше, чем разница в 5 заказов. В итоге алгоритм кластеризует людей практически только по деньгам, а частота заказов на разбиение почти не влияет, хотя вы вроде бы её тоже подали.
Лечится это приведением всех признаков к одному масштабу. Самый ходовой вариант — стандартизация: у каждого признака вычитаем среднее и делим на стандартное отклонение, чтобы у всех получилось среднее около нуля и разброс около единицы. В sklearn это StandardScaler. Если в данных много выбросов (а в деньгах и в выручке их всегда много), иногда лучше заходит RobustScaler, который опирается на медиану и квартили и меньше пляшет от одного кита, потратившего миллион.
Правило простое: масштабируйте почти всегда, кроме случая, когда все признаки уже в одинаковых сопоставимых единицах. Пропустили шаг — получили кластеры «по самому крупному числу» и сами того не заметили.
Сколько кластеров брать: метод локтя или силуэт?
Универсального ответа нет, k подбирают, и два рабочих инструмента — метод локтя и коэффициент силуэта.
Метод локтя: перебираем k от 2 до, скажем, 10, для каждого считаем inertia (тот самый внутрикластерный разброс) и рисуем график. Сначала с ростом k разброс падает быстро, потом всё медленнее. Точка, где кривая заметно перегибается и дальше идёт почти полого, — это «локоть», разумный компромисс. Минус метода в том, что чёткого локтя часто просто нет, и приходится смотреть на мутный изгиб и спорить с собой.
Коэффициент силуэта честнее. Для каждого объекта он сравнивает, насколько тот близок к своему кластеру и насколько далёк от соседнего, и выдаёт число от -1 до 1. Ближе к 1 — точка уверенно в своей группе, около 0 — сидит на границе, отрицательное — вообще попала не туда. Средний силуэт по всем точкам можно посчитать для разных k и взять тот, где он выше.
На практике я смотрю оба и добавляю третий критерий — здравый смысл. Если силуэт советует k=8, а маркетинг физически не сделает восемь разных рассылок, я возьму 4-5. Кластеризация, которую невозможно применить, никому не нужна. Так что финальное число — это всегда торг между математикой и тем, что бизнес реально переварит.
Как сделать сегментацию клиентов на sklearn: мини-пример
Соберём поведенческие признаки клиентов и разложим их на сегменты. Признаки для RFM обычно проще всего собрать SQL-запросом к таблице orders — потренироваться в этом можно в SQL-тренажёре, а сам код кластеризации погонять в Python-песочнице.
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# df: одна строка = один клиент
# recency — сколько дней назад была последняя покупка
# frequency — сколько заказов за период
# monetary — суммарная выручка с клиента
features = df[["recency", "frequency", "monetary"]]
# 1. Масштабируем — без этого деньги задавят всё остальное
X = StandardScaler().fit_transform(features)
# 2. Подбираем k по силуэту
for k in range(2, 8):
labels = KMeans(n_clusters=k, n_init=10, random_state=42).fit_predict(X)
print(k, round(silhouette_score(X, labels), 3))
# 3. Фиксируем финальное k и приписываем сегменты обратно к клиентам
model = KMeans(n_clusters=4, n_init=10, random_state=42)
df["segment"] = model.fit_predict(X)
# 4. Смотрим профиль каждого сегмента в исходных единицах
print(df.groupby("segment")[["recency", "frequency", "monetary"]].mean())
Последний шаг — самый важный и его часто забывают. Номер кластера 0, 1, 2, 3 сам по себе ничего не значит. Смысл появляется, когда вы усредняете исходные (немасштабированные) признаки по группам и читаете таблицу: «в сегменте 2 люди покупали недавно, часто и на большие суммы — это лояльное ядро; в сегменте 0 давно не заходили и почти не тратили — спящие». Вот эти человеческие ярлыки и есть результат, который вы отдаёте бизнесу, а не голые цифры от нуля до трёх.
Как k-means связан с RFM-анализом?
RFM и k-means решают одну задачу — сегментацию клиентов, но с разных сторон, и хорошо работают в паре.
Классический RFM — это ручная разметка: берём три признака (Recency — давность покупки, Frequency — частота, Monetary — деньги), каждый режем на квантили (например, на 5 корзин по 20%) и присваиваем баллы. Получается решётка вроде «555 — лучшие, 111 — потерянные». Плюс подхода в том, что он прозрачен и его легко объяснить кому угодно. Минус — границы корзин вы задаёте сами, и они не учитывают, что клиенты внутри одной ячейки могут быть очень разными, а на стыке ячеек — почти одинаковыми.
k-means по тем же трём признакам находит границы сам, по естественной плотности данных, а не по ровным 20%. Часто это даёт более цельные группы: он видит, что «активные транжиры» и «активные экономные» — это два разных облака, даже если по RFM-баллам они попали в соседние клетки.
Мой рабочий рецепт: считаю RFM-признаки в SQL (справочник по функциям всегда под рукой — SQL-справочник), а дальше либо режу их на квантили для быстрой прозрачной версии, либо кидаю в k-means, когда нужно разбиение поумнее. Разбор готовых сегментаций на реальных данных есть в кейсах — там видно, как одни и те же клиенты раскладываются по-разному в зависимости от метода.
Где k-means ломается и чего он не умеет?
У алгоритма есть встроенные допущения, и когда данные им не соответствуют, результат получается красивый, но неправильный. Держите ограничения в голове до запуска, а не после.
- Он любит сферические кластеры примерно одного размера. k-means режет пространство на выпуклые «шары» вокруг центров. Если реальные группы вытянутые, изогнутые или сильно разной плотности, он их порвёт или слепит. Классика — два кольца или полумесяца: глазами группы очевидны, а k-means проведёт границу поперёк.
- Он чувствителен к выбросам. Центроид — это среднее, а среднее тянется за экстремальными значениями. Один клиент с покупкой на десять миллионов может утащить центр целого кластера. Отсюда важность чистки данных и робастного масштабирования.
- Он требует задать
kзаранее и всегда вернёт ровно столько групп, даже если структуры в данных нет вообще. На случайном шуме он тоже нарисует «сегменты». - Он работает с числами и евклидовым расстоянием. Категориальные признаки (город, тип устройства) в него честно не засунешь — их надо либо кодировать с большой осторожностью, либо брать другой алгоритм.
Если чувствуете, что группы не сферические, посмотрите в сторону DBSCAN (ищет области плотности, сам определяет число кластеров и умеет помечать выбросы как шум) или иерархической кластеризации. Но для стандартной клиентской сегментации на RFM-подобных признаках k-means чаще всего достаточно — просто не притворяйтесь, что он видит то, чего не видит.
Какие ошибки чаще всего убивают кластеризацию?
Собрал те, что вижу у себя и у джунов регулярно — почти все стоят дороже, чем сам алгоритм.
Первая — забыть масштабирование. Про неё уже сказал, но она настолько частая, что повторю: без scaler кластеризация идёт по самому крупному по числам признаку, и это тихая ошибка, которую не видно по коду.
Вторая — гнаться за «правильным» k вместо применимого. Силуэт — ориентир, а не приказ. Финальное число выбирается с оглядкой на то, сколько сегментов бизнес реально сможет обслужить.
Третья — не проинтерпретировать кластеры. Отдать заказчику табличку с номерами 0-3 без человеческих названий и профилей — значит не доделать работу. Всегда усредняйте исходные признаки по группам и давайте им имена.
Четвёртая — прогнать один раз и поверить. Меняйте random seed, смотрите, устойчивы ли группы. Если при перезапуске сегменты разваливаются, значит структуры в данных мало и разбиению нельзя доверять.
Кластеризацию любят спрашивать на собеседованиях именно потому, что она проверяет понимание, а не зубрёжку: почему масштабируем, чем силуэт лучше локтя, где k-means сломается. Отработать формулировки удобно на подборке вопросов с интервью, а закрепить руками — на практических задачах по Python и SQL.
Если хотите довести сегментацию до автоматизма — от сбора RFM-признаков в SQL до готовых кластеров на sklearn — в Pro открыты все задачи тренажёров, кейсы и разборы без лимитов. Без спешки, в своём темпе.