24.2 Klaszterezés és stabilitás

24.2.1 Cél és konkrét helyzet

A Northstar-rendeléseket nyolc, vegyes mértékegységű jellemzőből szeretnénk csoportosítani, hogy a kosármintázatokból értelmezhető szegmenseket kapjunk. Mielőtt a valódi, 8 dimenziós téren hoznánk döntést, két ismert 2 dimenziós szintetikus geometrián kalibráljuk az eszközt: make_blobs három közel gömb alakú csoporttal, és make_moons két nem konvex félholddal. A megfigyelési egység itt egy mintasor; a szintetikus generáló címke csak utólagos ellenőrzésre van, a klaszterező algoritmus bemenete kizárólag a X jellemzőmátrix.

A kimenet egy döntési jegyzőkönyv: a KMeans és DBSCAN paraméterei, a standardizálás utáni silhouette-érték, a stabilitási összehasonlítás (adjusted Rand index, ARI) és egy klaszterprofil. Előzetes becslés a futtatás előtt: a holdakon a KMeans k=2 valószínűleg magas silhouette-értéket is elérhet, mert kompakt darabokat vág, de a generáló holdakhoz képest az ARI csak 0,2–0,4 körül lesz, mert a holdak nem lineárisan szeparálhatók. A DBSCAN megfelelő eps és min_samples mellett gyakran jó eredményt adhat, de ezt a zajszint és a paraméterezés befolyásolja; közel tökéletes eredmény nem garantált. Ha ez a mintázat beigazolódik, a silhouette önmagában nem elég a geometriai alakzat ellenőrzésére.

24.2.2 Távolság, standardizálás és a KMeans alakfeltevése

A klaszterezés (clustering) célfüggvény nélküli partíciókeresés. A KMeans a centroid (klaszterközéppont) és a pontok euklideszi távolságának négyzetét minimalizálja:

WCSS=∑k=1K∑xi∈Ck∥xi−μk∥22, \text{WCSS} = \sum_{k=1}^K \sum_{x_i \in C_k} \|x_i-\mu_k\|_2^2,

ahol X∈ℝn×pX\in\mathbb{R}^{n\times p} a mátrix, xix_i egy pp hosszú sorvektor, CkC_k a kk-adik klaszterbe sorolt pontok halmaza, μk=1|Ck|∑xi∈Ckxi\mu_k=\frac{1}{|C_k|}\sum_{x_i\in C_k}x_i a centroid, és ∥v∥2=∑jvj2\|v\|_2=\sqrt{\sum_j v_j^2} az euklideszi norma. Az összeget gyakran within-cluster sum of squares (WCSS) vagy inertia néven hívják; mértékegysége a jellemzők négyzetösszegének egysége, ezért standardizálás után dimenziótlan.

Az algoritmus két lépést váltogat: minden pontot a legközelebbi centroidhoz rendel, majd a centroidokat újraszámolja. Mindkét lépés nem növeli a WCSS-t, de a módszer lokális optimumba ragadhat. A n_init=10 azt jelenti, hogy 10 különböző inicializálásból indulunk, és a legjobb célfüggvényű partíció marad; a random_state a seedet rögzíti.

A KMeans döntési határai a centroidokat összekötő szakaszok felező merőlegesei, ezért a kapott klaszterek közel konvexek, nagyjából gömb alakúak, és hasonló méretre törekszenek. A távolságalapú célfüggvény erősen skálafüggő: ha az egyik jellemzőt kilométerben, a másikat centben mérjük, a második dominál. Ezért a jellemzőnkénti z-score standardizálás (standardization) kötelező előfeldolgozás:

zij=xij−x‾jsj, z_{ij} = \frac{x_{ij}-\bar x_j}{s_j},

ahol x‾j\bar x_j a jj-edik oszlop átlaga, sjs_j a szórása. A standardizált oszlopok átlaga közel 0, varianciája 1. A laborban StandardScaler-t használunk; ha a klaszterezést később új adatra is szeretnénk alkalmazni, a scalert a vizsgált mintán illesztjük, és ugyanazt a transformot fagyasztjuk le a jövőbeli sorokra.

24.2.3 DBSCAN: sűrűség, eps és zaj

A DBSCAN (Density-Based Spatial Clustering of Applications with Noise) nem vár gömb alakú csoportokat, hanem sűrűséggel összekapcsolható magpontokból növeszti a klasztereket. Két paramétere: eps, a szomszédság sugara, és min_samples, a magponthoz szükséges minimális szomszédszám. A scikit-learn implementációban maga a vizsgált pont is beleszámít, így min_samples=5 legalább 4 másik szomszédot jelent az eps sugarú környezetben. A magpont (core point) feltétele:

|Nϵ(xi)|≥m,Nϵ(xi)={xj:d(xi,xj)≤ϵ}. |N_\epsilon(x_i)| \ge m,\qquad N_\epsilon(x_i)=\{x_j : d(x_i,x_j)\le\epsilon\}.

A klaszter a magpontokból sűrűségen keresztül elérhető maximális halmaz. A határpont olyan nem magpont, amelyik magpont szomszédja; a zaj (noise) egyik sem, és a scikit-learn a -1 címkét adja neki. A DBSCAN-nak nem kell előre megadni a klaszterszámot, de egyetlen eps nem kezeli az erősen változó sűrűségű adatot.

Kis kézi kontroll 1 dimenzión: legyenek a pontok 0, 0.4, 1.2, 5, 5.3, eps=0.5, min_samples=2. A 0.4-nek a 0 szomszédja, tehát {0,0.4}\{0, 0.4\} egy klaszter; az 5 és 5.3 alkotja a másikat. Az 1.2-nek nincs szomszédja az eps sugáron belül (0.8 távolságra van a 0.4-től), ezért zaj, -1 címkével.

24.2.4 Sziluett és stabilitás: két különböző kérdés

A silhouette-együttható (silhouette coefficient) azt méri, mennyire konzisztens egy pont a saját klaszterével a legközelebbi másik klaszterhez képest:

s(i)=b(i)−a(i)max(a(i),b(i)), s(i)=\frac{b(i)-a(i)}{\max(a(i),b(i))},

ahol a(i)a(i) az ii pont átlagos távolsága a saját klaszterének többi pontjától, b(i)b(i) pedig a legközelebbi másik klaszter pontjaitól mért átlagos távolság. Az érték [−1,1][-1,1]-be esik: 1 a sűrű, jól elkülönült pont, 0 az átfedő határ, negatív a rossz besorolás. A silhouette_score a pontonkénti értékek átlaga.

Kézi példa: A={0,1}A=\{0,1\}, B={4,5}B=\{4,5\}. A 00 ponthoz a(0)=1a(0)=1, b(0)=(4+5)/2=4,5b(0)=(4+5)/2=4{,}5, tehát s(0)=(4,5−1)/4,5≈0,778s(0)=(4{,}5-1)/4{,}5\approx0{,}778. A 11 ponthoz b(1)=(3+4)/2=3,5b(1)=(3+4)/2=3{,}5, ezért s(1)≈0,714s(1)\approx0{,}714. A 44 és 55 szimmetrikusak, így a teljes átlag 0,7460{,}746.

A stabilitás más kérdés: ha egy kicsit megváltoztatjuk a seedet vagy a paramétert, ugyanazt a partíciót kapjuk-e? A KMeans esetén a különböző random_state értékekkel kapott címkéket az adjusted Rand indexszel (ARI) hasonlítjuk össze. Az ARI a két partíció közötti pontpár-egyezést méri, és levonja a véletlen várható egyezést; 1 a tökéletes egyezés, 0 a véletlen szint, negatív érték a véletlennél rosszabb. A DBSCAN adott adatokkal, metrikával, paraméterekkel és bemeneti sorrenddel determinisztikusan fut, bár a határpontok csoporthoz rendelése a bemeneti sorrendre érzékeny lehet. Ezért itt az eps paraméterrács szomszédos értékein kapott partíciókat hasonlítjuk össze: ha egy kis eps változtatás drámai ARI-csökkenést okoz, a megoldás érzékeny. A stabilitási mérés nem statisztikai intervallum; ugyanúgy csak az eljárás viselkedését jellemzi, mint a foldok közötti szórás a Keresztvalidáció a függőségi szerkezethez alfejezetbeli CV-nél.

24.2.5 Klaszterek jelentése: cluster semantics

A klasztercímke önmagában nem jelentés. A kapott klaszterekhez mindig numerikus profilt kell számolni: a standardizált térben a centroidot vagy a jellemzőnkénti mediánt, majd ezt visszafejteni az eredeti egységekbe. A szintetikus holdaknál a generáló címke csak laboratóriumi igazság; a valódi Northstar-klaszterezésnél nincs ilyen referencia, ott a domain-szótár ad értelmet, például „közeli, sürgős kosár” vagy „távoli, nagy értékű rendelés”. Ha a klaszterek nem kapnak tartalmi értelmezést, a partíció nem bevethető.

24.2.6 Teljes minimális munkapélda

A szkriptet sch24/s094_cluster_lab.py néven mentsük, és a projektgyökérből futtassuk CPU-s tanulási környezetben, a scikit-learn projektfüggőség telepítése után:

# macOS/Linux terminál és Windows PowerShell esetén is
python sch24/s094_cluster_lab.py
import numpy as np
from sklearn.datasets import make_blobs, make_moons
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, DBSCAN
from sklearn.metrics import silhouette_score, adjusted_rand_score

DATA_SEED = 20261020

blobs, y_blobs = make_blobs(
    n_samples=450, centers=3, cluster_std=[0.7, 0.9, 1.1],
    random_state=DATA_SEED
)
moons, y_moons = make_moons(
    n_samples=450, noise=0.08, random_state=DATA_SEED
)

Xb = StandardScaler().fit_transform(blobs)
Xm = StandardScaler().fit_transform(moons)

km_blobs = KMeans(n_clusters=3, n_init=10, random_state=7).fit(Xb)
km_moons = KMeans(n_clusters=2, n_init=10, random_state=7).fit(Xm)
db_moons = DBSCAN(eps=0.2, min_samples=5).fit(Xm)

def safe_silhouette(X, labels):
    labels = np.asarray(labels)
    n_clusters = len(set(labels) - {-1})
    if n_clusters < 2:
        return np.nan
    return float(silhouette_score(X, labels))

print("Xb oszlopátlagai:", Xb.mean(axis=0))
print("Xb oszlopszórásai:", Xb.std(axis=0))
print("Blobs K=3 silhouette:",
      round(safe_silhouette(Xb, km_blobs.labels_), 3))
print("Moons K=2 silhouette:",
      round(safe_silhouette(Xm, km_moons.labels_), 3))
print("Moons DBSCAN silhouette:",
      round(safe_silhouette(Xm, db_moons.labels_), 3))
print("Moons DBSCAN noise arány:",
      round((db_moons.labels_ == -1).mean(), 3))
print("Moons KMeans ARI a generáló holdakkal:",
      round(adjusted_rand_score(y_moons, km_moons.labels_), 3))
print("Moons DBSCAN ARI a generáló holdakkal:",
      round(adjusted_rand_score(y_moons, db_moons.labels_), 3))

# Kézi inercia-kontroll: a WCSS újraszámolása a centroidokból.
centroids = km_blobs.cluster_centers_[km_blobs.labels_]
manual_inertia = float(((Xb - centroids) ** 2).sum())
print("Manuális WCSS:", round(manual_inertia, 4),
      "| Modell inertia_:", round(km_blobs.inertia_, 4))

# Stabilitás: KMeans seed-lista a holdakon.
seeds = [1, 2, 3, 4, 5]
first = None
sil_vals, ari_vals = [], []
for seed in seeds:
    labels = KMeans(n_clusters=2, n_init=10,
                    random_state=seed).fit_predict(Xm)
    sil_vals.append(safe_silhouette(Xm, labels))
    if first is None:
        first = labels
    else:
        ari_vals.append(adjusted_rand_score(first, labels))
print("Moons K=2 silhouette seed-lista:",
      [round(x, 3) for x in sil_vals])
print("Moons K=2 ARI az első seedhez:",
      [round(x, 3) for x in ari_vals])

# Stabilitás: DBSCAN eps-rács a holdakon.
eps_grid = [0.14, 0.17, 0.2, 0.24, 0.28]
prev = None
for eps in eps_grid:
    labels = DBSCAN(eps=eps, min_samples=5).fit_predict(Xm)
    n_clusters = len(set(labels) - {-1})
    noise_share = float((labels == -1).mean())
    ari_prev = "-" if prev is None else round(
        adjusted_rand_score(prev, labels), 3)
    print(f"eps={eps:.2f} n_clusters={n_clusters} "
          f"noise={noise_share:.3f} ari_prev={ari_prev}")
    prev = labels

A kimenet itt szemléltető, elméletileg várt tartomány, nem ezen a gépen futott mérés. A blobs három klaszterénél a silhouette általában 0,60–0,75 közé esik; a holdakon a KMeans silhouette 0,45–0,55 is lehet, de az ARI a generáló holdakhoz 0,2–0,4 körül marad, míg a DBSCAN eps=0.2-vel az ARI jellemzően 0,9 felett van, a noise arány 0,05 alatt. Ha a te futtatásod ettől lényegesen eltér, a következő szakasz ellenőrzéseit futtasd le, mielőtt a paramétereket módosítanád.

24.2.7 Eredmény értelmezése és ellenőrzés

A standardizálás invariantjai: Xb.mean(axis=0) abszolút értéke 10−1410^{-14} alatti, Xb.std(axis=0) 1, mert a StandardScaler oszloponkénti z-score-t ad. A KMeans illesztése után km_blobs.labels_ shape-je (450,), dtype egész, a cluster_centers_ shape-je (3, 2). A manuális és a modell-inercia azonos, numerikus tolerancián belül; ezt a labor 4 tizedesjegyig ellenőrzi.

A legfontosabb értelmezési eredmény: a holdak KMeans- és DBSCAN-partícióját az ARI különbözteti meg. A silhouette a KMeans-nél is lehet viszonylag magas, mert a lineáris vágás után mindkét darab kompakt marad; a generáló holdakkal való ARI azonban elárulja, hogy a KMeans nem a valódi geometriai alakzatot találta. A valós adatban ilyen generáló igazság nincs, ezért ott a stabilitás és a feature-profil lép a helyére.

Ha a DBSCAN eps=0.14 mellett minden pont zaj, vagy eps=0.28-nál a két hold összeolvad, a safe_silhouette nan-t adhat, és az eps-rács ARI-értékei lecsökkennek. Ez nem hiba a kódban, hanem a paraméterérzékenység diagnózisa: az éles, stabil klaszterszámú tartomány közepéről válasszunk eps-t, nem a széléről.

24.2.8 Feladatok

  1. Kézi sziluett. Számold ki kézzel a sziluett-együtthatót az A={0,1}A=\{0,1\}, B={4,5}B=\{4,5\} partíció minden pontjára, majd az átlagot. Ellenőrizd a kódot silhouette_score-szal.

  2. Körgyűrű-labor implementálása. Generálj make_circles(n_samples=500, noise=0.05, factor=0.4, random_state=123) adatot, standardizálás után futtasd a KMeans k=2-t és egy DBSCAN eps-rácsot min_samples=5 mellett. Melyik módszer ARI-ja van 0,9 felett a generáló gyűrűcímkével, és miért?

  3. Hibadiagnózis. A holdakon DBSCAN(eps=0.01, min_samples=5) után a közvetlen silhouette_score(Xm, labels) ValueError: Number of labels is 1... hibát dob. Mi a hiba oka, és hogyan kezeli a safe_silhouette? Mutass hasonló esetet eps=0.8 környékén is, ahol egyetlen óriásklaszter keletkezhet.

  4. Megváltoztatott skála. A blobs X = blobs.copy() második oszlopát szorozd 20-szal, majd standardizálás nélkül futtass KMeans(n_clusters=3, n_init=10, random_state=7)-et. Számold ki az ARI-t a standardizált megoldással, és magyarázd meg a távolságtorzulás hatását. Miért lehet a silhouette mégis magas?

  5. Stabilitás átvitele. A holdakon futtass KMeans k=3-mal is seed-listát, és számold ki az ARI-kat az első seedhez. Miért alacsonyabb ez a stabilitás a k=2-höz képest? Fogalmazd meg, hogyan használod ezt a k választásánál.

24.2.9 Megoldások és indoklás

  1. A 00 pont: a=1a=1, b=4,5b=4{,}5, s=0,7778s=0{,}7778. Az 11 pont: a=1a=1, b=3,5b=3{,}5, s=0,7143s=0{,}7143. A 44 és 55 szimmetrikusak, így az átlag (0,7778+0,7143+0,7143+0,7778)/4=0,7460(0{,}7778+0{,}7143+0{,}7143+0{,}7778)/4=0{,}7460. Kódellenőrzés: silhouette_score([[0],[1],[4],[5]], [0,0,1,1]) ugyanezt adja.

  2. A megoldás váza:

from sklearn.datasets import make_circles
circles, y_c = make_circles(n_samples=500, noise=0.05,
                            factor=0.4, random_state=123)
Xc = StandardScaler().fit_transform(circles)
km_c = KMeans(n_clusters=2, n_init=10,
              random_state=7).fit_predict(Xc)
print("ARI KMeans:", round(adjusted_rand_score(y_c, km_c), 3))
for eps in [0.15, 0.2, 0.25, 0.3, 0.35]:
    lab = DBSCAN(eps=eps, min_samples=5).fit_predict(Xc)
    print(eps, len(set(lab) - {-1}),
          round(safe_silhouette(Xc, lab), 3),
          round(adjusted_rand_score(y_c, lab), 3))

A KMeans a gyűrűket átmetszi, ezért az ARI jellemzően 0,2 alatt marad; a DBSCAN a gyűrűk közötti sűrűségrés miatt 0,9 feletti ARI-t ad a megfelelő eps-tartomány közepén. A tartalmi indoklás: a KMeans konvex alakzatot feltételez, a koncentrikus körök nem konvexek.

  1. Az eps=0.01 esetben minden pont noise, a DBSCAN egyetlen -1 címkét ad; a sklearn silhouette_score nem tud egyetlen klaszterrel számolni. Az eps=0.8 esetén a -1-et elhagyva is egyetlen klaszter marad, hasonló hiba. A safe_silhouette megszámolja a tényleges klasztercímkéket a -1 kizárásával, és nan-t ad, ha kevesebb mint kettő van; a hurok így nem áll le, hanem rögzíti, hogy ez a paraméter nem értékelhető.

  2. A 20-szoros nyújtás után a második koordináta különbségei a négyzetes távolság 400-szorosát adják, ezért a KMeans lényegében a második tengely mentén vág sávokat. Az ARI a standardizált megoldással szemben gyakran 0,4–0,7 közé esik, pedig a generáló három csoport ugyanaz. A silhouette ettől még magas lehet, mert a sávok is kompaktak; ez újabb érv, hogy a silhouette nem igazolja a természetes geometriát.

  3. A k=3 a holdakon arra kényszeríti a KMeans-t, hogy az egyik holdat kettévágja, és a vágás helye a seedtől függ. Az első seedhez viszonyított ARI ezért jellemzően 0,6 alá esik, míg k=2-nél közel 1. A klaszterszám-választásnál tehát a silhouette-maximum mellett a stabilitási küszöböt is megköveteljük, például ARI >0,8>0{,}8 a seed-lista páronkénti összehasonlításában.

24.2.10 Gyakori tévedés és önellenőrzés

Tévedés: „A KMeans k=4-re is talált 4 klasztert, tehát az adatban 4 természetes csoport van.” Ellenpélda: generáljunk 600 pontot a [−1,1]2[-1,1]^2 négyzeten egyenletesen, és futtassunk rá KMeans(n_clusters=4, n_init=10, random_state=0)-t. A kapott 4 klaszter mindig létezik, a silhouette értéke paraméter- és mintafüggő, és a seedek közötti ARI akár magas is lehet stabil, mesterséges térfelosztás esetén. Egyik mutató önmagában sem bizonyít természetes klasztereket. A klaszterező algoritmus outputja nem bizonyít természetes csoportot; a stabilitás és a domain-profil együtt szükséges. Ez párhuzamos a PCA: skála, vetítés és megőrzött variancia alfejezetben látott PCA-tanulsággal: a variancia iránya nem feltétlenül oksági vagy üzleti szegmens. A DBSCAN sűrűségfogalma pedig a Sűrűség és eloszlásfüggvény alfejezetbeli sűrűségfüggvény analógiájára épül, de a paraméterválasztás a mi felelősségünk.

Önellenőrzés: (1) Meg tudom-e mondani, mi a megfigyelési egység, és hogy a resamplingnél a sor vagy az ügyfél a helyes egység? (2) Számolok-e centroidprofilt és visszafejtem-e a standardizálást az értelmezéshez? (3) Külön kezelem-e a silhouette-t mint belső konzisztenciát és az ARI-t mint partícióegyezést? (4) Ha a DBSCAN minden pontot noise-nak ad, kivédem-e a silhouette-hibát? (5) Tudok-e olyan példát mondani, ahol magas silhouette mellett is rossz a geometriai alakzat?

24.2.11 Rövid összefoglaló

A KMeans euklideszi távolságot, közel gömb alakú, konvex klasztereket és hasonló méretet feltételez; a DBSCAN ehelyett sűrűséggel összekapcsolható magpontokat keres, és a zajt -1 címkével kihagyja. A standardizálás távolságalapú módszereknél gyakran indokolt, de nem kötelező: a skálát a változók értelmezéséhez és a választott távolságfogalomhoz kell igazítani. A silhouette a klaszter belső konzisztenciáját méri, a stabilitás pedig a seed- vagy paraméterérzékenységet; egyik sem bizonyít természetes csoportot. A holdak példája megmutatja, hogy a KMeans magas silhouette mellett is elhibázhatja a nem konvex alakzatot. A következő Anomália és ritka esemény felügyelet nélkül alfejezetben ugyanezt a távolság- és sűrűséggondolkodást visszük tovább anomáliadetektálásra.