Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Bashorat — jadval: belgilar faqat o'tmishdan
- 2.2. shift tuzog'i
- 2.3. Global va lokal modellar
- 2.4. Ko'p qadamli bashorat strategiyalari
- 2.5. Backtest va raqiblar
- 2.6. Noaniqlik: kvantil regressiya va konformal intervallar
- 2.7. Ierarxik bashorat (qisqa)
- 2.8. Chuqur modellar — g'oyasi
- 2.9. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Supervised jadval, HistGB va shift tuzog'i
- Misol 2 — Global va lokal modellar, sovuq start
- Misol 3 — Ko'p qadamli strategiyalar va HistGB, ETS, seasonal naive backtesti
- Misol 4 — Noaniqlik intervallari va ierarxik bashorat
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
28.3-dars: ML bilan vaqt qatori bashorati
28-QISM — MAXSUS MAVZULAR · 3-dars
1. Kirish va motivatsiya
Oldingi ikki darsda vaqt qatorini tushunish va halol baholashni 28.1-bob, keyin esa unga maxsus yaratilgan klassik modellarni — ETS va SARIMA ni 28.2-bob ko'rdik. Ular bitta qatorni yaxshi modellaydi. Lekin haqiqiy biznesda qator bitta emas: savdo tarmog'ida 200 ta do'kon va 5 000 ta mahsulot, taksi xizmatida har mahalla uchun alohida talab, bankda har filial uchun naqd pul ehtiyoji bor. Buning ustiga savdo aksiyalar, bayramlar, narx va ob-havoga bog'liq — klassik modellar bunday belgilarni qo'shishda cheklangan.
Bu yerda kursning asosiy qismida o'rgangan ML vositalari — ayniqsa gradient boosting (15-qism) — kuchga kiradi. G'oya oddiy: bashorat vazifasini jadvalga aylantiramiz. Har qator — "bashorat qilinadigan kun", ustunlar — o'tmishdagi qiymatlar (lag), o'tmishdagi o'rtachalar, kalendar, bayram va aksiya belgilari; maqsad — o'sha kungi savdo. Shundan keyin istalgan regressor ishlaydi va minglab qatorni bitta global model bilan o'qitish mumkin.
Lekin bu yo'lda tuzoqlar ko'p va ular jim ishlaydi. 17.5-darsda shift(1) ni ko'rgan edik: rolling o'rtacha bugungi kunni o'z ichiga olsa, model kelajakni "ko'radi". Vaqt qatorida bu yanada nozik: 7 kun oldinga bashorat qilsangiz, 1 kunlik lag ishlab chiqarishda mavjud emas; ko'p qadamli bashoratda model o'z xatolarini o'zi ustiga yig'adi; oraliq baholari esa model farazlariga tayanib, va'da qilgan qamrovni bermaydi.
Real vaziyat. Toshkentdagi supermarketlar tarmog'ining tahlilchisi 12 do'kon uchun gradient boosting modelini qurdi: belgilar — kechagi savdo, oxirgi 7 kun o'rtachasi, hafta kuni. Validatsiyada natija seasonal naive dan ancha yaxshi chiqdi. Model har dushanba keyingi hafta uchun buyurtma berishda ishlatildi — va natija validatsiyadagidan ancha yomon bo'ldi. Sabab ikkita edi: "oxirgi 7 kun o'rtachasi" rolling(7, center=True) bilan hisoblangan (ichida kelajak bor), "kechagi savdo" esa 7 kun oldinga bashoratda yakshanbadan boshqa kunlar uchun hali ma'lum emas edi. Bu darsda ikkala xatoni o'lchab ko'rsatamiz va to'g'ri qurilgan ML modelini klassik modellarga qarshi halol backtestda sinaymiz.
Bu darsda vaqt qatorini supervised vazifaga to'g'ri aylantirishni, global modelni, ko'p qadamli strategiyalarni va kalibrlangan noaniqlik intervallarini o'rganamiz.
Bu darsda:
- Bashoratni supervised vazifaga aylantirish: lag, rolling (faqat o'tmishdan!), kalendar, bayram, tashqi o'zgaruvchilar
-
shifttuzog'i: sizishni o'lchab ko'rsatish - Global (ko'p qatorli) model va lokal modellar; sovuq start
- Ko'p qadamli bashorat: rekursiv, to'g'ridan-to'g'ri, ko'p chiqishli; ufq bo'yicha xato egri chizig'i
- HistGradientBoosting vs seasonal naive vs ETS — rolling backtest, juftlashgan
- Noaniqlik: kvantil regressiya, split conformal (noldan), CQR; qamrovni o'lchash
- Ierarxik bashorat: bottom-up va top-down (qisqa)
- Chuqur modellar (LSTM, N-BEATS, Temporal Fusion Transformer) — g'oyasi
ℹ Misollar real numpy/pandas/sklearn/statsmodels bilan (Python 3.14). Ma'lumot sintetik, urug' bilan yaratiladi: 12 ta do'kon, 2023-01-01 dan 912 kun; "bayram kunlari" — sintetik taqvimda belgilangan sanalar.
2. Nazariya — chuqur tushuntirish
2.1. Bashorat — jadval: belgilar faqat o'tmishdan
VAQT QATORI: ... y_{t-14} ... y_{t-7} ... y_{t-2} y_{t-1} | y_t y_{t+1} ...
origin
JADVAL QATORI (maqsad kuni t uchun):
lag_1, lag_2, lag_7, lag_14 = y_{t-1}, y_{t-2}, y_{t-7}, y_{t-14}
orta_7 = mean(y_{t-7} .. y_{t-1}) <- shift(1), keyin rolling
kalendar = hafta kuni, yil kuni, oy <- t da MA'LUM
bayram, arafa1, arafa2 = sintetik taqvimdan <- oldindan MA'LUM
aksiya = marketing rejasidan <- oldindan MA'LUM
maqsad = log y_t
OLTIN QOIDA: har belgi BASHORAT QILINADIGAN PAYTDA mavjud bo'lishi kerak.
h kun oldinga bashorat -> eng kichik lag = h, rolling ham shift(h) dan
kalendar va rejalashtirilgan tadbirlar - istalgan ufqqa ma'lum
ob-havo, raqobatchi narxi - o'zi bashorat (28.2, 2.10)17.5-darsda lag va rolling belgilarni, 17.8-darsda leakage ni ko'rgan edik. Vaqt qatori bashoratida ularga uchta qo'shimcha bor:
- Ufq belgilarni cheklaydi. 7 kun oldinga bashorat qiladigan model uchun
lag_1— kelajak. Uni o'quvda ishlatsangiz, validatsiya ajoyib, ishlab chiqarish esa ishlamaydi (yokilag_1o'rniga bo'sh qiymat keladi). - Log maqsad. Savdo ko'paytiruvchi tuzilishga ega 28.1-bob;
log yda trend, mavsum va do'kon darajasi qo'shiluvchi bo'ladi, global model uchun turli o'lchamdagi do'konlar bir shkalaga tushadi. - Daraxtlar ekstrapolyatsiya qilmaydi (28.1, 3-misol). Lag belgilar buni qisman hal qiladi: model "kun raqami" emas, "o'tgan haftaga nisbatan" o'rganadi — daraja o'ssa, lag ham o'sadi.
2.2. shift tuzog'i
df["orta_7"] = df.groupby("dokon")["y"].transform(
lambda s: s.rolling(7).mean()) # ⚠️ y_t ning o'zi ichida
df["orta_7"] = df.groupby("dokon")["y"].transform(
lambda s: s.rolling(7, center=True).mean()) # ⚠️ y_{t+1..t+3} ham ichida
df["orta_7"] = df.groupby("dokon")["y"].transform(
lambda s: s.shift(1).rolling(7).mean()) # ✅ faqat o'tmishSizishning ikki yuzi bor: validatsiya bahosi yolg'on yaxshi, ishlab chiqarishda esa model yomonroq — chunki u o'quvda kelajakka tayanishni o'rgangan, ishlab chiqarishda esa bu belgi (majburan o'tmishdan hisoblanganda) boshqacha ma'noga ega. 1-misolda ikkala raqamni o'lchaymiz. Qo'shimcha xavf: groupby siz shift — birinchi do'konning oxirgi kunlari ikkinchi do'konning birinchi kunlariga "lag" bo'lib qoladi.
2.3. Global va lokal modellar
LOKAL: har qator (do'kon) uchun alohida model
+ qatorning o'ziga xosligini to'liq o'rganadi
- har modelda ma'lumot kam (800 kun); yillik mavsum 2 marta ko'rilgan
- 5000 mahsulot = 5000 model: o'qitish, monitoring, versiyalash
- yangi do'kon/mahsulot (SOVUQ START) - tarixi yo'q, model yo'q
GLOBAL: barcha qatorlar bitta jadvalda, bitta model
+ ma'lumot N barobar ko'p; umumiy qonuniyatlar (bayram, hafta, aksiya
effekti) barcha do'konlardan o'rganiladi
+ yangi qator darhol bashorat oladi (boshqalardan "qarz oladi")
+ bitta artefakt - MLOps oson (27-qism)
- qatorlar o'lchami juda farq qilsa: log maqsad yoki masshtablash
- qator identifikatori belgi sifatida (kategoriya) - ehtiyotkorlik bilan
AMALDA: M5 (Walmart savdosi, 30 000 qator) musobaqasida g'oliblar asosan
global gradient boosting modellari edi2.4. Ko'p qadamli bashorat strategiyalari
Ertaga emas, keyingi 14 kun kerak bo'lsa-chi?
1. REKURSIV (iterativ):
bitta model f: (y_{t-1}, ..., y_{t-14}, kalendar_t) -> y_t
T+1 ni bashorat qil -> uni "haqiqat" deb tarixga qo'sh -> T+2 -> ...
+ bitta model, har qanday ufq
- XATO TO'PLANADI: h=5 da model 4 ta o'z bashoratiga tayanadi
- o'quvda model haqiqiy lag larni ko'rgan, bashoratda - silliqroq,
xatoli qiymatlarni (taqsimot siljishi)
2. TO'G'RIDAN-TO'G'RI (direct):
har ufq h uchun alohida f_h: (y_{T}, ..., y_{T-13}, kalendar_{T+h}) -> y_{T+h}
+ xato to'planmaydi; har model o'z ufqiga moslashadi
- H ta model (H marta o'qitish, H marta monitoring)
- ufqlar orasida bog'liqlik yo'q - bashorat "tishli" bo'lishi mumkin
3. KO'P CHIQISHLI (MIMO):
bitta model: (tarix, kelajak kalendari) -> vektor (y_{T+1}, ..., y_{T+H})
+ bitta model, ufqlar birgalikda (izchil shakl)
- vektor chiqishni qo'llaydigan model kerak (ExtraTrees, KNN, neyron
tarmoq; HistGB - yo'q)
GIBRID: to'g'ridan-to'g'ri + oldingi ufq bashoratlari belgi sifatidaQaysi biri yaxshi — oldindan aytib bo'lmaydi; ufq bo'yicha xato egri chizig'i va juftlashgan backtest hal qiladi (3-misol).
2.5. Backtest va raqiblar
Baholash 28.1 dagidek: rolling-origin, qoplanmaydigan oynalar, juftlashgan farq, SE, soddalik tartibi. ML model ikki raqibni yengishi kerak: seasonal naive (bazaviy) va klassik model (ETS — 28.2 da eng sodda munosib chiqqan). ML ning qo'shimcha narxi bor: belgilar quvuri, bayram va aksiya taqvimi manbalari, ko'proq monitoring. Farq sezilarli bo'lmasa, qoida soddaroq modelni tanlaydi.
SODDALIK TARTIBI (bu darsda):
seasonal naive < ETS < HistGB (belgilar quvuri bilan)2.6. Noaniqlik: kvantil regressiya va konformal intervallar
Ombor uchun "o'rtacha 180 chek" yetarli emas — "90% ehtimol bilan 150 va 220 orasida" kerak. Uch yo'l:
1. KVANTIL REGRESSIYA (13.11, pinball loss):
HistGradientBoostingRegressor(loss="quantile", quantile=0.05) va 0.95
+ belgilarga bog'liq kenglik (bayram kuni - kengroq)
- o'quvda moslashgan kvantil testda TORROQ bo'lishi mumkin (overfit)
- qamrov kafolati yo'q
2. SPLIT CONFORMAL (noldan, 5 qator):
ma'lumot: o'quv | KALIBRLASH | test (vaqt bo'yicha!)
nuqtali model o'quvda o'qitiladi
kalibrlashda: r_i = |y_i - f(x_i)|, i = 1..n
q = r lar orasida ceil((n+1)(1-alfa)) - kichigi
interval: [f(x) - q, f(x) + q]
KAFOLAT: qatorlar ALMASHTIRILADIGAN bo'lsa, P(y ichida) >= 1 - alfa
- kenglik hamma joyda bir xil (log fazoda - nisbiy bir xil)
3. CQR (conformalized quantile regression):
kvantil modellar o'quvda; kalibrlashda ball
s_i = max(q_past(x_i) - y_i, y_i - q_yuq(x_i))
q = ball lar ceil((n+1)(1-alfa)) - kichigi
interval: [q_past(x) - q, q_yuq(x) + q]
= kvantilning moslashuvchan kengligi + konformal kafolat
VAQT QATORIDA OGOHLANTIRISH:
almashtiriladiganlik buziladi (trend, drift, mavsum) - kafolat taxminiy
kalibrlash davri testga YAQIN bo'lsin; muntazam yangilang
kuchliroq yechim: adaptiv conformal (ACI) - har qadamda alfa ni
so'nggi qamrovga qarab tuzatadi (qamrov past -> interval kengayadi)
YOMON YO'L: q ni O'QUV qoldiqlaridan olish - model o'quvga moslashgan,
qoldiqlar kichik -> interval tor -> qamrov pastInterval ham bashorat — uni o'lchang. Qamrov (haqiqiy qiymat intervalga tushgan ulush) nominalga yaqinmi, kenglik qanchalik, va qamrov muhim segmentlarda (bayramlar!) ham saqlanadimi.
2.7. Ierarxik bashorat (qisqa)
VILOYAT (jami)
/ | \
do'kon 1 do'kon 2 ... do'kon 12
IZCHILLIK: do'konlar bashoratlari yig'indisi = viloyat bashorati bo'lishi kerak
(moliya bo'limi viloyat rejasini, ombor - do'kon rejasini ishlatadi)
BOTTOM-UP: do'konlarni bashorat qil, yig'
+ do'kon darajasidagi ma'lumot to'liq ishlatiladi
- past daraja shovqinli bo'lsa, yig'indi ham xato to'playdi
TOP-DOWN: jamini bashorat qil, tarixiy ulushlar bilan taqsimla
+ yuqori daraja silliq, bashorat qilish oson
- do'konlarning o'ziga xos dinamikasi (aksiya, bayram) yo'qoladi
MIDDLE-OUT, OPTIMAL MOSLASHTIRISH (MinT):
hamma darajani bashorat qilib, keyin kovariatsiyani hisobga olib
"eng yaqin izchil" to'plamga proyeksiya2.8. Chuqur modellar — g'oyasi
LSTM / GRU 23.9-bob: qatorni ketma-ket o'qiydi, yashirin holatda xotira
seq2seq: enkoder tarixni o'qiydi, dekoder H qadamni chiqaradi
N-BEATS: faqat to'liq bog'langan bloklar; har blok "backcast" (tarixni
tushuntirish) va "forecast" beradi; qoldiq keyingi blokka o'tadi;
talqin qilinadigan varianti - trend va mavsum bazis funksiyalari
TEMPORAL FUSION TRANSFORMER (24-qism): attention + belgilarni tanlash;
o'tmish belgilari, KELAJAKDA MA'LUM belgilar (taqvim, aksiya) va
statik belgilar (do'kon turi) alohida kiradi; kvantil chiqishlar
FOUNDATION MODELLAR (TimesFM, Chronos): millionlab qatorda oldindan
o'qitilgan; "zero-shot" bashorat
QACHON ARZIYDI: juda ko'p qator (global), uzun tarix, murakkab o'zaro
ta'sirlar. M-musobaqalarida gradient boosting va oddiy ansambllar
ko'pincha teng yoki yaxshiroq, arzonroq va barqarorroq.
Qaysi biri bo'lmasin - xuddi shu backtest, bazaviy va qamrov o'lchovi.2.9. Tuzoqlar
Asosiy tuzoqlar: rolling ni shift siz yoki center=True bilan hisoblash; groupby siz shift; ufqdan qisqa lag (7 kun oldinga bashoratda lag_1); kelajakda noma'lum tashqi o'zgaruvchini haqiqiy qiymati bilan backtest qilish; ko'p qatorli jadvalni tasodifiy bo'lish; rekursiv strategiyada xato to'planishini tekshirmaslik; bitta kesimda qoplanuvchi originlar bo'yicha SE hisoblash; interval kvantilini o'quv qoldiqlaridan olish; kvantil regressiya qamroviga tekshirmasdan ishonish; konformal kafolatni vaqt qatorida so'zsiz qabul qilish; bayram kunlaridagi qamrovni alohida tekshirmaslik; ierarxiya darajalari orasida izchillikni unutish; ML modelni klassik raqibsiz baholash.
3. Tez ma'lumotnoma
import numpy as np
from sklearn.ensemble import HistGradientBoostingRegressor
df = df.sort_values(["dokon", "sana"])
df["y"] = np.log(df["sotuv"])
g = df.groupby("dokon")["y"]
h = 7 # ufq
for k in [h, h + 1, 14, 21, 28]:
df[f"lag_{k}"] = g.shift(k) # eng kichik lag = h
df["orta_28"] = g.transform(lambda s: s.shift(h).rolling(28).mean())
df["hafta_kuni"] = df["sana"].dt.dayofweek
df = df.dropna()
glob = HistGradientBoostingRegressor(categorical_features=["dokon"])
glob.fit(tr[belgilar + ["dokon"]], tr["y"]) # global model
q05 = HistGradientBoostingRegressor(loss="quantile", quantile=0.05).fit(X, y)
q95 = HistGradientBoostingRegressor(loss="quantile", quantile=0.95).fit(X, y)
# split conformal (kalibrlash davri - o'quvdan KEYIN, testdan OLDIN)
r = np.sort(np.abs(y_kal - model.predict(X_kal)))
q = r[int(np.ceil((len(r) + 1) * (1 - alfa))) - 1]
past, yuqori = model.predict(X_te) - q, model.predict(X_te) + q
qamrov = np.mean((y_te >= past) & (y_te <= yuqori))
# rekursiv: bashoratni tarixga qo'shib takrorlash
for k in range(H):
p = model.predict(belgi(tarix, kalendar[T + k]))
tarix = np.append(tarix[1:], p)
bottom_up = dokon_bashorat.groupby("sana").sum()
top_down = jami_bashorat * ulush_dokon # oxirgi 28 kun ulushlariQaysi vaziyatda nima
| Vaziyat | Yondashuv |
|---|---|
| Ko'p qator, umumiy qonuniyat | global HistGB, log maqsad, qator id kategoriya |
| Yangi do'kon / mahsulot | global model (sovuq start) |
| Aksiya, bayram, narx rejasi | belgi sifatida (kelajakda ma'lum) |
| 1-7 kunlik ufq | to'g'ridan-to'g'ri yoki rekursiv; egri chiziqni o'lchang |
| Izchil ko'p kunlik trayektoriya | ko'p chiqishli |
| Interval kerak | split conformal yoki CQR; qamrovni o'lchang |
| Ierarxiya (do'kon → viloyat) | bottom-up (izchil), kerak bo'lsa moslashtirish |
| Juda ko'p qator, uzun tarix | chuqur global modellar — backtest bilan |
ML bashorat xulosasi
belgilar faqat o'tmishdan: shift(h) keyin rolling; eng kichik lag = ufq
global model - ma'lumot ko'p, sovuq start hal; log maqsad
rekursiv (xato to'planadi) | to'g'ridan-to'g'ri (H model) | ko'p chiqishli
raqiblar: seasonal naive VA ETS; juftlashgan backtest, soddalik qoidasi
interval: conformal/CQR, kalibrlash o'quvdan keyin; qamrov o'lchanadi4. Batafsil misollar
Misollar real numpy/pandas/sklearn/statsmodels bilan (Python 3.14). Har misol mustaqil ishlaydi; to'rttasida bir xil generator (
dokonlar) — 12 do'kon, trend, sayr qiluvchi daraja, haftalik va yillik mavsum, sintetik taqvimdagi bayram va arafalar, tasodifiy aksiya kunlari (5%, +25%) va AR(1) shovqin.
Misol 1 — Supervised jadval, HistGB va shift tuzog'i
"""Bashoratni supervised vazifaga aylantirish: lag, rolling, kalendar, bayram; shift tuzog'i."""
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor
# sintetik taqvim: "bayram kunlari" (yillar bo'yicha to'qilgan sanalar)
BAYRAMLAR = pd.to_datetime([
"2023-03-21", "2024-03-21", "2025-03-21", # Navro'z
"2023-04-21", "2024-04-10", "2025-03-30", # Ramazon hayiti
"2023-06-28", "2024-06-16", "2025-06-06", # Qurbon hayiti
"2023-12-31", "2024-12-31", # Yangi yil arafasi
])
HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])
def dokonlar(n_dokon=12, kun=912, seed=0):
"""Toshkent va Samarqanddagi do'konlar kunlik savdosi (uzun format)."""
rng = np.random.default_rng(seed)
sana = pd.date_range("2023-01-01", periods=kun, freq="D")
t = np.arange(kun)
bayram = sana.isin(BAYRAMLAR).astype(int)
arafa1 = sana.isin(BAYRAMLAR - pd.Timedelta(days=1)).astype(int)
arafa2 = sana.isin(BAYRAMLAR - pd.Timedelta(days=2)).astype(int)
qismlar = []
for d in range(n_dokon):
e = rng.normal(0, 0.08, kun)
shovqin = np.zeros(kun)
for i in range(1, kun):
shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
aksiya = (rng.random(kun) < 0.05).astype(int)
ly = (np.log(rng.uniform(80, 400))
+ rng.normal(0.10, 0.05) * t / 365.25
+ np.cumsum(rng.normal(0, 0.004, kun))
+ rng.uniform(0.7, 1.3) * (HAFTA - HAFTA.mean())[sana.dayofweek]
+ 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25)
+ 0.30 * bayram + 0.20 * arafa1 + 0.10 * arafa2
+ 0.25 * aksiya + shovqin)
qismlar.append(pd.DataFrame({
"sana": sana, "dokon": d, "sotuv": np.exp(ly), "aksiya": aksiya,
"bayram": bayram, "arafa1": arafa1, "arafa2": arafa2}))
return pd.concat(qismlar, ignore_index=True)
def belgilar(df, rejim="shift1"):
"""Bir kun oldinga (h=1) bashorat uchun belgilar; maqsad - log sotuv.
rejim: "shift1" (to'g'ri), "shiftsiz" (bugungi y ichida), "markaz" (kelajak ham)."""
df = df.sort_values(["dokon", "sana"]).copy()
df["y"] = np.log(df["sotuv"])
g = df.groupby("dokon")["y"]
for k in [1, 2, 7, 14]:
df[f"lag_{k}"] = g.shift(k)
for w in [7, 28]: # FAQAT o'tmishdan: avval shift, keyin rolling
if rejim == "shift1":
f = lambda s, w=w: s.shift(1).rolling(w).mean()
elif rejim == "shiftsiz":
f = lambda s, w=w: s.rolling(w).mean() # y_t ichida!
else:
f = lambda s, w=w: s.rolling(w, center=True).mean() # kelajak ichida!
df[f"orta_{w}"] = g.transform(f)
df["hafta_kuni"] = df["sana"].dt.dayofweek
df["yil_kuni"] = df["sana"].dt.dayofyear
df["oy"] = df["sana"].dt.month
return df.dropna()
USTUNLAR = ["lag_1", "lag_2", "lag_7", "lag_14", "orta_7", "orta_28",
"hafta_kuni", "yil_kuni", "oy", "bayram", "arafa1", "arafa2",
"aksiya"]
def hgb():
return HistGradientBoostingRegressor(max_iter=200, learning_rate=0.05,
random_state=0)
def mae(a, b):
return float(np.mean(np.abs(a - b)))
def main() -> None:
df = dokonlar()
bir = df[df["dokon"] == 0]
kesim = pd.Timestamp("2025-01-01")
print("=== 1. Vaqt qatoridan jadvalga (Chilonzor do'koni, h=1) ===")
jad = belgilar(bir)
print(f" {len(bir)} kun -> {len(jad)} qator (lag va rolling uchun boshi "
f"tashlandi), {len(USTUNLAR)} belgi")
ko = jad[jad["sana"].isin(pd.to_datetime(["2024-03-19", "2024-03-20",
"2024-03-21"]))]
for _, r in ko.iterrows():
print(f" {r['sana'].date()} y={np.exp(r['y']):6.1f} "
f"lag_1={np.exp(r['lag_1']):6.1f} lag_7={np.exp(r['lag_7']):6.1f}"
f" orta_7={np.exp(r['orta_7']):6.1f} hk={r['hafta_kuni']} "
f"bayram={r['bayram']} arafa1={r['arafa1']}")
print("\n=== 2. HistGB va bazaviylar (test: 2025-01-01 dan, h=1) ===")
tr, te = jad[jad["sana"] < kesim], jad[jad["sana"] >= kesim]
m = hgb().fit(tr[USTUNLAR], tr["y"])
haq = np.exp(te["y"].to_numpy())
prog = {"naive (kecha)": np.exp(te["lag_1"].to_numpy()),
"seasonal naive": np.exp(te["lag_7"].to_numpy()),
"HistGB": np.exp(m.predict(te[USTUNLAR]))}
hafta = ((te["sana"] - kesim).dt.days // 7).to_numpy()
xato = {k: np.abs(v - haq) for k, v in prog.items()}
for k, v in xato.items():
print(f" {k:<16} MAE {v.mean():6.2f}")
for k in ["naive (kecha)", "seasonal naive"]:
d = pd.Series(xato["HistGB"] - xato[k]).groupby(hafta).mean()
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" HistGB - {k:<15} {d.mean():+6.2f} SE {se:.2f} "
f"({len(d)} hafta bo'yicha) -> "
f"{'sezilarli' if abs(d.mean()) > 2 * se else 'sezilarli emas'}")
print("\n=== 3. shift tuzog'i: rolling o'rtacha o'tmishdan tashqariga chiqsa ===")
print(" 12 do'kon, global model; 'ishlab chiqarish' - belgi faqat o'tmishdan")
togri = belgilar(df)
tr_t, te_t = togri[togri["sana"] < kesim], togri[togri["sana"] >= kesim]
haq = np.exp(te_t["y"].to_numpy())
ust = USTUNLAR + ["dokon"]
print(f" {'rolling':<24} {'validatsiya MAE':>16} {'ishlab chiqarish':>17}")
for nom, rejim in [("shift(1) - to'g'ri", "shift1"),
("shiftsiz (y_t ichida)", "shiftsiz"),
("center=True (kelajak)", "markaz")]:
x = belgilar(df, rejim)
tr, te = x[x["sana"] < kesim], x[x["sana"] >= kesim]
mm = hgb().fit(tr[ust], tr["y"])
val = mae(np.exp(mm.predict(te[ust])), np.exp(te["y"].to_numpy()))
ish = mae(np.exp(mm.predict(te_t[ust])), haq)
print(f" {nom:<24} {val:>16.2f} {ish:>17.2f}")
print("\n=== 4. Bayram belgilari: bor va yo'q (butun 12 do'kon, h=1) ===")
hamma = belgilar(df)
tr, te = hamma[hamma["sana"] < kesim], hamma[hamma["sana"] >= kesim]
maxsus = (te[["bayram", "arafa1", "arafa2"]].sum(axis=1) > 0).to_numpy()
haq = np.exp(te["y"].to_numpy())
ustun_bayramsiz = [c for c in USTUNLAR if c not in ("bayram", "arafa1", "arafa2")]
print(f" test: {len(te)} qator, ulardan bayram/arafa: {int(maxsus.sum())}")
print(f" {'belgilar':<18} {'bayram/arafa':>13} {'oddiy kunlar':>13}")
for nom, ust in [("bayramsiz", ustun_bayramsiz), ("bayram bilan", USTUNLAR)]:
mm = hgb().fit(tr[ust + ["dokon"]], tr["y"])
x = np.abs(np.exp(mm.predict(te[ust + ["dokon"]])) - haq)
print(f" {nom:<18} {x[maxsus].mean():>13.2f} {x[~maxsus].mean():>13.2f}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Vaqt qatoridan jadvalga (Chilonzor do'koni, h=1) ===
912 kun -> 884 qator (lag va rolling uchun boshi tashlandi), 13 belgi
2024-03-19 y= 176.4 lag_1= 146.1 lag_7= 117.2 orta_7= 150.8 hk=1 bayram=0 arafa1=0
2024-03-20 y= 193.9 lag_1= 176.4 lag_7= 141.9 orta_7= 159.9 hk=2 bayram=0 arafa1=1
2024-03-21 y= 288.3 lag_1= 193.9 lag_7= 181.8 orta_7= 167.2 hk=3 bayram=1 arafa1=0
=== 2. HistGB va bazaviylar (test: 2025-01-01 dan, h=1) ===
naive (kecha) MAE 18.71
seasonal naive MAE 16.79
HistGB MAE 12.36
HistGB - naive (kecha) -6.37 SE 1.18 (26 hafta bo'yicha) -> sezilarli
HistGB - seasonal naive -4.41 SE 1.13 (26 hafta bo'yicha) -> sezilarli
=== 3. shift tuzog'i: rolling o'rtacha o'tmishdan tashqariga chiqsa ===
12 do'kon, global model; 'ishlab chiqarish' - belgi faqat o'tmishdan
rolling validatsiya MAE ishlab chiqarish
shift(1) - to'g'ri 23.38 23.38
shiftsiz (y_t ichida) 21.74 23.52
center=True (kelajak) 19.41 24.45
=== 4. Bayram belgilari: bor va yo'q (butun 12 do'kon, h=1) ===
test: 2172 qator, ulardan bayram/arafa: 108
belgilar bayram/arafa oddiy kunlar
bayramsiz 64.66 22.14
bayram bilan 39.08 22.55Natija tahlili.
1-bo'lim — jadval. 912 kunlik qatordan 884 ta qator qoldi: birinchi 28 kun orta_28 va lag_14 uchun tarix yo'qligi sababli tashlandi. Navro'z atrofidagi uch kunga qarang: 21-mart (bayram=1) savdo 288.3 — kechagidan (193.9) ~50% yuqori, 20-mart arafa (arafa1=1) ham oddiy seshanbadan baland. Bunday sakrashni hech bir lag oldindan bilmaydi — faqat taqvim belgisi.
2-bo'lim — bir kun oldinga bashorat, 2025 yilning birinchi yarmida. HistGB 12.36, seasonal naive 16.79, naive 18.71. Juftlashgan farqlar 26 hafta bo'yicha (kunlar emas — kunlik xatolar bir-biriga bog'liq, haftalik o'rtachalar mustaqilroq): seasonal naive dan -4.41 (SE 1.13) — sezilarli yaxshi.
3-bo'lim — shift tuzog'i, 12 do'konli global modelda. To'g'ri model (shift(1)) validatsiyada ham, ishlab chiqarishda ham 23.38. shift siz rolling (bugungi y ichida) validatsiyada 21.74 va'da qildi — ishlab chiqarishda 23.52. center=True (3 kun kelajak ichida) — validatsiyada 19.41, "17% yaxshilanish", ishlab chiqarishda 24.45 — to'g'ri modeldan yomon. Sizgan model kelajakka tayanishni o'rgangan; ishlab chiqarishda belgi faqat o'tmishdan hisoblanganda, model unga noto'g'ri ma'no beradi. E'tibor bering: shift siz variantda ishlab chiqarishdagi zarar kichik (+0.14) — asosiy zarar yolg'on validatsiya bahosi: jamoa 7% yaxshilanishni kutadi, amalda esa hech narsa yo'q.
4-bo'lim — bayram belgilari. 2172 test qatorining 108 tasi bayram yoki arafa. Belgisiz modelda bu kunlarda xato 64.66 — oddiy kunlardagidan (22.14) uch barobar katta. Bayram belgilari bilan 39.08 ga tushdi, oddiy kunlarda deyarli o'zgarmadi (22.55). Umumiy MAE da bu farq kichik ko'rinadi (qatorlarning 5% i), lekin ombor uchun eng muhim kunlar aynan shular.
Misol 2 — Global va lokal modellar, sovuq start
"""Global va lokal modellar: 12 do'kon, 7 kun oldinga bashorat, sovuq start."""
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor
# sintetik taqvim: "bayram kunlari" (yillar bo'yicha to'qilgan sanalar)
BAYRAMLAR = pd.to_datetime([
"2023-03-21", "2024-03-21", "2025-03-21", # Navro'z
"2023-04-21", "2024-04-10", "2025-03-30", # Ramazon hayiti
"2023-06-28", "2024-06-16", "2025-06-06", # Qurbon hayiti
"2023-12-31", "2024-12-31", # Yangi yil arafasi
])
HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])
def dokonlar(n_dokon=12, kun=912, seed=0):
"""Toshkent va Samarqanddagi do'konlar kunlik savdosi (uzun format)."""
rng = np.random.default_rng(seed)
sana = pd.date_range("2023-01-01", periods=kun, freq="D")
t = np.arange(kun)
bayram = sana.isin(BAYRAMLAR).astype(int)
arafa1 = sana.isin(BAYRAMLAR - pd.Timedelta(days=1)).astype(int)
arafa2 = sana.isin(BAYRAMLAR - pd.Timedelta(days=2)).astype(int)
qismlar = []
for d in range(n_dokon):
e = rng.normal(0, 0.08, kun)
shovqin = np.zeros(kun)
for i in range(1, kun):
shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
aksiya = (rng.random(kun) < 0.05).astype(int)
ly = (np.log(rng.uniform(80, 400))
+ rng.normal(0.10, 0.05) * t / 365.25
+ np.cumsum(rng.normal(0, 0.004, kun))
+ rng.uniform(0.7, 1.3) * (HAFTA - HAFTA.mean())[sana.dayofweek]
+ 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25)
+ 0.30 * bayram + 0.20 * arafa1 + 0.10 * arafa2
+ 0.25 * aksiya + shovqin)
qismlar.append(pd.DataFrame({
"sana": sana, "dokon": d, "sotuv": np.exp(ly), "aksiya": aksiya,
"bayram": bayram, "arafa1": arafa1, "arafa2": arafa2}))
return pd.concat(qismlar, ignore_index=True)
def belgilar(df, h=7):
"""h kun oldinga bashorat: barcha lag va rolling lar kamida h kun eski."""
df = df.sort_values(["dokon", "sana"]).copy()
df["y"] = np.log(df["sotuv"])
g = df.groupby("dokon")["y"]
for k in [h, h + 1, 14, 21, 28]:
df[f"lag_{k}"] = g.shift(k)
for w in [7, 28]:
df[f"orta_{w}"] = g.transform(lambda s, w=w: s.shift(h).rolling(w).mean())
df["hafta_kuni"] = df["sana"].dt.dayofweek
df["yil_kuni"] = df["sana"].dt.dayofyear
return df.dropna()
def hgb(kat=None):
return HistGradientBoostingRegressor(max_iter=100, learning_rate=0.1,
categorical_features=kat,
random_state=0)
def ustunlar(df):
return [c for c in df.columns if c.startswith(("lag_", "orta_"))] + [
"hafta_kuni", "yil_kuni", "bayram", "arafa1", "arafa2", "aksiya"]
def main() -> None:
df = dokonlar()
jad = belgilar(df)
ust = ustunlar(jad)
kesim = pd.Timestamp("2025-01-01")
tr, te = jad[jad["sana"] < kesim], jad[jad["sana"] >= kesim]
print("=== 1. Masala: 12 do'kon, 7 kun oldinga bashorat ===")
print(f" o'quv {len(tr)} qator, test {len(te)} qator; belgilar: "
f"{len(ust)} ta (+ do'kon raqami global modelda)")
print("\n=== 2. Global (bitta model) va lokal (har do'konga alohida) ===")
glob = hgb(["dokon"]).fit(tr[ust + ["dokon"]], tr["y"])
p_glob = np.exp(glob.predict(te[ust + ["dokon"]]))
p_lok = np.empty(len(te))
for d in range(12):
i_tr, i_te = (tr["dokon"] == d).to_numpy(), (te["dokon"] == d).to_numpy()
m = hgb().fit(tr.loc[i_tr, ust], tr.loc[i_tr, "y"])
p_lok[i_te] = np.exp(m.predict(te.loc[i_te, ust]))
haq = np.exp(te["y"].to_numpy())
snaive = np.exp(te["lag_7"].to_numpy())
dokon = te["dokon"].to_numpy()
ortacha = te.groupby("dokon")["sotuv"].mean().to_numpy()
xato = {}
for nom, p in [("seasonal naive", snaive), ("lokal HistGB", p_lok),
("global HistGB", p_glob)]:
# har do'kon uchun nisbiy MAE (do'kon hajmiga bo'lingan) - solishtirish uchun
xato[nom] = np.array([np.mean(np.abs(p - haq)[dokon == d]) / ortacha[d]
for d in range(12)])
print(f" {nom:<16} o'rtacha nisbiy MAE {xato[nom].mean():.4f}")
for a, b in [("global HistGB", "lokal HistGB"),
("global HistGB", "seasonal naive")]:
d = xato[a] - xato[b]
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" {a} - {b}: {d.mean():+.4f} SE {se:.4f} (12 do'kon) -> "
f"{'sezilarli' if abs(d.mean()) > 2 * se else 'sezilarli emas'}; "
f"global yaxshi: {int(np.sum(d < 0))}/12 do'konda")
print("\n=== 3. Yangi do'kon: tarixi atigi 120 kun (sovuq start) ===")
yangi = 11
bosh = kesim - pd.Timedelta(days=120)
tr_y = tr[(tr["dokon"] != yangi) | (tr["sana"] >= bosh)]
i_te = (te["dokon"] == yangi).to_numpy()
haq_y = haq[i_te]
lok = hgb().fit(tr_y.loc[tr_y["dokon"] == yangi, ust],
tr_y.loc[tr_y["dokon"] == yangi, "y"])
glb = hgb(["dokon"]).fit(tr_y[ust + ["dokon"]], tr_y["y"])
print(f" o'quvda yangi do'kon qatorlari: {int((tr_y['dokon'] == yangi).sum())}")
for nom, p in [("seasonal naive", snaive[i_te]),
("lokal (120 kun)", np.exp(lok.predict(te.loc[i_te, ust]))),
("global (hamma do'kon)",
np.exp(glb.predict(te.loc[i_te, ust + ["dokon"]])))]:
x = np.abs(p - haq_y)
haftalik = pd.Series(x).groupby(np.arange(len(x)) // 7).mean()
print(f" {nom:<22} MAE {x.mean():6.2f} (haftalar: min "
f"{haftalik.min():.1f}, max {haftalik.max():.1f})")
yil_kuni_tr = tr_y.loc[tr_y["dokon"] == yangi, "sana"]
print(f" lokal model ko'rgan davr: {yil_kuni_tr.min().date()} - "
f"{yil_kuni_tr.max().date()} (yillik mavsumning uchdan biri)")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Masala: 12 do'kon, 7 kun oldinga bashorat ===
o'quv 8364 qator, test 2172 qator; belgilar: 13 ta (+ do'kon raqami global modelda)
=== 2. Global (bitta model) va lokal (har do'konga alohida) ===
seasonal naive o'rtacha nisbiy MAE 0.1301
lokal HistGB o'rtacha nisbiy MAE 0.1041
global HistGB o'rtacha nisbiy MAE 0.0837
global HistGB - lokal HistGB: -0.0204 SE 0.0043 (12 do'kon) -> sezilarli; global yaxshi: 12/12 do'konda
global HistGB - seasonal naive: -0.0464 SE 0.0030 (12 do'kon) -> sezilarli; global yaxshi: 12/12 do'konda
=== 3. Yangi do'kon: tarixi atigi 120 kun (sovuq start) ===
o'quvda yangi do'kon qatorlari: 120
seasonal naive MAE 55.88 (haftalar: min 27.5, max 106.4)
lokal (120 kun) MAE 65.37 (haftalar: min 23.1, max 157.9)
global (hamma do'kon) MAE 43.27 (haftalar: min 20.6, max 92.4)
lokal model ko'rgan davr: 2024-09-03 - 2024-12-31 (yillik mavsumning uchdan biri)Natija tahlili.
Bu misolda ufq 7 kun: barcha lag va rolling lar kamida 7 kun eski (lag_7, lag_8, ..., shift(7) dan keyin rolling) — ya'ni har bir belgi dushanba kuni keyingi yakshanbagacha bo'lgan har qanday kun uchun mavjud.
1-2-bo'limlar — do'konlar o'lchami turlicha (kuniga 80 dan 400 gacha chek), shuning uchun MAE ni har do'konning o'rtacha savdosiga bo'lib, nisbiy qildik. Global model 0.0837, lokal modellar 0.1041, seasonal naive 0.1301. Juftlashgan farq 12 do'kon bo'yicha: global lokaldan -0.0204 (SE 0.0043) — sezilarli, va 12 do'konning 12 tasida yaxshi. Sabab — ma'lumot: har lokal model ~700 qatorda yillik mavsumni ikki marta, har bayramni ikki marta ko'rgan; global model esa har bayramni 24 marta ko'radi va bayram effektini barcha do'konlardan o'rganadi.
3-bo'lim — sovuq start. 11-do'konning o'quvda faqat oxirgi 120 kuni bor (2024-09-03 dan). Lokal model 65.37 — hatto seasonal naive dan (55.88) yomon: u bahor va yozni umuman ko'rmagan, Navro'z va Ramazon hayitini bilmaydi, eng yomon haftada xato 157.9. Global model 43.27: yillik mavsum va bayram effektlarini boshqa do'konlardan "qarz oldi", o'z darajasini esa lag lardan oldi. Yangi mahsulot va yangi filial — global modelning eng katta amaliy afzalligi.
Misol 3 — Ko'p qadamli strategiyalar va HistGB, ETS, seasonal naive backtesti
"""Ko'p qadamli bashorat: rekursiv, to'g'ridan-to'g'ri, ko'p chiqishli; HistGB vs ETS backtest."""
import warnings
import numpy as np
import pandas as pd
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor
from statsmodels.tsa.exponential_smoothing.ets import ETSModel
# sintetik taqvim: "bayram kunlari" (yillar bo'yicha to'qilgan sanalar)
BAYRAMLAR = pd.to_datetime([
"2023-03-21", "2024-03-21", "2025-03-21", # Navro'z
"2023-04-21", "2024-04-10", "2025-03-30", # Ramazon hayiti
"2023-06-28", "2024-06-16", "2025-06-06", # Qurbon hayiti
"2023-12-31", "2024-12-31", # Yangi yil arafasi
])
HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])
def dokonlar(n_dokon=12, kun=912, seed=0):
"""Toshkent va Samarqanddagi do'konlar kunlik savdosi (uzun format)."""
rng = np.random.default_rng(seed)
sana = pd.date_range("2023-01-01", periods=kun, freq="D")
t = np.arange(kun)
bayram = sana.isin(BAYRAMLAR).astype(int)
arafa1 = sana.isin(BAYRAMLAR - pd.Timedelta(days=1)).astype(int)
arafa2 = sana.isin(BAYRAMLAR - pd.Timedelta(days=2)).astype(int)
qismlar = []
for d in range(n_dokon):
e = rng.normal(0, 0.08, kun)
shovqin = np.zeros(kun)
for i in range(1, kun):
shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
aksiya = (rng.random(kun) < 0.05).astype(int)
ly = (np.log(rng.uniform(80, 400))
+ rng.normal(0.10, 0.05) * t / 365.25
+ np.cumsum(rng.normal(0, 0.004, kun))
+ rng.uniform(0.7, 1.3) * (HAFTA - HAFTA.mean())[sana.dayofweek]
+ 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25)
+ 0.30 * bayram + 0.20 * arafa1 + 0.10 * arafa2
+ 0.25 * aksiya + shovqin)
qismlar.append(pd.DataFrame({
"sana": sana, "dokon": d, "sotuv": np.exp(ly), "aksiya": aksiya,
"bayram": bayram, "arafa1": arafa1, "arafa2": arafa2}))
return pd.concat(qismlar, ignore_index=True)
H = 14 # bashorat ufqi, kun
def hgb():
return HistGradientBoostingRegressor(max_iter=60, learning_rate=0.15,
max_leaf_nodes=15, random_state=0)
def tarix_belgilari(ly, o):
"""Origin o (o-1 gacha ma'lum) uchun o'tmish belgilari: lag 1..7, 14, orta_7."""
o = np.asarray(o)
lag = np.column_stack([ly[o - k] for k in range(1, 8)] + [ly[o - 14]])
return np.column_stack([lag, lag[:, :7].mean(axis=1)])
def rekursiv_bashorat(model, ly, kal, boshlar):
"""Bir qadamli modelni o'z bashoratlari bilan 14 marta takrorlash."""
boshlar = np.asarray(boshlar)
ichki = np.tile(np.arange(-14, 0), (len(boshlar), 1)) + boshlar[:, None]
tarix = ly[ichki] # (origin, 14) - oxirgi 14 kun
natija = np.empty((len(boshlar), H))
for k in range(H):
lag = np.column_stack([tarix[:, -j] for j in range(1, 8)] + [tarix[:, -14]])
X = np.column_stack([lag, lag[:, :7].mean(axis=1), kal[boshlar + k]])
p = model.predict(X)
natija[:, k] = p
tarix = np.column_stack([tarix[:, 1:], p]) # bashorat "haqiqat" o'rnida
return natija
def main() -> None:
df = dokonlar()
bir = df[df["dokon"] == 0].reset_index(drop=True)
ly = np.log(bir["sotuv"].to_numpy())
sana = bir["sana"]
kal = np.column_stack([sana.dt.dayofweek, sana.dt.dayofyear,
bir[["bayram", "arafa1", "arafa2", "aksiya"]]])
kesim = int(np.flatnonzero(sana == pd.Timestamp("2025-01-01"))[0])
oquv_o = np.arange(28, kesim) # o'quv originlari
test_o = np.arange(kesim, len(ly) - H + 1) # test originlari
haq = np.array([ly[o:o + H] for o in test_o])
print("=== 1. Uch strategiya: bitta kesim, test davrida har kuni origin ===")
# rekursiv: bitta bir qadamli model
Xr = np.column_stack([tarix_belgilari(ly, oquv_o), kal[oquv_o]])
rek = hgb().fit(Xr, ly[oquv_o])
p_rek = rekursiv_bashorat(rek, ly, kal, test_o)
# to'g'ridan-to'g'ri: har ufq uchun alohida model (faqat origingacha ma'lumot)
p_dir = np.empty_like(p_rek)
for h in range(H):
o = oquv_o[oquv_o + h < kesim]
m = hgb().fit(np.column_stack([tarix_belgilari(ly, o), kal[o + h]]),
ly[o + h])
p_dir[:, h] = m.predict(np.column_stack([tarix_belgilari(ly, test_o),
kal[test_o + h]]))
# ko'p chiqishli: bitta model 14 ta chiqish (ExtraTrees vektor bashorat qiladi)
def mimo_X(o):
kel = np.column_stack([kal[o + h, 2:] for h in range(H)]) # bayram, aksiya
return np.column_stack([tarix_belgilari(ly, o), kal[o, :2], kel])
o = oquv_o[oquv_o + H <= kesim]
mimo = ExtraTreesRegressor(n_estimators=100, min_samples_leaf=3, n_jobs=1,
random_state=0)
mimo.fit(mimo_X(o), np.array([ly[i:i + H] for i in o]))
p_mimo = mimo.predict(mimo_X(test_o))
p_sn = np.array([[ly[o - 7 + (h % 7)] for h in range(H)] for o in test_o])
prog = {"seasonal naive": p_sn, "rekursiv HistGB": p_rek,
"to'g'ridan HistGB": p_dir, "ko'p chiqishli ET": p_mimo}
print(f" {len(test_o)} origin x {H} kun; o'quvda modellar soni: rekursiv 1,"
f" to'g'ridan {H}, ko'p chiqishli 1")
egri = {k: np.mean(np.abs(np.exp(v) - np.exp(haq)), axis=0)
for k, v in prog.items()}
print(f" {'ufq':<4}" + "".join(f"{k:>19}" for k in prog))
for h in [0, 1, 2, 6, 9, 13]:
print(f" h={h + 1:<2}" + "".join(f"{egri[k][h]:>19.2f}" for k in prog))
print(f" {'o_rt':<4}" + "".join(f"{egri[k].mean():>19.2f}" for k in prog))
print(f" rekursiv xatosining o'sishi h=1 -> h=14: "
f"{egri['rekursiv HistGB'][0]:.2f} -> {egri['rekursiv HistGB'][-1]:.2f}")
mustaqil = np.arange(0, len(test_o), H) # qoplanmaydigan originlar
for a in ["to'g'ridan HistGB", "ko'p chiqishli ET"]:
d = np.array([np.mean(np.abs(np.exp(prog[a][i]) - np.exp(haq[i])))
- np.mean(np.abs(np.exp(p_rek[i]) - np.exp(haq[i])))
for i in mustaqil])
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" {a} - rekursiv ({len(d)} qoplanmaydigan origin): "
f"{d.mean():+.2f} SE {se:.2f} -> "
f"{'sezilarli' if abs(d.mean()) > 2 * se else 'sezilarli emas'}")
print("\n=== 2. Rolling backtest: 10 oyna x 14 kun, qayta o'qitish bilan ===")
boshlar = [kesim + H * i for i in range(10)]
xato = {"seasonal naive": [], "ETS(A,Ad,A) m=7": [], "rekursiv HistGB": []}
for b in boshlar:
o = np.arange(28, b)
m = hgb().fit(np.column_stack([tarix_belgilari(ly, o), kal[o]]), ly[o])
p = rekursiv_bashorat(m, ly, kal, [b])[0]
with warnings.catch_warnings():
warnings.simplefilter("ignore")
e = ETSModel(pd.Series(ly[:b], index=sana[:b].to_numpy()),
error="add", trend="add", damped_trend=True,
seasonal="add", seasonal_periods=7).fit(disp=False)
p_ets = e.forecast(H).to_numpy()
haq_b = np.exp(ly[b:b + H])
for nom, pp in [("seasonal naive", np.array([ly[b - 7 + (h % 7)]
for h in range(H)])),
("ETS(A,Ad,A) m=7", p_ets), ("rekursiv HistGB", p)]:
xato[nom].append(np.mean(np.abs(np.exp(pp) - haq_b)))
xato = {k: np.array(v) for k, v in xato.items()}
for k, v in xato.items():
print(f" {k:<18} MAE {v.mean():6.2f} (oynalar: {v.min():.1f} - {v.max():.1f})")
eng = min(xato, key=lambda k: xato[k].mean())
print(f" eng past MAE: {eng}; juftlashgan farq (qator - eng yaxshi):")
munosib = []
for k in xato: # soddalik tartibida
d = xato[k] - xato[eng]
se = d.std(ddof=1) / np.sqrt(len(d)) if k != eng else 0.0
if k == eng or d.mean() <= 2 * se:
munosib.append(k)
if k != eng:
print(f" {k:<18} {d.mean():+6.2f} SE {se:.2f}")
print(f" qoida bo'yicha tanlov: {munosib[0]}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Uch strategiya: bitta kesim, test davrida har kuni origin ===
168 origin x 14 kun; o'quvda modellar soni: rekursiv 1, to'g'ridan 14, ko'p chiqishli 1
ufq seasonal naive rekursiv HistGB to'g'ridan HistGB ko'p chiqishli ET
h=1 16.52 11.75 11.75 12.90
h=2 16.55 12.91 11.96 13.32
h=3 16.58 13.76 11.99 13.50
h=7 16.64 13.65 11.94 14.13
h=10 19.86 13.58 12.78 14.67
h=14 20.08 13.35 12.19 13.98
o_rt 18.35 13.52 12.26 13.98
rekursiv xatosining o'sishi h=1 -> h=14: 11.75 -> 13.35
to'g'ridan HistGB - rekursiv (12 qoplanmaydigan origin): -1.16 SE 0.72 -> sezilarli emas
ko'p chiqishli ET - rekursiv (12 qoplanmaydigan origin): +0.84 SE 1.02 -> sezilarli emas
=== 2. Rolling backtest: 10 oyna x 14 kun, qayta o'qitish bilan ===
seasonal naive MAE 15.84 (oynalar: 6.2 - 26.6)
ETS(A,Ad,A) m=7 MAE 13.25 (oynalar: 6.3 - 23.6)
rekursiv HistGB MAE 12.37 (oynalar: 6.3 - 17.0)
eng past MAE: rekursiv HistGB; juftlashgan farq (qator - eng yaxshi):
seasonal naive +3.47 SE 1.07
ETS(A,Ad,A) m=7 +0.88 SE 1.06
qoida bo'yicha tanlov: ETS(A,Ad,A) m=7Natija tahlili.
1-bo'lim — uch strategiya, 14 kunlik ufq, 168 ta origin. Ufq bo'yicha xato egri chizig'i:
- Seasonal naive 1-7 kunda
~16.6, 8-14 kunda~20: 8-kundan boshlab u ikki hafta oldingi qiymatni takrorlaydi. - Rekursiv 1-kunda
11.75, 3-kunda13.76— dastlabki qadamlarda tez o'sadi: model o'z bashoratlariga tayana boshlaydi va ular haqiqiy lag lardan silliqroq. Keyin taxminan13.3-13.7da barqarorlashadi (lag lar to'liq "bashorat" bo'lgach, model faktik ravishda kalendar bo'yicha o'rtacha profilni beradi). - To'g'ridan-to'g'ri eng tekis:
11.75dan12.19gacha; o'rtacha12.26— uchtasining eng yaxshisi. 1-kunda u rekursiv bilan aynan bir xil — chunkih = 1da ikkalasi bir xil model. - Ko'p chiqishli ExtraTrees o'rtacha
13.98— boshqa model sinfi (vektor chiqish HistGB da yo'q), shuning uchun taqqoslash strategiyani ham, modelni ham o'z ichiga oladi.
Lekin farqlar sezilarlimi? Qoplanuvchi originlar bo'yicha SE hisoblab bo'lmaydi (qo'shni originlar bir xil kunlarni bashorat qiladi), shuning uchun har 14-originni oldik — 12 ta mustaqil oyna: to'g'ridan-to'g'ri rekursivdan -1.16 (SE 0.72), ko'p chiqishli +0.84 (SE 1.02) — ikkalasi ham sezilarli emas. Egri chiziqlar ko'rinishi turlicha, lekin 12 oynada ularni ishonchli ajratib bo'lmaydi. Soddalik va narx bo'yicha (1 model va 14 model) — rekursiv.
2-bo'lim — asosiy savol: ML klassik modeldan yaxshimi? 10 ta qoplanmaydigan 14 kunlik oyna, har oynada qayta o'qitish. Rekursiv HistGB 12.37, ETS(A,Ad,A) (haftalik mavsum, log qator) 13.25, seasonal naive 15.84. Seasonal naive eng yaxshisidan sezilarli yomon (+3.47, SE 1.07), ETS esa yo'q (+0.88, SE 1.06). Qoida "eng yaxshisidan sezilarli yomon bo'lmagan eng sodda" — ETS. Bu halol natija: HistGB o'rtacha 7% yaxshi va eng yomon oynasi ham yaxshiroq (17.0 va 23.6), lekin 10 oynada farq shovqindan ajralmaydi; ML esa belgilar quvuri, taqvim manbasi va qo'shimcha monitoring talab qiladi. Qaror o'zgarishi mumkin bo'lgan holatlar — ko'proq oyna (SE kichrayadi), bayram kunlaridagi xato alohida qadrlansa (1-misol, 4-bo'lim) yoki ko'p do'kon uchun global model (2-misol) — ularni ham xuddi shu backtestda o'lchash kerak.
Misol 4 — Noaniqlik intervallari va ierarxik bashorat
"""Noaniqlik: kvantil regressiya va split conformal intervallar; ierarxik bashorat."""
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor
# sintetik taqvim: "bayram kunlari" (yillar bo'yicha to'qilgan sanalar)
BAYRAMLAR = pd.to_datetime([
"2023-03-21", "2024-03-21", "2025-03-21", # Navro'z
"2023-04-21", "2024-04-10", "2025-03-30", # Ramazon hayiti
"2023-06-28", "2024-06-16", "2025-06-06", # Qurbon hayiti
"2023-12-31", "2024-12-31", # Yangi yil arafasi
])
HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])
def dokonlar(n_dokon=12, kun=912, seed=0):
"""Toshkent va Samarqanddagi do'konlar kunlik savdosi (uzun format)."""
rng = np.random.default_rng(seed)
sana = pd.date_range("2023-01-01", periods=kun, freq="D")
t = np.arange(kun)
bayram = sana.isin(BAYRAMLAR).astype(int)
arafa1 = sana.isin(BAYRAMLAR - pd.Timedelta(days=1)).astype(int)
arafa2 = sana.isin(BAYRAMLAR - pd.Timedelta(days=2)).astype(int)
qismlar = []
for d in range(n_dokon):
e = rng.normal(0, 0.08, kun)
shovqin = np.zeros(kun)
for i in range(1, kun):
shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
aksiya = (rng.random(kun) < 0.05).astype(int)
ly = (np.log(rng.uniform(80, 400))
+ rng.normal(0.10, 0.05) * t / 365.25
+ np.cumsum(rng.normal(0, 0.004, kun))
+ rng.uniform(0.7, 1.3) * (HAFTA - HAFTA.mean())[sana.dayofweek]
+ 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25)
+ 0.30 * bayram + 0.20 * arafa1 + 0.10 * arafa2
+ 0.25 * aksiya + shovqin)
qismlar.append(pd.DataFrame({
"sana": sana, "dokon": d, "sotuv": np.exp(ly), "aksiya": aksiya,
"bayram": bayram, "arafa1": arafa1, "arafa2": arafa2}))
return pd.concat(qismlar, ignore_index=True)
def belgilar(df, h=7):
"""h kun oldinga bashorat: barcha lag va rolling lar kamida h kun eski."""
df = df.sort_values(["dokon", "sana"]).copy()
df["y"] = np.log(df["sotuv"])
g = df.groupby("dokon")["y"]
for k in [h, h + 1, 14, 21, 28]:
df[f"lag_{k}"] = g.shift(k)
for w in [7, 28]:
df[f"orta_{w}"] = g.transform(lambda s, w=w: s.shift(h).rolling(w).mean())
df["hafta_kuni"] = df["sana"].dt.dayofweek
df["yil_kuni"] = df["sana"].dt.dayofyear
return df.dropna()
UST = ["lag_7", "lag_8", "lag_14", "lag_21", "lag_28", "orta_7", "orta_28",
"hafta_kuni", "yil_kuni", "bayram", "arafa1", "arafa2", "aksiya", "dokon"]
def hgb(**kw):
return HistGradientBoostingRegressor(max_iter=100, learning_rate=0.1,
categorical_features=["dokon"],
random_state=0, **kw)
def qamrov(y, past, yuq):
return float(np.mean((y >= past) & (y <= yuq)))
def main() -> None:
df = dokonlar()
jad = belgilar(df)
k1, k2 = pd.Timestamp("2024-09-01"), pd.Timestamp("2025-01-01")
tr = jad[jad["sana"] < k1]
kal = jad[(jad["sana"] >= k1) & (jad["sana"] < k2)]
te = jad[jad["sana"] >= k2]
y_te = te["y"].to_numpy()
alfa = 0.10 # nominal qamrov 90%
print("=== 1. Bo'linish: o'quv / kalibrlash / test (vaqt bo'yicha) ===")
print(f" o'quv {len(tr)}, kalibrlash {len(kal)}, test {len(te)} qator; "
f"nominal qamrov {1 - alfa:.2f}")
print("\n=== 2. To'rt xil 90% interval (log fazoda qurilib, test qamrovi) ===")
nuqta = hgb().fit(tr[UST], tr["y"])
p_te = nuqta.predict(te[UST])
# (a) sodda: o'QUV qoldiqlari kvantili
q_oquv = np.quantile(np.abs(tr["y"] - nuqta.predict(tr[UST])), 1 - alfa)
# (b) split conformal: KALIBRLASH qoldiqlari, (n+1) tuzatish bilan
r = np.sort(np.abs(kal["y"].to_numpy() - nuqta.predict(kal[UST])))
n = len(r)
q_konf = r[int(np.ceil((n + 1) * (1 - alfa))) - 1]
# (c) kvantil regressiya (o'quv + kalibrlash ma'lumotida)
tk = pd.concat([tr, kal])
q_past = hgb(loss="quantile", quantile=alfa / 2).fit(tk[UST], tk["y"])
q_yuq = hgb(loss="quantile", quantile=1 - alfa / 2).fit(tk[UST], tk["y"])
# (d) CQR: kvantil modellar (faqat o'quvda) + kalibrlashda konformal tuzatish
c_past = hgb(loss="quantile", quantile=alfa / 2).fit(tr[UST], tr["y"])
c_yuq = hgb(loss="quantile", quantile=1 - alfa / 2).fit(tr[UST], tr["y"])
ball = np.sort(np.maximum(c_past.predict(kal[UST]) - kal["y"].to_numpy(),
kal["y"].to_numpy() - c_yuq.predict(kal[UST])))
q_cqr = ball[int(np.ceil((n + 1) * (1 - alfa))) - 1]
intervallar = {
"o'quv qoldiqlari": (p_te - q_oquv, p_te + q_oquv),
"split conformal": (p_te - q_konf, p_te + q_konf),
"kvantil HistGB": (q_past.predict(te[UST]), q_yuq.predict(te[UST])),
"CQR": (c_past.predict(te[UST]) - q_cqr, c_yuq.predict(te[UST]) + q_cqr),
}
print(f" tuzatish (log): o'quv qoldiqlari {q_oquv:.4f}, "
f"kalibrlash {q_konf:.4f}, CQR {q_cqr:+.4f}")
sotuv = te["sotuv"].to_numpy()
oy = te["sana"].dt.month.to_numpy()
print(f" {'usul':<18} {'qamrov':>7} {'kenglik':>8} {'yan-mar':>8} "
f"{'apr-iyun':>9}")
for nom, (a, b) in intervallar.items():
a, b = np.minimum(a, b), np.maximum(a, b)
kenglik = np.mean(np.exp(b) - np.exp(a)) / np.mean(sotuv)
print(f" {nom:<18} {qamrov(y_te, a, b):>7.3f} {kenglik:>8.3f} "
f"{qamrov(y_te[oy <= 3], a[oy <= 3], b[oy <= 3]):>8.3f} "
f"{qamrov(y_te[oy > 3], a[oy > 3], b[oy > 3]):>9.3f}")
print(" kenglik - o'rtacha savdoga nisbatan")
maxsus = (te[["bayram", "arafa1", "arafa2"]].sum(axis=1) > 0).to_numpy()
a, b = intervallar["split conformal"]
ak, bk = intervallar["kvantil HistGB"]
print(f" bayram/arafa kunlari ({int(maxsus.sum())} qator): conformal "
f"{qamrov(y_te[maxsus], a[maxsus], b[maxsus]):.3f}, kvantil "
f"{qamrov(y_te[maxsus], ak[maxsus], bk[maxsus]):.3f}")
print("\n=== 3. Ierarxiya: 12 do'kon yig'indisi = viloyat ===")
jami = (df.groupby("sana", as_index=False)
.agg(sotuv=("sotuv", "sum"), aksiya=("aksiya", "sum"),
bayram=("bayram", "first"), arafa1=("arafa1", "first"),
arafa2=("arafa2", "first")))
jami["dokon"] = 0
jj = belgilar(jami)
jtr, jte = jj[jj["sana"] < k2], jj[jj["sana"] >= k2]
tr2 = jad[jad["sana"] < k2]
dok = hgb().fit(tr2[UST], tr2["y"])
p_dok = pd.Series(np.exp(dok.predict(te[UST])), index=te.index)
yuqori = HistGradientBoostingRegressor(max_iter=100, learning_rate=0.1,
random_state=0)
yuqori.fit(jtr[UST], jtr["y"])
p_jami = pd.Series(np.exp(yuqori.predict(jte[UST])), index=jte["sana"].to_numpy())
bu = p_dok.groupby(te["sana"].to_numpy()).sum() # bottom-up
haq_jami = jte.set_index("sana")["sotuv"]
print(f" viloyat darajasi MAE: bottom-up (do'konlar yig'indisi) "
f"{np.mean(np.abs(bu - haq_jami)):.1f}, top (alohida model) "
f"{np.mean(np.abs(p_jami - haq_jami)):.1f}")
ulush = (tr2[tr2["sana"] >= k2 - pd.Timedelta(days=28)]
.groupby("dokon")["sotuv"].sum())
ulush = ulush / ulush.sum() # top-down ulushlar
td = p_jami.loc[te["sana"].to_numpy()].to_numpy() * ulush.loc[te["dokon"]].to_numpy()
print(f" do'kon darajasi MAE: bottom-up {np.mean(np.abs(p_dok - sotuv)):.2f}, "
f"top-down (ulush bilan) {np.mean(np.abs(td - sotuv)):.2f}")
print(f" bottom-up yig'indisi mos: {np.allclose(bu.sum(), p_dok.sum())}; "
f"top-down do'konlar yig'indisi = top bashorat: "
f"{np.allclose(td.sum(), p_jami.sum())}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bo'linish: o'quv / kalibrlash / test (vaqt bo'yicha) ===
o'quv 6900, kalibrlash 1464, test 2172 qator; nominal qamrov 0.90
=== 2. To'rt xil 90% interval (log fazoda qurilib, test qamrovi) ===
tuzatish (log): o'quv qoldiqlari 0.1266, kalibrlash 0.1640, CQR +0.0286
usul qamrov kenglik yan-mar apr-iyun
o'quv qoldiqlari 0.766 0.243 0.771 0.761
split conformal 0.885 0.316 0.893 0.877
kvantil HistGB 0.795 0.292 0.795 0.794
CQR 0.873 0.350 0.869 0.878
kenglik - o'rtacha savdoga nisbatan
bayram/arafa kunlari (108 qator): conformal 0.778, kvantil 0.611
=== 3. Ierarxiya: 12 do'kon yig'indisi = viloyat ===
viloyat darajasi MAE: bottom-up (do'konlar yig'indisi) 165.4, top (alohida model) 178.8
do'kon darajasi MAE: bottom-up 26.10, top-down (ulush bilan) 32.39
bottom-up yig'indisi mos: True; top-down do'konlar yig'indisi = top bashorat: TrueNatija tahlili.
1-bo'lim — uch bo'lak vaqt bo'yicha: o'quv (2024-09 gacha), kalibrlash (2024-09..12), test (2025). Kalibrlash davri o'quvdan keyin va testdan oldin — konformal usul uchun zarur.
2-bo'lim — to'rt xil 90% interval (nominal 0.90):
- O'quv qoldiqlari kvantili —
0.766. O'quvda model qoldiqlari kichik (log fazoda kvantil0.1266), kalibrlashda esa0.1640— ya'ni o'quvdagi xato yangi ma'lumotdagi xatodan ~23% kam. Interval tor, qamrov va'dadan ancha past. - Split conformal —
0.885, nominalga yaqin; ikkala chorakda barqaror (0.893,0.877). Narxi — kenglik: o'rtacha savdoning31.6%i. - Kvantil HistGB —
0.795: belgilarga moslashadi, lekin o'quv ma'lumotiga ham moslashgan, shuning uchun testda tor. Kafolat yo'q. - CQR —
0.873: kvantil modellarga konformal tuzatish (+0.0286log birlik) qo'shildi va qamrov nominalga yaqinlashdi.
Split conformal va CQR ham nominaldan biroz past (0.885, 0.873) — vaqt qatorida almashtiriladiganlik to'liq bajarilmaydi (trend va sayr qiluvchi daraja: test davri kalibrlashdan uzoqlashgan sari xato o'sadi). Bayram va arafa kunlarida esa rasm boshqacha: conformal 0.778, kvantil 0.611 — o'rtacha qamrov yaxshi bo'lsa ham, eng muhim kunlarda interval yetarli emas. Konformal usul o'rtacha qamrovni kafolatlaydi, har segmentda emas; bayram kunlari uchun alohida kalibrlash (Mondrian conformal — har guruh uchun o'z q) kerak.
3-bo'lim — ierarxiya. Viloyat (12 do'kon yig'indisi) darajasida bottom-up (165.4) alohida viloyat modelidan (178.8) yaxshi; do'kon darajasida bottom-up 26.10, top-down (viloyat bashoratini oxirgi 28 kun ulushlari bilan taqsimlash) 32.39. Top-down do'konlarning o'ziga xos aksiyalarini yo'qotadi — ulushlar o'rtacha, aksiya esa bitta do'konda. Ikkala usul ham izchil: bottom-up yig'indisi va top-down ulushlari yig'indisi mos (True, True). Bu ma'lumotda bottom-up ikkala darajada ham yutdi — bu har doim shunday emas: past daraja juda shovqinli bo'lsa (kuniga 2-3 dona sotiladigan mahsulotlar), top-down yoki optimal moslashtirish yaxshiroq bo'lishi mumkin.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"rolling(7).mean() — oddiy belgi" |
shift siz y_t ichida; center=True da kelajak ham — validatsiya yolg'on yaxshi |
| "Sizish faqat validatsiyani buzadi, model baribir yaxshi" | center=True modeli ishlab chiqarishda to'g'ri modeldan yomon (24.45 va 23.38) |
| "7 kun oldinga bashoratda kechagi savdo eng kuchli belgi" | U bashorat paytida mavjud emas — eng kichik lag = ufq |
| "Har do'konga alohida model aniqroq" | Global model 12/12 do'konda yaxshi; yangi do'konda farq katta |
| "Rekursiv strategiya har doim yomon" | Xato to'planadi, lekin 12 oynada farq sezilarli emas |
| "ML klassik modeldan albatta yaxshi" | 10 oynada ETS dan farq +0.88 (SE 1.06) — sezilarli emas |
| "Kvantil regressiya 90% qamraydi" | Testda 0.795; kafolat yo'q — konformal tuzatish kerak |
| "Konformal — har joyda kafolat" | O'rtacha qamrov; bayram kunlarida 0.778; vaqt qatorida taxminiy |
| "Interval kvantilini o'quv qoldiqlaridan olsa bo'ladi" | O'quv xatosi kichik — qamrov 0.766 |
| "Top-down do'konlar uchun ham yaxshi" | Do'konlarning o'ziga xos tadbirlari yo'qoladi (32.39 va 26.10) |
6. Keng tarqalgan xatolar va yechimlari
1. shift siz rolling
df["orta_7"] = g.transform(lambda s: s.rolling(7).mean()) # ⚠️
df["orta_7"] = g.transform(lambda s: s.shift(h).rolling(7).mean()) # ✅ h - ufq2. groupby siz shift
df["lag_7"] = df["y"].shift(7) # ⚠️ do'konlar aralashadi
df["lag_7"] = df.groupby("dokon")["y"].shift(7) # ✅3. Ufqdan qisqa lag
belgilar = ["lag_1", "lag_7", "hafta_kuni"] # ⚠️ 7 kun oldinga bashoratda
belgilar = ["lag_7", "lag_8", "lag_14", "hafta_kuni"] # ✅4. Ko'p qatorli jadvalni tasodifiy bo'lish
train_test_split(jad, test_size=0.2, shuffle=True) # ⚠️
tr, te = jad[jad["sana"] < kesim], jad[jad["sana"] >= kesim] # ✅5. O'quv qoldiqlaridan interval
q = np.quantile(np.abs(y_tr - model.predict(X_tr)), 0.9) # ⚠️ tor
q = konformal_q(y_kal, model.predict(X_kal), alfa=0.1) # ✅ kalibrlash6. Qoplanuvchi originlarda SE
d = xato_a - xato_b # 168 ta har kungi origin ⚠️ SE kichik
d = d[::H] # qoplanmaydigan originlar ✅7. Faqat bazaviyga qarshi baholash
print(mae_hgb < mae_snaive) # ⚠️
# seasonal naive VA ETS bilan juftlashgan backtest, soddalik qoidasi # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 13.11-dars (o'tilgan): kvantil regressiya va pinball loss
- 15.8-15.10-darslar (o'tilgan): gradient boosting, HistGB, LightGBM — global modelning asosi
- 17.5, 17.8-darslar (o'tilgan): sana, lag va rolling belgilar, leakage
- 23.9-dars (o'tilgan): LSTM va GRU — ketma-ketlik modellari; 24-qism — transformerlar (TFT g'oyasi)
- 27.11-27.13-darslar (o'tilgan): monitoring, drift va qayta o'qitish — bashorat modelida qamrov ham kuzatiladi
- 28.1, 28.2-darslar (o'tilgan): backtest, bazaviylar, ETS/SARIMA — ML modelning raqiblari
- 28.4-dars: tavsiya tizimlari — yana "vaqt bo'yicha bo'lish" va "mashhurlik bazaviysi" g'oyalari qaytadi
8. Eng yaxshi amaliyotlar
Har belgi uchun "bashorat paytida bu ma'lummi?" deb so'rang; eng kichik lag = ufq, rolling —
shift(h)dan keyin.groupbybilanshift; log maqsad; qator id — kategoriya.Ko'p qator bo'lsa — global model; yangi qatorlar uchun ayniqsa.
Bayram, aksiya kabi kelajakda ma'lum tadbirlarni belgi qiling va ularning kunlaridagi xatoni alohida o'lchang.
Ko'p qadamli strategiyani ufq bo'yicha xato egri chizig'i va qoplanmaydigan oynalar bilan tanlang.
ML ni seasonal naive VA klassik model bilan juftlashgan backtestda solishtiring.
Intervallarni konformal (yoki CQR) bilan kalibrlang; qamrovni umumiy va segmentlar bo'yicha o'lchang.
Ierarxiyada izchillikni ta'minlang; bottom-up va top-down ni ikkala darajada o'lchang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # 14 kun oldinga bashorat - eng kichik ruxsat etilgan lag?
2. # s.rolling(7, center=True).mean() - nechta kelajak kuni ichida?
3. # groupby siz shift(1) 2-do'konning birinchi kuniga nima beradi?
4. # rekursiv strategiyada h=5 da model nechta o'z bashoratiga tayanadi?
5. # H=14 uchun to'g'ridan-to'g'ri strategiya nechta model?
6. # HistGB ko'p chiqishli regressiyani qo'llaydimi?
7. # split conformal da n=99, alfa=0.1 - nechanchi qoldiq olinadi?
8. # konformal kafolatning asosiy sharti?
9. # o'quv qoldiqlaridan interval - qamrov qaysi tomonga siljiydi?
10. # bottom-up bashoratlar izchilmi?
11. # yangi do'kon (60 kun tarix) - lokal yoki global?
12. # 168 ta har kungi origin, ufq 14 - SE uchun nechta mustaqil oyna?Javoblar
lag_14(va rollingshift(14)dan keyin)- 3 ta (7 kunlik markazlashgan oyna: 3 o'tmish, bugun, 3 kelajak)
- 1-do'konning oxirgi kungi savdosini — boshqa do'kon ma'lumoti
- 4 ta (T+1..T+4)
- 14 ta
- Yo'q —
ExtraTrees,KNeighbors,Ridgekabi modellar yoki har ufqqa alohida model ceil(100 * 0.9) = 90— saralangan qoldiqlarning 90-si- Qatorlar almashtiriladigan (exchangeable) bo'lishi
- Past tomonga — interval tor (o'quv xatosi kichik)
- Ha — yig'indi ta'rif bo'yicha mos
- Global — lokal model mavsum va bayramlarni ko'rmagan
168 / 14 = 12ta
Vazifa 2: Xatolarni tuzating
1. df["orta_7"] = df["y"].rolling(7, center=True).mean() # 12 do'kon, h=7
2. X = df[["lag_1", "lag_2", "lag_7", "hafta_kuni"]] # 7 kun oldinga
3. tr, te = train_test_split(jad, test_size=0.2, random_state=0)
4. q = np.quantile(np.abs(y_tr - m.predict(X_tr)), 0.9)
past, yuqori = m.predict(X_te) - q, m.predict(X_te) + q
5. d = xato_dir - xato_rek # 168 ta kunlik origin
se = d.std(ddof=1) / np.sqrt(len(d))Javoblar
1. df["orta_7"] = df.groupby("dokon")["y"].transform(
lambda s: s.shift(7).rolling(7).mean())
2. X = df[["lag_7", "lag_8", "lag_14", "hafta_kuni", "bayram", "aksiya"]]
3. tr, te = jad[jad["sana"] < kesim], jad[jad["sana"] >= kesim]
4. r = np.sort(np.abs(y_kal - m.predict(X_kal))) # kalibrlash davri
q = r[int(np.ceil((len(r) + 1) * 0.9)) - 1]
5. d = d[::14] # qoplanmaydigan originlar
se = d.std(ddof=1) / np.sqrt(len(d))Vazifa 3: Belgilar
Modellang (1-misol asosida):
lag_1ni olib tashlab, faqatlag_7va undan eskilari bilan h=1 modelini o'qiting — qancha yomonlashadi?groupbysizshiftni ataylab qiling va birinchi kunlar xatosini o'lchang- "Bayramgacha qolgan kunlar" (0-7) belgisini qo'shing — arafa kunlaridagi xato o'zgaradimi?
- Aksiya belgisini olib tashlab, aksiya kunlaridagi xatoni oddiy kunlar bilan solishtiring
Vazifa 4: Global model
Modellang (2-misol asosida):
- Global modelga
dokonni bermang — natija qanday o'zgaradi? - Do'konlar sonini 3, 6, 12 qilib, global va lokal farqini o'lchang
- Sovuq start: yangi do'kon tarixini 30, 60, 120, 365 kun qilib, lokal va global MAE egri chizig'ini chiqaring
- Maqsadni
log yemas,y / do'kon o'rtachasiqiling — farq bormi?
Vazifa 5: Strategiyalar
Modellang (3-misol asosida):
- Gibrid strategiya: to'g'ridan-to'g'ri modelga oldingi ufq bashoratini belgi qilib qo'shing
- Ko'p chiqishli uchun
KNeighborsRegressor(masshtablangan belgilar bilan) sinang - Backtest oynalarini 20 ga oshiring (ma'lumot oxiridan orqaga) — HistGB va ETS farqi sezilarli bo'ladimi?
- Backtestga 2-misoldagi global modelni qo'shing (bitta do'kon uchun baholab)
Vazifa 6: Intervallar
Modellang (4-misol asosida):
- Mondrian conformal: bayram/arafa kunlari va oddiy kunlar uchun alohida
q— bayram qamrovi tiklanadimi? - Adaptiv conformal (ACI): test davrida har hafta
alfa_tnialfa_t + gamma * (alfa - xato_t)bilan yangilang - Kalibrlash davrini 30, 60, 120 kun qilib, qamrov va kenglik o'zgarishini o'lchang
- Pinball loss ni noldan yozib, kvantil modellarini test davrida baholang
Vazifa 7: O'ylash
Mahsulot rahbari: "LightGBM modelimiz validatsiyada ETS dan 25% yaxshi. Uni 5 000 mahsulot uchun ishga tushiramiz. Omborga 95% intervalning yuqori chegarasicha buyurtma beramiz." Validatsiya — 2025 yilning tasodifiy 20% kunlari; belgilar orasida rolling(14).mean() va lag_1; bashorat har dushanba keyingi hafta uchun qilinadi; interval kvantil LightGBM dan. Nima deysiz?
Javob
Qisqa javob: 25% ning katta qismi sizish bo'lishi mumkin, interval esa kalibrlanmagan. Ishga tushirishdan oldin to'rtta tuzatish kerak.
1. Uchta sizish manbasi.
- Tasodifiy 20% kun — aralash bo'lish (28.1, 3-misol: baho
1.51marta optimistik chiqqan edi). rolling(14).mean()shiftsiz — bugungi savdo belgi ichida (1-misol: validatsiya23.38→21.74yolg'on yaxshilanish).lag_1— dushanba kuni keyingi yakshanba uchun kechagi savdo ma'lum emas. Haftalik bashoratda eng kichik lag 7 bo'lishi kerak.
# belgilar: lag_7, lag_8, lag_14, ...; rolling - shift(7) dan keyin
# backtest: har dushanba origin, ufq 7, kamida 10-20 qoplanmaydigan oyna
# raqiblar: seasonal naive, ETS - bir xil oynalarda, juftlashgan SE2. Qaror qoidasi. 3-misolda to'g'ri qurilgan HistGB ETS dan o'rtacha 7% yaxshi chiqdi, lekin 10 oynada farq sezilarli emas edi. 5 000 mahsulot — global model uchun qulay (2-misol), lekin qaror halol backtest va oldindan yozilgan qoida bilan.
3. Interval. Kvantil modeli o'quvga moslashadi: 4-misolda 90% kvantil intervali testda 0.795 qamradi, bayram kunlarida 0.611. Buyurtma yuqori chegaraga bog'lansa, aynan shu xato tanqislikka aylanadi. Kerak: CQR yoki split conformal (kalibrlash davri — o'quvdan keyin), qamrovni umumiy va bayram/aksiya kunlarida alohida o'lchash, kerak bo'lsa segmentlar bo'yicha alohida kalibrlash.
4. Izchillik. 5 000 mahsulot buyurtmasi ombor va moliya rejalari bilan mos kelishi kerak — bottom-up yig'indisini yuqori darajadagi reja bilan solishtiring.
Rahbarga javob: "Hozirgi 25% uch xil sizish bilan o'lchangan, shuning uchun unga tayanmaymiz. Bir hafta ichida belgilarni tuzatib, 20 ta haftalik oynada LightGBM ni ETS va 'o'tgan hafta' qoidasi bilan solishtiraman. Intervallarni konformal usul bilan kalibrlab, bayram haftalaridagi qamrovni ham ko'rsataman. Buyurtma qoidasini shu o'lchangan qamrovga va tanqislik narxiga qarab belgilaymiz."
Nimani mustahkamlaydi: 2.1, 2.2, 2.4, 2.6-bo'limlar.
Xulosa
Bu darsda vaqt qatori bashoratini ML vazifasiga to'g'ri aylantirish, global modellar, ko'p qadamli strategiyalar va kalibrlangan intervallarni o'rgandik.
Eng muhim uch fikr:
Belgilar faqat o'tmishdan va bashorat paytida mavjud bo'lsin. 1-misolda
center=Truerolling validatsiyada19.41va'da qildi, ishlab chiqarishda24.45berdi — to'g'ri modeldan (23.38) yomon;shiftsiz variant ham yolg'on yaxshilanish ko'rsatdi. Bayram belgilari bayram va arafa kunlaridagi xatoni64.66dan39.08ga tushirdi. 2-misolda global model lokal modellardan 12 do'konning 12 tasida yaxshi chiqdi (-0.0204,SE 0.0043), 120 kunlik tarixli yangi do'konda esa lokal model seasonal naive ga ham yutqazdi (65.37va55.88), global —43.27.Strategiya va model — backtest bilan. 3-misolda rekursiv xato dastlabki qadamlarda o'sdi (
11.75→13.76), to'g'ridan-to'g'ri eng tekis egri chiziq berdi, lekin 12 mustaqil oynada farqlar sezilarli emas edi. Rolling backtestda HistGB seasonal naive dan sezilarli yaxshi, ETS dan esa sezilarli farq qilmadi (+0.88,SE 1.06) — qoida eng sodda munosib model sifatida ETS ni tanladi.Interval — kalibrlanadi va o'lchanadi. 4-misolda o'quv qoldiqlaridan olingan 90% interval
0.766, kvantil HistGB0.795qamradi; split conformal (0.885) va CQR (0.873) nominalga yaqinlashdi. Lekin bayram kunlarida conformal ham0.778— o'rtacha qamrov segment qamrovini kafolatlamaydi. Ierarxiyada bottom-up ikkala darajada ham top-down dan yaxshi va izchil chiqdi.
Keyingi darsda Tavsiya tizimlari asoslari: foydalanuvchi-mahsulot o'zaro ta'sirlari matritsasi, mashhurlik bazaviysi, kontentga asoslangan va hamkorlikdagi filtrlash, vaqt bo'yicha bo'lish va ranking metrikalari (precision@k, recall@k, NDCG).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!