IlmHamroh
Data Science va sun'iy intellekt/Feature engineering9/10-dars20 daqiqa
Mundarija (22)

17.9-dars: Pipeline

17-QISM — FEATURE ENGINEERING · 9-dars


1. Kirish va motivatsiya

Oldingi sakkiz darsda bir xil maslahat qaytarildi: Pipeline ichida ishlang. Endi uni batafsil ko'ramiz — chunki Pipeline shunchaki qulaylik emas, balki leakage ga qarshi tuzilmaviy himoya.

Pipeline uchta muammoni bir vaqtda hal qiladi: leakage (har fit faqat o'quv qismida bajariladi), takrorlanuvchanlik (butun tayyorlash bitta obyektda) va ishlab chiqarish (model va tayyorlash birga versiyalanadi va saqlanadi).

Bundan tashqari, Pipeline giperparametr sozlashni kengaytiradi: GridSearchCV faqat model parametrlarini emas, tayyorlash parametrlarini ham (n_components, min_df, k) sozlay oladi.

Bu darsda: Pipeline asoslari, ColumnTransformer, maxsus transformer yozish, FunctionTransformer, Pipeline da giperparametr sozlash, nested CV va tayyor quvurni saqlash.

Real vaziyat. Jamoada uch odam bir xil ma'lumot bilan ishlardi va har biri o'z tayyorlash kodini yozgandi. Modellar solishtirilganda farq 0.06 AUC edi — lekin sabab model emas, tayyorlashdagi farq bo'lib chiqdi. Bitta umumiy Pipeline ga o'tilgach, taqqoslash ma'noli bo'ldi.

Bu darsda Pipeline ni o'rganamiz.

Bu darsda:

  • Pipeline asoslari
  • ColumnTransformer
  • Maxsus transformer
  • FunctionTransformer
  • Giperparametr sozlash
  • Nested CV va saqlash
  • Tuzoqlar
  • Amaliy: to'liq quvur

ℹ Misollar real numpy/pandas/sklearn bilan (Python 3.14).


2. Nazariya — chuqur tushuntirish

2.1. Pipeline asoslari

python
from sklearn.pipeline import Pipeline, make_pipeline

quvur = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(max_iter=2000)),
])
quvur.fit(Xtr, ytr)
quvur.predict(Xte)

make_pipeline(SimpleImputer(), StandardScaler(), LogisticRegression())
text
QANDAY ISHLAYDI:
  fit:      har bosqich fit_transform, oxirgisi fit
  predict:  har bosqich transform, oxirgisi predict

KIRISH:
  quvur.named_steps["scaler"]       - bosqichga murojaat
  quvur[:-1]                        - modelsiz qism
  quvur[-1]                         - faqat model
  quvur.get_params()                - barcha parametrlar

OXIRGI bosqich model bo'lishi SHART EMAS
  (faqat transformerlardan iborat quvur ham bo'ladi)

quvur[:-1] — modelsiz qism: u tayyorlangan ma'lumotni ko'rish, markazlarni teskari aylantirish va oraliq natijalarni tekshirish uchun juda qulay.

2.2. ColumnTransformer

python
from sklearn.compose import ColumnTransformer, make_column_selector

tayyor = ColumnTransformer([
    ("sonli", Pipeline([("imp", SimpleImputer(strategy="median")),
                        ("sc", StandardScaler())]), sonli_ustunlar),
    ("kategoriya", OneHotEncoder(handle_unknown="ignore"), kat_ustunlar),
    ("matn", TfidfVectorizer(min_df=5), "izoh"),        # ustun NOMI
], remainder="drop", verbose_feature_names_out=False)
text
remainder:
  "drop"          - qolgan ustunlar tashlanadi (standart)
  "passthrough"   - o'zgarishsiz qo'shiladi
  transformer     - qolganlarga qo'llaniladi

make_column_selector - ustunlarni AVTOMATIK tanlash:
  make_column_selector(dtype_include="number")
  make_column_selector(dtype_include=["object", "category"])

get_feature_names_out() - natija ustunlari nomlari

make_column_selector ustunlarni dtype bo'yicha avtomatik tanlaydi — bu yangi ustun qo'shilganda kodni o'zgartirmaslikka imkon beradi.

2.3. Maxsus transformer

python
from sklearn.base import BaseEstimator, TransformerMixin

class NisbatBelgilari(BaseEstimator, TransformerMixin):
    def __init__(self, juftliklar=None):
        self.juftliklar = juftliklar          # __init__ da FAQAT saqlash

    def fit(self, X, y=None):
        self.n_features_in_ = X.shape[1]      # o'rgangan narsalar _ bilan
        return self

    def transform(self, X):
        natija = [X]
        for a, b in self.juftliklar:
            natija.append((X[:, a] / np.where(X[:, b] == 0, np.nan,
                                              X[:, b])).reshape(-1, 1))
        return np.hstack(natija)
text
QOIDALAR (sklearn API):
  1. __init__ da faqat parametrlarni SAQLANG (tekshirmang, o'zgartirmang)
  2. O'rganilgan narsalar oxirida _ bilan (self.ortacha_)
  3. fit(X, y=None) -> self qaytaradi
  4. transform(X) -> massiv yoki DataFrame
  5. get_feature_names_out() - ixtiyoriy, lekin foydali

TEKSHIRUV: sklearn.utils.estimator_checks.check_estimator

__init__ da parametrlarni o'zgartirmang — bu get_params/set_params ni buzadi va GridSearchCV ishlamay qoladi. Barcha tekshiruv va hisoblash fit da bo'lishi kerak.

2.4. FunctionTransformer

python
from sklearn.preprocessing import FunctionTransformer

FunctionTransformer(np.log1p, inverse_func=np.expm1, validate=True)
FunctionTransformer(lambda X: X[:, :5])          # ustun tanlash
FunctionTransformer(func, kw_args={"parametr": 3})
text
QACHON:
  + oddiy, holatsiz (stateless) transformatsiyalar
  + log, sqrt, ustun tanlash, qayta shakllantirish
  - o'rganish kerak bo'lsa (o'rtacha, min/max) -> maxsus transformer

feature_names_out="one-to-one" - nomlarni saqlash
validate=True - kirishni massivga aylantiradi (DataFrame yo'qoladi)

FunctionTransformer holatsiz transformatsiyalar uchun: agar sizga o'quvdan biror narsa o'rganish kerak bo'lsa (o'rtacha, kvantil, lug'at), maxsus transformer yozing.

2.5. Giperparametr sozlash

python
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV

setka = {
    "tayyor__matn__min_df": [2, 5, 10],
    "tayyor__matn__ngram_range": [(1, 1), (1, 2)],
    "tanlov__k": [20, 50, 100],
    "model__C": [0.1, 1.0, 10.0],
    "model": [LogisticRegression(max_iter=2000), LinearSVC()],   # MODEL ham
}
q = GridSearchCV(quvur, setka, cv=5, scoring="roc_auc").fit(X, y)
text
NOM BERISH: bosqich__parametr, ichma-ich: bosqich__ichki__parametr
  "tayyor__matn__min_df" = tayyor (ColumnTransformer)
                           -> matn (TfidfVectorizer) -> min_df

MODELNI HAM SOZLASH mumkin: {"model": [A(), B()]}
BOSQICHNI O'CHIRISH: {"tanlov": ["passthrough"]}

memory="cache_papka" - takroriy tayyorlashni keshlaydi (tezlashtiradi)

Bosqichni "passthrough" bilan o'chirish — juda foydali naqsh: {"pca": ["passthrough", PCA(10), PCA(30)]} "PCA kerakmi?" degan savolni CV bilan hal qiladi.

2.6. Nested CV va saqlash

python
from sklearn.model_selection import cross_val_score, GridSearchCV

# ichki CV - giperparametr, tashqi CV - halol baho
ichki = GridSearchCV(quvur, setka, cv=5, scoring="roc_auc")
ballar = cross_val_score(ichki, X, y, cv=5, scoring="roc_auc")

# saqlash (15.13)
import joblib
joblib.dump({"quvur": q.best_estimator_, "belgilar": list(X.columns),
             "versiyalar": {...}}, "model.joblib", compress=3)
text
NESTED CV NEGA:
  oddiy GridSearchCV.best_score_ OPTIMISTIK
  (ko'p konfiguratsiya sinalgan -> eng yaxshisi tasodifan yaxshi)
  nested CV halol baho beradi, lekin qimmat (k_tashqi * k_ichki * n_iter)

AMALDA: alohida test to'plami ko'pincha yetarli va arzonroq

GridSearchCV.best_score_ optimistik: u 100 konfiguratsiya orasidan eng yaxshisi, shuning uchun u tasodifan yuqori bo'ladi. Halol baho uchun alohida test yoki nested CV.

2.7. Tuzoqlar

Asosiy tuzoqlar: tayyorlashni Pipeline tashqarisida qilish; maxsus transformer __init__ ida parametrni o'zgartirish; ColumnTransformer da matn ustunini ro'yxat sifatida berish; remainder ni e'tiborsiz qoldirish (ustunlar jim tashlanadi); GridSearchCV.best_score_ ni halol baho deb qabul qilish; FunctionTransformer ni o'rganish kerak bo'lgan joyda ishlatish; bosqich nomlarida __ ishlatish; memory ni katta setkada unutish.

2.8. Tuzilmaviy himoya

Pipeline leakage ni tuzilma darajasida oldini oladi: har fit faqat o'quv qismida bajariladi. ColumnTransformer turli ustun turlariga turli tayyorlashni qo'llaydi (matn ustuni ro'yxatsiz). Maxsus transformer da __init__ faqat saqlaydi, o'rganilgan narsalar _ bilan tugaydi. Pipeline giperparametr sozlashni kengaytiradi (bosqich__parametr, "passthrough") va butun tayyorlashni bitta obyektda saqlashga imkon beradi. Keyingi dars — amaliyot.


3. Tez ma'lumotnoma

python
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline, make_pipeline

tayyor = ColumnTransformer([
    ("s", Pipeline([("imp", SimpleImputer()), ("sc", StandardScaler())]),
     make_column_selector(dtype_include="number")),
    ("k", OneHotEncoder(handle_unknown="ignore"),
     make_column_selector(dtype_include=["object", "category"]))],
    remainder="drop", verbose_feature_names_out=False)

quvur = Pipeline([("t", tayyor), ("m", model)], memory="cache")
quvur[:-1].transform(X)                   # modelsiz qism
tayyor.get_feature_names_out()            # natija ustunlari

GridSearchCV(quvur, {"t__s__imp__strategy": ["mean", "median"],
                     "m__C": [0.1, 1.0]}, cv=5)
QOIDA: hamma tayyorlash ichida · matn ustuni ro'yxatsiz ·
       __init__ da faqat saqla · best_score_ optimistik

Pipeline xulosasi

Pipeline: fit faqat o'quvda -> leakage ga tuzilmaviy himoya
ColumnTransformer: ustun turlariga turli tayyorlash
Maxsus transformer: __init__ saqlaydi, o'rganilgan _ bilan
Sozlash: bosqich__parametr; "passthrough" bilan bosqichni o'chirish

4. Batafsil misollar

Misollar real numpy/pandas/sklearn bilan (Python 3.14).

Misol 1 — Pipeline va ColumnTransformer

python
"""Aralash turdagi ma'lumot uchun to'liq tayyorlash (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler


def yarat(seed: int = 7, n: int = 5000) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    hudud = rng.choice(["toshkent", "samarqand", "buxoro", "fargona"], n)
    kanal = rng.choice(["ilova", "sayt", "qongiroq"], n, p=[0.5, 0.35, 0.15])
    summa = rng.lognormal(12.0, 0.7, n)
    masofa = rng.gamma(3, 50, n)
    yosh = rng.integers(18, 70, n).astype(float)
    hq = pd.Series(hudud).map({"toshkent": 0.0, "samarqand": 0.4,
                               "buxoro": 0.85, "fargona": 0.3}).to_numpy()
    kuch = -1.8 + hq + 0.004 * masofa + 0.35 * (np.log(summa) - 12.0) \
        - 0.012 * (yosh - 40) + 0.4 * (kanal == "qongiroq")
    y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
    df = pd.DataFrame({"hudud": hudud, "kanal": kanal, "summa": summa,
                       "masofa": masofa, "yosh": yosh, "kechikdi": y})
    df.loc[rng.random(n) < 0.06, "yosh"] = np.nan
    df.loc[rng.random(n) < 0.03, "masofa"] = np.nan
    return df


def main() -> None:
    df = yarat()
    X = df.drop(columns="kechikdi")
    y = df["kechikdi"].to_numpy()
    cv = StratifiedKFold(5, shuffle=True, random_state=0)

    print("=== 1. Ma'lumot ===")
    print(f"  {len(df)} qator, {X.shape[1]} belgi")
    print(f"  {'ustun':<10} {'dtype':<10} {'yo_qolgan':>10}")
    for ustun in X.columns:
        print(f"  {ustun:<10} {str(X[ustun].dtype):<10} "
              f"{int(X[ustun].isna().sum()):>10}")

    print("\n=== 2. ColumnTransformer qo'lda ===")
    sonli = ["summa", "masofa", "yosh"]
    kategoriya = ["hudud", "kanal"]
    tayyor = ColumnTransformer([
        ("s", Pipeline([("imp", SimpleImputer(strategy="median")),
                        ("sc", StandardScaler())]), sonli),
        ("k", OneHotEncoder(handle_unknown="ignore", sparse_output=False),
         kategoriya)], verbose_feature_names_out=False)
    Z = tayyor.fit_transform(X, y)
    print(f"  natija o'lchami: {Z.shape}")
    print(f"  ustun nomlari: {list(tayyor.get_feature_names_out())}")

    print("\n=== 3. make_column_selector bilan avtomatik ===")
    avto = ColumnTransformer([
        ("s", Pipeline([("imp", SimpleImputer(strategy="median")),
                        ("sc", StandardScaler())]),
         make_column_selector(dtype_include="number")),
        ("k", OneHotEncoder(handle_unknown="ignore", sparse_output=False),
         make_column_selector(dtype_include=["object", "category"]))],
        verbose_feature_names_out=False)
    Z2 = avto.fit_transform(X, y)
    print(f"  natija o'lchami: {Z2.shape} (qo'lda bilan bir xil: "
          f"{Z.shape == Z2.shape})")
    # yangi ustun qo'shilganda avtomatik tanlov uni oladi
    X2 = X.assign(yangi_belgi=np.arange(len(X), dtype=float))
    print(f"  yangi ustun qo'shilgandan keyin: "
          f"{avto.fit_transform(X2, y).shape}")

    print("\n=== 4. To'liq quvur va CV ===")
    modellar = {
        "LogReg": LogisticRegression(max_iter=2000),
        "HistGB": HistGradientBoostingClassifier(learning_rate=0.1,
                                                 max_iter=200,
                                                 random_state=0),
    }
    print(f"  {'model':<10} {'CV ROC AUC':>12} {'std':>8}")
    for nom, m in modellar.items():
        quvur = Pipeline([("t", avto), ("m", m)])
        b = cross_val_score(quvur, X, y, cv=cv, scoring="roc_auc")
        print(f"  {nom:<10} {b.mean():>12.4f} {b.std():>8.4f}")
    quvur = Pipeline([("t", avto), ("m", modellar["LogReg"])]).fit(X, y)
    print(f"  bosqichlar: {list(quvur.named_steps)}")
    print(f"  modelsiz qism o'lchami: {quvur[:-1].transform(X[:3]).shape}")
    print("  ⭐ ColumnTransformer ustun turlarini ajratadi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot ===
  5000 qator, 5 belgi
  ustun      dtype       yo_qolgan
  hudud      str                 0
  kanal      str                 0
  summa      float64             0
  masofa     float64           158
  yosh       float64           298

=== 2. ColumnTransformer qo'lda ===
  natija o'lchami: (5000, 10)
  ustun nomlari: ['summa', 'masofa', 'yosh', 'hudud_buxoro', 'hudud_fargona', 'hudud_samarqand', 'hudud_toshkent', 'kanal_ilova', 'kanal_qongiroq', 'kanal_sayt']

=== 3. make_column_selector bilan avtomatik ===
  natija o'lchami: (5000, 10) (qo'lda bilan bir xil: True)
  yangi ustun qo'shilgandan keyin: (5000, 11)

=== 4. To'liq quvur va CV ===
  model        CV ROC AUC      std
  LogReg           0.6408   0.0141
  HistGB           0.5833   0.0130
  bosqichlar: ['t', 'm']
  modelsiz qism o'lchami: (3, 10)
  ⭐ ColumnTransformer ustun turlarini ajratadi

Nima ko'rsatdi: 2.1, 2.2-bo'limlar.

Misol 2 — Maxsus transformer

python
"""sklearn API ga mos transformer yozish (real numpy/pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler


class NisbatQoshuvchi(BaseEstimator, TransformerMixin):
    """Berilgan juftliklar uchun nisbat belgilarini qo'shadi."""

    def __init__(self, juftliklar=None):
        self.juftliklar = juftliklar          # FAQAT saqlash

    def fit(self, X, y=None):
        X = np.asarray(X, dtype=float)
        self.n_features_in_ = X.shape[1]
        self.juftliklar_ = list(self.juftliklar or [])
        return self

    def transform(self, X):
        X = np.asarray(X, dtype=float)
        ustunlar = [X]
        for a, b in self.juftliklar_:
            bolvuchi = np.where(np.abs(X[:, b]) < 1e-12, np.nan, X[:, b])
            ustunlar.append((X[:, a] / bolvuchi).reshape(-1, 1))
        return np.hstack(ustunlar)

    def get_feature_names_out(self, input_features=None):
        asl = (list(input_features) if input_features is not None
               else [f"x{i}" for i in range(self.n_features_in_)])
        return np.array(asl + [f"{asl[a]}_bo_{asl[b]}"
                               for a, b in self.juftliklar_])


class GuruhOrtachasi(BaseEstimator, TransformerMixin):
    """Guruh bo'yicha o'rtachani O'QUVDAN o'rganadi (leakage ga qarshi)."""

    def __init__(self, guruh_ustuni: int = 0, qiymat_ustuni: int = 1):
        self.guruh_ustuni = guruh_ustuni
        self.qiymat_ustuni = qiymat_ustuni

    def fit(self, X, y=None):
        X = np.asarray(X)
        guruhlar = X[:, self.guruh_ustuni]
        qiymatlar = X[:, self.qiymat_ustuni].astype(float)
        self.ortachalar_ = {}
        for g in np.unique(guruhlar):
            self.ortachalar_[g] = float(qiymatlar[guruhlar == g].mean())
        self.umumiy_ = float(qiymatlar.mean())
        return self

    def transform(self, X):
        X = np.asarray(X)
        guruhlar = X[:, self.guruh_ustuni]
        qiymatlar = X[:, self.qiymat_ustuni].astype(float)
        ort = np.array([self.ortachalar_.get(g, self.umumiy_) for g in guruhlar])
        return np.column_stack([qiymatlar, ort, qiymatlar / np.where(
            np.abs(ort) < 1e-12, np.nan, ort)])


def yarat(seed: int = 11, n: int = 5000):
    rng = np.random.default_rng(seed)
    daromad = rng.lognormal(14.0, 0.5, n)
    qarz = daromad * rng.lognormal(-0.5, 0.6, n)
    tolov = rng.integers(6, 60, n).astype(float)
    kechikish = rng.binomial(tolov.astype(int), rng.beta(1.5, 12, n)).astype(float)
    kuch = -1.0 + 2.5 * (qarz / daromad) + 4.0 * (kechikish / tolov)
    y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
    return np.column_stack([daromad, qarz, tolov, kechikish]), y


def main() -> None:
    X, y = yarat()
    nomlar = ["daromad", "qarz", "tolov", "kechikish"]
    cv = StratifiedKFold(5, shuffle=True, random_state=0)

    def model():
        return HistGradientBoostingClassifier(learning_rate=0.1, max_iter=200,
                                              random_state=0)

    print("=== 1. Bazaviy ===")
    b0 = cross_val_score(model(), X, y, cv=cv, scoring="roc_auc")
    print(f"  CV ROC AUC: {b0.mean():.4f} (+-{b0.std():.4f})")

    print("\n=== 2. Maxsus transformer bilan ===")
    nq = NisbatQoshuvchi(juftliklar=[(1, 0), (3, 2)])
    quvur = Pipeline([("n", nq), ("m", model())])
    b1 = cross_val_score(quvur, X, y, cv=cv, scoring="roc_auc")
    print(f"  CV ROC AUC: {b1.mean():.4f} (+-{b1.std():.4f})")
    print(f"  yaxshilanish: {b1.mean() - b0.mean():+.4f}")
    nq.fit(X)
    print(f"  belgilar: {list(nq.get_feature_names_out(nomlar))}")

    print("\n=== 3. sklearn API talablari ===")
    print(f"  get_params(): {sorted(nq.get_params())}")
    nq2 = NisbatQoshuvchi().set_params(juftliklar=[(1, 0)])
    print(f"  set_params ishladi: {nq2.juftliklar}")
    print(f"  o'rganilgan atributlar (_ bilan): "
          f"{[a for a in vars(nq) if a.endswith('_')]}")
    from sklearn.base import clone
    nusxa = clone(nq)
    print(f"  clone() ishladi: {nusxa.juftliklar == nq.juftliklar}")

    print("\n=== 4. GridSearchCV bilan sozlash ===")
    quvur = Pipeline([("n", NisbatQoshuvchi()), ("m", model())])
    setka = {"n__juftliklar": [[], [(1, 0)], [(3, 2)], [(1, 0), (3, 2)]],
             "m__max_iter": [100, 200]}
    q = GridSearchCV(quvur, setka, cv=cv, scoring="roc_auc", n_jobs=1).fit(X, y)
    print(f"  konfiguratsiyalar: {len(q.cv_results_['params'])}")
    print(f"  eng yaxshi juftliklar: {q.best_params_['n__juftliklar']}")
    print(f"  eng yaxshi CV: {q.best_score_:.4f}")
    print(f"  {'juftliklar':<22} {'CV AUC':>9}")
    for p, s in zip(q.cv_results_["params"], q.cv_results_["mean_test_score"]):
        if p["m__max_iter"] == 200:
            print(f"  {str(p['n__juftliklar']):<22} {s:>9.4f}")
    print("  ⭐ __init__ faqat saqlaydi, o'rganilgan atributlar _ bilan")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bazaviy ===
  CV ROC AUC: 0.6849 (+-0.0057)

=== 2. Maxsus transformer bilan ===
  CV ROC AUC: 0.6874 (+-0.0063)
  yaxshilanish: +0.0025
  belgilar: [np.str_('daromad'), np.str_('qarz'), np.str_('tolov'), np.str_('kechikish'), np.str_('qarz_bo_daromad'), np.str_('kechikish_bo_tolov')]

=== 3. sklearn API talablari ===
  get_params(): ['juftliklar']
  set_params ishladi: [(1, 0)]
  o'rganilgan atributlar (_ bilan): ['n_features_in_', 'juftliklar_']
  clone() ishladi: True

=== 4. GridSearchCV bilan sozlash ===
  konfiguratsiyalar: 8
  eng yaxshi juftliklar: [(1, 0)]
  eng yaxshi CV: 0.6983
  juftliklar                CV AUC
  []                        0.6849
  [(1, 0)]                  0.6867
  [(3, 2)]                  0.6833
  [(1, 0), (3, 2)]          0.6874
  ⭐ __init__ faqat saqlaydi, o'rganilgan atributlar _ bilan

Nima ko'rsatdi: 2.3, 2.4-bo'limlar.

Misol 3 — Giperparametr sozlash va passthrough

python
"""Tayyorlash parametrlarini ham sozlash (real numpy/sklearn)."""

import numpy as np
from sklearn.decomposition import PCA
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import (GridSearchCV, StratifiedKFold,
                                     cross_val_score, train_test_split)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import (PowerTransformer, QuantileTransformer,
                                   StandardScaler)


def yarat(seed: int = 5, n: int = 3000, p: int = 40):
    rng = np.random.default_rng(seed)
    X = rng.lognormal(0, 1, (n, p))            # qiyshiq taqsimot
    kuch = np.log(X[:, :6]) @ rng.normal(0, 1.0, 6)
    y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
    return X, y


def main() -> None:
    X, y = yarat()
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=0,
                                          stratify=y)
    cv = StratifiedKFold(4, shuffle=True, random_state=0)

    print("=== 1. Bazaviy quvur ===")
    quvur = Pipeline([
        ("trans", StandardScaler()),
        ("tanlov", "passthrough"),
        ("model", LogisticRegression(max_iter=3000)),
    ])
    b = cross_val_score(quvur, Xtr, ytr, cv=cv, scoring="roc_auc")
    print(f"  CV ROC AUC: {b.mean():.4f} (+-{b.std():.4f})")

    print("\n=== 2. Transformatsiyani sozlash ===")
    setka = {"trans": [StandardScaler(), PowerTransformer(),
                       QuantileTransformer(n_quantiles=200, random_state=0)]}
    q = GridSearchCV(quvur, setka, cv=cv, scoring="roc_auc", n_jobs=1).fit(Xtr, ytr)
    print(f"  {'transformatsiya':<24} {'CV AUC':>9}")
    for p, s in zip(q.cv_results_["params"], q.cv_results_["mean_test_score"]):
        print(f"  {type(p['trans']).__name__:<24} {s:>9.4f}")

    print("\n=== 3. Bosqichni 'passthrough' bilan o'chirish ===")
    setka2 = {"tanlov": ["passthrough", SelectKBest(f_classif, k=10),
                         SelectKBest(f_classif, k=20), PCA(n_components=10,
                                                           random_state=0)]}
    q2 = GridSearchCV(quvur, setka2, cv=cv, scoring="roc_auc",
                      n_jobs=1).fit(Xtr, ytr)
    print(f"  {'tanlov bosqichi':<28} {'CV AUC':>9}")
    for p, s in zip(q2.cv_results_["params"], q2.cv_results_["mean_test_score"]):
        nom = ("passthrough" if p["tanlov"] == "passthrough"
               else f"{type(p['tanlov']).__name__}"
                    f"({getattr(p['tanlov'], 'k', getattr(p['tanlov'], 'n_components', ''))})")
        print(f"  {nom:<28} {s:>9.4f}")

    print("\n=== 4. To'liq setka va halol baho ===")
    toliq = {
        "trans": [StandardScaler(), PowerTransformer()],
        "tanlov": ["passthrough", SelectKBest(f_classif, k=15)],
        "model": [LogisticRegression(max_iter=3000),
                  HistGradientBoostingClassifier(learning_rate=0.1,
                                                 max_iter=150,
                                                 random_state=0)],
    }
    q3 = GridSearchCV(quvur, toliq, cv=cv, scoring="roc_auc",
                      n_jobs=1).fit(Xtr, ytr)
    print(f"  konfiguratsiyalar: {len(q3.cv_results_['params'])}")
    eng = {k: (type(v).__name__ if not isinstance(v, str) else v)
           for k, v in q3.best_params_.items()}
    print(f"  eng yaxshi: {eng}")
    from sklearn.metrics import roc_auc_score
    test = roc_auc_score(yte, q3.predict_proba(Xte)[:, 1])
    print(f"  GridSearchCV.best_score_: {q3.best_score_:.4f} (OPTIMISTIK)")
    print(f"  alohida test to'plamida:  {test:.4f}")
    print(f"  optimizm: {q3.best_score_ - test:+.4f}")
    print("  ⭐ best_score_ - eng yaxshisi, shuning uchun optimistik")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bazaviy quvur ===
  CV ROC AUC: 0.8431 (+-0.0219)

=== 2. Transformatsiyani sozlash ===
  transformatsiya             CV AUC
  StandardScaler              0.8431
  PowerTransformer            0.8867
  QuantileTransformer         0.8818

=== 3. Bosqichni 'passthrough' bilan o'chirish ===
  tanlov bosqichi                 CV AUC
  passthrough                     0.8431
  SelectKBest(10)                 0.8488
  SelectKBest(20)                 0.8467
  PCA(10)                         0.6775

=== 4. To'liq setka va halol baho ===
  konfiguratsiyalar: 8
  eng yaxshi: {'model': 'LogisticRegression', 'tanlov': 'SelectKBest', 'trans': 'PowerTransformer'}
  GridSearchCV.best_score_: 0.8920 (OPTIMISTIK)
  alohida test to'plamida:  0.8760
  optimizm: +0.0161
  ⭐ best_score_ - eng yaxshisi, shuning uchun optimistik

Nima ko'rsatdi: 2.5, 2.6-bo'limlar.

Misol 4 — Nested CV va saqlash

python
"""Halol baho va tayyor quvurni saqlash (real numpy/sklearn/joblib)."""

import io
import pickle

import numpy as np
import sklearn
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import (GridSearchCV, StratifiedKFold,
                                     cross_val_score, train_test_split)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler


def yarat(seed: int = 3, n: int = 1500, p: int = 60, signal: int = 6):
    rng = np.random.default_rng(seed)
    X = rng.normal(0, 1, (n, p))
    kuch = X[:, :signal] @ rng.normal(0, 1.0, signal)
    y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
    return X, y


def hajm_kb(obyekt) -> float:
    b = io.BytesIO()
    pickle.dump(obyekt, b, protocol=pickle.HIGHEST_PROTOCOL)
    return b.tell() / 1024


def main() -> None:
    X, y = yarat()
    ichki_cv = StratifiedKFold(3, shuffle=True, random_state=0)
    tashqi_cv = StratifiedKFold(4, shuffle=True, random_state=1)

    quvur = Pipeline([("sc", StandardScaler()),
                      ("s", SelectKBest(f_classif, k=20)),
                      ("m", LogisticRegression(max_iter=3000))])
    setka = {"s__k": [10, 20, 40], "m__C": [0.1, 1.0, 10.0]}

    print("=== 1. Oddiy GridSearchCV ===")
    q = GridSearchCV(quvur, setka, cv=ichki_cv, scoring="roc_auc",
                     n_jobs=1).fit(X, y)
    print(f"  konfiguratsiyalar: {len(q.cv_results_['params'])}")
    print(f"  eng yaxshi: {q.best_params_}")
    print(f"  best_score_: {q.best_score_:.4f}")

    print("\n=== 2. Nested CV (halol baho) ===")
    ichki = GridSearchCV(quvur, setka, cv=ichki_cv, scoring="roc_auc",
                         n_jobs=1)
    nested = cross_val_score(ichki, X, y, cv=tashqi_cv, scoring="roc_auc")
    print(f"  nested CV: {nested.mean():.4f} (+-{nested.std():.4f})")
    print(f"  best_score_ dan farq: {q.best_score_ - nested.mean():+.4f}")
    print(f"  (best_score_ ko'p konfiguratsiya orasidan eng yaxshisi)")

    print("\n=== 3. Alohida test to'plami (arzonroq muqobil) ===")
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0,
                                          stratify=y)
    q2 = GridSearchCV(quvur, setka, cv=ichki_cv, scoring="roc_auc",
                      n_jobs=1).fit(Xtr, ytr)
    from sklearn.metrics import roc_auc_score
    test = roc_auc_score(yte, q2.predict_proba(Xte)[:, 1])
    print(f"  ichki best_score_: {q2.best_score_:.4f}")
    print(f"  alohida test:      {test:.4f}")
    print(f"  nested CV:         {nested.mean():.4f}")
    print(f"  (uchalasi ham bir xil savolga javob beradi)")

    print("\n=== 4. Tayyor quvurni saqlash ===")
    yakuniy = q.best_estimator_
    paket = {
        "quvur": yakuniy,
        "belgilar_soni": X.shape[1],
        "eng_yaxshi_parametrlar": q.best_params_,
        "nested_cv": round(float(nested.mean()), 4),
        "versiyalar": {"sklearn": sklearn.__version__,
                       "numpy": np.__version__},
        "sana": "2026-09-21",
    }
    print(f"  kalitlar: {sorted(paket)}")
    print(f"  paket hajmi: {hajm_kb(paket):.1f} KB")
    print(f"  bosqichlar: {list(yakuniy.named_steps)}")
    tanlangan = yakuniy.named_steps["s"].get_support().sum()
    print(f"  tanlangan belgilar: {tanlangan} / {X.shape[1]}")
    yangi = np.random.default_rng(99).normal(0, 1, (3, X.shape[1]))
    print(f"  yangi qatorlar uchun bashorat: "
          f"{np.round(yakuniy.predict_proba(yangi)[:, 1], 4).tolist()}")
    print("  ⭐ Butun tayyorlash va model bitta obyektda saqlanadi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Oddiy GridSearchCV ===
  konfiguratsiyalar: 9
  eng yaxshi: {'m__C': 10.0, 's__k': 10}
  best_score_: 0.9470

=== 2. Nested CV (halol baho) ===
  nested CV: 0.9448 (+-0.0129)
  best_score_ dan farq: +0.0022
  (best_score_ ko'p konfiguratsiya orasidan eng yaxshisi)

=== 3. Alohida test to'plami (arzonroq muqobil) ===
  ichki best_score_: 0.9516
  alohida test:      0.9290
  nested CV:         0.9448
  (uchalasi ham bir xil savolga javob beradi)

=== 4. Tayyor quvurni saqlash ===
  kalitlar: ['belgilar_soni', 'eng_yaxshi_parametrlar', 'nested_cv', 'quvur', 'sana', 'versiyalar']
  paket hajmi: 3.8 KB
  bosqichlar: ['sc', 's', 'm']
  tanlangan belgilar: 10 / 60
  yangi qatorlar uchun bashorat: [0.0136, 0.6868, 0.9445]
  ⭐ Butun tayyorlash va model bitta obyektda saqlanadi

Nima ko'rsatdi: 2.6-bo'lim.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Pipeline — faqat qulaylik" Leakage ga tuzilmaviy himoya
"init da tekshirish mumkin" Faqat saqlash
"ColumnTransformer da ['matn']" Ustun nomi ro'yxatsiz
"remainder ahamiyatsiz" Standartda ustunlar tashlanadi
"best_score_ — halol baho" Optimistik
"FunctionTransformer universal" Holatsiz transformatsiyalar uchun
"Bosqichni o'chirib bo'lmaydi" "passthrough"
"Nested CV shart" Alohida test ko'pincha yetarli

6. Keng tarqalgan xatolar va yechimlari

1. Tayyorlash Pipeline tashqarisida

python
Xs = scaler.fit_transform(X); GridSearchCV(model, setka).fit(Xs, y)  # ⚠️
GridSearchCV(Pipeline([("sc", scaler), ("m", model)]), setka).fit(X, y) # ✅

2. init da o'zgartirish

python
def __init__(self, k): self.k = int(k) if k else 10                  # ⚠️
def __init__(self, k=10): self.k = k    # tekshiruv fit da           # ✅

3. Matn ustunini ro'yxat sifatida

python
ColumnTransformer([("t", TfidfVectorizer(), ["izoh"])])              # ⚠️
ColumnTransformer([("t", TfidfVectorizer(), "izoh")])                # ✅

4. remainder ni unutish

python
ColumnTransformer([("s", sc, sonli)])    # kategoriyalar TASHLANDI   # ⚠️
ColumnTransformer([...], remainder="passthrough")                    # ✅

5. best_score_ ni halol baho deb olish

python
print(f"Model natijasi: {q.best_score_}")                            # ⚠️
print(f"Test natijasi: {roc_auc_score(yte, q.predict_proba(Xte)[:, 1])}") # ✅

6. O'rganish kerak bo'lgan joyda FunctionTransformer

python
FunctionTransformer(lambda X: X - X.mean(axis=0))   # test o'z o'rtachasi # ⚠️
StandardScaler(with_std=False)   # o'quv o'rtachasini eslab qoladi       # ✅

7. Katta setkada memory ni unutish

python
GridSearchCV(quvur, katta_setka)     # tayyorlash qayta-qayta          # ⚠️
Pipeline([...], memory="cache_papka")                                  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 17.8-dars (o'tilgan): Leakage
  • 12.9-dars (o'tilgan): Leakage asoslari
  • 15.13-dars (o'tilgan): Ishlab chiqarish
  • 19-qism: scikit-learn to'liq
  • 17.10-dars: Amaliyot

8. Eng yaxshi amaliyotlar

  1. Hamma tayyorlashni Pipeline ichida.

  2. ColumnTransformer bilan ustun turlarini ajrating.

  3. make_column_selector dan foydalaning.

  4. remainder ni aniq belgilang.

  5. Maxsus transformer da API qoidalariga rioya qiling.

  6. Tayyorlash parametrlarini ham sozlang.

  7. best_score_ ni halol baho deb olmang.

  8. Butun quvurni saqlang.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # Pipeline fit da nima qiladi?
2.  # predict da?
3.  # quvur[:-1] nima?
4.  # ColumnTransformer nima uchun?
5.  # matn ustuni qanday beriladi?
6.  # remainder standart qiymati?
7.  # make_column_selector nima qiladi?
8.  # __init__ da nima qilish mumkin?
9.  # o'rganilgan atributlar qanday nomlanadi?
10. # setkada nom berish?
11. # bosqichni qanday o'chirish mumkin?
12. # best_score_ halolmi?
Javoblar
  1. Har bosqich fit_transform, oxirgisi fit
  2. Har bosqich transform, oxirgisi predict
  3. Modelsiz qism
  4. Turli ustunlarga turli tayyorlash
  5. Ustun nomi, ro'yxatsiz
  6. "drop"
  7. dtype bo'yicha ustun tanlaydi
  8. Faqat parametrlarni saqlash
  9. Oxirida _ bilan
  10. bosqich__parametr
  11. "passthrough"
  12. Yo'q, optimistik

Vazifa 2: Xatolarni tuzating

python
1.  Xs = scaler.fit_transform(X); GridSearchCV(model, setka).fit(Xs, y)

2.  def __init__(self, k): self.k = int(k) if k else 10

3.  ColumnTransformer([("t", TfidfVectorizer(), ["izoh"])])

4.  ColumnTransformer([("s", sc, sonli)])   # kategoriyalar ham bor

5.  print(f"Model natijasi: {q.best_score_}")
Javoblar
python
1.  GridSearchCV(Pipeline([("sc", scaler), ("m", model)]), setka).fit(X, y)

2.  def __init__(self, k=10): self.k = k

3.  ColumnTransformer([("t", TfidfVectorizer(), "izoh")])

4.  ColumnTransformer([...], remainder="passthrough")

5.  print(f"Test natijasi: {roc_auc_score(yte, ...)}")

Vazifa 3: ColumnTransformer

Modellang:

  1. Ma'lumot
  2. Qo'lda
  3. Avtomatik
  4. To'liq quvur

Vazifa 4: Maxsus transformer

Modellang:

  1. Bazaviy
  2. Transformer
  3. API talablari
  4. GridSearchCV

Vazifa 5: Sozlash

Modellang:

  1. Bazaviy
  2. Transformatsiya
  3. passthrough
  4. To'liq setka

Vazifa 6: Nested CV

Modellang:

  1. GridSearchCV
  2. Nested CV
  3. Alohida test
  4. Saqlash

Vazifa 7: O'ylash

Pipeline kodni murakkablashtiradi: oddiy scaler.fit_transform(X) o'rniga uch qator yozish kerak. Bu narx nima uchun oqlanadi?

Javob

Qisqa javob: Pipeline kodni murakkablashtirmaydi, balki murakkablikni ko'rinadigan joyga ko'chiradi. scaler.fit_transform(X) sodda ko'rinadi, lekin u yashirin qaror qabul qiladi: "qaysi ma'lumotda fit qilamiz?" — va bu qaror ko'pincha noto'g'ri bo'ladi.

1. Haqiqiy narx taqqoslashi

Yondashuv Kod uzunligi Leakage xavfi Ishlab chiqarish
Qo'lda transformatsiyalar Qisqaroq Yuqori Qo'lda takrorlash kerak
Pipeline 2-3 qator uzunroq Nolga yaqin joblib.dump yetarli

Ikki qator uchun leakage xavfini yo'qotish — juda arzon savdo.

2. Yashirin narxlar (Pipeline siz)

  1. Takrorlash: tayyorlashni o'quv, test va ishlab chiqarish uchun uch marta yozish
  2. Nomuvofiqlik: uch joyda uch xil versiya paydo bo'ladi
  3. Sozlash cheklovi: tayyorlash parametrlarini GridSearchCV bilan sozlab bo'lmaydi
  4. Saqlash: model va tayyorlashni alohida saqlash va versiyalash
  5. Jamoaviy ish: har kim o'z tayyorlashini yozadi (dars kirishidagi holat)

3. Qo'shimcha imkoniyatlar

python
# tayyorlash parametrlarini sozlash
GridSearchCV(quvur, {"t__matn__min_df": [2, 5, 10]})

# bosqichni o'chirish savolini CV bilan hal qilish
{"pca": ["passthrough", PCA(10), PCA(30)]}

# keshlash bilan tezlashtirish
Pipeline([...], memory="cache")

# bitta obyekt sifatida saqlash
joblib.dump(quvur, "model.joblib")

Bularning hech biri qo'lda yondashuvda mumkin emas.

4. Qachon Pipeline shart emas

  • Tez tadqiqot, natija hech qayerga chiqmaydi
  • Transformatsiya butunlay holatsiz (log, ustun tanlash)
  • Bir martalik tahlil, model yo'q

Lekin bu hollarda ham odat sifatida ishlatish zarar qilmaydi.

5. Xulosa

  1. Narx — 2-3 qator kod
  2. Foyda — leakage himoyasi, sozlash, saqlash
  3. Yashirin narxlar Pipeline siz ancha katta
  4. Odat sifatida har doim ishlating

Nimani mustahkamlaydi: 2.1, 2.5-bo'limlar.


Xulosa

Bu darsda Pipeline ni o'rgandik.

Eng muhim uch fikr:

  1. Pipeline — leakage ga tuzilmaviy himoya. fit da har bosqich faqat o'quv qismida o'qitiladi, predict da esa transform qilinadi. Bu leakage ni eslab qolish masalasidan tuzilma masalasiga aylantiradi. ColumnTransformer turli ustun turlariga turli tayyorlashni qo'llaydi; matn ustuni ro'yxatsiz beriladi va remainder ni aniq belgilang (standartda qolgan ustunlar tashlanadi).

  2. Maxsus transformer — uchta qoida. __init__ da faqat parametrlarni saqlang (tekshirmang, o'zgartirmang — aks holda get_params/clone buziladi), o'rganilgan narsalarni _ bilan tugaydigan atributlarda saqlang, fit esa self qaytarsin. Holatsiz transformatsiyalar uchun esa FunctionTransformer yetarli.

  3. best_score_ optimistik. GridSearchCV 100 konfiguratsiya orasidan eng yaxshisini tanlaydi, shuning uchun uning bali tasodifan yuqori bo'ladi. Halol baho uchun alohida test to'plami (arzon) yoki nested CV (qimmat, lekin to'liqroq). Pipeline shuningdek tayyorlash parametrlarini sozlashga imkon beradi (bosqich__parametr) va bosqichni "passthrough" bilan o'chirib, "bu qadam kerakmi?" savolini CV bilan hal qiladi.

Keyingi darsda amaliyot: 17-qism bo'yicha to'liq loyiha.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.9-dars: Pipeline — IlmHamroh