# python/01-ml-pipeline-design.py"""Machine learning pipeline design patterns and model training workflows"""fromtypingimportList,Dict,Any,Optional,Union,Tuple,Callablefromdataclassesimportdataclass,asdictfromenumimportEnumimportpandasaspdimportnumpyasnpfromsklearn.model_selectionimporttrain_test_split,cross_val_score,GridSearchCVfromsklearn.preprocessingimportStandardScaler,LabelEncoder,OneHotEncoderfromsklearn.ensembleimportRandomForestClassifier,GradientBoostingClassifierfromsklearn.linear_modelimportLogisticRegression,LinearRegressionfromsklearn.svmimportSVCfromsklearn.metricsimportaccuracy_score,precision_score,recall_score,f1_scorefromsklearn.metricsimportmean_squared_error,r2_score,classification_reportfromsklearn.pipelineimportPipelinefromsklearn.composeimportColumnTransformerimportjoblibimportjsonfromdatetimeimportdatetime,timedeltaimportloggingfromabcimportABC,abstractmethod# Configure logginglogging.basicConfig(level=logging.INFO)logger=logging.getLogger(__name__)classModelType(Enum):"""Model type enumeration"""CLASSIFICATION="classification"REGRESSION="regression"CLUSTERING="clustering"DIMENSIONALITY_REDUCTION="dimensionality_reduction"classModelStatus(Enum):"""Model status enumeration"""TRAINING="training"TRAINED="trained"EVALUATED="evaluated"DEPLOYED="deployed"RETIRED="retired"@dataclassclassModelMetrics:"""Model metrics definition"""accuracy:float=0.0precision:float=0.0recall:float=0.0f1_score:float=0.0mse:float=0.0r2_score:float=0.0cross_val_score:float=0.0training_time:float=0.0inference_time:float=0.0defto_dict(self)->Dict[str,Any]:"""Convert to dictionary"""returnasdict(self)classDataPreprocessor:"""Data preprocessing utilities"""def__init__(self):self.scalers={}self.encoders={}self.feature_names=[]self.preprocessing_pipeline=Nonedefprepare_features(self,X:pd.DataFrame,y:pd.Series=None,fit:bool=True)->Tuple[np.ndarray,List[str]]:"""Prepare features for training"""# Separate numeric and categorical columnsnumeric_cols=X.select_dtypes(include=[np.number]).columns.tolist()categorical_cols=X.select_dtypes(include=['object','category']).columns.tolist()# Create preprocessing pipelinenumeric_transformer=StandardScaler()categorical_transformer=OneHotEncoder(handle_unknown='ignore')preprocessor=ColumnTransformer(transformers=[('num',numeric_transformer,numeric_cols),('cat',categorical_transformer,categorical_cols)])iffit:X_processed=preprocessor.fit_transform(X)self.preprocessing_pipeline=preprocessorelse:X_processed=self.preprocessing_pipeline.transform(X)# Get feature namesfeature_names=numeric_cols.copy()ifcategorical_cols:cat_feature_names=self.preprocessing_pipeline.named_transformers_['cat'].get_feature_names_out(categorical_cols)feature_names.extend(cat_feature_names)self.feature_names=feature_namesreturnX_processed,feature_namesdefsave_preprocessor(self,filepath:str)->None:"""Save preprocessing pipeline"""ifself.preprocessing_pipelineisnotNone:joblib.dump(self.preprocessing_pipeline,filepath)defload_preprocessor(self,filepath:str)->None:"""Load preprocessing pipeline"""self.preprocessing_pipeline=joblib.load(filepath)classModelTrainer(ABC):"""Abstract model trainer"""def__init__(self,model_type:ModelType):self.model_type=model_typeself.model=Noneself.metrics=ModelMetrics()self.training_history=[]@abstractmethoddeftrain(self,X:np.ndarray,y:np.ndarray,**kwargs)->None:"""Train the model"""pass@abstractmethoddefpredict(self,X:np.ndarray)->np.ndarray:"""Make predictions"""pass@abstractmethoddefevaluate(self,X:np.ndarray,y:np.ndarray)->ModelMetrics:"""Evaluate the model"""passdefsave_model(self,filepath:str)->None:"""Save trained model"""ifself.modelisnotNone:joblib.dump(self.model,filepath)defload_model(self,filepath:str)->None:"""Load trained model"""self.model=joblib.load(filepath)classClassificationTrainer(ModelTrainer):"""Classification model trainer"""def__init__(self,algorithm:str="random_forest"):super().__init__(ModelType.CLASSIFICATION)self.algorithm=algorithmself._initialize_model()def_initialize_model(self)->None:"""Initialize the model"""ifself.algorithm=="random_forest":self.model=RandomForestClassifier(random_state=42)elifself.algorithm=="gradient_boosting":self.model=GradientBoostingClassifier(random_state=42)elifself.algorithm=="logistic_regression":self.model=LogisticRegression(random_state=42)elifself.algorithm=="svm":self.model=SVC(random_state=42)else:raiseValueError(f"Unsupported algorithm: {self.algorithm}")deftrain(self,X:np.ndarray,y:np.ndarray,**kwargs)->None:"""Train the classification model"""start_time=datetime.utcnow()# Hyperparameter tuning if specifiedif'param_grid'inkwargs:param_grid=kwargs['param_grid']cv=kwargs.get('cv',5)grid_search=GridSearchCV(self.model,param_grid,cv=cv,scoring='accuracy')grid_search.fit(X,y)self.model=grid_search.best_estimator_logger.info(f"Best parameters: {grid_search.best_params_}")logger.info(f"Best cross-validation score: {grid_search.best_score_:.4f}")else:self.model.fit(X,y)training_time=(datetime.utcnow()-start_time).total_seconds()self.metrics.training_time=training_timelogger.info(f"Model trained in {training_time:.2f} seconds")defpredict(self,X:np.ndarray)->np.ndarray:"""Make predictions"""ifself.modelisNone:raiseValueError("Model not trained yet")start_time=datetime.utcnow()predictions=self.model.predict(X)inference_time=(datetime.utcnow()-start_time).total_seconds()self.metrics.inference_time=inference_timereturnpredictionsdefpredict_proba(self,X:np.ndarray)->np.ndarray:"""Get prediction probabilities"""ifself.modelisNone:raiseValueError("Model not trained yet")ifhasattr(self.model,'predict_proba'):returnself.model.predict_proba(X)else:raiseValueError(f"Model {self.algorithm} does not support probability predictions")defevaluate(self,X:np.ndarray,y:np.ndarray)->ModelMetrics:"""Evaluate the classification model"""predictions=self.predict(X)# Calculate metricsaccuracy=accuracy_score(y,predictions)precision=precision_score(y,predictions,average='weighted')recall=recall_score(y,predictions,average='weighted')f1=f1_score(y,predictions,average='weighted')# Cross-validation scorecv_scores=cross_val_score(self.model,X,y,cv=5)cv_score=cv_scores.mean()self.metrics.accuracy=accuracyself.metrics.precision=precisionself.metrics.recall=recallself.metrics.f1_score=f1self.metrics.cross_val_score=cv_scorelogger.info(f"Model evaluation - Accuracy: {accuracy:.4f}, F1: {f1:.4f}")returnself.metricsclassRegressionTrainer(ModelTrainer):"""Regression model trainer"""def__init__(self,algorithm:str="linear_regression"):super().__init__(ModelType.REGRESSION)self.algorithm=algorithmself._initialize_model()def_initialize_model(self)->None:"""Initialize the model"""ifself.algorithm=="linear_regression":self.model=LinearRegression()elifself.algorithm=="random_forest":self.model=RandomForestClassifier(random_state=42)elifself.algorithm=="gradient_boosting":self.model=GradientBoostingClassifier(random_state=42)else:raiseValueError(f"Unsupported algorithm: {self.algorithm}")deftrain(self,X:np.ndarray,y:np.ndarray,**kwargs)->None:"""Train the regression model"""start_time=datetime.utcnow()# Hyperparameter tuning if specifiedif'param_grid'inkwargs:param_grid=kwargs['param_grid']cv=kwargs.get('cv',5)grid_search=GridSearchCV(self.model,param_grid,cv=cv,scoring='neg_mean_squared_error')grid_search.fit(X,y)self.model=grid_search.best_estimator_logger.info(f"Best parameters: {grid_search.best_params_}")logger.info(f"Best cross-validation score: {grid_search.best_score_:.4f}")else:self.model.fit(X,y)training_time=(datetime.utcnow()-start_time).total_seconds()self.metrics.training_time=training_timelogger.info(f"Model trained in {training_time:.2f} seconds")defpredict(self,X:np.ndarray)->np.ndarray:"""Make predictions"""ifself.modelisNone:raiseValueError("Model not trained yet")start_time=datetime.utcnow()predictions=self.model.predict(X)inference_time=(datetime.utcnow()-start_time).total_seconds()self.metrics.inference_time=inference_timereturnpredictionsdefevaluate(self,X:np.ndarray,y:np.ndarray)->ModelMetrics:"""Evaluate the regression model"""predictions=self.predict(X)# Calculate metricsmse=mean_squared_error(y,predictions)r2=r2_score(y,predictions)# Cross-validation scorecv_scores=cross_val_score(self.model,X,y,cv=5,scoring='neg_mean_squared_error')cv_score=-cv_scores.mean()self.metrics.mse=mseself.metrics.r2_score=r2self.metrics.cross_val_score=cv_scorelogger.info(f"Model evaluation - MSE: {mse:.4f}, R²: {r2:.4f}")returnself.metricsclassModelEvaluator:"""Model evaluation utilities"""def__init__(self):self.evaluation_results={}defcross_validate(self,model,X:np.ndarray,y:np.ndarray,cv:int=5,scoring:str=None)->Dict[str,Any]:"""Perform cross-validation"""ifscoringisNone:scoring='accuracy'iflen(np.unique(y))<10else'neg_mean_squared_error'cv_scores=cross_val_score(model,X,y,cv=cv,scoring=scoring)result={"cv_scores":cv_scores.tolist(),"mean_score":cv_scores.mean(),"std_score":cv_scores.std(),"cv_folds":cv,"scoring":scoring}returnresultdeflearning_curve(self,model,X:np.ndarray,y:np.ndarray,train_sizes:List[float]=None)->Dict[str,Any]:"""Generate learning curve"""fromsklearn.model_selectionimportlearning_curveiftrain_sizesisNone:train_sizes=np.linspace(0.1,1.0,10)train_sizes_abs,train_scores,val_scores=learning_curve(model,X,y,train_sizes=train_sizes,cv=5)result={"train_sizes":train_sizes_abs.tolist(),"train_scores":train_scores.tolist(),"val_scores":val_scores.tolist(),"train_scores_mean":train_scores.mean(axis=1).tolist(),"val_scores_mean":val_scores.mean(axis=1).tolist()}returnresultdeffeature_importance(self,model,feature_names:List[str])->Dict[str,Any]:"""Get feature importance"""ifhasattr(model,'feature_importances_'):importances=model.feature_importances_elifhasattr(model,'coef_'):importances=np.abs(model.coef_[0])else:return{"error":"Model does not support feature importance"}# Sort features by importancefeature_importance=list(zip(feature_names,importances))feature_importance.sort(key=lambdax:x[1],reverse=True)result={"feature_importance":feature_importance,"top_features":feature_importance[:10]}returnresultclassModelPipeline:"""Complete ML pipeline"""def__init__(self,model_type:ModelType,algorithm:str=None):self.model_type=model_typeself.algorithm=algorithmself.preprocessor=DataPreprocessor()self.trainer=Noneself.evaluator=ModelEvaluator()self.status=ModelStatus.TRAININGself.pipeline_metrics={}defsetup_trainer(self,algorithm:str)->None:"""Setup model trainer"""ifself.model_type==ModelType.CLASSIFICATION:self.trainer=ClassificationTrainer(algorithm)elifself.model_type==ModelType.REGRESSION:self.trainer=RegressionTrainer(algorithm)else:raiseValueError(f"Unsupported model type: {self.model_type}")deftrain_pipeline(self,X:pd.DataFrame,y:pd.Series,test_size:float=0.2,**kwargs)->Dict[str,Any]:"""Train complete pipeline"""# Split dataX_train,X_test,y_train,y_test=train_test_split(X,y,test_size=test_size,random_state=42)# Preprocess featuresX_train_processed,feature_names=self.preprocessor.prepare_features(X_train,y_train,fit=True)X_test_processed,_=self.preprocessor.prepare_features(X_test,fit=False)# Train modelself.trainer.train(X_train_processed,y_train.values,**kwargs)self.status=ModelStatus.TRAINED# Evaluate modeltrain_metrics=self.trainer.evaluate(X_train_processed,y_train.values)test_metrics=self.trainer.evaluate(X_test_processed,y_test.values)# Cross-validationcv_results=self.evaluator.cross_validate(self.trainer.model,X_train_processed,y_train.values)# Feature importancefeature_importance=self.evaluator.feature_importance(self.trainer.model,feature_names)self.pipeline_metrics={"train_metrics":train_metrics.to_dict(),"test_metrics":test_metrics.to_dict(),"cv_results":cv_results,"feature_importance":feature_importance,"feature_names":feature_names}self.status=ModelStatus.EVALUATEDreturnself.pipeline_metricsdefpredict(self,X:pd.DataFrame)->np.ndarray:"""Make predictions on new data"""ifself.statusnotin[ModelStatus.TRAINED,ModelStatus.EVALUATED,ModelStatus.DEPLOYED]:raiseValueError("Model not trained yet")X_processed,_=self.preprocessor.prepare_features(X,fit=False)returnself.trainer.predict(X_processed)defsave_pipeline(self,filepath:str)->None:"""Save complete pipeline"""pipeline_data={"model_type":self.model_type.value,"algorithm":self.algorithm,"status":self.status.value,"metrics":self.pipeline_metrics,"feature_names":self.preprocessor.feature_names}# Save modelself.trainer.save_model(f"{filepath}_model.pkl")# Save preprocessorself.preprocessor.save_preprocessor(f"{filepath}_preprocessor.pkl")# Save pipeline metadatawithopen(f"{filepath}_metadata.json",'w')asf:json.dump(pipeline_data,f,indent=2)defload_pipeline(self,filepath:str)->None:"""Load complete pipeline"""# Load metadatawithopen(f"{filepath}_metadata.json",'r')asf:pipeline_data=json.load(f)self.model_type=ModelType(pipeline_data["model_type"])self.algorithm=pipeline_data["algorithm"]self.status=ModelStatus(pipeline_data["status"])self.pipeline_metrics=pipeline_data["metrics"]# Load modelself.setup_trainer(self.algorithm)self.trainer.load_model(f"{filepath}_model.pkl")# Load preprocessorself.preprocessor.load_preprocessor(f"{filepath}_preprocessor.pkl")self.preprocessor.feature_names=pipeline_data["feature_names"]# Usage examplesdefexample_ml_pipeline():"""Example ML pipeline usage"""# Create sample datanp.random.seed(42)n_samples=1000X=pd.DataFrame({'feature1':np.random.normal(0,1,n_samples),'feature2':np.random.normal(0,1,n_samples),'feature3':np.random.normal(0,1,n_samples),'category':np.random.choice(['A','B','C'],n_samples)})y=(X['feature1']+X['feature2']+np.random.normal(0,0.1,n_samples)>0).astype(int)# Create classification pipelinepipeline=ModelPipeline(ModelType.CLASSIFICATION)pipeline.setup_trainer("random_forest")# Train pipelinemetrics=pipeline.train_pipeline(X,y,test_size=0.2)print(f"Training metrics: {metrics['train_metrics']}")print(f"Test metrics: {metrics['test_metrics']}")# Make predictionsnew_data=pd.DataFrame({'feature1':[1,-1,0],'feature2':[1,-1,0],'feature3':[1,-1,0],'category':['A','B','C']})predictions=pipeline.predict(new_data)print(f"Predictions: {predictions}")# Save pipelinepipeline.save_pipeline("my_model")# Load pipelinenew_pipeline=ModelPipeline(ModelType.CLASSIFICATION)new_pipeline.load_pipeline("my_model")# Make predictions with loaded pipelineloaded_predictions=new_pipeline.predict(new_data)print(f"Loaded pipeline predictions: {loaded_predictions}")
# 1. Data preprocessingpreprocessor=DataPreprocessor()X_processed,feature_names=preprocessor.prepare_features(X,y,fit=True)# 2. Model trainingtrainer=ClassificationTrainer("random_forest")trainer.train(X_processed,y.values)# 3. Model evaluationmetrics=trainer.evaluate(X_test,y_test)print(f"Model accuracy: {metrics.accuracy:.4f}")# 4. Complete pipelinepipeline=ModelPipeline(ModelType.CLASSIFICATION)pipeline.setup_trainer("random_forest")metrics=pipeline.train_pipeline(X,y)# 5. Model validationvalidator=ModelValidator()cv_results=validator.stratified_cv(model,X,y)
# Complete ML setupdefsetup_ml_pipeline():"""Setup complete ML pipeline environment"""# Data preprocessorpreprocessor=DataPreprocessor()# Model trainertrainer=ClassificationTrainer("random_forest")# Model evaluatorevaluator=ModelEvaluator()# Model validatorvalidator=ModelValidator()# Model comparatorcomparator=ModelComparator()# Complete pipelinepipeline=ModelPipeline(ModelType.CLASSIFICATION)print("ML pipeline setup complete!")