Scikit-learn Pipeline i ColumnTransformer: Vodič za produkcijski ML workflow (2026)
Praktičan vodič za scikit-learn Pipeline i ColumnTransformer. Kako spriječiti data leakage, tunirati hiperparametre s GridSearchCV, pisati custom transformere i deployati Pipeline u produkciju s joblibom.
Scikit-learn Pipeline je klasa koja lančano povezuje korake predprocesiranja i model u jedan estimator, kojim se rukuje kao s bilo kojim drugim scikit-learn objektom (pozivom fit, predict i score). U kombinaciji s ColumnTransformerom omogućuje primjenu različitih transformacija na različite stupce (numeričke, kategoričke, tekstualne) unutar jednog atomarnog artefakta. To je razlog zašto su Pipelinei standard za bilo koji ML kod koji ide dalje od Jupyter notebooka. Bez njih se data leakage uvlači kroz cross-validation, a serializirani model odbija predvidjeti na novim ulazima jer nedostaje neki scaler koji je bio "negdje u notebooku".
Pipeline sprječava data leakage jer se svi fit pozivi predprocesora izvršavaju isključivo na trening splitu unutar cross-validationa.
ColumnTransformer primjenjuje različite transformacije na različite stupce paralelno, pa nema potrebe za ručnim mergeanjem transformiranih DataFrameova.
Od scikit-learn 1.2 nadalje, set_config(transform_output="pandas") globalno vraća DataFrame iz svake transformacije, što čuva imena stupaca.
GridSearchCV može tunirati hiperparametre bilo kojeg koraka u Pipelineu koristeći sintaksu korak__parametar.
Za produkcijski deploy koristite joblib.dump na cijeli Pipeline objekt, nikad ne serializirajte samo model bez predprocesora.
Custom transformer nasljeđivanjem BaseEstimator i TransformerMixin radi domenske transformacije kompatibilne s CV, gridom i set_output API-jem.
Zašto koristiti scikit-learn Pipeline?
Kad sam prije nekoliko godina preuzeo pipeline koji je "radio u notebooku", proveo sam tri dana rekonstruirajući redoslijed StandardScalera, OneHotEncodera i SimpleImputera koji su bili razbacani po ćelijama. Kad smo ga konačno pokrenuli na produkcijskim podacima, F1 score je pao za 12 postotnih bodova. Razlog? StandardScaler je bio fitan na cijelom datasetu prije train/test splita, što je klasični data leakage koji je odražavao lažno visoku offline metriku.
Pipeline rješava tri problema odjednom. Prvo, enkapsulira redoslijed transformacija, pa je nemoguće zaboraviti korak. Drugo, garantira da se fit poziva samo na trening podacima tijekom svake CV petlje, pa je leakage strukturno nemoguć. Treće, čini artefakt serializabilnim kao jedinstvenu cjelinu: joblib.dump(pipeline, "model.joblib") spašava sve, uključujući srednje vrijednosti scalera i vokabular encodera. Za bilo koji model koji ide na produkcijski servis (batch job, REST endpoint, Kafka consumer), Pipeline je minimalni granularni artefakt koji smijete deployati.
Iz perspektive on-calla, ovo je ključno. Kad u 3 ujutro dođe alert da modelov endpoint vraća 500-ke, ne želite otkriti da nedostaje neki scaler koji je bio u odvojenoj .pkl datoteci koju je bivši kolega zaboravio commitati.
Osnove Pipeline klase i make_pipeline
Pipeline se konstruira kao lista (ime_koraka, estimator) parova. Svi koraci osim posljednjeg moraju biti transformeri (imati fit_transform), dok posljednji korak može biti bilo koji estimator (transformer ili prediktor).
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=1000)),
])
pipe.fit(X_train, y_train)
predictions = pipe.predict(X_test)
Kad pozovemo pipe.fit, scikit-learn izvršava sljedeći redoslijed: imputer.fit_transform(X_train), pa rezultat ide u scaler.fit_transform(...), pa u clf.fit(..., y_train). Pri predict, redoslijed je imputer.transform, zatim scaler.transform, zatim clf.predict. Ključno je da se fit ne poziva na transformerima tijekom predict. Stanje (medijani, srednje vrijednosti, standardne devijacije) je zamrznuto u trenutku treniranja.
make_pipeline vs Pipeline
Funkcija make_pipeline je skraćenica koja automatski generira imena koraka na temelju klase transformera (u lowercaseu):
from sklearn.pipeline import make_pipeline
pipe = make_pipeline(
SimpleImputer(strategy="median"),
StandardScaler(),
LogisticRegression(max_iter=1000),
)
# Imena koraka su automatski: "simpleimputer", "standardscaler", "logisticregression"
U produkciji preferiram eksplicitni Pipeline s ručno biranim imenima. Kad tuniramo hiperparametre kroz GridSearchCV koristeći sintaksu korak__parametar, eksplicitna imena tipa "clf" ostaju stabilna čak i ako promijenimo klasu klasifikatora, dok bi make_pipeline zahtijevao izmjenu svih grid ključeva pri prelasku s LogisticRegression na RandomForestClassifier.
ColumnTransformer za mješovite tipove značajki
Realistični datasetovi imaju numeričke, kategoričke i ponekad tekstualne stupce koji zahtijevaju različite transformacije. ColumnTransformer, uveden u scikit-learn sklearn.compose modulu, primjenjuje različite transformere na različite podskupove stupaca paralelno i horizontalno spaja rezultate.
Nekoliko produkcijski relevantnih detalja. handle_unknown="ignore" na OneHotEncoderu je obavezno u produkciji, jer nova kategorija koja se nije pojavila u trening podacima inače ruši predict s ValueError-om (u produkciji to znači 500 status). Argument remainder="drop" eksplicitno kaže da se svi stupci koji nisu navedeni odbacuju; alternativa remainder="passthrough" ih prosljeđuje kroz pipeline netransformirano.
Očuvanje imena stupaca s set_output API-jem
Od scikit-learn 1.2 postoji set_output API koji vraća pandas DataFrame umjesto NumPy arraya, čime se čuvaju imena stupaca kroz cijeli pipeline. Ovo je posebno korisno za debugging feature importancea i za feature store integraciju.
from sklearn import set_config
set_config(transform_output="pandas")
# Sada preprocessor.fit_transform(X_train) vraca DataFrame
# s imenima poput "num__age", "cat__city_zagreb", itd.
transformed = preprocessor.fit_transform(X_train)
print(transformed.columns.tolist()[:5])
# ['num__age', 'num__salary', 'num__credit_score', 'cat__city_split', 'cat__city_zagreb']
Za rad s tekstualnim značajkama preporučujem prvo očistiti sirovi tekst kroz pandas .str akcesor (pogledajte naš vodič za pandas string metode), a zatim ih uključiti u ColumnTransformer kroz TfidfVectorizer.
Cross-validation i sprječavanje data leakagea
Klasična greška koja i danas prolazi kroz code review: fitanje StandardScalera na cijelom datasetu prije train_test_splita. Statistike (srednja vrijednost, standardna devijacija) tada sadrže informaciju iz test skupa, što daje optimistički pristrane offline metrike.
# POGRESNO: data leakage
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # koristi statistike iz cijelog X
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y)
# ISPRAVNO: Pipeline unutar cross_val_score
from sklearn.model_selection import cross_val_score
pipe = Pipeline([("scaler", StandardScaler()), ("clf", LogisticRegression())])
scores = cross_val_score(pipe, X, y, cv=5, scoring="roc_auc")
# fit_transform se poziva samo na trening foldu unutar svake iteracije
Kad se Pipeline preda cross_val_score, GridSearchCV ili HalvingGridSearchCV, scikit-learn interno kloniranjem stvara svježu kopiju za svaki fold i poziva fit samo na trening dijelu tog folda. Statistike scalera se onda primjenjuju na validation fold kroz transform, nikad kroz fit. Ovo je jedini pouzdan način dobivanja iskrenih offline metrika.
Tuniranje hiperparametara s GridSearchCV
Pipelineovi izlažu hiperparametre svojih koraka pod korak__parametar sintaksom. Ovo omogućuje istovremeno tuniranje predprocesnih i modelskih hiperparametara.
Za grid s više od nekoliko desetaka kombinacija koristite HalvingGridSearchCV ili HalvingRandomSearchCV. Oni koriste successive halving strategiju koja rano odbacuje slabe kandidate i tipično je 5-10× brža za sličan rezultat. U mojoj praksi, za budget-constrained treniranje ovo je default, ne alternativa.
Ako radite s vremenskim podacima, koristite TimeSeriesSplit umjesto standardnog KFolda, inače leakage kroz vremensku dimenziju uništava validnost metrika. Za više o temporal aspektima podataka vidite naš vodič za pandas vremenske serije.
Pisanje custom transformera
Prije ili kasnije, trebat će vam domenska transformacija koja ne postoji u scikit-learnu, recimo ratio značajka izračunata iz dva stupca, ili target encoding po grupi. Rješenje je klasa koja nasljeđuje BaseEstimator i TransformerMixin.
from sklearn.base import BaseEstimator, TransformerMixin
import numpy as np
import pandas as pd
class RatioFeatureAdder(BaseEstimator, TransformerMixin):
def __init__(self, numerator_col, denominator_col, output_name):
self.numerator_col = numerator_col
self.denominator_col = denominator_col
self.output_name = output_name
def fit(self, X, y=None):
# Bez ucenja parametara -- samo bezstateful transformacija.
return self
def transform(self, X):
X = X.copy()
# Zastita od dijeljenja s nulom -- vraca NaN, ne inf.
denom = X[self.denominator_col].replace(0, np.nan)
X[self.output_name] = X[self.numerator_col] / denom
return X
def get_feature_names_out(self, input_features=None):
return np.array(list(input_features) + [self.output_name])
get_feature_names_out nije striktno obavezan, ali je nužan ako želite da set_output(transform="pandas") ispravno propagira imena kroz cijeli pipeline. Bez njega, izlaz je NumPy array s izgubljenim identitetom stupaca, što, kad debugirate feature importance nakon incidenta u produkciji u 2 ujutro, boli.
Za transformacije koje zahtijevaju učenje statistika (kao target encoding), fit mora spremiti stanje u atribute s trailing underscoreom (npr. self.category_means_). To je scikit-learn konvencija koja označava da je atribut naučen tijekom fita.
Serializacija i deploy u produkciju
Fitani Pipeline serializira se pomoću joblib-a, koji je za NumPy-heavy objekte znatno brži i kompaktniji od standardnog pickle-a.
Serializirani Pipelineovi nose implicitnu ovisnost o verziji scikit-learna kojom su fitani. Loadanje pipelinea fitanog s 1.3 u okruženju s 1.6 može raditi, može tiho vratiti krive rezultate, ili može eksplicitno pucnuti. Prema službenoj scikit-learn dokumentaciji o modelskoj persistenciji, uvijek pinnajte točnu verziju u requirements.txt ili Docker imageu koji koristi inference server. Za dugoročnu portabilnost razmotrite ONNX format preko skl2onnx knjižnice, jezično i verzijski neutralan format koji preživljava upgrade scikit-learna.
Metadata artefakta
Serializiram i JSON metafile pored svakog .joblib-a s: verzijom sklearn-a, hash-om trening dataseta, git commitom trening skripte, i offline metrikama (AUC, precision, recall). Ovo je zlata vrijedno pri debagiranju kad model regresira u A/B testu, jer bez metadate ne možete reproducirati trening.
Česte greške i kako ih izbjeći
Fitanje predprocesora izvan Pipelinea prije CV-a. Najčešća greška, uzrokuje pristrano previsoke offline metrike. Uvijek stavite sve transformere unutar Pipelinea prije cross_val_score.
Zaboravljeni handle_unknown="ignore" na OneHotEncoderu. U produkciji dolaze nove kategorije. Bez ove opcije, prvi novi user iz grada koji nije bio u trening skupu ruši servis.
Serializacija samo modela, ne cijelog Pipelinea. Vidi savjet gore: cijeli artefakt ili ništa.
Non-pinnane verzije u produkciji. Pipeline treniran s scikit-learn==1.4.0 ne garantira identično ponašanje s 1.6.0. Vidi našu diskusiju o čišćenju podataka u vodiču za čišćenje podataka s pandasom, jer reproducibilnost počinje kod pripreme podataka i mora se protegnuti do artefakta.
Pretjerano tuniranje na malom validation skupu. GridSearchCV s previše kombinacija na malim podacima pravi overfit na CV metriku. Uvijek držite hold-out test set koji se dotakne točno jednom, nakon finalnog izbora modela.
Često postavljana pitanja
Koja je razlika između Pipeline i make_pipeline?
Pipeline zahtijeva eksplicitna imena koraka kao (ime, estimator) parove, dok make_pipeline automatski generira imena iz klase transformera. Za produkciju preferirajte eksplicitni Pipeline jer stabilna imena čine GridSearchCV grid ključeve otpornim na promjene tipa estimatora.
Kako spriječiti data leakage u scikit-learnu?
Stavite sve transformere (scaler, imputer, encoder) unutar Pipeline-a i predajte ga cross_val_score ili GridSearchCV-u. Time se fit poziva isključivo na trening foldu, dok se validation fold obrađuje samo kroz transform s parametrima naučenim na treningu.
Kako spremiti scikit-learn model za produkciju?
Koristite joblib.dump(pipeline, "model.joblib", compress=3) na cijelom Pipeline objektu, ne samo na modelu. Pinnajte točnu verziju scikit-learna u requirements.txt inference servera i uz artefakt serializirajte JSON metadataju s verzijom, git commitom i offline metrikama.
Što je ColumnTransformer i kada ga koristiti?
ColumnTransformer primjenjuje različite transformere na različite podskupove stupaca paralelno. Koristite ga uvijek kad dataset ima mješavinu numeričkih i kategoričkih (ili tekstualnih) značajki koje trebaju različit predprocessing.
Kako tunirati hiperparametre unutar Pipelinea?
GridSearchCV koristi sintaksu korak__parametar, a za ugniježđene Pipelineove unutar ColumnTransformera nastavlja s još jednim __, npr. preprocess__num__scaler__with_mean. Za veće gridove koristite HalvingGridSearchCV, tipično 5-10× brže.
Pandas .str akcesor vektorizira Python string metode nad DataFrame stupcima. Vodič pokriva contains, replace, split, extract i PyArrow StringDtype u pandas 3.0 s primjerima koda.
Praktični vodič za rukovanje NaN vrijednostima u pandasu 2.2: fillna, dropna, ffill, bfill i interpolate, s primjerima iz stvarnih projekata i smjernicama kada koja metoda ima smisla.