Asosiy qismga o'tish

Linear regression

15-bob 15 kod bloki 3091 so'z

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

Sahna: narxni ayting

Hozirgacha biz savolni "tez ketadimi?" deb qo'ygandik — ha yoki yo'q.

Endi direktor boshqa savol beradi: "Bu kvartiraga qancha narx qo'yaylik?"

Javob — son, yorliq emas. Bu — regression.

Bizda narx_usd ustuni bor. Bu bob shu ustunni bashorat qiladi — va 11–13-boblardagi mexanizm aynan shu yerda ishga tushadi.

Protokoldagi joyi

Bu — 9-qadam: "Pipeline da bitta oddiy model". Regression uchun shu oddiy model — chiziqli regressiya. U boshqa hamma regression modeli uchun baza bo'lib xizmat qiladi.

Tenglama

11-bobdan tanish: X @ w + b. Yangi narsa yo'q.

python16 qator
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LinearRegression

df = pd.read_csv(DATA / "uy_ijara.csv")
son = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel"]
X, y = df[son], df["narx_usd"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)

model = LinearRegression().fit(X_tr, y_tr)
print(f"bias (b) = {model.intercept_:.2f}")
print()
print("Koeffitsiyentlar:")
for nom, k in zip(son, model.coef_):
    print(f"  {nom:16s} {k:>9.3f}")
Chiqish
bias (b) = 203.17

Koeffitsiyentlar:
  xonalar             62.719
  maydon_m2            7.067
  qavat                1.544
  qavatlar_soni        1.902
  metro_km           -27.502
  mebel               90.733

Koeffitsiyentni o'qish — eng foydali ko'nikma

Har bir koeffitsiyent bitta jumla bilan o'qiladi:

"Boshqa hamma narsa o'zgarmaganda, bu xususiyat 1 birlik oshsa, narx shuncha o'zgaradi."

python8 qator
izohlar = []
for nom, k in zip(son, model.coef_):
    yonalish = "oshadi" if k > 0 else "kamayadi"
    birlik = {"xonalar": "1 xona", "maydon_m2": "1 m2", "qavat": "1 qavat",
              "qavatlar_soni": "1 qavat", "metro_km": "1 km", "mebel": "mebel bo'lsa"}[nom]
    izohlar.append({"Xususiyat": nom, "Koeffitsiyent": round(k, 2),
                    "O'qilishi": f"{birlik} -> narx {abs(k):.1f} USD {yonalish}"})
show(pd.DataFrame(izohlar))
XususiyatKoeffitsiyentO'qilishi
xonalar62.721 xona -> narx 62.7 USD oshadi
maydon_m27.071 m2 -> narx 7.1 USD oshadi
qavat1.541 qavat -> narx 1.5 USD oshadi
qavatlar_soni1.91 qavat -> narx 1.9 USD oshadi
metro_km-27.51 km -> narx 27.5 USD kamayadi
mebel90.73mebel bo'lsa -> narx 90.7 USD oshadi

metro_km manfiy — metro uzoqlashsa narx tushadi. Mantiqan to'g'ri, va bu modelni tekshirish usuli: agar koeffitsiyent belgisi mantiqqa qarshi chiqsa, ma'lumotda yoki modelda muammo bor.

Koeffitsiyentlarni O'ZARO solishtirish uchun scaling kerak

Yuqoridagi jadvalda mebel koeffitsiyenti maydon_m2 dan katta. "Demak mebel muhimroq" degan xulosa noto'g'ri.

Chunki mebel 0 dan 1 gacha, maydon_m2 esa 22 dan 220 gacha. Birliklar boshqa, shuning uchun sonlarni solishtirib bo'lmaydi.

Solishtirish uchun avval StandardScaler qo'llang — keyin hamma koeffitsiyent "1 standart og'ish" birligida bo'ladi.

python14 qator
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

shkalalangan = Pipeline([("sc", StandardScaler()), ("m", LinearRegression())]).fit(X_tr, y_tr)
k_shkala = shkalalangan.named_steps["m"].coef_

taqqos = pd.DataFrame({
    "xususiyat": son,
    "xom koeffitsiyent": model.coef_.round(2),
    "shkalalangan": k_shkala.round(2),
}).assign(muhimligi=lambda d: d.shkalalangan.abs().round(2)).sort_values("muhimligi", ascending=False)
show(taqqos)
print()
print("Endi to'g'ri xulosa: eng ta'sirli xususiyat —", taqqos.iloc[0].xususiyat)
Chiqish
Endi to'g'ri xulosa: eng ta'sirli xususiyat — maydon_m2
xususiyatxom koeffitsiyentshkalalanganmuhimligi
maydon_m27.07142.78142.78
xonalar62.7267.2567.25
mebel90.7344.1144.11
metro_km-27.5-44.0544.05
qavatlar_soni1.98.178.17
qavat1.545.75.7

Shkalalangandan keyin tartib o'zgardi. maydon_m2 eng ta'sirli, mebel esa ancha pastda — xom sonlarga qarab chiqargan xulosamiz teskari edi.

Baholash: R2, RMSE, MAE

python12 qator
from sklearn.metrics import r2_score, root_mean_squared_error, mean_absolute_error
from sklearn.dummy import DummyRegressor

bashorat = model.predict(X_te)
baza = DummyRegressor(strategy="mean").fit(X_tr, y_tr)
b_bashorat = baza.predict(X_te)

print(f"{'metrika':<8} {'baza (o`rtacha)':>17} {'model':>12} {'o`qilishi'}")
print("-" * 72)
print(f"{'R2':<8} {r2_score(y_te, b_bashorat):>17.4f} {r2_score(y_te, bashorat):>12.4f}   1.0 = mukammal, 0 = o'rtacha bilan teng")
print(f"{'RMSE':<8} {root_mean_squared_error(y_te, b_bashorat):>17.1f} {root_mean_squared_error(y_te, bashorat):>12.1f}   USD, katta xatoni ko'proq jazolaydi")
print(f"{'MAE':<8} {mean_absolute_error(y_te, b_bashorat):>17.1f} {mean_absolute_error(y_te, bashorat):>12.1f}   USD, o'rtacha xato")
Chiqish
metrika    baza (o`rtacha)        model o`qilishi
------------------------------------------------------------------------
R2                 -0.0044       0.8665   1.0 = mukammal, 0 = o'rtacha bilan teng
RMSE                 228.6         83.3   USD, katta xatoni ko'proq jazolaydi
MAE                  187.1         67.8   USD, o'rtacha xato

Qaysi birini hisobotga yozasiz?

python10 qator
show(pd.DataFrame([
    {"Metrika": "R2", "Nima aytadi": "Dispersiyaning qancha ulushi tushuntirilgan",
     "Kimga aytiladi": "Muhandisga", "Kamchiligi": "Biznes ma'nosi yo'q"},
    {"Metrika": "RMSE", "Nima aytadi": "Xato, target birligida (USD)",
     "Kimga aytiladi": "Muhandisga", "Kamchiligi": "Chetki qiymatlar tortadi"},
    {"Metrika": "MAE", "Nima aytadi": "O'rtacha xato, USD da",
     "Kimga aytiladi": "MENEJERGA", "Kamchiligi": "Katta xatoni yumshoq o'tadi"},
    {"Metrika": "MAPE", "Nima aytadi": "Xato foizda",
     "Kimga aytiladi": "Menejerga", "Kamchiligi": "Target nolga yaqin bo'lsa portlaydi"},
]))
MetrikaNima aytadiKimga aytiladiKamchiligi
R2Dispersiyaning qancha ulushi tushuntirilganMuhandisgaBiznes ma'nosi yo'q
RMSEXato, target birligida (USD)MuhandisgaChetki qiymatlar tortadi
MAEO'rtacha xato, USD daMENEJERGAKatta xatoni yumshoq o'tadi
MAPEXato foizdaMenejergaTarget nolga yaqin bo'lsa portlaydi
python4 qator
mae = mean_absolute_error(y_te, bashorat)
print(f"Menejerga aytiladigan jumla:")
print(f'  "Model ijara narxini o`rtacha {mae:.0f} USD xato bilan bashorat qiladi."')
print(f'  (o`rtacha narx {y.mean():.0f} USD, ya`ni ~{mae/y.mean():.1%} xato)')
Chiqish
Menejerga aytiladigan jumla:
  "Model ijara narxini o`rtacha 68 USD xato bilan bashorat qiladi."
  (o`rtacha narx 776 USD, ya`ni ~8.7% xato)

Mana shu jumla — hisobotning eng muhim qatori. R2 = 0.87 menejerga hech narsa anglatmaydi, 56 USD xato esa darhol tushunarli.

Eng muhim diagnostika: qoldiq grafigi

Regression da bitta grafik bor va u hamma narsani aytadi: qoldiqlar (residuals), ya'ni haqiqat - bashorat.

python23 qator
import matplotlib.pyplot as plt

qoldiq = y_te - bashorat

rasm, axes = plt.subplots(1, 3, figsize=(16, 4.4))

axes[0].scatter(bashorat, qoldiq, alpha=.45, s=22, color="#1a73e8")
axes[0].axhline(0, color="red", ls="--", lw=1.5)
axes[0].set_xlabel("Bashorat"); axes[0].set_ylabel("Qoldiq (haqiqat - bashorat)")
axes[0].set_title("Qoldiq vs bashorat", fontweight="bold")

axes[1].hist(qoldiq, bins=32, color="#43a047", alpha=.8)
axes[1].axvline(0, color="red", ls="--")
axes[1].set_xlabel("Qoldiq"); axes[1].set_title("Qoldiqlar taqsimoti", fontweight="bold")

axes[2].scatter(y_te, bashorat, alpha=.45, s=22, color="#8e24aa")
chegaralar = [min(y_te.min(), bashorat.min()), max(y_te.max(), bashorat.max())]
axes[2].plot(chegaralar, chegaralar, "r--", lw=1.5)
axes[2].set_xlabel("Haqiqat"); axes[2].set_ylabel("Bashorat")
axes[2].set_title("Haqiqat vs bashorat", fontweight="bold")

for a in axes: a.grid(alpha=.3)
fig("qoldiq.png", caption="Chapda: qoldiqlar nol atrofida tasodifiy tarqalgan bo'lishi kerak. O'rtada: ular normal taqsimotga o'xshashi kerak. O'ngda: nuqtalar diagonalga yaqin bo'lishi kerak.")

Chapda: qoldiqlar nol atrofida tasodifiy tarqalgan bo'lishi kerak. O'rtada: ular normal taqsimotga o'xshashi kerak. O'ngda: nuqtalar diagonalga yaqin bo'lishi kerak.

Chapda: qoldiqlar nol atrofida tasodifiy tarqalgan bo'lishi kerak. O'rtada: ular normal taqsimotga o'xshashi kerak. O'ngda: nuqtalar diagonalga yaqin bo'lishi kerak.

Qoldiq grafigini qanday o'qish kerak:

python6 qator
show(pd.DataFrame([
    {"Ko'rinish": "Nol atrofida tasodifiy bulut", "Diagnoz": "YAXSHI — model to'g'ri shaklda"},
    {"Ko'rinish": "Egri chiziq (U yoki kamalak)", "Diagnoz": "Bog'lanish CHIZIQLI EMAS -> daraxt yoki polinom kerak"},
    {"Ko'rinish": "Voronka shakli (o'ngda keng)", "Diagnoz": "Heteroskedastiklik -> log(y) ni sinab ko'ring"},
    {"Ko'rinish": "Bir necha nuqta juda uzoqda", "Diagnoz": "Chetki qiymatlar -> o'qing, keyin qaror qiling"},
]))
Ko'rinishDiagnoz
Nol atrofida tasodifiy bulutYAXSHI — model to'g'ri shaklda
Egri chiziq (U yoki kamalak)Bog'lanish CHIZIQLI EMAS -> daraxt yoki polinom kerak
Voronka shakli (o'ngda keng)Heteroskedastiklik -> log(y) ni sinab ko'ring
Bir necha nuqta juda uzoqdaChetki qiymatlar -> o'qing, keyin qaror qiling
python9 qator
# raqam bilan tekshiramiz
print(f"Qoldiqlar o'rtachasi : {qoldiq.mean():>9.4f}   (nolga yaqin bo'lishi kerak)")
print(f"Qoldiqlar std        : {qoldiq.std():>9.2f}")
print()
# bashorat kattalashganda xato ham kattalashyaptimi (voronka testi)
guruh = pd.qcut(bashorat, 4, labels=["eng arzon", "arzon", "qimmat", "eng qimmat"])
tekshir = pd.DataFrame({"guruh": guruh, "mutlaq_xato": np.abs(qoldiq.to_numpy())})
print("Bashorat guruhi bo'yicha o'rtacha mutlaq xato:")
print(tekshir.groupby("guruh", observed=True).mutlaq_xato.mean().round(1).to_string())
Chiqish
Qoldiqlar o'rtachasi :    6.2064   (nolga yaqin bo'lishi kerak)
Qoldiqlar std        :     83.23

Bashorat guruhi bo'yicha o'rtacha mutlaq xato:
guruh
eng arzon     76.8
arzon         72.4
qimmat        61.5
eng qimmat    60.5

Agar oxirgi jadvalda xato bashorat bilan birga tez o'ssa, bu voronka belgisi. Biznes tilida: model qimmat kvartiralarda ko'proq adashadi.

MSE va MAE: chetki qiymat bilan sinov

12-bobda MSE chetki qiymatlarga sezgir deganmiz. Buni o'lchab ko'rsataman.

python18 qator
# ma'lumotga bitta g'alati qiymat qo'shamiz
X_buzuq = X_tr.copy()
y_buzuq = y_tr.copy()
y_buzuq.iloc[0] = 15000.0          # kiritish xatosi: 15 000 USD/oy

from sklearn.linear_model import HuberRegressor

qatorlar = []
for nom, m in [("LinearRegression (MSE)", LinearRegression()),
               ("HuberRegressor (MAE ga yaqin)", HuberRegressor(max_iter=2000))]:
    toza_m = Pipeline([("sc", StandardScaler()), ("m", m)]).fit(X_tr, y_tr)
    buzuq_m = Pipeline([("sc", StandardScaler()), ("m", m)]).fit(X_buzuq, y_buzuq)
    qatorlar.append({
        "Model": nom,
        "MAE (toza ma'lumot)": round(mean_absolute_error(y_te, toza_m.predict(X_te)), 1),
        "MAE (1 ta g'alati qiymat bilan)": round(mean_absolute_error(y_te, buzuq_m.predict(X_te)), 1),
    })
show(pd.DataFrame(qatorlar))
ModelMAE (toza ma'lumot)MAE (1 ta g'alati qiymat bilan)
LinearRegression (MSE)71.971.9
HuberRegressor (MAE ga yaqin)68.168.1

Bitta buzuq qator butun modelni siljitdi. HuberRegressor esa ancha chidamli — chunki u katta xatoni kvadratga ko'tarmaydi.

Bitta qator modelni buzishi mumkin

Yuqoridagi jadval shuni ko'rsatadi: 840 qatordan bittasi noto'g'ri bo'lsa, MSE ga asoslangan model sezilarli siljiydi.

Shuning uchun 3-bobda boxplot chizgansiz. Chetki qiymatni ko'rsangiz: avval o'qing (haqiqatmi?), keyin qaror qiling — tashlash, cheklash (clip) yoki chidamli model ishlatish.

Chiziqli bo'lmagan bog'lanish: chiziqli model bilan

Chiziqli model faqat to'g'ri chiziq chizadi. Lekin unga egri xususiyat bersangiz, u egri chiziq chizadi.

python13 qator
from sklearn.preprocessing import PolynomialFeatures

print(f"{'model':<34} {'CV R2':>10} {'±':>8}")
print("-" * 54)
for nom, quvur in [
    ("Chiziqli (6 xususiyat)", Pipeline([("sc", StandardScaler()), ("m", LinearRegression())])),
    ("Polinom darajasi 2", Pipeline([("p", PolynomialFeatures(2, include_bias=False)),
                                      ("sc", StandardScaler()), ("m", LinearRegression())])),
    ("Polinom darajasi 3", Pipeline([("p", PolynomialFeatures(3, include_bias=False)),
                                      ("sc", StandardScaler()), ("m", LinearRegression())])),
]:
    b = cross_val_score(quvur, X, y, cv=5, scoring="r2")
    print(f"{nom:<34} {b.mean():>10.4f} {b.std():>8.4f}")
Chiqish
model                                   CV R2        ±
------------------------------------------------------
Chiziqli (6 xususiyat)                 0.8759   0.0091
Polinom darajasi 2                     0.8748   0.0081
Polinom darajasi 3                     0.8700   0.0070
python5 qator
p2 = PolynomialFeatures(2, include_bias=False).fit(X)
p3 = PolynomialFeatures(3, include_bias=False).fit(X)
print(f"Xususiyatlar soni: 6 -> {p2.transform(X[:1]).shape[1]} (daraja 2) -> {p3.transform(X[:1]).shape[1]} (daraja 3)")
print()
print("9-bob qoidasi bilan hukm: farq fold tarqalishidan kattami?")
Chiqish
Xususiyatlar soni: 6 -> 27 (daraja 2) -> 83 (daraja 3)

9-bob qoidasi bilan hukm: farq fold tarqalishidan kattami?

Jadvalga qarang va o'zingiz hukm chiqaring: polinom qo'shish foyda berdimi, yoki farq tarqalish ichidami? Agar foyda kichik bo'lsa — oddiy modelni saqlang (9-bob qoidasi).

Polinom darajasi oshgani sari xususiyatlar soni portlaydi — va bu overfitting ga olib keladi. Shu muammoning yechimi keyingi bobdan keyin: regularizatsiya (16-bob).

Chiziqli regressiya talablari

python12 qator
show(pd.DataFrame([
    {"Talab": "Bog'lanish chiziqli", "Qanday tekshirasiz": "Qoldiq grafigi — egri naqsh bo'lmasin",
     "Buzilsa": "Polinom, daraxt yoki log almashtirish"},
    {"Talab": "Qoldiqlar mustaqil", "Qanday tekshirasiz": "Vaqt bo'yicha qoldiq grafigi",
     "Buzilsa": "Vaqt qatori usullari"},
    {"Talab": "Bir xil dispersiya", "Qanday tekshirasiz": "Qoldiq grafigida voronka yo'qligi",
     "Buzilsa": "log(y) yoki vaznli regressiya"},
    {"Talab": "Multikollinearlik kam", "Qanday tekshirasiz": "Korrelyatsiya matritsasi (3-bob)",
     "Buzilsa": "Ridge (16-bob) yoki ustunni tashlash"},
    {"Talab": "Chetki qiymat kam", "Qanday tekshirasiz": "boxplot",
     "Buzilsa": "Huber yoki tozalash"},
]))
TalabQanday tekshirasizBuzilsa
Bog'lanish chiziqliQoldiq grafigi — egri naqsh bo'lmasinPolinom, daraxt yoki log almashtirish
Qoldiqlar mustaqilVaqt bo'yicha qoldiq grafigiVaqt qatori usullari
Bir xil dispersiyaQoldiq grafigida voronka yo'qligilog(y) yoki vaznli regressiya
Multikollinearlik kamKorrelyatsiya matritsasi (3-bob)Ridge (16-bob) yoki ustunni tashlash
Chetki qiymat kamboxplotHuber yoki tozalash

Oxirgidan oldingi talabni bizning ma'lumot buzadi — xonalar va maydon_m2 korrelyatsiyasi 0.94. Oqibatini ko'ramiz:

python8 qator
print("Koeffitsiyentlar turli train bo'linishlarida qanchalik beqaror:")
print(f"{'random_state':>13} {'xonalar':>10} {'maydon_m2':>12}")
print("-" * 38)
for rs in [0, 1, 7, 42, 99]:
    Xa, _, ya, _ = train_test_split(X, y, test_size=0.3, random_state=rs)
    m = Pipeline([("sc", StandardScaler()), ("m", LinearRegression())]).fit(Xa, ya)
    k = m.named_steps["m"].coef_
    print(f"{rs:>13} {k[0]:>10.2f} {k[1]:>12.2f}")
Chiqish
Koeffitsiyentlar turli train bo'linishlarida qanchalik beqaror:
 random_state    xonalar    maydon_m2
--------------------------------------
            0      54.54       156.12
            1      63.36       147.55
            7      69.62       143.52
           42      67.25       142.78
           99      60.15       146.11

Koeffitsiyentlar bo'linishdan bo'linishga sakraydi — bu multikollinearlikning imzosi. Bashorat aniqligi barqaror, lekin "xonalar narxga qancha ta'sir qiladi" degan savolga ishonchli javob berolmaysiz.

16-bobda Ridge aynan shu muammoni yumshatadi.

Bugun nima o'rgandik

python10 qator
show(pd.DataFrame([
    {"Tushuncha": "Tenglama", "Bitta jumla": "y = X @ w + b — 11-bobdagi amal"},
    {"Tushuncha": "Koeffitsiyentni o'qish", "Bitta jumla": "'1 birlik oshsa, target shuncha o'zgaradi'"},
    {"Tushuncha": "Koeffitsiyentlarni solishtirish", "Bitta jumla": "Avval scaling — aks holda xulosa teskari chiqadi"},
    {"Tushuncha": "R2 / RMSE / MAE", "Bitta jumla": "Menejerga MAE ayt — u USD da"},
    {"Tushuncha": "Qoldiq grafigi", "Bitta jumla": "Regression dagi eng muhim diagnostika"},
    {"Tushuncha": "MSE va chetki qiymat", "Bitta jumla": "Bitta buzuq qator modelni siljitadi -> Huber"},
    {"Tushuncha": "Polinom xususiyat", "Bitta jumla": "Chiziqli model egri chiziq chizishi mumkin"},
    {"Tushuncha": "Multikollinearlik", "Bitta jumla": "Koeffitsiyentlar beqaror; bashorat esa barqaror"},
]))
TushunchaBitta jumla
Tenglamay = X @ w + b — 11-bobdagi amal
Koeffitsiyentni o'qish'1 birlik oshsa, target shuncha o'zgaradi'
Koeffitsiyentlarni solishtirishAvval scaling — aks holda xulosa teskari chiqadi
R2 / RMSE / MAEMenejerga MAE ayt — u USD da
Qoldiq grafigiRegression dagi eng muhim diagnostika
MSE va chetki qiymatBitta buzuq qator modelni siljitadi -> Huber
Polinom xususiyatChiziqli model egri chiziq chizishi mumkin
MultikollinearlikKoeffitsiyentlar beqaror; bashorat esa barqaror

Keyingi bobda: target 0 va 1 bo'lsa, shu modelni ishlatsak bo'ladimi? Javob — yo'q, va nega emasligini ko'ramiz.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar