Стаття
Машинне навчання · Алгоритми · ⏱ ≈ 12 хв читання

K-Means та DBSCAN — дві філософії кластеризації

Кластеризація — це навчання без учителя: групування даних у змістовні підмножини без міток. K-Means (MacQueen, 1967) розбиває, мінімізуючи внутрішньокластерну дисперсію — просто, швидко, але припускає сферичні кластери схожого розміру. DBSCAN (Ester та ін., 1996) визначає кластери за щільністю — виявляє довільні форми та явно позначає шум. Знання того, коли використовувати кожен, як налаштовувати їхні параметри та як оцінювати результати, відрізняє компетентний аналіз даних від машинного навчання за принципом «карго-культу».

1. Алгоритм K-Means

K-Means мінімізує внутрішньокластерну суму квадратів відстаней (інерцію) за допомогою EM-подібної ітерації Ллойда:

Цільова функція (мінімізувати): J = Σₖ Σ_{x ∈ Cₖ} ||x − μₖ||² Алгоритм Ллойда: ІНІЦ: обрати K початкових центроїдів μ₁ … μₖ (випадково або K-Means++) E-крок: призначити кожен x до найближчого центроїда c(x) = argminₖ ||x − μₖ||² M-крок: перерахувати центроїди μₖ = середнє всіх x, призначених до Cₖ ПОВТОРЮВАТИ, доки центроїди не перестануть рухатися (або max_iter) Збіжність: гарантовано збігається (J ніколи не зростає), але може збігтися до локального мінімуму → запускайте кілька разів, зберігайте найкраще J. Складність: O(n·K·d·ітерацій) n = точки даних, K = кластери, d = виміри

2. Ініціалізація K-Means++

Випадкова ініціалізація центроїдів часто призводить до поганих локальних мінімумів. K-Means++ обирає початкові центроїди з імовірністю, пропорційною квадрату відстані від уже обраних центроїдів:

Ініціалізація K-Means++: 1. Обрати перший центроїд μ₁ рівномірно випадково 2. Для i = 2 … K: D(x) = min_j ||x − μⱼ||² (відстань до найближчого обраного центроїда) Обрати наступний центроїд з даних з імовірністю ∝ D(x)² 3. Продовжити стандартною ітерацією Ллойда Теоретична гарантія: E[J_kmeans++] ≤ 8(ln K + 2) · J_optimal На практиці: K-Means++ зазвичай досягає інерції в межах 5–10% від оптимуму з першої спроби, набагато краще за ~50% розрив випадкової ініціалізації.

3. Вибір K: метод ліктя та силует

Інерція (метод ліктя): Запустити K-Means для K = 1, 2, … K_max Побудувати графік інерції від K: шукати «лікоть», де приріст зменшується Проблема: лікоть часто неоднозначний або відсутній Силует (для кожної точки): a(i) = середня внутрішньокластерна відстань для точки i b(i) = середня відстань до найближчого ІНШОГО кластера s(i) = (b(i) − a(i)) / max(a(i), b(i)) ∈ [−1, 1] s(i) ≈ 1 → добре кластеризовано s(i) ≈ 0 → на межі кластера s(i) < 0 → неправильно класифіковано Середній силует: обрати K, що його максимізує. Складність: O(n²) на K → дорого для великих наборів даних; використовуйте наближену версію (на основі вибірки) за масштабу.

4. Алгоритм DBSCAN

DBSCAN визначає кластери як щільні ділянки, розділені розрідженими. Два параметри: ε (радіус околу) та MinPts (поріг щільності):

Визначення: ε-окіл N_ε(p) = {q : dist(p,q) ≤ ε} Ядрова точка: |N_ε(p)| ≥ MinPts Гранична точка: у N_ε ядрової точки, але сама не ядрова Шум: не ядрова точка й не в N_ε жодної ядрової Алгоритм: ДЛЯ кожної невідвіданої точки p: ЯКЩО p — ядрова точка: розширити кластер від p (BFS/DFS через щільно-зв'язані точки) ІНАКШЕ: позначити як шум (може бути перепризначена, якщо знайдена в N_ε ядрової) Досяжність за щільністю (транзитивна): p досяжна за щільністю від q, якщо існує ланцюг q → p₁ → p₂ → … → p, де кожен крок ядровий + у межах ε Складність: O(n · log n) зі просторовим індексом, O(n²) наївно

5. Налаштування ε та MinPts

Емпіричне правило для MinPts: MinPts ≥ d + 1 (d = виміри) MinPts = 2 → мінімальне кістякове дерево (надто слабко) Поширений вибір: MinPts = 2·d Вибір ε через графік відстаней k-NN: 1. Обчислити відстань до k-го найближчого сусіда для кожної точки (k=MinPts) 2. Відсортувати відстані та побудувати графік 3. Обрати ε на «коліні» відсортованої кривої відстаней → розділяє щільні ділянки від розріджених Вплив ε: ε надто мале → більшість точок шум, багато мікрокластерів ε надто велике → усі точки в одному кластері Вплив MinPts: MinPts надто мале → шумні, нерегулярні форми кластерів MinPts надто велике → лише дуже щільні ділянки кваліфікуються як кластери

6. Порівняння та коли який використовувати

K-Means ✓

Коли K відоме, кластери приблизно сферичні та схожого розміру, дані числові, важлива швидкість (O(nK) на ітерацію). Чудово підходить для квантування кольорів, сегментації клієнтів.

K-Means ✗

Не справляється з видовженими/кільцевими кластерами, нерівними щільностями, даними з викидами. Чутливий до масштабу — завжди нормалізуйте. Не може ідентифікувати точки шуму.

DBSCAN ✓

Довільні форми кластерів, автоматичне виявлення шуму, без потреби вказувати K. Працює з географічними хмарами точок, виявленням аномалій, сегментацією зображень із просторовою зв'язністю.

DBSCAN ✗

Не справляється з кластерами змінної щільності (HDBSCAN це виправляє). Дані великої розмірності: ε-околи стають беззмістовними (прокляття розмірності).

7. Реалізація на JavaScript

// ── K-Means ────────────────────────────────────────
function kMeans(points, K, maxIter = 300) {
  const n = points.length, d = points[0].length;
  // Ініціалізація K-Means++
  const centroids = [points[Math.floor(Math.random()*n)].slice()];
  while (centroids.length < K) {
    const dists = points.map(p => Math.min(...centroids.map(c => dist2(p, c))));
    const sum = dists.reduce((a, b) => a + b, 0);
    let r = Math.random() * sum;
    const idx = dists.findIndex(d => (r -= d) <= 0);
    centroids.push(points[idx < 0 ? n-1 : idx].slice());
  }

  let labels = new Int32Array(n);
  for (let iter = 0; iter < maxIter; iter++) {
    // E-крок: призначення
    let changed = 0;
    for (let i = 0; i < n; i++) {
      let best = 0, bestD = Infinity;
      for (let k = 0; k < K; k++) {
        const dd = dist2(points[i], centroids[k]);
        if (dd < bestD) { bestD = dd; best = k; }
      }
      if (labels[i] !== best) { labels[i] = best; changed++; }
    }
    if (!changed) break;
    // M-крок: перерахунок центроїдів
    for (let k = 0; k < K; k++) centroids[k].fill(0);
    const counts = new Int32Array(K);
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < d; j++) centroids[labels[i]][j] += points[i][j];
      counts[labels[i]]++;
    }
    for (let k = 0; k < K; k++)
      if (counts[k]) for (let j = 0; j < d; j++) centroids[k][j] /= counts[k];
  }
  return {labels, centroids};
}

// ── DBSCAN ────────────────────────────────────────
function dbscan(points, eps, minPts) {
  const n = points.length;
  const labels = new Int32Array(n).fill(-1); // -1 = невідвідано
  let cluster = 0;
  const eps2 = eps * eps;

  function neighbours(i) {
    return points.reduce((acc, p, j) =>
      (dist2(points[i], p) <= eps2 ? acc.push(j) : 0, acc), []);
  }

  for (let i = 0; i < n; i++) {
    if (labels[i] !== -1) continue;
    const nb = neighbours(i);
    if (nb.length < minPts) { labels[i] = 0; continue; } // шум (0)
    const c = ++cluster;
    labels[i] = c;
    const Q = [...nb];
    while (Q.length) {
      const q = Q.pop();
      if (labels[q] === 0) labels[q] = c; // гранична точка, перепризначити
      if (labels[q] !== -1) continue;
      labels[q] = c;
      const qNb = neighbours(q);
      if (qNb.length >= minPts) Q.push(...qNb);
    }
  }
  return labels; // 0 = шум, 1…K = ідентифікатори кластерів
}

function dist2(a, b) {
  let s = 0;
  for (let i = 0; i < a.length; i++) s += (a[i]-b[i])**2;
  return s;
}

8. HDBSCAN та спектральна кластеризація

HDBSCAN

Ієрархічний DBSCAN будує ієрархію кластерів, неперервно змінюючи ε. Видобуває стабільні кластери змінної щільності. Працює там, де фіксоване ε DBSCAN не справляється. Передовий рівень для загальної кластеризації.

Спектральна кластеризація

Будуємо граф схожості, обчислюємо власні вектори лапласіана графа, потім запускаємо K-Means у власному просторі. Виявляє кластери будь-якої форми. Дорого O(n³), але потужно для багатовидових даних.

Гаусові суміші (GMM)

М'який K-Means: кожна точка має дробову належність до кожного кластера. EM-алгоритм підганяє гаусіани. Явно моделює форму кластера та невизначеність; BIC обирає K.

OPTICS

Подібний до DBSCAN, але створює графік досяжності (впорядкування), що кодує ієрархічну структуру кластерів, дозволяючи зчитати кілька порогів ε за один прохід.