Asosiy qismga o'tish

Normal tenglama va gradient descent

14-bob 12 kod bloki 2834 so'z

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

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.

python13 qator
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))
Chiqish
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.  ]]
python13 qator
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")
Chiqish
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

python12 qator
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_])))
Chiqish
Bir xilmi: True
parametrnormal tenglamasklearnfarq
bias229.507229.5070
xonalar57.96357.9630
maydon_m27.3177.3170
metro_km-27.8-27.80
mebel89.00589.0050

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

python24 qator
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(",", " "))
Chiqish
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:

.

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

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

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

python10 qator
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"},
]))
XususiyatNormal tenglamaGradient descent
IteratsiyaYO'Q — bir urinishdaMinglab qadam
Learning rateKERAK EMASTanlash kerak, nozik
ScalingKerak emasSHART (12-bob)
Ustunlar ko'p (m > 10 000)Juda sekin — O(m^3)Ishlaydi
Qatorlar ko'p (n > 1 mln)Xotira muammosiMini-batch bilan ishlaydi
Ma'lumot oqimda keladiIshlamaydiSGD bilan ishlaydi
Multikollinearlikinv() yiqiladi (pinv kerak)Ishlaydi
Boshqa modellarga mosFAQAT chiziqli + MSELogistic, 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.

python8 qator
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"},
]))
ModelYopiq formula bormisklearn nimani ishlatadi
Linear RegressionHAlstsq (yopiq)
RidgeHAYopiq yoki iterativ (solver=)
LassoYO'QKoordinata bo'yicha tushish
Logistic RegressionYO'Qlbfgs / newton-cg (iterativ)
Neyron tarmoqYO'QMini-batch GD (Adam)
XGBoostYO'QGradient 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.

python7 qator
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"},
]))
HolatTanlov
Kichik ma'lumot, kam ustunNormal tenglama (LinearRegression)
Ustunlar 10 000 dan ko'pSGDRegressor yoki Ridge(solver='sag')
Qatorlar millionlabSGDRegressor (mini-batch)
Ma'lumot oqimda keladiSGDRegressor + partial_fit
Chiziqli bo'lmagan modelBoshqa yo'l yo'q — gradient descent
python15 qator
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")
Chiqish
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

python8 qator
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"},
]))
TushunchaBitta jumla
Normal tenglamaw = (X^T X)^-1 X^T y — bir urinishda aniq javob
LinearRegression ichiGradient descent EMAS — yopiq yechim (lstsq)
inv() xavfiMultikollinearlikda yiqiladi — pinv() yoki lstsq() ishlat
Normal tenglama narxiO(m^3) — ustunlar ko'p bo'lsa qimmat
GD narxiUstunlar bo'yicha chiziqli — katta m da arzonroq
Nega GD universalYopiq 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.

Telegramda yozish Narxlar