Tahrirsiz: titanic bilan birinchi uchrashuv
Bu bob nega boshqacha
Kitoblarda yechim javob topilgandan keyin yoziladi. Shuning uchun u to'g'ri chiziq bo'lib ko'rinadi: ma'lumotni yuklaymiz → tozalaymiz → model → 0.87 → tamom.
Hamma noto'g'ri yo'l tahrirda o'chirilgan.
Talaba shuni o'qiydi va xulosa qilad: "bilimdon odam shunday ishlaydi." Keyin o'zi ishlaganda yo'li zigzag bo'ladi — va o'zini aybdor his qiladi.
Bu bobda zigzag qoldirilgan. Men titanic.csv ni ochaman va har bir qarorni, shu jumladan xato qarorlarni ham, sodir bo'lgan tartibda yozaman. To'rtta urinish bor. Ikkitasi noto'g'ri. Bittasida mening gipotezam butunlay rad etiladi.
Nega aynan titanic
Chunki bu — dunyoda eng ko'p boshlanadigan Kaggle dataseti. Sizning talabangiz ham undan boshlaydi. Va uning ichida men o'ylab topmagan, haqiqiy tuzoq bor.
1-urinish: faylni ochaman
Protokolning 1-qadami (0-bob). Uchta buyruq.
import pandas as pd df = pd.read_csv(DATA / "titanic.csv") print("shakli:", df.shape) print() print(df.dtypes.to_string())
shakli: (891, 15) survived int64 pclass int64 sex object age float64 sibsp int64 parch int64 fare float64 embarked object class object who object adult_male bool deck object embark_town object alive object alone bool
891 qator, 15 ustun. Birinchi fikrim: "15 ustun — ko'p emas, hammasini modelga beraman."
Bu fikr xato edi. Nega — 10 daqiqadan keyin bilib oldim.
show(df.head(5))
| survived | pclass | sex | age | sibsp | parch | fare | embarked | class | who | adult_male | deck | embark_town | alive | alone |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 3 | male | 22 | 1 | 0 | 7.25 | S | Third | man | True | NaN | Southampton | no | False |
| 1 | 1 | female | 38 | 1 | 0 | 71.283 | C | First | woman | False | C | Cherbourg | yes | False |
| 1 | 3 | female | 26 | 0 | 0 | 7.925 | S | Third | woman | False | NaN | Southampton | yes | True |
| 1 | 1 | female | 35 | 1 | 0 | 53.1 | S | First | woman | False | C | Southampton | yes | False |
| 0 | 3 | male | 35 | 0 | 0 | 8.05 | S | Third | man | True | NaN | Southampton | no | True |
Target aniq: survived. 2-qadam jumlasi:
"Men yo'lovchining tirik qolganini uning sinfi, jinsi, yoshi va chiptasi dan bashorat qilaman."
3-qadam: target 0/1 — binary classification.
4-qadam, target ni sanayman:
print(df.survived.value_counts(normalize=True).sort_index().to_string()) print() print(f"Agar model HAMMAGA 'o'ldi' desa, accuracy = {1 - df.survived.mean():.4f}")
survived 0 0.616162 1 0.383838 Agar model HAMMAGA 'o'ldi' desa, accuracy = 0.6162
0.6162. Mana mening chegaram. Modelim shundan yuqori bo'lishi kerak.
2-urinish: hamma ustunni modelga beraman
Split qilaman, dummy ni o'lchayman, keyin hamma ustunni beraman.
import numpy as np from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.linear_model import LogisticRegression from sklearn.dummy import DummyClassifier from sklearn.metrics import accuracy_score X, y = df.drop(columns=["survived"]), df["survived"] X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y) def quvur_qur(son, matn): return Pipeline([ ("tayyorla", ColumnTransformer([ ("son", Pipeline([("toldir", SimpleImputer(strategy="median")), ("shkala", StandardScaler())]), son), ("matn", Pipeline([("toldir", SimpleImputer(strategy="most_frequent")), ("kod", OneHotEncoder(handle_unknown="ignore"))]), matn), ])), ("model", LogisticRegression(max_iter=3000)), ]) baza = accuracy_score(y_te, DummyClassifier(strategy="most_frequent") .fit(X_tr[["pclass"]], y_tr).predict(X_te[["pclass"]])) son = X.select_dtypes(include=[np.number, bool]).columns.tolist() matn = [c for c in X.columns if c not in son] print("son ustunlar :", son) print("matn ustunlar:", matn) q1 = quvur_qur(son, matn).fit(X_tr, y_tr) a1 = accuracy_score(y_te, q1.predict(X_te)) print() print(f"BAZA : {baza:.4f}") print(f"1-urinish : {a1:.4f}")
son ustunlar : ['pclass', 'age', 'sibsp', 'parch', 'fare', 'adult_male', 'alone'] matn ustunlar: ['sex', 'embarked', 'class', 'who', 'deck', 'embark_town', 'alive'] BAZA : 0.6157 1-urinish : 1.0000
Men ekranda 1.0000 ko'rdim.
Rostini aytaman: birinchi yarim sekundda xursand bo'ldim. Keyin oshqozonim siqildi. Chunki 0-bobda o'zim yozganman: mukammal natija — bayram emas, signal.
891 yo'lovchi. Yuz foiz. Bunday bo'lishi mumkin emas.
Tuzoqni izlayman
Qaysi ustun javobni aytib turibdi? Har bir matn ustunini target bilan guruhlab ko'raman.
for ustun in matn: jadval = df.groupby(ustun, dropna=False).survived.agg(["count", "mean"]) aniq = ((jadval["mean"] == 0) | (jadval["mean"] == 1)).all() belgi = " <-- TARGET NI TO'LIQ AJRATADI" if aniq else "" print(f"{ustun}:{belgi}") print(jadval.round(3).to_string()) print()
sex:
count mean
sex
female 314 0.742
male 577 0.189
embarked:
count mean
embarked
C 168 0.554
Q 77 0.390
S 644 0.337
NaN 2 1.000
class:
count mean
class
First 216 0.630
Second 184 0.473
Third 491 0.242
who:
count mean
who
child 83 0.590
man 537 0.164
woman 271 0.756
deck:
count mean
deck
A 15 0.467
B 47 0.745
C 59 0.593
D 33 0.758
E 32 0.750
F 13 0.615
G 4 0.500
NaN 688 0.299
embark_town:
count mean
embark_town
Cherbourg 168 0.554
Queenstown 77 0.390
Southampton 644 0.337
NaN 2 1.000
alive: <-- TARGET NI TO'LIQ AJRATADI
count mean
alive
no 549 0.0
yes 342 1.0
Mana. alive ustuni.
alive = "no" → survived o'rtachasi 0.0. alive = "yes" → 1.0. Hech qanday aralashuv yo'q.
print("alive ustunining qiymatlari:", df.alive.unique().tolist()) print("'alive == yes' 'survived == 1' ga MOS keladimi:", bool(((df.alive == "yes") == (df.survived == 1)).all()))
alive ustunining qiymatlari: ['no', 'yes'] 'alive == yes' 'survived == 1' ga MOS keladimi: True
alive — bu survived ning matn ko'rinishi. Ya'ni target ning o'zi, boshqa nom bilan.
Men uni ko'rmadim, chunki df.head() ga shoshib qaradim va 15 ustunni o'qib chiqmadim. Bu — mening xatom, datasetning aybi emas.
Haqiqiy datasetlarda target ikki marta uchraydi
Bu seaborn ning titanic versiyasi va u ataylab shunday qilinmagan — shunchaki survived ning o'qishga qulay ko'rinishi qo'shilgan. Lekin amalda har doim shunday bo'ladi: status va status_text, is_churned va churn_date, berildi va komissiya.
Qoida: har bir ustunni target bilan guruhlab ko'ring. Agar bittasi target ni toza ajratsa — u xususiyat emas.
3-urinish: alive ni tashlayman
X2_tr, X2_te = X_tr.drop(columns=["alive"]), X_te.drop(columns=["alive"]) son2 = [c for c in son if c != "alive"] matn2 = [c for c in matn if c != "alive"] q2 = quvur_qur(son2, matn2).fit(X2_tr, y_tr) a2 = accuracy_score(y_te, q2.predict(X2_te)) print(f"BAZA : {baza:.4f}") print(f"1-urinish : {a1:.4f} (leakage)") print(f"2-urinish : {a2:.4f} (alive tashlandi)")
BAZA : 0.6157 1-urinish : 1.0000 (leakage) 2-urinish : 0.8172 (alive tashlandi)
0.8172. Bazadan ancha yuqori. Endi bu — haqiqiy raqam.
Missing qiymatlarga qarayman
yoq = df.isna().sum() yoq = yoq[yoq > 0].sort_values(ascending=False) for ustun, n in yoq.items(): print(f" {ustun:14s} {n:3d} ta yo'q ({n/len(df):.1%})")
deck 688 ta yo'q (77.2%) age 177 ta yo'q (19.9%) embarked 2 ta yo'q (0.2%) embark_town 2 ta yo'q (0.2%)
deck — 688 ta yo'q, ya'ni 77.2%. To'rt yo'lovchidan uchtasida bu ma'lumot yo'q.
Men SimpleImputer(strategy="most_frequent") bilan uni to'ldirdim. Ya'ni 688 yo'lovchiga eng ko'p uchraydigan kemani "taxmin qilib" yozdim. Bu — o'ylab topilgan ma'lumot.
Gipotezam: bu ustun modelga shovqin qo'shadi. Tashlasam, natija yaxshilanishi kerak.
X3_tr, X3_te = X2_tr.drop(columns=["deck"]), X2_te.drop(columns=["deck"]) son3 = [c for c in son2 if c != "deck"] matn3 = [c for c in matn2 if c != "deck"] q3 = quvur_qur(son3, matn3).fit(X3_tr, y_tr) a3 = accuracy_score(y_te, q3.predict(X3_te)) print(f"2-urinish : {a2:.4f} (deck bor, 77% to'ldirilgan)") print(f"3-urinish : {a3:.4f} (deck tashlandi)") print(f"O'zgarish : {a3 - a2:+.4f}")
2-urinish : 0.8172 (deck bor, 77% to'ldirilgan) 3-urinish : 0.8246 (deck tashlandi) O'zgarish : +0.0075
0.8246. Yaxshilandi — ustun olib tashlanganda.
Bu talabalar uchun g'alati tuyuladi: ma'lumotni kamaytirib, natijani yaxshiladik. Sabab oddiy — 77% o'ylab topilgan qiymat ma'lumot emas, shovqin.
4-urinish: gipotezam va uning rad etilishi
Endi qolgan ustunlarni diqqat bilan o'qiyman. Va shubha paydo bo'ladi: ulardan ba'zilari boshqa ustunlardan hisoblangan emasmi?
Tekshiraman.
print("class ~ pclass:") print(df.groupby("pclass")["class"].unique().to_string()) print() print("adult_male == (sex=='male' va age>=18):", bool((df.adult_male == ((df.sex == "male") & (df.age >= 18))).all())) print("alone == (sibsp + parch == 0) :", bool((df.alone == ((df.sibsp + df.parch) == 0)).all())) print("who qiymatlari :", df.who.unique().tolist()) print("embark_town ~ embarked :", df.groupby("embarked")["embark_town"].unique().to_dict())
class ~ pclass:
pclass
1 [First]
2 [Second]
3 [Third]
adult_male == (sex=='male' va age>=18): False
alone == (sibsp + parch == 0) : True
who qiymatlari : ['man', 'woman', 'child']
embark_town ~ embarked : {'C': array(['Cherbourg'], dtype=object), 'Q': array(['Queenstown'], dtype=object), 'S': array(['Southampton'], dtype=object)}
Hammasi tasdiqlandi. Beshta ustun yangi ma'lumot emas:
show(pd.DataFrame([ {"Ustun": "class", "Nimadan hisoblangan": "pclass (1→First, 2→Second, 3→Third)"}, {"Ustun": "adult_male", "Nimadan hisoblangan": "sex va age"}, {"Ustun": "alone", "Nimadan hisoblangan": "sibsp + parch"}, {"Ustun": "who", "Nimadan hisoblangan": "sex va age (man/woman/child)"}, {"Ustun": "embark_town", "Nimadan hisoblangan": "embarked (S→Southampton, ...)"}, ]))
| Ustun | Nimadan hisoblangan |
|---|---|
| class | pclass (1→First, 2→Second, 3→Third) |
| adult_male | sex va age |
| alone | sibsp + parch |
| who | sex va age (man/woman/child) |
| embark_town | embarked (S→Southampton, ...) |
Mening gipotezam: bu beshtasi takrorlanish. Ularni tashlasam, model tozalanadi va natija yaxshilanadi yoki o'zgarmaydi.
Faqat ettita "halol, takrorlanmaydigan" ustun qoldiraman: pclass, sex, age, sibsp, parch, fare, embarked.
halol = ["pclass", "sex", "age", "sibsp", "parch", "fare", "embarked"] son4 = ["pclass", "age", "sibsp", "parch", "fare"] matn4 = ["sex", "embarked"] q4 = quvur_qur(son4, matn4).fit(X_tr[halol], y_tr) a4 = accuracy_score(y_te, q4.predict(X_te[halol])) print(f"3-urinish : {a3:.4f} (13 ustun)") print(f"4-urinish : {a4:.4f} (7 'halol' ustun)") print(f"O'zgarish : {a4 - a3:+.4f}")
3-urinish : 0.8246 (13 ustun) 4-urinish : 0.7985 (7 'halol' ustun) O'zgarish : -0.0261
0.7985. Yomonlashdi.
Mening gipotezam rad etildi. Men "takrorlanish zarar" deb o'ylaganman — o'lchov buni tasdiqlamadi.
Nega? Chunki who ustuni sex va age dan hisoblangan bo'lsa ham, u ma'lumotni boshqacha ko'rsatadi: "child" degan toifa chiziqli modelga "age < 16" qoidasini bepul beradi. Logistic regression o'zi bunday chegara topa olmaydi — u faqat chiziqli bog'lanishni ko'radi.
Takrorlanuvchi ustun har doim zararli emas
Bu — mening eng foydali xatom. Statistikada multikollinearlik koeffitsiyentlarni beqaror qiladi — bu to'g'ri. Lekin bashorat aniqligi uchun u ko'pincha muhim emas, ba'zan esa foydali: hisoblangan ustun modelga u o'zi topa olmaydigan shaklni beradi.
Shuning uchun: "bu ustun ortiqcha" deb o'ylab tashlamang. O'lchab tashlang.
Eng muhim qism: bu farqlar haqiqiymi?
Menda to'rtta raqam bor: 0.8172, 0.8246, 0.7985. Ular orasidagi farq 0.026 ga yetadi.
Lekin hammasi bitta train/test bo'linishidan. random_state=42 ni boshqa songa o'zgartirsam, raqamlar o'zgaradi.
Shuni tekshiraman — cross-validation bilan.
from sklearn.model_selection import cross_val_score cv3 = cross_val_score(quvur_qur(son3, matn3), X.drop(columns=["alive", "deck"]), y, cv=5) cv4 = cross_val_score(quvur_qur(son4, matn4), X[halol], y, cv=5) print("3-urinish (13 ustun):") print(" fold lar:", cv3.round(4).tolist()) print(f" o'rtacha: {cv3.mean():.4f} ±{cv3.std():.4f}") print() print("4-urinish (7 ustun):") print(" fold lar:", cv4.round(4).tolist()) print(f" o'rtacha: {cv4.mean():.4f} ±{cv4.std():.4f}") print() print(f"O'rtachalar farqi : {abs(cv3.mean() - cv4.mean()):.4f}") print(f"Fold lar tarqalishi: ±{max(cv3.std(), cv4.std()):.4f}")
3-urinish (13 ustun): fold lar: [0.8212, 0.7978, 0.8034, 0.8034, 0.8652] o'rtacha: 0.8182 ±0.0248 4-urinish (7 ustun): fold lar: [0.7765, 0.7865, 0.7809, 0.7697, 0.8258] o'rtacha: 0.7879 ±0.0198 O'rtachalar farqi : 0.0303 Fold lar tarqalishi: ±0.0248
Mana bu — bobning eng muhim xulosasi. Va men bu yerda yana bir marta adashdim.
Cross-validation ni ishga tushirganimda, men "farq shovqin ichida chiqadi" deb kutdim. Ya'ni 13 ustun bilan 7 ustun orasidagi farq tasodif bo'lib chiqadi deb o'yladim.
O'lchov buni ham tasdiqlamadi. Qarang:
- O'rtachalar farqi: 0.0303 (0.8182 va 0.7879)
- Fold lar tarqalishi: ±0.0248
Farq tarqalishdan kattaroq. Ya'ni 13 ustun haqiqatan yaxshiroq — mening 4-urinishdagi gipotezam ikki marta xato bo'ldi.
Lekin diqqat qiling, bu "ishonchli isbot" degani emas. Fold larni yonma-yon qo'yaylik:
- 3-urinish fold lari: 0.8212, 0.7978, 0.8034, 0.8034, 0.8652
- 4-urinish fold lari: 0.7765, 0.7865, 0.7809, 0.7697, 0.8258
4-urinishning eng yaxshi fold i (0.8258) 3-urinishning eng yomon fold idan (0.7978) yuqori. Ya'ni diapazonlar bir-birining ustiga tushadi.
Demak: agar men baxtsiz bitta bo'linishni tanlab, 4-urinishni 0.8258 da, 3-urinishni 0.7978 da o'lchagan bo'lsam — teskari xulosa chiqargan bo'lardim. O'rtacha to'g'ri yo'nalishni ko'rsatadi, lekin bitta split hukm chiqarish uchun yetarli emas.
Bitta split ga ishonmang
Men bu bobni yozganda ikki marta teskari tomonga adashdim: avval farqni muhim deb o'yladim, keyin uni shovqin deb o'yladim. Ikkisi ham tekshirilmagan taxmin edi.
Qoida: ikki modelni taqqoslayotgan bo'lsangiz, cross-validation ishlatasiz — va faqat o'rtachaga emas, fold lar diapazoniga qarasangiz. Diapazonlar ustma-ust tushsa, "aniq yaxshiroq" deb aytolmaysiz, garchi o'rtacha farq ko'rinsa ham. 9-bob shu haqda.
Butun yo'l bitta jadvalda
show(pd.DataFrame([ {"Urinish": "0. Baza (hammaga 'o'ldi')", "Accuracy": f"{baza:.4f}", "Xulosa": "Chegara"}, {"Urinish": "1. Hamma 14 ustun", "Accuracy": f"{a1:.4f}", "Xulosa": "LEAKAGE — alive = target"}, {"Urinish": "2. alive tashlandi", "Accuracy": f"{a2:.4f}", "Xulosa": "Birinchi halol raqam"}, {"Urinish": "3. deck ham tashlandi (77% bo'sh)", "Accuracy": f"{a3:.4f}", "Xulosa": "Ustun kamaydi, natija yaxshilandi"}, {"Urinish": "4. Faqat 7 'halol' ustun", "Accuracy": f"{a4:.4f}", "Xulosa": "Gipotezam RAD ETILDI"}, {"Urinish": "CV: 3-urinish", "Accuracy": f"{cv3.mean():.4f} ±{cv3.std():.4f}", "Xulosa": "Haqiqiy baho — 13 ustun yetakchi"}, {"Urinish": "CV: 4-urinish", "Accuracy": f"{cv4.mean():.4f} ±{cv4.std():.4f}", "Xulosa": "Diapazonlar ustma-ust — ishonch cheklangan"}, ]))
| Urinish | Accuracy | Xulosa |
|---|---|---|
| 0. Baza (hammaga 'o'ldi') | 0.6157 | Chegara |
| 1. Hamma 14 ustun | 1.0000 | LEAKAGE — alive = target |
| 2. alive tashlandi | 0.8172 | Birinchi halol raqam |
| 3. deck ham tashlandi (77% bo'sh) | 0.8246 | Ustun kamaydi, natija yaxshilandi |
| 4. Faqat 7 'halol' ustun | 0.7985 | Gipotezam RAD ETILDI |
| CV: 3-urinish | 0.8182 ±0.0248 | Haqiqiy baho — 13 ustun yetakchi |
| CV: 4-urinish | 0.7879 ±0.0198 | Diapazonlar ustma-ust — ishonch cheklangan |
Nimani o'rgandik
Men bu datasetni ochganimda ikki marta adashdim va bir marta gipotezam rad etildi:
show(pd.DataFrame([ {"Xato": "Hamma ustunni modelga berdim", "Oqibat": "1.0000 — yolg'on natija", "Dars": "Har ustunni target bilan groupby qilib ko'r"}, {"Xato": "77% bo'sh ustunni to'ldirdim", "Oqibat": "Natija pastroq edi", "Dars": "Ko'p bo'sh ustun — to'ldirilmaydi, tashlanadi"}, {"Xato": "Takrorlanuvchi ustunlarni tashladim", "Oqibat": "Natija yomonlashdi", "Dars": "'Ortiqcha' ni o'ylab emas, o'lchab tashla"}, {"Xato": "Bitta split ga ishondim", "Oqibat": "Farq haqiqiy chiqdi, lekin diapazonlar ustma-ust", "Dars": "CV: o'rtachaga EMAS, fold diapazoniga qara"}, ]))
| Xato | Oqibat | Dars |
|---|---|---|
| Hamma ustunni modelga berdim | 1.0000 — yolg'on natija | Har ustunni target bilan groupby qilib ko'r |
| 77% bo'sh ustunni to'ldirdim | Natija pastroq edi | Ko'p bo'sh ustun — to'ldirilmaydi, tashlanadi |
| Takrorlanuvchi ustunlarni tashladim | Natija yomonlashdi | 'Ortiqcha' ni o'ylab emas, o'lchab tashla |
| Bitta split ga ishondim | Farq haqiqiy chiqdi, lekin diapazonlar ustma-ust | CV: o'rtachaga EMAS, fold diapazoniga qara |
Agar men bu bobni "tozalab" yozsam, u shunday bo'lardi:
titanic.csv ni yuklaymiz,
alivevadeckni tashlaymiz, Pipeline quramiz, accuracy 0.8246. Tamom.
To'rt qator. Va siz undan hech narsa o'rganmas edingiz — chunki sizda alive ni qanday topish kerakligi haqida tasavvur qolmas edi.
Talabaga aytadigan gap
Agar sizning yo'lingiz zigzag bo'lsa — siz to'g'ri ishlayapsiz. Zigzag — bu ishning o'zi, uning nuqsoni emas.
Farq shundaki: tajribali odam zigzagni tezroq aniqlaydi va har bir burilishdan keyin nima o'rganganini yozib oladi.
Uyga vazifa
Shu protokolni o'zingiz uchta datasetda yuring (42, 43, 44-boblar). Har biri uchun daftarda yozib boring:
- Target nima? (bitta jumla)
- Bazam qancha?
- Qaysi ustunlarni tashladim va nega?
- Birinchi halol natijam qancha?
- Qaysi gipotezam rad etildi?
Beshinchi savol eng muhimi. Agar bitta ham gipotezangiz rad etilmagan bo'lsa — demak siz hech narsani sinab ko'rmadingiz.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.