Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. To'liq oqim
- 2.2. Qaror nuqtalari
- 2.3. Barqarorlik va tekshiruv
- 2.4. Talqin va nomlash
- 2.5. Ishlab chiqarishga chiqarish
- 2.6. Hujjatlashtirish
- 2.7. Tuzoqlar
- 2.8. Tartib va hujjat
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Ma'lumot, tayyorlash va anomaliya
- Misol 2 — Tuzilma, algoritm va k tanlash
- Misol 3 — Talqin, nomlash va tekshiruv
- Misol 4 — Chiqarish va segmentatsiya kartasi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
16.12-dars: Amaliyot — to'liq nazoratsiz loyiha
16-QISM — NAZORATSIZ O'RGANISH · 12-dars
1. Kirish va motivatsiya
Bu qismda klasterlash, o'lchamni kamaytirish va anomaliya aniqlashni alohida o'rgandik. Endi ularni bitta loyihada birlashtiramiz: xom ma'lumotdan boshlab, tekshirilgan va topshirishga tayyor segmentatsiyagacha.
Nazoratsiz loyihaning nazoratlidan asosiy farqi — to'xtash qoidasi yo'q. Klassifikatsiyada "test AUC 0.87" deb yozib, ishni tugatish mumkin. Bu yerda esa siz o'zingiz belgilagan mezonga, barqarorlik tekshiruviga va domen tasdig'iga tayanasiz.
Bu darsda: to'liq oqim (tayyorlash → anomaliya → o'lchamni kamaytirish → klasterlash → baholash → talqin → topshirish), qaror nuqtalari va ularni asoslash, natijani hujjatlashtirish va segmentni ishlab chiqarishga chiqarish.
Real vaziyat. Ko'p nazoratsiz loyiha "biz 5 ta segment topdik" bilan tugaydi va hech qachon ishlatilmaydi — chunki segmentlar barqaror emas, tushunarsiz va yangi mijozni ularga joylashtirish usuli yo'q. Bu darsdagi tartib shu holatning oldini oladi.
Bu darsda to'liq nazoratsiz loyihani quramiz.
Bu darsda:
- To'liq oqim
- Qaror nuqtalari
- Barqarorlik va tekshiruv
- Talqin va nomlash
- Ishlab chiqarishga chiqarish
- Hujjatlashtirish
- Tuzoqlar
- Amaliy: yakuniy loyiha
ℹ Misollar real numpy/pandas/sklearn bilan (Python 3.14).
2. Nazariya — chuqur tushuntirish
2.1. To'liq oqim
1. MA'LUMOT: hajm, yo'qolgan qiymatlar, taqsimotlar, qiyshiqlik
2. TAYYORLASH: log1p, masshtablash, kategoriyalarni kodlash
3. ANOMALIYA: chetlanishlarni topish va qaror qabul qilish
4. TUZILMA BORMI: gap statistikasi, barqarorlik 16.3-bob
5. O'LCHAM: PCA (kerak bo'lsa), vizualizatsiya uchun t-SNE
6. KLASTERLASH: bir necha algoritm, bir necha k
7. BAHOLASH: jadval (silhouette, DB, barqarorlik, o'lchamlar)
8. TALQIN: markazlar asl birliklarda, segment nomlari
9. TEKSHIRUV: domen mutaxassisi, bashorat testi
10. CHIQARISH: Pipeline saqlash, yangi obyektni biriktirish3 va 4-qadamni o'tkazib yubormang: anomaliyalar klaster markazlarini tortadi 16.2-bob, tuzilma yo'qligi esa butun loyihani ma'nosiz qiladi.
2.2. Qaror nuqtalari
Har qadamda yozib boriladigan qarorlar:
transformatsiya - nega log1p? (qiyshiqlik koeffitsiyenti)
masshtablash - nega StandardScaler? (chetlanishlar bormi -> Robust)
anomaliya - olib tashlandimi yoki qoldirildimi? nega?
o'lcham - PCA ishlatildimi? nechta komponent? nega?
algoritm - nega K-means? (shakl, tezlik, predict kerakmi)
k - qaysi mezonlar bo'yicha? (jadval)
nomlash - kim tasdiqladi?
Bu qarorlar TAKRORLANUVCHANLIKning asosiQarorlarni yozib borish nazoratsiz loyihada nazoratlidan muhimroq: bu yerda "CV natijasi" degan yakuniy hakam yo'q, shuning uchun mulohaza zanjiri asosiy hujjat bo'ladi.
2.3. Barqarorlik va tekshiruv
UCH TEKSHIRUV:
1. BARQARORLIK 16.3-bob
bootstrap/yarim bo'lish -> ARI
> 0.75 yaxshi, < 0.5 shubhali
2. BASHORAT TESTI
klasterlash X1 belgilarida bajariladi
klasterlar X2 (ishlatilmagan) belgilarni bashorat qila oladimi?
-> klasterlar haqiqiy tuzilmani aks ettiradi
3. VAQT BO'YICHA
oldingi davr ma'lumotida klasterlab, yangi davrga qo'llash
segmentlar ulushlari barqaror qoldimi?Bashorat testi — eng kam qo'llaniladigan, lekin eng ishonarli tekshiruv: agar segmentlar klasterlashda ishlatilmagan o'zgaruvchini (masalan, keyingi oydagi xarid) bashorat qila olsa, ular haqiqiy.
2.4. Talqin va nomlash
Har segment uchun:
1. MARKAZ asl birliklarda (inverse_transform + expm1)
2. UMUMIY o'rtachadan farq (foizda) - eng farq qiladigan 3 belgi
3. O'LCHAM (n va ulush)
4. NOM - qisqa, marketing tilida, harakatga ishora qiladigan
YOMON NOM: "Klaster 2", "Yuqori RFM guruhi"
YAXSHI NOM: "Tez-tez, kam summa", "Yo'qolayotgan qimmatli"
Nomni DOMEN mutaxassisi tasdiqlashi kerakSegment nomi harakatga ishora qilishi kerak: "Yo'qolayotgan qimmatli" nomi darhol nima qilish kerakligini aytadi, "Klaster 3" esa hech narsa demaydi.
2.5. Ishlab chiqarishga chiqarish
# 1. Butun quvurni saqlash (15.13 kabi)
joblib.dump({"quvur": quvur, "belgilar": nomlar, "nomlar": segment_nomlari,
"versiyalar": {...}, "sana": "2026-09-21"}, "segment.joblib")
# 2. Yangi mijozni biriktirish
segment = quvur.predict(yangi_df) # K-means/GMM da bor
# 3. Ierarxik/DBSCAN da predict yo'q -> markazlarni saqlang
markazlar = np.array([Xs[yorliq == k].mean(axis=0) for k in ...])
segment = np.argmin(((Xs_yangi[:, None] - markazlar) ** 2).sum(axis=2), axis=1)
# 4. Kuzatuv
# segment ulushlari vaqt bo'yicha o'zgaryaptimi (PSI - 15.13)
# yangi mijozlarning markazgacha masofasi oshyaptimiSegment ulushlarini kuzating: agar bir segment ulushi 20% dan 5% ga tushsa, ma'lumot drift qilgan va qayta klasterlash vaqti kelgan.
2.6. Hujjatlashtirish
TOPSHIRISHDA BO'LISHI KERAK:
[ ] Ma'lumot ta'rifi (davr, filtrlar, belgilar)
[ ] Tayyorlash qadamlari va ularning sababi
[ ] Anomaliyalar bo'yicha qaror
[ ] Algoritm va k tanlovi (jadval bilan)
[ ] Barqarorlik natijasi
[ ] Segment profillari (markazlar asl birliklarda)
[ ] Segment nomlari va kim tasdiqlagani
[ ] Yangi obyektni biriktirish kodi
[ ] Qayta klasterlash siyosati
[ ] Cheklovlar va ma'lum muammolar
Bu - "segmentatsiya kartasi" (model card ning analogi)Cheklovlarni yozish — professional hujjatning belgisi: "bu segmentatsiya faqat faol mijozlar uchun", "yangi mijozlarda 3 oy ma'lumot to'planishi kerak" kabi eslatmalar keyingi xatolarning oldini oladi.
2.7. Tuzoqlar
Asosiy tuzoqlar: anomaliyalarni tekshirmasdan klasterlash; tuzilma borligini tekshirmaslik; bitta algoritm bilan cheklanish; barqarorlikni o'lchamaslik; segmentlarni tushunarsiz nom bilan topshirish; yangi obyektni biriktirish usulini bermaslik; qarorlarni hujjatlashtirmaslik; t-SNE fazosida klasterlash 16.9-bob; natijani domen mutaxassisiga ko'rsatmaslik.
2.8. Tartib va hujjat
Nazoratsiz loyihada to'xtash qoidasi yo'q, shuning uchun tartib va hujjat hal qiluvchi. Oqim: tayyorlash → anomaliya → tuzilma bormi → o'lcham → klasterlash → jadval bilan baholash → talqin va nomlash → tekshiruv → chiqarish. Barqarorlik, bashorat testi va domen tasdig'i — uch asosiy tekshiruv. Yakunda segmentatsiya kartasi topshiriladi. Bu bilan 16-qism yakunlanadi.
3. Tez ma'lumotnoma
import joblib
import numpy as np
from sklearn.cluster import KMeans
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import IsolationForest
from sklearn.metrics import adjusted_rand_score, silhouette_samples
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
quvur = Pipeline([
("log", FunctionTransformer(np.log1p, inverse_func=np.expm1)),
("sc", StandardScaler()),
("km", KMeans(n_clusters=5, n_init=10, random_state=0))])
yorliq = quvur.fit_predict(X)
# markazlar asl birliklarda
markaz = quvur[:-1].inverse_transform(quvur["km"].cluster_centers_)
# yangi mijoz
quvur.predict(yangi_X)
joblib.dump({"quvur": quvur, "nomlar": segment_nomlari}, "segment.joblib")
QOIDA: anomaliyani tekshir · tuzilmani tasdiqla · barqarorlikni o'lcha ·
nom ber va hujjatlashtirAmaliyot xulosasi
1 ma'lumot -> 2 tayyorlash -> 3 anomaliya -> 4 tuzilma -> 5 o'lcham
-> 6 klasterlash -> 7 baholash -> 8 talqin -> 9 tekshiruv -> 10 chiqarish
Uch tekshiruv: barqarorlik, bashorat testi, domen tasdig'i
Topshirishda: segmentatsiya kartasi4. Batafsil misollar
Misollar real numpy/pandas/sklearn bilan (Python 3.14).
Misol 1 — Ma'lumot, tayyorlash va anomaliya
"""1-3 qadamlar (real numpy/pandas/sklearn)."""
import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
def yarat(seed: int = 42, n: int = 5000) -> pd.DataFrame:
"""Onlayn do'kon mijozlari: 5 ta tabiiy segment + anomaliyalar."""
rng = np.random.default_rng(seed)
segment = rng.choice(5, n, p=[0.30, 0.25, 0.20, 0.15, 0.10])
profil = np.array([
[25.0, 4.0, 180_000.0, 2.0], # oddiy
[8.0, 14.0, 95_000.0, 5.0], # tez-tez, kichik chek
[75.0, 2.0, 420_000.0, 1.5], # kamdan-kam, katta chek
[120.0, 1.0, 150_000.0, 1.0], # uyquda
[15.0, 9.0, 850_000.0, 6.0], # qimmatli
])
X = profil[segment] * rng.lognormal(0, 0.28, (n, 4))
df = pd.DataFrame(X, columns=["oxirgi_kun", "xaridlar", "ortacha_chek",
"toifalar"])
df["segment"] = segment
# anomaliyalar: ulgurji xaridorlar
anomaliya = rng.random(n) < 0.012
df.loc[anomaliya, "ortacha_chek"] *= rng.uniform(15, 40, anomaliya.sum())
df.loc[anomaliya, "xaridlar"] *= rng.uniform(3, 8, anomaliya.sum())
df["anomaliya"] = anomaliya.astype(int)
return df
def main() -> None:
df = yarat()
nomlar = ["oxirgi_kun", "xaridlar", "ortacha_chek", "toifalar"]
print("=== 1. Ma'lumot ===")
print(f" {len(df)} mijoz, {len(nomlar)} belgi")
print(f" yo'qolgan qiymatlar: {int(df[nomlar].isna().sum().sum())}")
print(f" {'belgi':<15} {'mediana':>12} {'o_rtacha':>12} {'max':>14} "
f"{'qiyshiqlik':>11}")
for nom in nomlar:
print(f" {nom:<15} {df[nom].median():>12,.1f} {df[nom].mean():>12,.1f} "
f"{df[nom].max():>14,.0f} {df[nom].skew():>11.2f}")
print("\n=== 2. Transformatsiya qarori ===")
X = df[nomlar].to_numpy()
Xlog = np.log1p(X)
print(f" {'belgi':<15} {'xom qiyshiqlik':>16} {'log1p dan keyin':>18}")
for i, nom in enumerate(nomlar):
xom = float(pd.Series(X[:, i]).skew())
yangi = float(pd.Series(Xlog[:, i]).skew())
print(f" {nom:<15} {xom:>16.2f} {yangi:>18.2f}")
print(" QAROR: log1p ishlatiladi (qiyshiqlik > 1)")
print("\n=== 3. Anomaliyalarni aniqlash ===")
izo = IsolationForest(contamination=0.015, n_estimators=200,
random_state=0, n_jobs=1).fit(X)
ball = -izo.score_samples(X)
belgilangan = izo.predict(X) == -1
haqiqiy = df["anomaliya"].to_numpy().astype(bool)
print(f" belgilangan: {int(belgilangan.sum())} "
f"({belgilangan.mean():.2%})")
print(f" haqiqiy anomaliyalar: {int(haqiqiy.sum())}")
print(f" to'g'ri topildi: {int((belgilangan & haqiqiy).sum())} "
f"({(belgilangan & haqiqiy).sum() / max(haqiqiy.sum(), 1):.1%})")
print(f" {'holat':<22} {'ortacha_chek':>15} {'xaridlar':>11}")
for nom, m in [("normal", ~belgilangan), ("belgilangan", belgilangan)]:
print(f" {nom:<22} {df.loc[m, 'ortacha_chek'].mean():>15,.0f} "
f"{df.loc[m, 'xaridlar'].mean():>11.1f}")
print("\n=== 4. Anomaliyalar bo'yicha qaror ===")
Xs_hammasi = StandardScaler().fit_transform(Xlog)
Xs_toza = StandardScaler().fit_transform(Xlog[~belgilangan])
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
print(f" {'variant':<22} {'n':>7} {'silhouette (k=5)':>18} "
f"{'eng kichik %':>14}")
for nom, Xa in [("hammasi bilan", Xs_hammasi), ("anomaliyasiz", Xs_toza)]:
yorliq = KMeans(5, n_init=10, random_state=0).fit_predict(Xa)
print(f" {nom:<22} {len(Xa):>7} {silhouette_score(Xa, yorliq):>18.4f} "
f"{np.bincount(yorliq).min() / len(Xa):>13.1%}")
print(" QAROR: anomaliyalar alohida segment sifatida ajratiladi")
print(" ⭐ Anomaliyalarni o'chirmang - alohida ko'rib chiqing")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Ma'lumot ===
5000 mijoz, 4 belgi
yo'qolgan qiymatlar: 0
belgi mediana o_rtacha max qiyshiqlik
oxirgi_kun 25.1 45.5 280 1.42
xaridlar 4.1 6.8 200 7.21
ortacha_chek 179,332.2 387,161.9 45,521,055 18.16
toifalar 2.1 3.0 16 1.18
=== 2. Transformatsiya qarori ===
belgi xom qiyshiqlik log1p dan keyin
oxirgi_kun 1.42 0.18
xaridlar 7.21 0.32
ortacha_chek 18.16 1.27
toifalar 1.18 0.46
QAROR: log1p ishlatiladi (qiyshiqlik > 1)
=== 3. Anomaliyalarni aniqlash ===
belgilangan: 75 (1.50%)
haqiqiy anomaliyalar: 63
to'g'ri topildi: 61 (96.8%)
holat ortacha_chek xaridlar
normal 278,785 6.4
belgilangan 7,503,923 31.5
=== 4. Anomaliyalar bo'yicha qaror ===
variant n silhouette (k=5) eng kichik %
hammasi bilan 5000 0.5385 10.7%
anomaliyasiz 4925 0.5560 9.6%
QAROR: anomaliyalar alohida segment sifatida ajratiladi
⭐ Anomaliyalarni o'chirmang - alohida ko'rib chiqingNima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — Tuzilma, algoritm va k tanlash
"""4-7 qadamlar (real numpy/pandas/sklearn)."""
import numpy as np
import pandas as pd
from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans
from sklearn.ensemble import IsolationForest
from sklearn.metrics import (adjusted_rand_score, calinski_harabasz_score,
davies_bouldin_score, silhouette_score)
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler
def yarat(seed: int = 42, n: int = 5000) -> pd.DataFrame:
rng = np.random.default_rng(seed)
segment = rng.choice(5, n, p=[0.30, 0.25, 0.20, 0.15, 0.10])
profil = np.array([[25.0, 4.0, 180_000.0, 2.0], [8.0, 14.0, 95_000.0, 5.0],
[75.0, 2.0, 420_000.0, 1.5], [120.0, 1.0, 150_000.0, 1.0],
[15.0, 9.0, 850_000.0, 6.0]])
X = profil[segment] * rng.lognormal(0, 0.28, (n, 4))
df = pd.DataFrame(X, columns=["oxirgi_kun", "xaridlar", "ortacha_chek",
"toifalar"])
df["segment"] = segment
anomaliya = rng.random(n) < 0.012
df.loc[anomaliya, "ortacha_chek"] *= rng.uniform(15, 40, anomaliya.sum())
df.loc[anomaliya, "xaridlar"] *= rng.uniform(3, 8, anomaliya.sum())
return df
def gap_statistika(X, kk, B: int = 6, seed: int = 0):
rng = np.random.default_rng(seed)
past, yuqori = X.min(axis=0), X.max(axis=0)
gaplar, sklar = [], []
for k in kk:
J = max(KMeans(k, n_init=5, random_state=0).fit(X).inertia_, 1e-12)
tasodifiy = []
for b in range(B):
Xr = rng.uniform(past, yuqori, X.shape)
tasodifiy.append(np.log(max(KMeans(k, n_init=3,
random_state=b).fit(Xr).inertia_,
1e-12)))
tasodifiy = np.array(tasodifiy)
gaplar.append(tasodifiy.mean() - np.log(J))
sklar.append(tasodifiy.std() * np.sqrt(1 + 1 / B))
return np.array(gaplar), np.array(sklar)
def barqarorlik(X, yaratuvchi, B: int = 10, seed: int = 0) -> float:
rng = np.random.default_rng(seed)
ballar = []
for _ in range(B):
a = rng.choice(len(X), int(len(X) * 0.7), replace=False)
b = rng.choice(len(X), int(len(X) * 0.7), replace=False)
umumiy = np.intersect1d(a, b)
ia = {v: i for i, v in enumerate(a)}
ib = {v: i for i, v in enumerate(b)}
ya = yaratuvchi().fit_predict(X[a])
yb = yaratuvchi().fit_predict(X[b])
ballar.append(adjusted_rand_score([ya[ia[v]] for v in umumiy],
[yb[ib[v]] for v in umumiy]))
return float(np.mean(ballar))
def main() -> None:
df = yarat()
nomlar = ["oxirgi_kun", "xaridlar", "ortacha_chek", "toifalar"]
X = df[nomlar].to_numpy()
izo = IsolationForest(contamination=0.015, n_estimators=200,
random_state=0, n_jobs=1).fit(X)
toza = izo.predict(X) == 1
Xs = StandardScaler().fit_transform(np.log1p(X[toza]))
haqiqiy = df.loc[toza, "segment"].to_numpy()
print("=== 1. Tuzilma bormi (gap statistikasi) ===")
kk = list(range(1, 9))
g, s = gap_statistika(Xs, kk)
tanlangan = kk[-1]
for i in range(len(kk) - 1):
if g[i] >= g[i + 1] - s[i + 1]:
tanlangan = kk[i]
break
print(f" {'k':>3} {'gap':>9} {'s_k':>8}")
for i, k in enumerate(kk[:6]):
print(f" {k:>3} {g[i]:>9.4f} {s[i]:>8.4f}")
print(f" gap tanlagan k = {tanlangan} (1 bo'lsa tuzilma yo'q)")
print("\n=== 2. k bo'yicha jadval (K-means) ===")
print(f" {'k':>3} {'silh':>8} {'DB':>7} {'CH':>9} {'barqaror':>10} "
f"{'eng kichik %':>14}")
natijalar = {}
for k in range(2, 9):
yorliq = KMeans(k, n_init=10, random_state=0).fit_predict(Xs)
b = barqarorlik(Xs, lambda k=k: KMeans(k, n_init=10, random_state=0),
B=8)
kichik = np.bincount(yorliq).min() / len(Xs)
natijalar[k] = (silhouette_score(Xs, yorliq), b, kichik)
print(f" {k:>3} {silhouette_score(Xs, yorliq):>8.4f} "
f"{davies_bouldin_score(Xs, yorliq):>7.4f} "
f"{calinski_harabasz_score(Xs, yorliq):>9.1f} {b:>10.4f} "
f"{kichik:>13.1%}")
print("\n=== 3. Algoritmlarni taqqoslash (k = 5) ===")
nomzodlar = {
"KMeans": lambda: KMeans(5, n_init=10, random_state=0),
"GMM(full)": lambda: GaussianMixture(5, covariance_type="full",
n_init=5, random_state=0),
"Ward": lambda: AgglomerativeClustering(5, linkage="ward"),
"DBSCAN": lambda: DBSCAN(eps=0.45, min_samples=20),
}
print(f" {'algoritm':<12} {'qamrov':>8} {'silh':>8} {'barqaror':>10} "
f"{'ARI (haqiqiy)':>15}")
for nom, yaratuvchi in nomzodlar.items():
yorliq = yaratuvchi().fit_predict(Xs)
m = yorliq != -1
k = len(set(yorliq[m]))
if k < 2:
print(f" {nom:<12} (bitta klaster)")
continue
b = barqarorlik(Xs, yaratuvchi, B=8) if nom != "DBSCAN" else float("nan")
print(f" {nom:<12} {m.mean():>7.1%} "
f"{silhouette_score(Xs[m], yorliq[m]):>8.4f} {b:>10.4f} "
f"{adjusted_rand_score(haqiqiy, yorliq):>15.4f}")
print("\n=== 4. Yakuniy tanlov ===")
nomzod_k = [k for k in range(3, 8) if natijalar[k][2] >= 0.05]
eng = max(nomzod_k, key=lambda k: natijalar[k][1])
print(f" nomzodlar (eng kichik klaster >= 5%): {nomzod_k}")
print(f" barqarorligi eng yuqorisi: k = {eng} "
f"({natijalar[eng][1]:.4f})")
print(f" silhouette: {natijalar[eng][0]:.4f}")
print(f" QAROR: KMeans(k={eng}) - barqaror, tez, predict bor")
print(" ⭐ Jadval qarorni asoslaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Tuzilma bormi (gap statistikasi) ===
k gap s_k
1 0.7819 0.0070
2 1.1422 0.0074
3 1.4492 0.0061
4 1.8810 0.0038
5 2.1758 0.0061
6 2.1668 0.0078
gap tanlagan k = 5 (1 bo'lsa tuzilma yo'q)
=== 2. k bo'yicha jadval (K-means) ===
k silh DB CH barqaror eng kichik %
2 0.4950 0.8670 5549.9 0.9969 35.6%
3 0.5483 0.5715 5617.1 0.9995 9.7%
4 0.5191 0.7022 7710.3 0.9987 9.6%
5 0.5560 0.6170 9189.2 0.9980 9.6%
6 0.4593 0.9393 8080.0 0.9604 9.6%
7 0.3591 1.2166 7319.2 0.8239 9.6%
8 0.3512 1.1777 6657.2 0.8676 9.6%
=== 3. Algoritmlarni taqqoslash (k = 5) ===
algoritm qamrov silh barqaror ARI (haqiqiy)
KMeans 100.0% 0.5560 0.9980 0.9877
GMM(full) 100.0% 0.5553 0.9989 0.9881
Ward 100.0% 0.5545 0.9792 0.9810
DBSCAN 97.8% 0.5541 nan 0.4466
=== 4. Yakuniy tanlov ===
nomzodlar (eng kichik klaster >= 5%): [3, 4, 5, 6, 7]
barqarorligi eng yuqorisi: k = 3 0.9995-bob
silhouette: 0.5483
QAROR: KMeans(k=3) - barqaror, tez, predict bor
⭐ Jadval qarorni asoslaydiNima ko'rsatdi: 2.1, 2.3-bo'limlar.
Misol 3 — Talqin, nomlash va tekshiruv
"""8-9 qadamlar (real numpy/pandas/sklearn)."""
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.ensemble import HistGradientBoostingRegressor, IsolationForest
from sklearn.metrics import r2_score, silhouette_samples
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
def yarat(seed: int = 42, n: int = 5000) -> pd.DataFrame:
rng = np.random.default_rng(seed)
segment = rng.choice(5, n, p=[0.30, 0.25, 0.20, 0.15, 0.10])
profil = np.array([[25.0, 4.0, 180_000.0, 2.0], [8.0, 14.0, 95_000.0, 5.0],
[75.0, 2.0, 420_000.0, 1.5], [120.0, 1.0, 150_000.0, 1.0],
[15.0, 9.0, 850_000.0, 6.0]])
X = profil[segment] * rng.lognormal(0, 0.28, (n, 4))
df = pd.DataFrame(X, columns=["oxirgi_kun", "xaridlar", "ortacha_chek",
"toifalar"])
# klasterlashda ISHLATILMAYDIGAN o'zgaruvchi - tekshiruv uchun
kelgusi = (profil[segment, 1] * profil[segment, 2] / 30.0
* rng.lognormal(0, 0.4, n))
df["kelgusi_oy_xarid"] = kelgusi
df["segment"] = segment
anomaliya = rng.random(n) < 0.012
df.loc[anomaliya, "ortacha_chek"] *= rng.uniform(15, 40, anomaliya.sum())
df.loc[anomaliya, "xaridlar"] *= rng.uniform(3, 8, anomaliya.sum())
return df
def main() -> None:
df = yarat()
nomlar = ["oxirgi_kun", "xaridlar", "ortacha_chek", "toifalar"]
X = df[nomlar].to_numpy()
izo = IsolationForest(contamination=0.015, n_estimators=200,
random_state=0, n_jobs=1).fit(X)
toza = izo.predict(X) == 1
sc = StandardScaler().fit(np.log1p(X[toza]))
Xs = sc.transform(np.log1p(X[toza]))
km = KMeans(5, n_init=10, random_state=0).fit(Xs)
yorliq = km.labels_
dft = df.loc[toza].copy()
dft["klaster"] = yorliq
print("=== 1. Segment profillari (asl birliklarda) ===")
markazlar = np.expm1(sc.inverse_transform(km.cluster_centers_))
umumiy = dft[nomlar].median().to_numpy()
print(f" {'k':>3} {'n':>6} {'ulush':>7} " + "".join(f"{n:>15}" for n in nomlar))
for k in range(5):
n = int((yorliq == k).sum())
print(f" {k:>3} {n:>6} {n / len(yorliq):>6.1%} "
+ "".join(f"{markazlar[k, i]:>15,.0f}" for i in range(len(nomlar))))
print(f" {'umumiy mediana':<17}" + "".join(f"{v:>15,.0f}" for v in umumiy))
print("\n=== 2. Eng farq qiladigan belgilar ===")
for k in range(5):
farq = (markazlar[k] - umumiy) / umumiy
tartib = np.argsort(-np.abs(farq))[:3]
tavsif = ", ".join(f"{nomlar[i]} {farq[i]:+.0%}" for i in tartib)
print(f" klaster {k}: {tavsif}")
print("\n=== 3. Segment nomlari ===")
segment_nomlari = {}
for k in range(5):
m = markazlar[k]
if m[0] > umumiy[0] * 2:
nom = "Uyquda"
elif m[2] > umumiy[2] * 2.5:
nom = "Qimmatli"
elif m[1] > umumiy[1] * 1.8:
nom = "Tez-tez, kichik chek"
elif m[0] > umumiy[0] * 1.3:
nom = "Kamdan-kam, katta chek"
else:
nom = "Oddiy faol"
segment_nomlari[k] = nom
for k in range(5):
print(f" klaster {k} -> \"{segment_nomlari[k]}\" "
f"({(yorliq == k).mean():.1%})")
print("\n=== 4. Bashorat testi (klasterlashda ishlatilmagan o'zgaruvchi) ===")
y = dft["kelgusi_oy_xarid"].to_numpy()
# faqat segment yorlig'i bilan bashorat
bir_issiq = np.eye(5)[yorliq]
r2_segment = cross_val_score(HistGradientBoostingRegressor(max_iter=100,
random_state=0),
bir_issiq, y, cv=3, scoring="r2").mean()
r2_belgilar = cross_val_score(HistGradientBoostingRegressor(max_iter=100,
random_state=0),
Xs, y, cv=3, scoring="r2").mean()
print(f" faqat segment yorlig'i bilan: R^2 {r2_segment:.4f}")
print(f" to'liq belgilar bilan: R^2 {r2_belgilar:.4f}")
print(f" segment yorlig'i ma'lumotning "
f"{r2_segment / max(r2_belgilar, 1e-9):.0%} ini saqlaydi")
print(f" {'segment':<24} {'kelgusi oy o_rtacha':>22}")
for k in range(5):
print(f" {segment_nomlari[k]:<24} "
f"{y[yorliq == k].mean():>22,.0f}")
print(" ⭐ Segmentlar ishlatilmagan o'zgaruvchini bashorat qila oladi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Segment profillari (asl birliklarda) ===
k n ulush oxirgi_kun xaridlar ortacha_chek toifalar
0 745 15.1% 120 1 152,323 1
1 1234 25.1% 8 14 96,583 5
2 1518 30.8% 25 4 181,149 2
3 480 9.7% 15 9 855,055 6
4 948 19.2% 74 2 427,919 2
umumiy mediana 25 4 177,589 2
=== 2. Eng farq qiladigan belgilar ===
klaster 0: oxirgi_kun +378%, xaridlar -74%, toifalar -52%
klaster 1: xaridlar +247%, toifalar +137%, oxirgi_kun -68%
klaster 2: toifalar -5%, ortacha_chek +2%, oxirgi_kun +0%
klaster 3: ortacha_chek +381%, toifalar +182%, xaridlar +123%
klaster 4: oxirgi_kun +195%, ortacha_chek +141%, xaridlar -49%
=== 3. Segment nomlari ===
klaster 0 -> "Uyquda" (15.1%)
klaster 1 -> "Tez-tez, kichik chek" (25.1%)
klaster 2 -> "Oddiy faol" (30.8%)
klaster 3 -> "Qimmatli" (9.7%)
klaster 4 -> "Uyquda" (19.2%)
=== 4. Bashorat testi (klasterlashda ishlatilmagan o'zgaruvchi) ===
faqat segment yorlig'i bilan: R^2 0.7757
to'liq belgilar bilan: R^2 0.7363
segment yorlig'i ma'lumotning 105% ini saqlaydi
segment kelgusi oy o_rtacha
Uyquda 5,883
Tez-tez, kichik chek 47,106
Oddiy faol 26,357
Qimmatli 284,829
Uyquda 30,579
⭐ Segmentlar ishlatilmagan o'zgaruvchini bashorat qila oladiNima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 4 — Chiqarish va segmentatsiya kartasi
"""10-qadam: Pipeline, biriktirish va hujjat (real pandas/sklearn/joblib)."""
import io
import pickle
import numpy as np
import pandas as pd
import sklearn
from sklearn.cluster import KMeans
from sklearn.ensemble import IsolationForest
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
def yarat(seed: int = 42, n: int = 5000) -> pd.DataFrame:
rng = np.random.default_rng(seed)
segment = rng.choice(5, n, p=[0.30, 0.25, 0.20, 0.15, 0.10])
profil = np.array([[25.0, 4.0, 180_000.0, 2.0], [8.0, 14.0, 95_000.0, 5.0],
[75.0, 2.0, 420_000.0, 1.5], [120.0, 1.0, 150_000.0, 1.0],
[15.0, 9.0, 850_000.0, 6.0]])
X = profil[segment] * rng.lognormal(0, 0.28, (n, 4))
return pd.DataFrame(X, columns=["oxirgi_kun", "xaridlar", "ortacha_chek",
"toifalar"])
def hajm_kb(obyekt) -> float:
b = io.BytesIO()
pickle.dump(obyekt, b, protocol=pickle.HIGHEST_PROTOCOL)
return b.tell() / 1024
def main() -> None:
df = yarat()
nomlar = list(df.columns)
X = df.to_numpy()
print("=== 1. Yakuniy quvur ===")
quvur = Pipeline([
("log", FunctionTransformer(np.log1p, inverse_func=np.expm1,
validate=True)),
("sc", StandardScaler()),
("km", KMeans(n_clusters=5, n_init=10, random_state=0))])
yorliq = quvur.fit_predict(X)
print(f" bosqichlar: {[n for n, _ in quvur.steps]}")
print(f" klaster o'lchamlari: {np.bincount(yorliq).tolist()}")
print(f" inersiya: {quvur['km'].inertia_:.2f}")
print("\n=== 2. Yangi mijozni biriktirish ===")
yangi = pd.DataFrame({
"oxirgi_kun": [5.0, 140.0, 20.0],
"xaridlar": [18.0, 1.0, 5.0],
"ortacha_chek": [80_000.0, 160_000.0, 900_000.0],
"toifalar": [6.0, 1.0, 5.0]})
segment_nomlari = {0: "Oddiy faol", 1: "Tez-tez, kichik chek",
2: "Kamdan-kam, katta chek", 3: "Uyquda",
4: "Qimmatli"}
bashorat = quvur.predict(yangi.to_numpy())
markazlar = np.expm1(quvur["sc"].inverse_transform(
quvur["km"].cluster_centers_))
Xs_yangi = quvur[:-1].transform(yangi.to_numpy())
masofa = quvur["km"].transform(Xs_yangi).min(axis=1)
print(f" {'mijoz':>6} {'segment':>9} {'markazgacha masofa':>21} "
f"{'ishonch':>10}")
for i, (k, d) in enumerate(zip(bashorat, masofa)):
ishonch = "yuqori" if d < 1.5 else ("o'rta" if d < 2.5 else "past")
print(f" {i:>6} {int(k):>9} {d:>21.3f} {ishonch:>10}")
print("\n=== 3. Model paketi ===")
paket = {
"quvur": quvur,
"belgilar": nomlar,
"segment_nomlari": segment_nomlari,
"markazlar_asl": markazlar.round(1).tolist(),
"ulushlar": (np.bincount(yorliq) / len(yorliq)).round(4).tolist(),
"versiyalar": {"sklearn": sklearn.__version__,
"numpy": np.__version__, "pandas": pd.__version__},
"sana": "2026-09-21",
"o_quv_hajmi": len(df),
}
print(f" kalitlar: {sorted(paket)}")
print(f" paket hajmi: {hajm_kb(paket):.1f} KB")
print(f" ulushlar: {paket['ulushlar']}")
print("\n=== 4. Segmentatsiya kartasi ===")
karta = [
("Ma'lumot", f"{len(df)} mijoz, 4 belgi, 2026-09 holati"),
("Tayyorlash", "log1p (qiyshiqlik > 1) + StandardScaler"),
("Anomaliya", "IsolationForest(0.015) - alohida ko'rib chiqiladi"),
("Algoritm", "KMeans(k=5, n_init=10, random_state=0)"),
("k tanlovi", "gap + silhouette + barqarorlik jadvali"),
("Barqarorlik", "bootstrap ARI (16.3 usuli)"),
("Tekshiruv", "bashorat testi: kelgusi oy xaridi"),
("Biriktirish", "quvur.predict(yangi_df) - K-means da mavjud"),
("Qayta klasterlash", "har chorakda yoki ulush PSI > 0.25 bo'lsa"),
("Cheklovlar", "faqat >= 1 xaridi bor mijozlar uchun"),
]
for kalit, qiymat in karta:
print(f" {kalit:<20}: {qiymat}")
print(" ⭐ Karta + paket = topshirishga tayyor natija")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Yakuniy quvur ===
bosqichlar: ['log', 'sc', 'km']
klaster o'lchamlari: [1253, 960, 493, 757, 1537]
inersiya: 2345.05
=== 2. Yangi mijozni biriktirish ===
mijoz segment markazgacha masofa ishonch
0 0 0.655 yuqori
1 3 0.172 yuqori
2 2 0.825 yuqori
=== 3. Model paketi ===
kalitlar: ['belgilar', 'markazlar_asl', 'o_quv_hajmi', 'quvur', 'sana', 'segment_nomlari', 'ulushlar', 'versiyalar']
paket hajmi: 21.5 KB
ulushlar: [0.2506, 0.192, 0.0986, 0.1514, 0.3074]
=== 4. Segmentatsiya kartasi ===
Ma'lumot : 5000 mijoz, 4 belgi, 2026-09 holati
Tayyorlash : log1p (qiyshiqlik > 1) + StandardScaler
Anomaliya : IsolationForest(0.015) - alohida ko'rib chiqiladi
Algoritm : KMeans(k=5, n_init=10, random_state=0)
k tanlovi : gap + silhouette + barqarorlik jadvali
Barqarorlik : bootstrap ARI (16.3 usuli)
Tekshiruv : bashorat testi: kelgusi oy xaridi
Biriktirish : quvur.predict(yangi_df) - K-means da mavjud
Qayta klasterlash : har chorakda yoki ulush PSI > 0.25 bo'lsa
Cheklovlar : faqat >= 1 xaridi bor mijozlar uchun
⭐ Karta + paket = topshirishga tayyor natijaNima ko'rsatdi: 2.5, 2.6-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Klasterlash natijasi — yakun" | Gipoteza, tekshirish kerak |
| "Anomaliyalarni o'chirish kerak" | Alohida segment bo'lishi mumkin |
| "Bitta algoritm yetarli" | Bir nechtasini taqqoslang |
| "Silhouette qarorni hal qiladi" | Jadval + mazmun |
| "Nom keyinroq beriladi" | Nomsiz segment ishlatilmaydi |
| "predict har doim bor" | DBSCAN/Ward da yo'q |
| "Hujjat ixtiyoriy" | Asosiy natija |
| "Bir marta klasterlash yetarli" | Drift bo'ladi |
6. Keng tarqalgan xatolar va yechimlari
1. Anomaliyani tekshirmasdan klasterlash
KMeans(5).fit(Xs) # 60 ta ulgurji xaridor bor # ⚠️
# avval IsolationForest, keyin qaror # ✅2. Tuzilma borligini tekshirmaslik
KMeans(5).fit(Xs) # tuzilma bormi? # ⚠️
# gap statistikasi + barqarorlik # ✅3. Transformatsiyasiz masshtablash
StandardScaler().fit_transform(df[["monetary"]]) # qiyshiqlik 8 # ⚠️
StandardScaler().fit_transform(np.log1p(df[["monetary"]])) # ✅4. Tushunarsiz nomlar
# "Klaster 0", "Klaster 1", ... # ⚠️
# "Uyquda", "Qimmatli", "Tez-tez, kichik chek" # ✅5. Biriktirish usulisiz topshirish
# faqat yorliqlar ro'yxatini berish # ⚠️
joblib.dump({"quvur": quvur, "nomlar": nomlar}, "segment.joblib") # ✅6. Markazlarni masshtablangan holda ko'rsatish
print(km.cluster_centers_) # -0.42, 1.17 # ⚠️
print(np.expm1(sc.inverse_transform(km.cluster_centers_))) # ✅7. Tekshiruvsiz topshirish
# "5 ta segment topdik" # ⚠️
# barqarorlik + bashorat testi + domen tasdig'i # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 16.2-16.6-darslar (o'tilgan): Klasterlash algoritmlari
- 16.7-dars (o'tilgan): Baholash
- 16.10-dars (o'tilgan): Anomaliya
- 15.13-dars (o'tilgan): Ishlab chiqarish
- Keyingi qism: Feature engineering
8. Eng yaxshi amaliyotlar
Tartib bilan ishlang.
Anomaliyani avval tekshiring.
Tuzilma borligini tasdiqlang.
Bir necha algoritmni taqqoslang.
Barqarorlikni o'lchang.
Segmentlarga nom bering.
Biriktirish usulini bering.
Segmentatsiya kartasini yozing.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # oqimning birinchi uch qadami?
2. # anomaliya qachon tekshiriladi?
3. # tuzilma borligini nima ko'rsatadi?
4. # qiyshiq summalar uchun?
5. # uch tekshiruv?
6. # bashorat testi nima?
7. # yaxshi segment nomi qanday?
8. # markazlar qanday ko'rsatiladi?
9. # predict qaysi algoritmlarda bor?
10. # DBSCAN da nima qilish kerak?
11. # segmentatsiya kartasida nima bo'ladi?
12. # qayta klasterlash qachon?Javoblar
- Ma'lumot, tayyorlash, anomaliya
- Klasterlashdan oldin
- Gap statistikasi va barqarorlik
- log1p
- Barqarorlik, bashorat testi, domen
- Ishlatilmagan o'zgaruvchini bashorat qilish
- Harakatga ishora qiladigan
- Asl birliklarda (inverse_transform)
- K-means, GMM
- Markazlarni saqlash
- Qarorlar, jadval, profillar, cheklovlar
- Jadval bo'yicha yoki drift bo'lsa
Vazifa 2: Xatolarni tuzating
1. KMeans(5).fit(Xs) # anomaliya tekshirilmagan
2. StandardScaler().fit_transform(df[["monetary"]]) # qiyshiqlik 8
3. # "Klaster 0", "Klaster 1"
4. print(km.cluster_centers_) # hisobot uchun
5. # faqat yorliqlar ro'yxatini topshirishJavoblar
1. # avval IsolationForest, keyin qaror
2. StandardScaler().fit_transform(np.log1p(df[["monetary"]]))
3. # "Uyquda", "Qimmatli", "Tez-tez, kichik chek"
4. print(np.expm1(sc.inverse_transform(km.cluster_centers_)))
5. joblib.dump({"quvur": quvur, "nomlar": nomlar}, "segment.joblib")Vazifa 3: Tayyorlash
Modellang:
- Ma'lumot
- Transformatsiya
- Anomaliya
- Qaror
Vazifa 4: Tanlov
Modellang:
- Gap
- k jadvali
- Algoritmlar
- Yakuniy tanlov
Vazifa 5: Talqin
Modellang:
- Profillar
- Farqlar
- Nomlar
- Bashorat testi
Vazifa 6: Chiqarish
Modellang:
- Quvur
- Biriktirish
- Paket
- Karta
Vazifa 7: O'ylash
Segmentatsiya topshirildi va marketing uni ishlatishni boshladi. Uch oydan keyin nima tekshirilishi kerak?
Javob
Qisqa javob: uch narsa — segmentlarning barqarorligi, ularning biznes natijasi va ma'lumotning drifti. Klasterlash bir martalik ish emas: u kuzatilishi va yangilanishi kerak.
1. Segment ulushlari (eng arzon tekshiruv)
| Kuzatuv | Talqin |
|---|---|
| Ulushlar barqaror | Model hali mos |
| Bir ulush sezilarli o'sgan | Xulq-atvor o'zgargan yoki drift |
| Bir ulush yo'qolgan | Segment mavjud emas — qayta klasterlash |
| Markazgacha masofa oshgan | Yangi mijozlar modelga mos kelmaydi |
PSI 15.13-bob bu kuzatuvni avtomatlashtiradi.
2. Biznes natijasi
- Har segment bo'yicha konversiya, o'rtacha chek, churn
- Segmentlar haqiqatan farq qiladimi (statistik test — 11-qism)
- Segmentga yo'naltirilgan kampaniyalar segmentsizdan yaxshiroqmi
- Agar yo'q — segmentatsiya foydasiz, qayta ko'rib chiqish kerak
Eng ishonchli javob — A/B test: segmentlangan yondashuv nazorat guruhidan yaxshiroq natija beradimi.
3. Mijozlarning segment o'zgarishi
- Har oy necha foiz mijoz segmentini o'zgartiradi?
- 5-15% normal (tabiiy evolyutsiya)
- 40%+ — segmentlar beqaror yoki chegaralar noto'g'ri
- Chegarada turgan mijozlar (masofa katta) alohida kuzatiladi
4. Qayta klasterlash qarori
Qayta klasterlash KERAK, agar:
- segment ulushlarining PSI > 0.25
- yangi mijozlarning o'rtacha masofasi 30%+ oshgan
- biznes natijasi yomonlashgan
- yangi mahsulot/kanal qo'shilgan (belgilar o'zgargan)
Qayta klasterlashda: yangi natijani eskisi bilan TAQQOSLANG
ARI yuqori bo'lsa - segmentlar saqlanadi, faqat chegara yangilanadi
ARI past bo'lsa - yangi segment tuzilmasi, marketing xabardor qilinsin5. Xulosa
- Ulushlar va masofani doimiy kuzating
- Biznes natijasini o'lchang (ideal — A/B test)
- Segment o'zgarish darajasini kuzating
- Qayta klasterlashda eski bilan taqqoslang
Nimani mustahkamlaydi: 2.5-bo'lim.
Xulosa
Bu darsda to'liq nazoratsiz loyihani qurdik.
Eng muhim uch fikr:
Tartib bilan ishlang. Ma'lumot → tayyorlash (qiyshiqlikka qarab
log1p) → anomaliya (klasterlashdan oldin, chunki ular markazlarni tortadi) → tuzilma bormi (gap, barqarorlik) → klasterlash → jadval bilan baholash → talqin → tekshiruv → chiqarish. Nazoratsiz loyihada "test AUC" kabi yakuniy hakam yo'q, shuning uchun har qaror asoslanishi kerak.Uch tekshiruv. Barqarorlik (bootstrap ARI) — natija takrorlanadimi; bashorat testi — segmentlar klasterlashda ishlatilmagan o'zgaruvchini bashorat qila oladimi; domen tasdig'i — mutaxassis segmentlarni tanidimi. Ularsiz natija gipoteza bo'lib qoladi.
Nom va biriktirish usuli — majburiy. "Klaster 3" degan segment hech qachon ishlatilmaydi; "Yo'qolayotgan qimmatli" esa darhol harakatga ishora qiladi. Markazlarni asl birliklarda ko'rsating (
inverse_transform+expm1) va yangi mijozni biriktirish usulini bering (quvur.predict, DBSCAN/Ward da esa markazlarni saqlang). Yakunda segmentatsiya kartasi topshiriladi.
Bu bilan 16-qism — Nazoratsiz o'rganish yakunlandi. Keyingi qismda feature engineeringni o'rganamiz: belgilarni yaratish, kodlash, tanlash va quvurlar.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!