IlmHamroh
Data Science va sun'iy intellekt/scikit learn toliq5/10-dars18 daqiqa
Mundarija (22)

19.5-dars: Kompozitsiya vositalari

19-QISM — SCIKIT-LEARN TO'LIQ · 5-dars


1. Kirish va motivatsiya

Pipeline qadamlarni ketma-ket ulaydi, ColumnTransformer — ustunlar bo'yicha parallel. Lekin haqiqiy loyihalarda boshqa naqshlar ham kerak bo'ladi:

  • bir xil ustunlardan bir necha xil belgi to'plamini yasash va ularni birlashtirish;
  • juda sodda, holatsiz o'zgartirishni (log, sqrt, bayroq) sinf yozmasdan quvurga qo'shish;
  • maqsad o'zgaruvchini o'zgartirish (log(y) bilan o'rgatib, bashoratni asl shkalaga qaytarish);
  • ustunlarni nom bilan tanlab olishni quvur ichida bajarish.

scikit-learn da bularning har biri uchun tayyor vosita bor: FeatureUnion, FunctionTransformer, TransformedTargetRegressor. Ular kichik, lekin ularni bilmaslik odamlarni quvurdan tashqarida kod yozishga majbur qiladi — va leakage qaytib keladi.

Bu darsda: FunctionTransformer (holatsiz o'zgartirish), FeatureUnion (parallel belgilar), TransformedTargetRegressor (maqsadni o'zgartirish), ularni birga ishlatish va qachon qaysi birini tanlash.

Real vaziyat. Uy narxini bashorat qilishda RMSE juda yuqori chiqardi: narxlar o'ng tomonga cho'zilgan, model qimmat uylarga moslashib, arzonlarida xato qilardi. TransformedTargetRegressor(func=np.log1p, inverse_func=np.expm1) qo'shilgach MAPE ikki barobar yaxshilandi — model kodining boshqa hech bir qatori o'zgarmadi.

Bu darsda kompozitsiya vositalarini o'rganamiz.

Bu darsda:

  • FunctionTransformer
  • TransformedTargetRegressor
  • FeatureUnion
  • Ustun tanlash quvur ichida
  • Qaysi vositani qachon
  • Kompozitsiyani sozlash
  • Tuzoqlar
  • Amaliy: uchtasini birga

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


2. Nazariya — chuqur tushuntirish

2.1. FunctionTransformer

python
from sklearn.preprocessing import FunctionTransformer

log_t = FunctionTransformer(
    func=np.log1p,
    inverse_func=np.expm1,            # inverse_transform uchun
    feature_names_out="one-to-one",   # nomlar o'zgarmaydi
    validate=False,                   # DataFrame ni o'tkazadi
    kw_args={"out": None},            # func ga qo'shimcha argument
)

QACHON:
  fit da HECH NARSA o'rganilmasa
  o'zgartirish sodda va parametrsiz bo'lsa

feature_names_out:
  "one-to-one"  -> kirish nomlari saqlanadi
  funksiya      -> lambda self, nomlar: [...]
  None (sukut)  -> get_feature_names_out ISHLAMAYDI

DIQQAT: func lambda bo'lsa model PICKLE QILINMAYDI
        modul darajasidagi funksiya yozing

func sifatida lambda yozmang — model joblib bilan saqlanmay qoladi (19.6-dars).

2.2. TransformedTargetRegressor

python
from sklearn.compose import TransformedTargetRegressor

ttr = TransformedTargetRegressor(
    regressor=Ridge(),
    func=np.log1p, inverse_func=np.expm1)
# yoki
ttr = TransformedTargetRegressor(regressor=Ridge(),
                                 transformer=QuantileTransformer(...))

ISHLASHI:
  fit:     y' = func(y) -> regressor.fit(X, y')
  predict: y' = regressor.predict(X) -> inverse_func(y')

NIMA UCHUN QUVUR ICHIDA:
  y ni QO'LDA log qilsangiz, CV metrikasi log shkalada bo'ladi
  va MAE/RMSE ASL birlikda emas -> taqqoslab bo'lmaydi
  TTR esa bashoratni AVTOMATIK qaytaradi

DIQQAT: func va inverse_func bir-birining teskarisi bo'lishi SHART
        (check_inverse=True sukut bo'yicha buni tekshiradi)

y ni qo'lda o'zgartirmang: TransformedTargetRegressor bashoratni asl shkalaga qaytaradi va metrikalar taqqoslanadigan bo'lib qoladi.

2.3. FeatureUnion

python
from sklearn.pipeline import FeatureUnion

fu = FeatureUnion([
    ("xom", "passthrough"),
    ("pca", PCA(n_components=5)),
    ("tanlov", SelectKBest(k=10)),
], transformer_weights={"pca": 0.5})

ISHLASHI:
  har transformer AYNI kirishni oladi
  natijalar GORIZONTAL birlashtiriladi

ColumnTransformer BILAN FARQI:
  ColumnTransformer - TURLI ustunlarga turli transformer
  FeatureUnion      - AYNI ustunlarga turli transformer

QACHON:
  bir xil ma'lumotdan bir necha ko'rinish kerak bo'lsa
  (xom + PCA, TF-IDF + matn statistikasi)

ColumnTransformer ustunlarni bo'ladi, FeatureUnion esa ko'paytiradi — ikkalasi bir-birini almashtirmaydi.

2.4. Ustun tanlash quvur ichida

python
# a) ColumnTransformer bilan (eng keng tarqalgan)
ColumnTransformer([("tanlangan", StandardScaler(), ["a", "b"])])

# b) FunctionTransformer bilan
def ustun_tanla(df):
    return df[["a", "b"]]
tanlagich = FunctionTransformer(ustun_tanla, validate=False)

# c) ColumnTransformer + passthrough (ustunni o'zgartirmasdan olish)
ColumnTransformer([("olingan", "passthrough", ["a", "b"])])

TAVSIYA: ColumnTransformer - u nomlarni ham to'g'ri hosil qiladi

Ustun tanlash uchun ColumnTransformer afzal: u get_feature_names_out ni ham to'g'ri qo'llab-quvvatlaydi.

2.5. Qaysi vositani qachon

text
Turli USTUNLARGA turli ishlov        -> ColumnTransformer
Bir xil ustunlardan KO'P ko'rinish   -> FeatureUnion
Ketma-ket bosqichlar                 -> Pipeline
Holatsiz sodda o'zgartirish          -> FunctionTransformer
MAQSAD o'zgaruvchini o'zgartirish    -> TransformedTargetRegressor
Holatli murakkab mantiq              -> o'z transformeringiz 19.4-bob

ODATIY TUZILMA:
  TransformedTargetRegressor(
      regressor=Pipeline([
          ("tayyor", ColumnTransformer([...])),
          ("model", ...)]),
      func=..., inverse_func=...)

TransformedTargetRegressor eng tashqarida bo'ladi: u butun quvurni o'rab oladi.

2.6. Kompozitsiyani sozlash

text
Parametr yo'llari ichma-ich nomlardan yig'iladi:

  ttr__regressor__tayyor__son__sc__with_mean
  |    |          |       |    |   |
  |    |          |       |    |   parametr
  |    |          |       |    qadam (Pipeline)
  |    |          |       transformer (ColumnTransformer)
  |    |          qadam (Pipeline)
  |    TTR ning regressor argumenti
  tashqi qadam nomi

TEKSHIRISH: sorted(model.get_params(deep=True)) ni chop eting
  va kerakli yo'lni ko'chiring - qo'lda yozmang

Parametr yo'lini qo'lda yozmang: get_params(deep=True) dan ko'chiring, aks holda xato param_grid jim ishlamaydi.

2.7. Tuzoqlar

Asosiy tuzoqlar: FunctionTransformer da lambda; feature_names_out ni qo'ymaslik; y ni qo'lda log qilish; func va inverse_func mos kelmasligi; FeatureUnion va ColumnTransformer ni chalkashtirish; FeatureUnion da ustunlar ikki marta chiqishi; parametr yo'lini qo'lda yozish; TransformedTargetRegressor ni klassifikatsiyada ishlatishga urinish.

2.8. To'rt g'isht

Kompozitsiyaning to'rt g'ishti bor: Pipeline (ketma-ket), ColumnTransformer (ustunlar bo'yicha), FeatureUnion (ayni kirishdan ko'p ko'rinish) va TransformedTargetRegressor (maqsadni o'zgartirish). FunctionTransformer esa ularning orasidagi yopishtiruvchi. Bu to'rttasi bilan deyarli har qanday tayyorlash mantiqini quvur ichida ifodalash mumkin — va shuning uchun leakage dan himoyalangan holda.


3. Tez ma'lumotnoma

python
import numpy as np
from sklearn.compose import ColumnTransformer, TransformedTargetRegressor
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler


def log_ozgartir(X):                    # lambda EMAS - pickle uchun
    return np.log1p(np.abs(X))


log_t = FunctionTransformer(log_ozgartir, feature_names_out="one-to-one")

fu = FeatureUnion([("xom", "passthrough"), ("log", log_t)])

model = TransformedTargetRegressor(
    regressor=Pipeline([("tayyor", ColumnTransformer([...])),
                        ("m", Ridge())]),
    func=np.log1p, inverse_func=np.expm1)

sorted(model.get_params(deep=True))     # parametr yo'llarini ko'chiring
QOIDA: lambda yozma · feature_names_out qo'y · y ni qo'lda o'zgartirma ·
       yo'llarni get_params dan ol

Kompozitsiya xulosasi

Pipeline: ketma-ket
ColumnTransformer: ustunlar bo'yicha
FeatureUnion: ayni kirishdan ko'p ko'rinish
FunctionTransformer: holatsiz o'zgartirish
TransformedTargetRegressor: maqsadni o'zgartirish (eng tashqarida)

4. Batafsil misollar

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

Misol 1 — FunctionTransformer

python
"""Holatsiz o'zgartirishlar quvur ichida (real numpy/pandas/sklearn)."""

import io
import pickle

import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler


def log_absolyut(X):
    """Modul darajasidagi funksiya - pickle qilinadi."""
    return np.log1p(np.abs(np.asarray(X, dtype=float)))


def nomlarni_yasa(transformer, input_features):
    return np.asarray([f"log_{n}" for n in input_features], dtype=object)


def main() -> None:
    rng = np.random.default_rng(0)
    df = pd.DataFrame({
        "summa": rng.lognormal(10, 1.2, 300),
        "masofa": rng.gamma(2, 100, 300),
    })

    print("=== 1. Oddiy ishlatish ===")
    t = FunctionTransformer(log_absolyut, inverse_func=np.expm1,
                            feature_names_out="one-to-one")
    chiqish = t.fit_transform(df)
    print(f"  kirish min/max: {df['summa'].min():.1f} / "
          f"{df['summa'].max():.1f}")
    print(f"  chiqish min/max: {chiqish[:, 0].min():.3f} / "
          f"{chiqish[:, 0].max():.3f}")
    print(f"  nomlar: {t.get_feature_names_out().tolist()}")

    print("\n=== 2. inverse_transform ===")
    qaytgan = t.inverse_transform(chiqish)
    print(f"  asl birinchi qiymat: {df['summa'].iloc[0]:.4f}")
    print(f"  qaytgan birinchi qiymat: {qaytgan[0, 0]:.4f}")
    print(f"  maksimal farq: "
          f"{np.abs(qaytgan - df.to_numpy()).max():.2e}")

    print("\n=== 3. feature_names_out variantlari ===")
    variantlar = {
        "one-to-one": FunctionTransformer(log_absolyut,
                                          feature_names_out="one-to-one"),
        "funksiya": FunctionTransformer(log_absolyut,
                                        feature_names_out=nomlarni_yasa),
    }
    for nom, tr in variantlar.items():
        tr.fit(df)
        print(f"  {nom:<12} -> {tr.get_feature_names_out().tolist()}")
    yoq = FunctionTransformer(log_absolyut).fit(df)
    try:
        yoq.get_feature_names_out()
        print("  None       -> ishladi (kutilmagan)")
    except Exception as xato:
        print(f"  None       -> {type(xato).__name__}: "
              f"{str(xato).splitlines()[0][:50]}")

    print("\n=== 4. lambda va pickle ===")
    modul_bilan = Pipeline([("log", FunctionTransformer(
        log_absolyut, feature_names_out="one-to-one")),
        ("sc", StandardScaler())]).fit(df)
    buf = io.BytesIO()
    pickle.dump(modul_bilan, buf)
    print(f"  modul funksiyasi bilan: saqlandi ({buf.tell()} bayt)")
    lambda_bilan = Pipeline([
        ("log", FunctionTransformer(lambda X: np.log1p(np.abs(X)))),
        ("sc", StandardScaler())]).fit(df)
    try:
        pickle.dump(lambda_bilan, io.BytesIO())
        print("  lambda bilan: saqlandi (kutilmagan)")
    except Exception as xato:
        print(f"  lambda bilan: {type(xato).__name__}: "
              f"{str(xato).splitlines()[0][:52]}")
    print("  ⭐ FunctionTransformer ga modul darajasidagi funksiya bering")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Oddiy ishlatish ===
  kirish min/max: 529.7 / 872610.1
  chiqish min/max: 6.274 / 13.679
  nomlar: ['summa', 'masofa']

=== 2. inverse_transform ===
  asl birinchi qiymat: 25613.5366
  qaytgan birinchi qiymat: 25613.5366
  maksimal farq: 6.98e-10

=== 3. feature_names_out variantlari ===
  one-to-one   -> ['summa', 'masofa']
  funksiya     -> ['log_summa', 'log_masofa']
  None       -> AttributeError: This 'FunctionTransformer' has no attribute 'get_f

=== 4. lambda va pickle ===
  modul funksiyasi bilan: saqlandi (944 bayt)
  lambda bilan: PicklingError: Can't pickle local object <function main.<locals>.<l
  ⭐ FunctionTransformer ga modul darajasidagi funksiya bering

Nima ko'rsatdi: 2.1-bo'lim.

Misol 2 — TransformedTargetRegressor

python
"""Cho'zilgan maqsadni o'zgartirish (real numpy/sklearn)."""

import numpy as np
from sklearn.compose import TransformedTargetRegressor
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.linear_model import Ridge
from sklearn.metrics import (mean_absolute_error,
                             mean_absolute_percentage_error,
                             r2_score)
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler


def yarat(n: int = 3000, seed: int = 0):
    """Narx: log-normal taqsimot, chiziqli bog'liqlik log shkalada."""
    rng = np.random.default_rng(seed)
    X = rng.normal(0, 1, (n, 6))
    log_narx = (11.0 + 0.6 * X[:, 0] + 0.4 * X[:, 1] - 0.3 * X[:, 2]
                + rng.normal(0, 0.35, n))
    y = np.exp(log_narx)
    return X, y


def main() -> None:
    X, y = yarat()
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3,
                                          random_state=0)

    print("=== 1. Maqsad taqsimoti ===")
    print(f"  min: {y.min():,.0f}, median: {np.median(y):,.0f}, "
          f"max: {y.max():,.0f}")
    print(f"  o'rtacha / median: {y.mean() / np.median(y):.2f}")
    print(f"  log(y) assimetriyasi ancha kichik: "
          f"{float(np.abs(np.log(y).mean() - np.median(np.log(y)))):.4f}")

    print("\n=== 2. Uch variant ===")
    asos = make_pipeline(StandardScaler(), Ridge(alpha=1.0))
    ttr = TransformedTargetRegressor(
        regressor=make_pipeline(StandardScaler(), Ridge(alpha=1.0)),
        func=np.log, inverse_func=np.exp)
    boost = HistGradientBoostingRegressor(max_iter=200,
                                          early_stopping=False,
                                          random_state=0)
    ttr_boost = TransformedTargetRegressor(
        regressor=HistGradientBoostingRegressor(max_iter=200,
                                                early_stopping=False,
                                                random_state=0),
        func=np.log, inverse_func=np.exp)

    modellar = {"Ridge (xom y)": asos, "Ridge + log(y)": ttr,
                "Boosting (xom y)": boost, "Boosting + log(y)": ttr_boost}
    print(f"  {'model':<22} {'MAE':>12} {'MAPE':>9} {'R2':>8}")
    for nom, m in modellar.items():
        m.fit(Xtr, ytr)
        p = m.predict(Xte)
        print(f"  {nom:<22} {mean_absolute_error(yte, p):>12,.0f} "
              f"{mean_absolute_percentage_error(yte, p):>9.4f} "
              f"{r2_score(yte, p):>8.4f}")

    print("\n=== 3. Bashoratlar asl shkalada ===")
    m = modellar["Ridge + log(y)"]
    p = m.predict(Xte[:5])
    print(f"  haqiqiy: {np.round(yte[:5], 0).tolist()}")
    print(f"  bashorat: {np.round(p, 0).tolist()}")
    print("  TTR bashoratni AVTOMATIK asl birlikka qaytardi")

    print("\n=== 4. Qo'lda log qilish nima uchun yomon ===")
    cv = KFold(5, shuffle=True, random_state=0)
    qolda = make_pipeline(StandardScaler(), Ridge(alpha=1.0))
    log_ball = -cross_val_score(qolda, X, np.log(y), cv=cv,
                                scoring="neg_mean_absolute_error").mean()
    ttr_ball = -cross_val_score(
        TransformedTargetRegressor(
            regressor=make_pipeline(StandardScaler(), Ridge(alpha=1.0)),
            func=np.log, inverse_func=np.exp),
        X, y, cv=cv, scoring="neg_mean_absolute_error").mean()
    print(f"  qo'lda log(y) bilan CV MAE: {log_ball:.4f}  (LOG birlikda)")
    print(f"  TTR bilan CV MAE: {ttr_ball:,.0f}  (ASL birlikda)")
    print("  birinchisini boshqa modellar bilan taqqoslab bo'lmaydi")
    print("  ⭐ y ni qo'lda o'zgartirsangiz metrikalar taqqoslanmaydi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Maqsad taqsimoti ===
  min: 2,643, median: 62,289, max: 1,053,234
  o'rtacha / median: 1.38
  log(y) assimetriyasi ancha kichik: 0.0161

=== 2. Uch variant ===
  model                           MAE      MAPE       R2
  Ridge (xom y)                32,645    0.6577   0.6008
  Ridge + log(y)               22,838    0.2798   0.7727
  Boosting (xom y)             26,421    0.3368   0.7190
  Boosting + log(y)            25,320    0.3101   0.7165

=== 3. Bashoratlar asl shkalada ===
  haqiqiy: [302588.0, 39955.0, 132663.0, 32777.0, 278549.0]
  bashorat: [125609.0, 56462.0, 104079.0, 22226.0, 245479.0]
  TTR bashoratni AVTOMATIK asl birlikka qaytardi

=== 4. Qo'lda log qilish nima uchun yomon ===
  qo'lda log(y) bilan CV MAE: 0.2750  (LOG birlikda)
  TTR bilan CV MAE: 23,084  (ASL birlikda)
  birinchisini boshqa modellar bilan taqqoslab bo'lmaydi
  ⭐ y ni qo'lda o'zgartirsangiz metrikalar taqqoslanmaydi

Nima ko'rsatdi: 2.2-bo'lim.

Misol 3 — FeatureUnion

python
"""Ayni kirishdan bir necha ko'rinish (real numpy/sklearn)."""

import numpy as np
from sklearn.datasets import make_classification
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import StandardScaler


def main() -> None:
    X, y = make_classification(n_samples=1500, n_features=30,
                               n_informative=8, n_redundant=12,
                               flip_y=0.15, class_sep=0.85, random_state=0)
    cv = StratifiedKFold(4, shuffle=True, random_state=0)

    print("=== 1. FeatureUnion tuzilishi ===")
    birlashma = FeatureUnion([
        ("xom", "passthrough"),
        ("pca", PCA(n_components=6, random_state=0)),
        ("tanlov", SelectKBest(f_classif, k=8)),
    ])
    quvur = Pipeline([("sc", StandardScaler()),
                      ("fu", birlashma),
                      ("m", LogisticRegression(max_iter=3000))])
    quvur.fit(X, y)
    print(f"  kirish ustunlari: {X.shape[1]}")
    print(f"  FeatureUnion chiqishi: "
          f"{quvur[:-1].transform(X).shape[1]}")
    print(f"  hisob: 30 (xom) + 6 (pca) + 8 (tanlov) = 44")

    print("\n=== 2. Har tarmoqning hissasi ===")
    tarmoqlar = {
        "faqat xom": FeatureUnion([("xom", "passthrough")]),
        "faqat pca": FeatureUnion([("pca", PCA(n_components=6,
                                               random_state=0))]),
        "faqat tanlov": FeatureUnion([("tanlov",
                                       SelectKBest(f_classif, k=8))]),
        "xom + pca": FeatureUnion([("xom", "passthrough"),
                                   ("pca", PCA(n_components=6,
                                               random_state=0))]),
        "uchalasi": birlashma,
    }
    print(f"  {'tarmoqlar':<16} {'ustunlar':>9} {'CV AUC':>9}")
    for nom, fu in tarmoqlar.items():
        q = Pipeline([("sc", StandardScaler()), ("fu", fu),
                      ("m", LogisticRegression(max_iter=3000))])
        ustunlar = q[:-1].fit(X, y).transform(X).shape[1]
        b = cross_val_score(q, X, y, cv=cv, scoring="roc_auc")
        print(f"  {nom:<16} {ustunlar:>9} {b.mean():>9.4f}")

    print("\n=== 3. Chiqish nomlari ===")
    nomlar = quvur[:-1].get_feature_names_out()
    print(f"  jami {len(nomlar)} ta")
    print(f"  birinchi 3: {nomlar[:3].tolist()}")
    print(f"  pca qismi: "
          f"{[n for n in nomlar if n.startswith('pca')][:3]}")
    print(f"  tanlov qismi: "
          f"{[n for n in nomlar if n.startswith('tanlov')][:3]}")

    print("\n=== 4. ColumnTransformer bilan farqi ===")
    from sklearn.compose import ColumnTransformer
    ct = ColumnTransformer([
        ("birinchi_yarim", PCA(n_components=4, random_state=0),
         list(range(15))),
        ("ikkinchi_yarim", StandardScaler(), list(range(15, 30)))])
    print(f"  ColumnTransformer: ustunlarni BO'LADI")
    print(f"    kirish 30 -> chiqish {ct.fit_transform(X).shape[1]} "
          f"(4 + 15)")
    print(f"  FeatureUnion: ustunlarni KO'PAYTIRADI")
    print(f"    kirish 30 -> chiqish "
          f"{birlashma.fit(StandardScaler().fit_transform(X), y).transform(StandardScaler().fit_transform(X)).shape[1]}")
    print("  ⭐ ColumnTransformer bo'ladi, FeatureUnion ko'paytiradi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. FeatureUnion tuzilishi ===
  kirish ustunlari: 30
  FeatureUnion chiqishi: 44
  hisob: 30 (xom) + 6 (pca) + 8 (tanlov) = 44

=== 2. Har tarmoqning hissasi ===
  tarmoqlar         ustunlar    CV AUC
  faqat xom               30    0.8726
  faqat pca                6    0.8293
  faqat tanlov             8    0.8738
  xom + pca               36    0.8726
  uchalasi                44    0.8726

=== 3. Chiqish nomlari ===
  jami 44 ta
  birinchi 3: ['xom__x0', 'xom__x1', 'xom__x2']
  pca qismi: ['pca__pca0', 'pca__pca1', 'pca__pca2']
  tanlov qismi: ['tanlov__x1', 'tanlov__x2', 'tanlov__x6']

=== 4. ColumnTransformer bilan farqi ===
  ColumnTransformer: ustunlarni BO'LADI
    kirish 30 -> chiqish 19 (4 + 15)
  FeatureUnion: ustunlarni KO'PAYTIRADI
    kirish 30 -> chiqish 44
  ⭐ ColumnTransformer bo'ladi, FeatureUnion ko'paytiradi

Nima ko'rsatdi: 2.3-bo'lim.

Misol 4 — Uchtasini birga va sozlash

python
"""To'liq kompozitsiya va parametr yo'llari (real numpy/pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.compose import (ColumnTransformer, TransformedTargetRegressor,
                             make_column_selector)
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.impute import SimpleImputer
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import (GridSearchCV, KFold, train_test_split)
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import (FunctionTransformer, OneHotEncoder,
                                   StandardScaler)


def log_absolyut(X):
    return np.log1p(np.abs(np.asarray(X, dtype=float)))


def yarat(n: int = 2500, seed: int = 0):
    rng = np.random.default_rng(seed)
    df = pd.DataFrame({
        "maydon": rng.gamma(4, 25, n),
        "yosh": rng.integers(0, 60, n).astype(float),
        "xonalar": rng.integers(1, 7, n).astype(float),
        "tuman": rng.choice(["markaz", "shimol", "janub", "chekka"], n),
    })
    tuman_qiymat = {"markaz": 0.55, "shimol": 0.2, "janub": 0.1,
                    "chekka": -0.3}
    log_narx = (10.2 + 0.012 * df["maydon"] - 0.008 * df["yosh"]
                + 0.08 * df["xonalar"]
                + df["tuman"].map(tuman_qiymat)
                + rng.normal(0, 0.3, n))
    return df, np.exp(log_narx)


def main() -> None:
    df, y = yarat()
    Xtr, Xte, ytr, yte = train_test_split(df, y, test_size=0.3,
                                          random_state=0)

    print("=== 1. Tuzilma ===")
    son_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="median")),
        ("ko_rinishlar", FeatureUnion([
            ("xom", "passthrough"),
            ("log", FunctionTransformer(log_absolyut,
                                        feature_names_out="one-to-one")),
        ])),
        ("sc", StandardScaler()),
    ])
    tayyor = ColumnTransformer([
        ("son", son_quvur, make_column_selector(dtype_include=np.number)),
        ("kat", OneHotEncoder(handle_unknown="ignore", sparse_output=False),
         make_column_selector(dtype_include="str")),
    ])
    model = TransformedTargetRegressor(
        regressor=Pipeline([
            ("tayyor", tayyor),
            ("m", HistGradientBoostingRegressor(max_iter=200,
                                                early_stopping=False,
                                                random_state=0)),
        ]),
        func=np.log, inverse_func=np.exp)
    model.fit(Xtr, ytr)
    print("  TransformedTargetRegressor")
    print("   +- Pipeline")
    print("      +- ColumnTransformer")
    print("      |  +- son: Pipeline(imp -> FeatureUnion(xom, log) -> sc)")
    print("      |  +- kat: OneHotEncoder")
    print("      +- HistGradientBoostingRegressor")

    print("\n=== 2. Belgilar ===")
    ichki = model.regressor_
    nomlar = ichki[:-1].get_feature_names_out()
    print(f"  jami belgilar: {len(nomlar)}")
    for i in range(0, min(len(nomlar), 12), 4):
        print(f"    {nomlar[i:i + 4].tolist()}")

    print("\n=== 3. Parametr yo'llari ===")
    yollar = [k for k in sorted(model.get_params(deep=True))
              if k.count("__") >= 3][:6]
    for yol in yollar:
        print(f"  {yol}")
    print(f"  jami parametr: {len(model.get_params(deep=True))}")

    print("\n=== 4. Sozlash ===")
    setka = {
        "regressor__m__max_leaf_nodes": [8, 31],
        "regressor__tayyor__son__ko_rinishlar__log": [
            "drop", FunctionTransformer(log_absolyut,
                                        feature_names_out="one-to-one")],
    }
    g = GridSearchCV(model, setka, cv=KFold(4, shuffle=True, random_state=0),
                     scoring="neg_mean_absolute_error", n_jobs=1)
    g.fit(Xtr, ytr)
    print(f"  {'barglar':>9} {'log tarmog_i':>14} {'CV MAE':>12}")
    for par, ball in zip(g.cv_results_["params"],
                         g.cv_results_["mean_test_score"]):
        log_bor = par["regressor__tayyor__son__ko_rinishlar__log"] != "drop"
        print(f"  {par['regressor__m__max_leaf_nodes']:>9} "
              f"{str(log_bor):>14} {-ball:>12,.0f}")
    print(f"  eng yaxshi CV MAE: {-g.best_score_:,.0f}")

    print("\n=== 5. Test ===")
    p = g.best_estimator_.predict(Xte)
    print(f"  test MAE: {mean_absolute_error(yte, p):,.0f}")
    print(f"  haqiqiy: {np.round(yte[:4], 0).tolist()}")
    print(f"  bashorat: {np.round(p[:4], 0).tolist()}")
    print("  ⭐ To'rt g'isht bilan butun mantiq quvur ichida")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Tuzilma ===
  TransformedTargetRegressor
   +- Pipeline
      +- ColumnTransformer
      |  +- son: Pipeline(imp -> FeatureUnion(xom, log) -> sc)
      |  +- kat: OneHotEncoder
      +- HistGradientBoostingRegressor

=== 2. Belgilar ===
  jami belgilar: 10
    ['son__xom__maydon', 'son__xom__yosh', 'son__xom__xonalar', 'son__log__maydon']
    ['son__log__yosh', 'son__log__xonalar', 'kat__tuman_chekka', 'kat__tuman_janub']
    ['kat__tuman_markaz', 'kat__tuman_shimol']

=== 3. Parametr yo'llari ===
  regressor__tayyor__kat__categories
  regressor__tayyor__kat__drop
  regressor__tayyor__kat__dtype
  regressor__tayyor__kat__feature_name_combiner
  regressor__tayyor__kat__handle_unknown
  regressor__tayyor__kat__max_categories
  jami parametr: 80

=== 4. Sozlash ===
    barglar   log tarmog_i       CV MAE
          8          False       36,909
          8           True       36,909
         31          False       38,910
         31           True       38,910
  eng yaxshi CV MAE: 36,909

=== 5. Test ===
  test MAE: 40,209
  haqiqiy: [50874.0, 336447.0, 28654.0, 78837.0]
  bashorat: [35906.0, 375558.0, 77185.0, 113379.0]
  ⭐ To'rt g'isht bilan butun mantiq quvur ichida

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


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

Noto'g'ri fikr To'g'risi
"FeatureUnion va ColumnTransformer bir xil" Biri ko'paytiradi, biri bo'ladi
"lambda qulay" Model pickle qilinmaydi
"y ni qo'lda log qilsa bo'ladi" Metrikalar taqqoslanmaydi
"feature_names_out ixtiyoriy" Usiz nomlar ishlamaydi
"TransformedTargetRegressor klassifikatsiyada ham" Faqat regressiya
"Parametr yo'lini yozish oson" get_params dan ko'chiring
"FunctionTransformer ga fit kerak emas" fit chaqiriladi (bo'sh)
"inverse_func ixtiyoriy" inverse_transform uchun shart

6. Keng tarqalgan xatolar va yechimlari

1. lambda ishlatish

python
FunctionTransformer(lambda X: np.log1p(X))       # pickle xato      # ⚠️
def log_t(X): return np.log1p(X)
FunctionTransformer(log_t)                                          # ✅

2. y ni qo'lda o'zgartirish

python
model.fit(X, np.log(y)); p = np.exp(model.predict(X))               # ⚠️
TransformedTargetRegressor(regressor=model, func=np.log,
                           inverse_func=np.exp)                     # ✅

3. feature_names_out yo'q

python
FunctionTransformer(log_t)      # get_feature_names_out xato        # ⚠️
FunctionTransformer(log_t, feature_names_out="one-to-one")          # ✅

4. Mos kelmaydigan teskari funksiya

python
func=np.log, inverse_func=np.log        # teskari emas              # ⚠️
func=np.log, inverse_func=np.exp                                    # ✅

5. Noto'g'ri vosita

python
FeatureUnion([("son", StandardScaler()), ("kat", OneHotEncoder())])  # ⚠️
ColumnTransformer([("son", StandardScaler(), sonli),
                   ("kat", OneHotEncoder(), kategoriya)])            # ✅

6. Parametr yo'lini taxmin qilish

python
setka = {"m__max_depth": [3, 5]}    # TTR ichida ishlamaydi         # ⚠️
setka = {"regressor__m__max_depth": [3, 5]}                         # ✅

7. Klassifikatsiyada TTR

python
TransformedTargetRegressor(regressor=LogisticRegression())          # ⚠️
# klassifikatsiyada sinf og'irliklari yoki chegara ishlatiladi      # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 19.2-dars (o'tilgan): Pipeline
  • 19.3-dars (o'tilgan): ColumnTransformer
  • 19.4-dars (o'tilgan): O'z transformeringiz
  • 19.6-dars: Model saqlash (lambda muammosi)
  • 19.10-dars: To'liq loyiha

8. Eng yaxshi amaliyotlar

  1. Modul darajasidagi funksiya.

  2. feature_names_out ni qo'ying.

  3. y ni TransformedTargetRegressor bilan.

  4. inverse_func ni tekshiring.

  5. To'g'ri vositani tanlang.

  6. Parametr yo'llarini get_params dan oling.

  7. TTR eng tashqarida.

  8. Tuzilmani hujjatlashtiring.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # FunctionTransformer qachon?
2.  # nima uchun lambda yaramaydi?
3.  # feature_names_out variantlari?
4.  # TTR nima qiladi?
5.  # nima uchun y ni qo'lda o'zgartirmaslik kerak?
6.  # FeatureUnion nima qiladi?
7.  # ColumnTransformer bilan farqi?
8.  # TTR quvurning qayerida?
9.  # parametr yo'li qanday tuziladi?
10. # yo'lni qayerdan olish kerak?
11. # inverse_func nima uchun?
12. # TTR klassifikatsiyada ishlaydimi?
Javoblar
  1. Holatsiz sodda o'zgartirish
  2. Pickle qilinmaydi
  3. "one-to-one", funksiya, None
  4. y ni o'zgartiradi va bashoratni qaytaradi
  5. Metrikalar log shkalada qoladi
  6. Ayni kirishdan ko'p ko'rinish
  7. ColumnTransformer bo'ladi, FeatureUnion ko'paytiradi
  8. Eng tashqarida
  9. Ichma-ich nomlar __ bilan
  10. get_params(deep=True)
  11. inverse_transform va TTR uchun
  12. Yo'q, faqat regressiya

Vazifa 2: Xatolarni tuzating

python
1.  FunctionTransformer(lambda X: np.log1p(X))

2.  model.fit(X, np.log(y)); p = np.exp(model.predict(X))

3.  FunctionTransformer(log_t)   # get_feature_names_out kerak

4.  func=np.log, inverse_func=np.log

5.  setka = {"m__max_depth": [3, 5]}   # TTR ichida
Javoblar
python
1.  def log_t(X): return np.log1p(X)
    FunctionTransformer(log_t)

2.  TransformedTargetRegressor(regressor=model, func=np.log,
                               inverse_func=np.exp)

3.  FunctionTransformer(log_t, feature_names_out="one-to-one")

4.  func=np.log, inverse_func=np.exp

5.  setka = {"regressor__m__max_depth": [3, 5]}

Vazifa 3: FunctionTransformer

Modellang:

  1. Oddiy ishlatish
  2. inverse_transform
  3. Nomlar
  4. lambda va pickle

Vazifa 4: TTR

Modellang:

  1. Taqsimot
  2. Uch variant
  3. Bashoratlar
  4. Qo'lda log

Vazifa 5: FeatureUnion

Modellang:

  1. Tuzilish
  2. Hissalar
  3. Nomlar
  4. Farq

Vazifa 6: Birga

Modellang:

  1. Tuzilma
  2. Belgilar
  3. Yo'llar
  4. Sozlash

Vazifa 7: O'ylash

TransformedTargetRegressor(func=np.log, inverse_func=np.exp) ishlatdingiz va CV MAE juda yaxshi chiqdi. Lekin ishlab chiqarishda ba'zi bashoratlar juda katta (millionlab) chiqmoqda. Nima bo'lishi mumkin?

Javob

Qisqa javob: exp eksponensial kuchaytirgich: log shkaladagi kichik xato asl shkalada ulkan xatoga aylanadi. Bu, ayniqsa, model ko'rmagan hududda yuz beradi.

1. Matematika

log shkalada xato:  +0.5
asl shkalada:       exp(+0.5) = 1.65 barobar
log shkalada xato:  +3.0
asl shkalada:       exp(+3.0) = 20 barobar

Ya'ni log shkaladagi kichik xato asl shkalada ko'paytiruvchi bo'lib ta'sir qiladi.

2. Qayerda yuz beradi

Holat Natija
Chegaradan tashqaridagi kirish Model ekstrapolyatsiya qiladi → katta log(y)
Chiziqli model + katta belgi qiymati Koeffitsiyent × katta qiymat
Yo'qolgan qiymat noto'g'ri to'ldirilgan Masalan 0 o'rniga median

3. Diagnostika

python
ichki = model.regressor_                     # ichki quvur
log_bashorat = ichki.predict(X_yangi)        # LOG shkalada
print(np.percentile(log_bashorat, [0, 1, 50, 99, 100]))
# o'quvdagi log(y) diapazoni bilan solishtiring
print(np.percentile(np.log(y_oquv), [0, 100]))

Agar log_bashorat o'quv diapazonidan chiqib ketgan bo'lsa — ekstrapolyatsiya.

4. Yechimlar

a) Bashoratni log shkalada cheklash

python
class CheklanganTTR(TransformedTargetRegressor):
    def predict(self, X, **kw):
        log_p = self.regressor_.predict(X)
        log_p = np.clip(log_p, self.past_, self.yuqori_)
        return self.inverse_func(log_p)

Chegaralarni o'quv log(y) ning [0.1%, 99.9%] kvantillaridan oling.

b) Kirishlarni cheklash — vinzorlash transformeri (19.4-dars) quvur boshida.

c) Daraxtli model ishlatish — HistGradientBoostingRegressor ekstrapolyatsiya qilmaydi: u o'quvda ko'rilgan barglar qiymatidan chiqmaydi.

d) log o'rniga boshqa o'zgartirish — QuantileTransformer(output_distribution="normal") chegaralangan va xavfsizroq.

5. Monitoring

python
katta = (bashorat > oquv_max * 3).mean()
if katta > 0.001:
    ogohlantir(f"bashoratlarning {katta:.2%} i o'quv maksimumidan 3x katta")

6. Xulosa

  1. exp xatoni ko'paytiruvchi qiladi
  2. Log shkaladagi bashoratni tekshiring
  3. Cheklash yoki daraxtli model
  4. Chegaradan chiqishni kuzating

Nimani mustahkamlaydi: 2.2-bo'lim.


Xulosa

Bu darsda kompozitsiya vositalarini o'rgandik.

Eng muhim uch fikr:

  1. FunctionTransformer — holatsiz o'zgartirishlar uchun yopishtiruvchi. Unga modul darajasidagi funksiya bering (lambda model pickle qilinishini buzadi) va feature_names_out="one-to-one" ni qo'shing, aks holda ustun nomlari ishlamaydi. fit da biror narsa o'rganilishi kerak bo'lsa — bu endi FunctionTransformer emas, o'z transformeringiz (19.4-dars).

  2. Maqsadni TransformedTargetRegressor bilan o'zgartiring. y ni qo'lda log qilsangiz CV metrikasi log shkalada qoladi va boshqa modellar bilan taqqoslab bo'lmaydi. TTR esa fit da func, predict da inverse_func qo'llaydi va bashoratni asl birlikka qaytaradi. U quvurning eng tashqarisida turadi.

  3. ColumnTransformer bo'ladi, FeatureUnion ko'paytiradi. Birinchisi turli ustunlarga turli ishlov beradi, ikkinchisi esa ayni kirishdan bir necha ko'rinish yasab birlashtiradi (xom + PCA, TF-IDF + statistika). Ikkalasi bir-birini almashtirmaydi va ko'pincha birga ishlatiladi.

Keyingi darsda modelni saqlash ni ko'ramiz: joblib, versiya muammolari, pickle ning xavfsizlik jihatlari va ishlab chiqarishga topshirish paketi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
19.5-dars: Kompozitsiya vositalari — IlmHamroh