Normal tenglama va gradient descent
Sahna: ikki yo'l, bitta javob
12-bobda biz kosaning tubini qadamma-qadam tushib topdik. 300 000 qadam kerak bo'ldi.
Endi men sizga aytaman: chiziqli regressiya uchun qadam kerak emas. Javobni bitta formula bilan, bir urinishda topish mumkin.
Bu — normal tenglama (normal equation). Unda alpha yo'q, qadam yo'q, iteratsiya yo'q.
Savol tug'iladi: agar aniq formula bo'lsa, nega umuman gradient descent kerak?
Bu bob shu savolga javob beradi. Va javob o'lchov bilan keladi.
Normal tenglama qayerdan chiqadi
Kosaning tubida qiyalik nol bo'ladi. Shuning uchun gradientni nolga tenglashtiramiz:
Uchta amal: X.T @ X, teskari matritsa, X.T @ y. Hammasi 11-bobdan tanish.
import pandas as pd import numpy as np import time df = pd.read_csv(DATA / "uy_ijara.csv") son = ["xonalar", "maydon_m2", "metro_km", "mebel"] X_xom = df[son].to_numpy(dtype=float) y = df["narx_usd"].to_numpy(dtype=float) # bias uchun birlar ustunini qo'shamiz X = np.c_[np.ones(len(X_xom)), X_xom] print(f"X shakli (bias ustuni bilan): {X.shape}") print(X[:3].round(2))
X shakli (bias ustuni bilan): (1200, 5) [[ 1. 2. 40.6 1.43 1. ] [ 1. 2. 55.5 4.18 1. ] [ 1. 5. 114. 3.84 1. ]]
def normal_tenglama(X, y): return np.linalg.inv(X.T @ X) @ X.T @ y t0 = time.perf_counter() teta = normal_tenglama(X, y) nt_vaqt = time.perf_counter() - t0 print("Normal tenglama natijasi:") print(f" bias (b) : {teta[0]:>8.3f}") for nom, v in zip(son, teta[1:]): print(f" {nom:10s}: {v:>8.3f}") print() print(f"Hisoblash vaqti: {nt_vaqt*1000:.3f} ms — BITTA urinishda")
Normal tenglama natijasi: bias (b) : 229.507 xonalar : 57.963 maydon_m2 : 7.317 metro_km : -27.800 mebel : 89.005 Hisoblash vaqti: 0.059 ms — BITTA urinishda
sklearn bilan tekshiramiz
from sklearn.linear_model import LinearRegression lr = LinearRegression().fit(X_xom, y) taqqos = pd.DataFrame({ "parametr": ["bias"] + son, "normal tenglama": np.round(teta, 4), "sklearn": np.round(np.r_[lr.intercept_, lr.coef_], 4), }) taqqos["farq"] = (taqqos["normal tenglama"] - taqqos["sklearn"]).abs().round(10) show(taqqos) print() print("Bir xilmi:", bool(np.allclose(teta, np.r_[lr.intercept_, lr.coef_])))
Bir xilmi: True
| parametr | normal tenglama | sklearn | farq |
|---|---|---|---|
| bias | 229.507 | 229.507 | 0 |
| xonalar | 57.963 | 57.963 | 0 |
| maydon_m2 | 7.317 | 7.317 | 0 |
| metro_km | -27.8 | -27.8 | 0 |
| mebel | 89.005 | 89.005 | 0 |
Aynan bir xil. Chunki LinearRegression ichida gradient descent yo'q — u ham yopiq formulani ishlatadi (aniqrog'i, scipy ning eng kichik kvadratlar yechuvchisini).
Bu muhim fakt: ko'p talaba LinearRegression().fit() ni "gradient descent ishlayapti" deb o'ylaydi. Yo'q.
Endi gradient descent bilan taqqoslaymiz
from sklearn.preprocessing import StandardScaler def mse(X, y, teta): return float(np.mean((X @ teta - y) ** 2)) def gd(X, y, alpha, qadamlar): teta = np.zeros(X.shape[1]) for _ in range(qadamlar): grad = (2 / len(y)) * X.T @ (X @ teta - y) teta = teta - alpha * grad return teta # shkalalangan ma'lumotda GD yaxshi ishlaydi (12-bob) Xs_xom = StandardScaler().fit_transform(X_xom) Xs = np.c_[np.ones(len(Xs_xom)), Xs_xom] teta_nt_s = normal_tenglama(Xs, y) print(f"{'usul':<28} {'MSE':>12} {'vaqt (ms)':>11}") print("-" * 53) t0 = time.perf_counter(); tn = normal_tenglama(Xs, y); tv = (time.perf_counter()-t0)*1000 print(f"{'Normal tenglama':<28} {mse(Xs, y, tn):>12,.1f} {tv:>11.3f}".replace(",", " ")) for n in [100, 1000, 10000]: t0 = time.perf_counter(); tg = gd(Xs, y, 0.1, n); tv = (time.perf_counter()-t0)*1000 print(f"{'GD (' + str(n) + ' qadam)':<28} {mse(Xs, y, tg):>12,.1f} {tv:>11.3f}".replace(",", " "))
usul MSE vaqt (ms) ----------------------------------------------------- Normal tenglama 6 566.6 0.016 GD (100 qadam) 6 586.5 0.439 GD (1000 qadam) 6 566.6 4.209 GD (10000 qadam) 6 566.6 42.027
Shkalalangan ma'lumotda gradient descent 1000 qadamda normal tenglamaga yetadi. Lekin normal tenglama uni bir urinishda topdi va tezroq ishladi.
Shunday bo'lsa — nega gradient descent kerak?
Javob: ustunlar soni
Normal tenglamada (X.T @ X) matritsasi bor. Uning o'lchami — ustunlar soni x ustunlar soni. Va uni teskarilash kerak.
Teskarilash narxi ustunlar soniga kubik bog'liq:
print("Ustunlar soni oshganda normal tenglama qancha vaqt oladi:") print(f"{'ustunlar':>10} {'X.T@X shakli':>16} {'normal tenglama':>18} {'GD (200 qadam)':>17}") print("-" * 66) r = np.random.default_rng(42) for m in [10, 100, 500, 1500]: n = 3000 Xb = r.normal(size=(n, m)) yb = Xb @ r.normal(size=m) + r.normal(scale=.5, size=n) t0 = time.perf_counter(); normal_tenglama(Xb, yb); nt = (time.perf_counter()-t0)*1000 t0 = time.perf_counter(); gd(Xb, yb, 0.01, 200); gt = (time.perf_counter()-t0)*1000 print(f"{m:>10} {f'{m}x{m}':>16} {nt:>15.1f} ms {gt:>14.1f} ms")
Ustunlar soni oshganda normal tenglama qancha vaqt oladi:
ustunlar X.T@X shakli normal tenglama GD (200 qadam)
------------------------------------------------------------------
10 10x10 0.1 ms 2.6 ms
100 100x100 5.3 ms 27.9 ms
500 500x500 11.3 ms 147.8 ms
1500 1500x1500 78.3 ms 1214.3 ms
Jadvalni o'qing: ustunlar soni oshgani sari normal tenglamaning vaqti tez o'sadi, gradient descent esa ancha yumshoq o'sadi.
Bu o'lchov MENING kompyuterimda
Aniq millisekundlar sizda boshqa chiqadi — protsessor, BLAS kutubxonasi, yuk ta'sir qiladi.
Lekin nisbat o'zgarmaydi: m oshganda normal tenglama kubik, gradient descent chiziqli o'sadi. Shuning uchun biror nuqtada GD arzonroq bo'lib qoladi.
O'z kompyuteringizda shu kodni ishga tushiring va o'zingizning kesishish nuqtangizni toping.
Eng yomon holat: teskarilanmaydigan matritsa
Normal tenglamaning yana bitta muammosi bor, va u amaliyotda uchraydi.
# ikki ustun BIR XIL (to'liq korrelyatsiya) — real ma'lumotda ham uchraydi X_buzuq = np.c_[np.ones(100), r.normal(size=100)] X_buzuq = np.c_[X_buzuq, X_buzuq[:, 1]] # 3-ustun 2-ustunning nusxasi y_buzuq = r.normal(size=100) print(f"X shakli: {X_buzuq.shape}") print(f"X.T @ X determinanti: {np.linalg.det(X_buzuq.T @ X_buzuq):.3e} <- nolga juda yaqin") print() try: np.linalg.inv(X_buzuq.T @ X_buzuq) print("inv() ishladi (lekin natija ishonchsiz)") except np.linalg.LinAlgError as e: print(f"inv() XATO berdi: {e}") print() print("YECHIM: pinv() — psevdo-teskari matritsa (sklearn ham shuni ishlatadi)") teta_p = np.linalg.pinv(X_buzuq) @ y_buzuq print(f" pinv() ishladi, natija: {teta_p.round(4)}")
X shakli: (100, 3) X.T @ X determinanti: 0.000e+00 <- nolga juda yaqin inv() XATO berdi: Singular matrix YECHIM: pinv() — psevdo-teskari matritsa (sklearn ham shuni ishlatadi) pinv() ishladi, natija: [-0.0836 0.0039 0.0039]
Shuning uchun amalda inv() emas, pinv() yoki np.linalg.lstsq() ishlatiladi.
print("Bizning ma'lumotda uchta usul bir xil javob beradimi:") t1 = np.linalg.inv(X.T @ X) @ X.T @ y t2 = np.linalg.pinv(X) @ y t3 = np.linalg.lstsq(X, y, rcond=None)[0] print(f" inv : {t1.round(4)}") print(f" pinv : {t2.round(4)}") print(f" lstsq: {t3.round(4)}") print(f" Hammasi mos: {bool(np.allclose(t1, t2) and np.allclose(t2, t3))}")
Bizning ma'lumotda uchta usul bir xil javob beradimi: inv : [229.5068 57.9632 7.3175 -27.8001 89.0049] pinv : [229.5068 57.9632 7.3175 -27.8001 89.0049] lstsq: [229.5068 57.9632 7.3175 -27.8001 89.0049] Hammasi mos: True
`inv()` ni ishlatmang — `pinv()` yoki `lstsq()` ni ishlating
Kitoblarda formula (X^T X)^{-1} deb yozilgan, shuning uchun talabalar np.linalg.inv yozadi.
Amalda bu xato: ustunlar o'zaro bog'liq bo'lsa (multikollinearlik — 3-bobdagi 0.94!), matritsa teskarilanmaydi yoki natija beqaror bo'ladi.
pinv() bu holatda ham ishlaydi. lstsq() esa bundan ham barqaror va tezroq.
Yuzma-yuz taqqoslash
show(pd.DataFrame([ {"Xususiyat": "Iteratsiya", "Normal tenglama": "YO'Q — bir urinishda", "Gradient descent": "Minglab qadam"}, {"Xususiyat": "Learning rate", "Normal tenglama": "KERAK EMAS", "Gradient descent": "Tanlash kerak, nozik"}, {"Xususiyat": "Scaling", "Normal tenglama": "Kerak emas", "Gradient descent": "SHART (12-bob)"}, {"Xususiyat": "Ustunlar ko'p (m > 10 000)", "Normal tenglama": "Juda sekin — O(m^3)", "Gradient descent": "Ishlaydi"}, {"Xususiyat": "Qatorlar ko'p (n > 1 mln)", "Normal tenglama": "Xotira muammosi", "Gradient descent": "Mini-batch bilan ishlaydi"}, {"Xususiyat": "Ma'lumot oqimda keladi", "Normal tenglama": "Ishlamaydi", "Gradient descent": "SGD bilan ishlaydi"}, {"Xususiyat": "Multikollinearlik", "Normal tenglama": "inv() yiqiladi (pinv kerak)", "Gradient descent": "Ishlaydi"}, {"Xususiyat": "Boshqa modellarga mos", "Normal tenglama": "FAQAT chiziqli + MSE", "Gradient descent": "Logistic, NN, boosting — hammasi"}, ]))
| Xususiyat | Normal tenglama | Gradient descent |
|---|---|---|
| Iteratsiya | YO'Q — bir urinishda | Minglab qadam |
| Learning rate | KERAK EMAS | Tanlash kerak, nozik |
| Scaling | Kerak emas | SHART (12-bob) |
| Ustunlar ko'p (m > 10 000) | Juda sekin — O(m^3) | Ishlaydi |
| Qatorlar ko'p (n > 1 mln) | Xotira muammosi | Mini-batch bilan ishlaydi |
| Ma'lumot oqimda keladi | Ishlamaydi | SGD bilan ishlaydi |
| Multikollinearlik | inv() yiqiladi (pinv kerak) | Ishlaydi |
| Boshqa modellarga mos | FAQAT chiziqli + MSE | Logistic, NN, boosting — hammasi |
Oxirgi qator eng muhimi.
Normal tenglama faqat chiziqli regressiya va MSE uchun mavjud. Logistic regression uchun yopiq formula yo'q — uning cost funksiyasi (log loss) uchun tenglamani analitik yechib bo'lmaydi. Neyron tarmoq uchun ham yo'q.
Ya'ni: normal tenglama — bitta maxsus holat uchun chiroyli yorliq. Gradient descent — universal mexanizm.
show(pd.DataFrame([ {"Model": "Linear Regression", "Yopiq formula bormi": "HA", "sklearn nimani ishlatadi": "lstsq (yopiq)"}, {"Model": "Ridge", "Yopiq formula bormi": "HA", "sklearn nimani ishlatadi": "Yopiq yoki iterativ (solver=)"}, {"Model": "Lasso", "Yopiq formula bormi": "YO'Q", "sklearn nimani ishlatadi": "Koordinata bo'yicha tushish"}, {"Model": "Logistic Regression", "Yopiq formula bormi": "YO'Q", "sklearn nimani ishlatadi": "lbfgs / newton-cg (iterativ)"}, {"Model": "Neyron tarmoq", "Yopiq formula bormi": "YO'Q", "sklearn nimani ishlatadi": "Mini-batch GD (Adam)"}, {"Model": "XGBoost", "Yopiq formula bormi": "YO'Q", "sklearn nimani ishlatadi": "Gradient boosting"}, ]))
| Model | Yopiq formula bormi | sklearn nimani ishlatadi |
|---|---|---|
| Linear Regression | HA | lstsq (yopiq) |
| Ridge | HA | Yopiq yoki iterativ (solver=) |
| Lasso | YO'Q | Koordinata bo'yicha tushish |
| Logistic Regression | YO'Q | lbfgs / newton-cg (iterativ) |
| Neyron tarmoq | YO'Q | Mini-batch GD (Adam) |
| XGBoost | YO'Q | Gradient boosting |
Qachon qaysi birini tanlaysiz
Amalda javob oddiy: siz tanlamaysiz. LinearRegression() yozasiz va sklearn o'zi to'g'ri usulni tanlaydi.
Lekin nega shunday ekanini bilish kerak — ayniqsa model sekin ishlaganda yoki yiqilganda.
show(pd.DataFrame([ {"Holat": "Kichik ma'lumot, kam ustun", "Tanlov": "Normal tenglama (LinearRegression)"}, {"Holat": "Ustunlar 10 000 dan ko'p", "Tanlov": "SGDRegressor yoki Ridge(solver='sag')"}, {"Holat": "Qatorlar millionlab", "Tanlov": "SGDRegressor (mini-batch)"}, {"Holat": "Ma'lumot oqimda keladi", "Tanlov": "SGDRegressor + partial_fit"}, {"Holat": "Chiziqli bo'lmagan model", "Tanlov": "Boshqa yo'l yo'q — gradient descent"}, ]))
| Holat | Tanlov |
|---|---|
| Kichik ma'lumot, kam ustun | Normal tenglama (LinearRegression) |
| Ustunlar 10 000 dan ko'p | SGDRegressor yoki Ridge(solver='sag') |
| Qatorlar millionlab | SGDRegressor (mini-batch) |
| Ma'lumot oqimda keladi | SGDRegressor + partial_fit |
| Chiziqli bo'lmagan model | Boshqa yo'l yo'q — gradient descent |
from sklearn.linear_model import SGDRegressor from sklearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score print("Ikki sklearn modeli, bir xil ma'lumot:") lr_ball = cross_val_score(LinearRegression(), Xs_xom, y, cv=5, scoring="r2") sgd_ball = cross_val_score(SGDRegressor(max_iter=5000, tol=1e-4, random_state=42), Xs_xom, y, cv=5, scoring="r2") print(f" LinearRegression (yopiq formula): R2 = {lr_ball.mean():.4f} ±{lr_ball.std():.4f}") print(f" SGDRegressor (gradient desc): R2 = {sgd_ball.mean():.4f} ±{sgd_ball.std():.4f}") print() print(f"Farq: {abs(lr_ball.mean() - sgd_ball.mean()):.4f} | tarqalish: ±{max(lr_ball.std(), sgd_ball.std()):.4f}") print("Hukm:", "farq tarqalish ichida — ikkisi ham bir xil javobga keladi" if abs(lr_ball.mean()-sgd_ball.mean()) < max(lr_ball.std(), sgd_ball.std()) else "farq sezilarli")
Ikki sklearn modeli, bir xil ma'lumot: LinearRegression (yopiq formula): R2 = 0.8741 ±0.0082 SGDRegressor (gradient desc): R2 = 0.8741 ±0.0083 Farq: 0.0000 | tarqalish: ±0.0083 Hukm: farq tarqalish ichida — ikkisi ham bir xil javobga keladi
9-bobdagi qoida bo'yicha hukm chiqardik: farq fold lar tarqalishi bilan solishtiriladi.
Bugun nima o'rgandik
show(pd.DataFrame([ {"Tushuncha": "Normal tenglama", "Bitta jumla": "w = (X^T X)^-1 X^T y — bir urinishda aniq javob"}, {"Tushuncha": "LinearRegression ichi", "Bitta jumla": "Gradient descent EMAS — yopiq yechim (lstsq)"}, {"Tushuncha": "inv() xavfi", "Bitta jumla": "Multikollinearlikda yiqiladi — pinv() yoki lstsq() ishlat"}, {"Tushuncha": "Normal tenglama narxi", "Bitta jumla": "O(m^3) — ustunlar ko'p bo'lsa qimmat"}, {"Tushuncha": "GD narxi", "Bitta jumla": "Ustunlar bo'yicha chiziqli — katta m da arzonroq"}, {"Tushuncha": "Nega GD universal", "Bitta jumla": "Yopiq formula faqat chiziqli+MSE uchun bor"}, ]))
| Tushuncha | Bitta jumla |
|---|---|
| Normal tenglama | w = (X^T X)^-1 X^T y — bir urinishda aniq javob |
| LinearRegression ichi | Gradient descent EMAS — yopiq yechim (lstsq) |
| inv() xavfi | Multikollinearlikda yiqiladi — pinv() yoki lstsq() ishlat |
| Normal tenglama narxi | O(m^3) — ustunlar ko'p bo'lsa qimmat |
| GD narxi | Ustunlar bo'yicha chiziqli — katta m da arzonroq |
| Nega GD universal | Yopiq formula faqat chiziqli+MSE uchun bor |
Shu bilan 3-qism tugadi. Siz endi fit() ning ichida nima borligini bilasiz: cost funksiya, gradient va yechish usuli.
Keyingi qismda shu mexanizm ustiga haqiqiy algoritmlar quriladi — chiziqli regressiyadan boshlab.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.