Модуль 9. Кластеризация: k-means

Цель модуля: освоить обучение без учителя на примере k-means: понять цикл «назначение → центроиды», почему он сходится, как выбрать число кластеров и где алгоритм ломается. После модуля вы умеете разбивать клиентов или тикеты на группы и трезво объяснять, что эти группы значат и чего они не значат.

Перед началом: модуль 1 (типы задач, kNN и расстояния) и модуль 8 (нормализация признаков). Инструменты: песочница сайта (чистый Python), сниппет ниже. Трудоёмкость: ~2 ч (чтение 45 мин · сниппет и практика 55 мин).

🔥 «Разделите клиентов на сегменты» — а на сколько?

Маркетинг приносит задачу: «У нас 50 тысяч клиентов, разделите их на сегменты — хотим разные офферы». Вы уточняете: на сколько сегментов? В ответ — «ну вы же аналитик, посмотрите». Тут и обнаруживается, что в задаче не хватает главного параметра, и никакой таргет его не подскажет.

Сравните с классификацией: там есть правильные ответы, и метрика говорит, стало лучше или хуже. Здесь ответов нет вообще. Разбить клиентов на 3 группы можно, на 5 — тоже, и обе «правильные». Хуже того: группировка ничем не лучше случайной, если она не помогает принять решение. Значит, критерий — не точность, а польза: сегмент должен быть интерпретируемым и вести к действию. Разберёмся, как это устроено. Реальность удобная: если маркетинг готов запустить три разных оффера — k = 3 не хуже «правильного».

📖 9.1. Обучение без учителя: ответов нет, есть структура

Вернитесь к карте типов задач из модуля 1: третий тип — кластеризация. Отличия от всего, что было в курсе, принципиальные:

  • Нет таргета. Ни правильных ответов, ни сплитов train/val/test в привычном смысле — нечего проверять на «экзамене».
  • Нет метрики «правильно». Считается только внутренняя согласованность: точки внутри кластера близки, разные кластеры далеки. Это не то же самое, что «кластеры осмысленны».
  • Кластеры — гипотеза, а не истина. Их интерпретирует человек: «это, похоже, „экономные опытные пользователи“» — и потом проверяет гипотезу продуктом.

Зачем это нужно в проде:

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

И главное правило без учителя: работа итеративная. Разбили → посмотрели на описания групп → поменяли фичи → разбили заново. Ответа «получилось» не будет; будет «стало полезнее для решения».

📖 9.2. Цикл k-means: назначение и центроиды

k-means — самый простой и быстрый способ разбить точки на k групп. Работа умещается в три строки:

  1. Выбрать k стартовых центроидов (случайно или умно — см. 9.4).
  2. Назначение: каждую точку отнести к ближайшему центроиду.
  3. Пересчёт: каждый центроид переставить в среднее своих точек. Повторять шаги 2–3, пока назначения не перестанут меняться.

Критерий качества — SSE (сумма квадратов расстояний точек до своих центроидов): меньше — компактнее кластеры. Почему цикл сходится? Оба шага могут только уменьшать SSE: назначение отправляет точку к ближайшему центроиду (дальше всех остальных она уже не станет), а пересчёт ставит среднее — точку, минимизирующую сумму квадратов. Разбиений конечное число, SSE снизу ограничена — значит, процесс обязан остановиться.

В сниппете kmeans.py три облака точек в двумерном пространстве, k = 3:

итерация 1: центроиды [(-1.79,-1.37), ( 0.43, 2.10), ( 2.39,-0.36)]  SSE =  11.84  размеры [8, 8, 8]
итерация 2: центроиды [(-1.79,-1.37), ( 0.43, 2.10), ( 2.39,-0.36)]  SSE =  11.84  размеры [8, 8, 8]
сошлось за 2 итераций

На первой итерации назначение уже идеальное — размеры кластеров ровно по 8 точек, — и центроиды не сдвинулись. Такая быстрая сходимость типична для хорошо разделённых данных; на реальных таблицах цикл крутится дольше.

📖 9.3. Выбор k: локоть SSE и предметная интерпретация

k — входной параметр алгоритма, и универсальной формулы для него нет. Стандартный приём — правило локтя. Считаем SSE для k = 1, 2, 3… и смотрим, где кривая перестаёт резко падать. В сниппете:

k=1: SSE = 132.79
k=2: SSE =  51.43
k=3: SSE =  11.84
k=4: SSE =  10.35
k=5: SSE =   8.51

Читается как история: первый кластер сжимает разброс в 2.6 раза (132.79 → 51.43), второй — ещё почти в 4 раза (51.43 → 11.84), а дальше каждый новый центроид выигрывает копейки: 11.84 → 10.35 → 8.51. Локоть — на k = 3, и это совпадает с правдой: данных действительно три облака. Что отмечено честно: в реальных данных локоть сглажен, потому что «истинных» k в таблице может не быть вовсе.

Поэтому второй, более важный критерий — предметная интерпретация. Посмотрите на центроиды: опишите каждый кластер словами и спросите, что продукт с этим сделает. Если кластер «клиенты из СПБ с высокими кликами, но редкими покупками» ведёт к конкретному офферу — кластер полезен. Если описать можно, а решения нет — он бесполезен, даже если локоть на нём. Для маркетинговых сегментов практическое правило: k от 2 до 6; больше — уже не сегменты, а склад личностей, которые никто не будет обслуживать по-разному. Альтернативные критерии (силуэт, gap statistic) существуют, но начинать всегда стоит с локтя плюс разговора с заказчиком.

📖 9.4. Ловушки: локальные минимумы и масштаб

Первая ловушка — локальные минимумы. Цикл k-means сходится всегда, но не обязательно к лучшему разбиению: при неудачном старте два центроида могут застрять в одном облаке, а третье — остаться в пустоте. Классический способ лечения — несколько запусков с разной инициализацией, и лучший по SSE побеждает. В сниппете три старта дают ровно один и тот же результат:

старт (0.0, 0.0) и др. → SSE = 11.84, размеры [8, 8, 8]
старт (2.5, -0.5) и др. → SSE = 11.84, размеры [8, 8, 8]
старт (0.5, 2.2) и др. → SSE = 11.84, размеры [8, 8, 8]

Это не свойство k-means, а свойство набора: облака плотные и далёкие — «лёгкий ландшафт», из любой точки притягиваешься в одно и то же место. На реальных данных локальные минимумы случаются регулярно, поэтому в библиотеках по умолчанию стоят сразу 10 рестартов и умная инициализация k-means++: она раскидывает стартовые центроиды по данным, чтобы они не родились в одном облаке. Вывод из сниппета так и говорит: пользуйтесь библиотеками, а не пишите цикл руками.

Вторая ловушка — масштаб фич, и это прямой привет из модуля 8. k-means считает евклидово расстояние, поэтому признак с большим размахом задаёт результат почти единолично. Два признака «клики за месяц» (0–5000) и «конверсия» (0–0.1) без нормализации превратят кластеризацию в разбиение по кликам, а конверсия в расстоянии просто не участвует. Правило без исключений: перед k-means — нормализуйте или стандартизируйте все числовые признаки. Кластеры будут не «в километрах», зато честные.

И третья, скрытая ловушка — интерпретация: «кластер» не равен «классу». Можно жить с кластерами как с гипотезами; нельзя — выдавать их за истинное деление клиентов. Мало того, k-means ищет компактные шарообразные группы, и вытянутые вдоль прямой данные он порежет нелепо. Если структура подозрительно сложная, прежде чем строить офферы, посмотрите на визуализацию или возьмите другой алгоритм.

📖 9.5. Компромиссы

  • k-means vs другие алгоритмы. Быстрый, простой, объяснимый; но навязывает шарообразные кластеры и не выделяет аномалии. Иерархическая кластеризация даёт дерево делений, DBSCAN — кластеры любой формы и шум, но оба дороже и капризнее к параметрам.
  • Много кластеров vs интерпретируемость. Больше k — меньше SSE и «точнее» разбиение; но сегментов, которым нельзя сопоставить оффер, никто не будет использовать. Каждый кластер обязан вести к действию.
  • Один запуск vs рестарты. Один запуск быстрее; 10 рестартов с k-means++ примерно никогда не попадают в ловушку локального минимума. Разница в секундах — качество сегментации важнее.
  • Метрика vs смысл. Локоть и силуэт дают число, но не дают ответа «что такое эти кластеры». Автоматический критерий — фильтр для отбора, человек — судья.
  • Нормализация vs единицы измерения. После нормализации центроиды живут в диапазоне [0, 1] и теряют физический смысл; зато ни один признак не подавлен другими. Интерпретацию сегментов делайте по исходным значениям внутри кластеров, а не по нормированным центроидам.

💻 Практика

  1. (15 мин) Сниппет с вариациями. Запустите kmeans.py. Задайте два стартовых центроида в одном облаке (например, обе точки около (−1.8, −1.4)) и посмотрите, куда сойдётся цикл и что будет с SSE. Это ваш собственный локальный минимум.
  2. (20 мин) Свои данные руками. Возьмите 8–10 объектов из рабочей жизни (клиенты, тикеты) и опишите их двумя числовыми признаками. Пропишите разбиение на k = 2 и k = 3, посчитайте SSE на бумаге или в сниппете. Какой вариант вы бы показали заказчику и почему?
  3. (15 мин) Назовите кластеры. Возьмите готовые кластеры из сниппета (по 8 точек) и придумайте каждому имя и действие: какой оффер, какая очередь, какое письмо. Если действие не придумывается — кластеру не место в отчёте.
  4. (10 мин) Нормализация вслепую. Для своих двух признаков из шага 2 проделайте min-max вручную и посчитайте, как изменятся центроиды. Сформулируйте одной фразой, зачем это было нужно.

🧩 Quiz "Проверь себя"

1. Почему цикл k-means сходится?
2. Зачем библиотеки запускают k-means 10 раз с k-means++?
3. Зачем нормализовать признаки перед k-means?
4. Локоть на графике SSE — это…
5. Чем кластеризация отличается от классификации?

✅ Итоги модуля

  • Объяснять, чем обучение без учителя отличается от задачи с таргетом.
  • Описывать цикл k-means и объяснять, почему он сходится.
  • Выбирать k по локтю SSE и подтверждать предметной интерпретацией.
  • Называть ловушки: локальные минимумы, немасштабированные фичи, шарообразность кластеров.
  • Строить кластеры через библиотеку с рестартами и k-means++, а не самодельным циклом.
  • Превращать кластеры в сегменты с действиями: оффер, очередь, кампания.

🔗 Мостик к Модулю 10

Заметьте: в этом модуле всё держалось на расстоянии до центроида. Но представьте точки, которые далеки от всех центроидов — клиента с необычным поведением, всплеск в метрике сервиса. k-means ответит «отнесу к ближайшему», хотя честнее сказать «это вообще не похоже ни на что». Так мы подходим к следующему модулю — аномалиям: от простого z-score и IQR до isolation forest. Там та же логика расстояний и порогов, только цель другая: не разложить всех по полочкам, а найти тех, кто на полки не влезает.