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.
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:
ahol a mátrix, egy hosszú sorvektor, a -adik klaszterbe sorolt pontok halmaza, a centroid, és 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:
ahol
a
-edik
oszlop átlaga,
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.
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:
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
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.
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:
ahol
az
pont átlagos távolsága a saját klaszterének többi pontjától,
pedig a legközelebbi másik klaszter pontjaitól mért átlagos távolság. Az
érték
-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 ponthoz , , tehát . A ponthoz , ezért . A és szimmetrikusak, így a teljes átlag .
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.
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ő.
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.pyimport 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 = labelsA 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.
A standardizálás invariantjai: Xb.mean(axis=0) abszolút
értéke
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.
Kézi sziluett. Számold ki kézzel a
sziluett-együtthatót az
,
partíció minden pontjára, majd az átlagot. Ellenőrizd a kódot
silhouette_score-szal.
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?
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.
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?
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.
A
pont:
,
,
.
Az
pont:
,
,
.
A
és
szimmetrikusak, így az átlag
.
Kódellenőrzés:
silhouette_score([[0],[1],[4],[5]], [0,0,1,1]) ugyanezt
adja.
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.
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ő.
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.
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
a seed-lista páronkénti összehasonlításában.
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
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?
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.