Asosiy qismga o'tish

Tahrirsiz: titanic bilan birinchi uchrashuv

40-bob 15 kod bloki 3258 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

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.

python6 qator
import pandas as pd

df = pd.read_csv(DATA / "titanic.csv")
print("shakli:", df.shape)
print()
print(df.dtypes.to_string())
Chiqish
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.

python1 qator
show(df.head(5))
survivedpclasssexagesibspparchfareembarkedclasswhoadult_maledeckembark_townalivealone
03male22107.25SThirdmanTrueNaNSouthamptonnoFalse
11female381071.283CFirstwomanFalseCCherbourgyesFalse
13female26007.925SThirdwomanFalseNaNSouthamptonyesTrue
11female351053.1SFirstwomanFalseCSouthamptonyesFalse
03male35008.05SThirdmanTrueNaNSouthamptonnoTrue

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:

python3 qator
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}")
Chiqish
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.

python37 qator
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}")
Chiqish
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.

python7 qator
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()
Chiqish
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.

python3 qator
print("alive ustunining qiymatlari:", df.alive.unique().tolist())
print("'alive == yes'  'survived == 1' ga MOS keladimi:",
      bool(((df.alive == "yes") == (df.survived == 1)).all()))
Chiqish
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

python9 qator
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)")
Chiqish
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

python4 qator
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%})")
Chiqish
  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.

python9 qator
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}")
Chiqish
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.

python10 qator
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())
Chiqish
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:

python7 qator
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, ...)"},
]))
UstunNimadan hisoblangan
classpclass (1→First, 2→Second, 3→Third)
adult_malesex va age
alonesibsp + parch
whosex va age (man/woman/child)
embark_townembarked (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.

python9 qator
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}")
Chiqish
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.

python15 qator
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}")
Chiqish
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

python9 qator
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"},
]))
UrinishAccuracyXulosa
0. Baza (hammaga 'o'ldi')0.6157Chegara
1. Hamma 14 ustun1.0000LEAKAGE — alive = target
2. alive tashlandi0.8172Birinchi halol raqam
3. deck ham tashlandi (77% bo'sh)0.8246Ustun kamaydi, natija yaxshilandi
4. Faqat 7 'halol' ustun0.7985Gipotezam RAD ETILDI
CV: 3-urinish0.8182 ±0.0248Haqiqiy baho — 13 ustun yetakchi
CV: 4-urinish0.7879 ±0.0198Diapazonlar ustma-ust — ishonch cheklangan

Nimani o'rgandik

Men bu datasetni ochganimda ikki marta adashdim va bir marta gipotezam rad etildi:

python10 qator
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"},
]))
XatoOqibatDars
Hamma ustunni modelga berdim1.0000 — yolg'on natijaHar ustunni target bilan groupby qilib ko'r
77% bo'sh ustunni to'ldirdimNatija pastroq ediKo'p bo'sh ustun — to'ldirilmaydi, tashlanadi
Takrorlanuvchi ustunlarni tashladimNatija yomonlashdi'Ortiqcha' ni o'ylab emas, o'lchab tashla
Bitta split ga ishondimFarq haqiqiy chiqdi, lekin diapazonlar ustma-ustCV: o'rtachaga EMAS, fold diapazoniga qara

Agar men bu bobni "tozalab" yozsam, u shunday bo'lardi:

titanic.csv ni yuklaymiz, alive va deck ni 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:

  1. Target nima? (bitta jumla)
  2. Bazam qancha?
  3. Qaysi ustunlarni tashladim va nega?
  4. Birinchi halol natijam qancha?
  5. 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.

Telegramda yozish Narxlar