• bitcoinBitcoin(BTC)$78,933.00-2.74%
  • ethereumEthereum(ETH)$2,268.27-3.73%
  • tetherTether(USDT)$1.00-0.05%
  • binancecoinBNB(BNB)$703.71-6.88%
  • rippleXRP(XRP)$1.25-5.79%
  • usd-coinUSDC(USDC)$1.00-0.04%
  • solanaSolana(SOL)$97.21-4.97%
  • tronTRON(TRX)$0.3563978.52%
  • Figure HelocFigure Heloc(FIGR_HELOC)$1.03-0.10%
  • HyperliquidHyperliquid(HYPE)$92.8214.31%
  • zcashZcash(ZEC)$1,256.97-3.84%
  • dogecoinDogecoin(DOGE)$0.076062-4.74%
  • moneroMonero(XMR)$616.842.71%
  • USDSUSDS(USDS)$1.00-0.22%
  • whitebitWhiteBIT Coin(WBT)$75.61-2.99%
  • leo-tokenLEO Token(LEO)$9.460.32%
  • chainlinkChainlink(LINK)$10.89-7.78%
  • cardanoCardano(ADA)$0.192427-6.51%
  • stellarStellar(XLM)$0.164148-5.07%
  • bitcoin-cashBitcoin Cash(BCH)$277.22-4.24%
  • nearNEAR Protocol(NEAR)$4.14-12.43%
  • RainRain(RAIN)$0.006728-3.08%
  • daiDai(DAI)$1.00-0.01%
  • Ethena USDeEthena USDe(USDE)$1.00-0.11%
  • litecoinLitecoin(LTC)$55.46-6.62%
  • uniswapUniswap(UNI)$6.42-11.49%
  • USD1USD1(USD1)$1.00-0.11%
  • CantonCanton(CC)$0.096462-4.01%
  • avalanche-2Avalanche(AVAX)$8.38-6.96%
  • suiSui(SUI)$0.89-6.32%
  • the-open-networkGram (prev. Toncoin)(GRAM)$1.18-4.81%
  • tether-goldTether Gold(XAUT)$4,011.51-0.97%
  • Global DollarGlobal Dollar(USDG)$1.000.00%
  • hedera-hashgraphHedera(HBAR)$0.069763-7.22%
  • paypal-usdPayPal USD(PYUSD)$1.00-0.17%
  • BittensorBittensor(TAO)$238.36-7.47%
  • shiba-inuShiba Inu(SHIB)$0.000005-6.14%
  • quant-networkQuant(QNT)$183.15-8.41%
  • Ripple USDRipple USD(RLUSD)$1.00-0.11%
  • crypto-com-chainCronos(CRO)$0.051506-3.35%
  • EthenaEthena(ENA)$0.182873-12.96%
  • Ondo US Dollar YieldOndo US Dollar Yield(USDY)$1.15-0.17%
  • Circle USYCCircle USYC(USYC)$1.140.01%
  • okbOKB(OKB)$111.260.01%
  • MemeCoreMemeCore(M)$1.02-3.07%
  • BitwayBitway(BTW)$1.53-11.29%
  • BlackRock USD Institutional Digital Liquidity FundBlackRock USD Institutional Digital Liquidity Fund(BUIDL)$1.000.00%
  • aaveAave(AAVE)$124.23-7.97%
  • pepePepe(PEPE)$0.000005-9.46%
  • OndoOndo(ONDO)$0.391135-7.17%
TradePoint.io
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop
No Result
View All Result
TradePoint.io
No Result
View All Result

How to Build an End-to-End Production Grade Machine Learning Pipeline with ZenML, Including Custom Materializers, Metadata Tracking, and Hyperparameter Optimization

May 4, 2026
in AI & Technology
Reading Time: 8 mins read
A A
How to Build an End-to-End Production Grade Machine Learning Pipeline with ZenML, Including Custom Materializers, Metadata Tracking, and Hyperparameter Optimization
ShareShareShareShareShare

In this tutorial, we walk through an end-to-end implementation of an advanced machine learning pipeline using ZenML. We begin by setting up the environment and initializing a ZenML project, then define a custom materializer that enables seamless serialization and metadata extraction for a domain-specific dataset object. As we progress, we build a modular pipeline that performs data loading, preprocessing, and a fan-out hyperparameter search across multiple models. We evaluate each candidate, log rich metadata at every step, and use a fan-in strategy to select and promote the best-performing model. Throughout the process, we leverage ZenML’s model control plane, artifact tracking, and caching mechanisms to ensure full reproducibility, transparency, and efficiency.

Copy CodeCopiedUse a different Browser
import os, sys, subprocess, json, shutil
from pathlib import Path


def _sh(cmd, check=True):
   print(f"$ {' '.join(cmd)}")
   return subprocess.run(cmd, check=check)


_sh([sys.executable, "-m", "pip", "install", "-q",
    "zenml[server]", "scikit-learn", "pandas", "pyarrow"])


PROJECT = Path("/content/zenml_advanced_tutorial") if Path("/content").exists() \
   else Path.cwd() / "zenml_advanced_tutorial"
if PROJECT.exists():
   shutil.rmtree(PROJECT)
PROJECT.mkdir(parents=True)
os.chdir(PROJECT)


os.environ["ZENML_ANALYTICS_OPT_IN"] = "false"
os.environ["ZENML_LOGGING_VERBOSITY"] = "WARN"
_sh(["zenml", "init"], check=False)

We set up the entire environment by installing required libraries and initializing a ZenML project workspace. We create a clean working directory and configure environment variables to control logging and analytics behavior. Finally, we bootstrap the ZenML repository so that all subsequent pipeline operations are properly tracked and managed.

YOU MAY ALSO LIKE

Meta Boosts Equity Offering to $98 Billion, Raising Its AI Bet

AI-Made Political Ads Jump Ahead of Midterms

Copy CodeCopiedUse a different Browser
from typing import Annotated, Tuple, Dict, List, Any
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score
from sklearn.preprocessing import StandardScaler


from zenml import pipeline, step, log_metadata, Model, get_step_context
from zenml.client import Client
from zenml.materializers.base_materializer import BaseMaterializer
from zenml.enums import ArtifactType
from zenml.io import fileio


class DatasetBundle:
   def __init__(self, X, y, feature_names, stats=None):
       self.X = np.asarray(X)
       self.y = np.asarray(y)
       self.feature_names = list(feature_names)
       self.stats = stats or {}


class DatasetBundleMaterializer(BaseMaterializer):
   ASSOCIATED_TYPES = (DatasetBundle,)
   ASSOCIATED_ARTIFACT_TYPE = ArtifactType.DATA


   def load(self, data_type):
       with fileio.open(os.path.join(self.uri, "X.npy"), "rb") as f:
           X = np.load(f)
       with fileio.open(os.path.join(self.uri, "y.npy"), "rb") as f:
           y = np.load(f)
       with fileio.open(os.path.join(self.uri, "meta.json"), "r") as f:
           meta = json.loads(f.read())
       return DatasetBundle(X, y, meta["feature_names"], meta["stats"])


   def save(self, bundle):
       with fileio.open(os.path.join(self.uri, "X.npy"), "wb") as f:
           np.save(f, bundle.X)
       with fileio.open(os.path.join(self.uri, "y.npy"), "wb") as f:
           np.save(f, bundle.y)
       with fileio.open(os.path.join(self.uri, "meta.json"), "w") as f:
           f.write(json.dumps({
               "feature_names": bundle.feature_names,
               "stats": bundle.stats,
           }))


   def extract_metadata(self, bundle):
       classes, counts = np.unique(bundle.y, return_counts=True)
       return {
           "n_samples": int(bundle.X.shape[0]),
           "n_features": int(bundle.X.shape[1]),
           "class_distribution": {str(c): int(n) for c, n in zip(classes, counts)},
       }

We import all necessary libraries and define a custom data container along with its materializer. We implement logic to save, load, and extract metadata from our dataset, enabling seamless artifact handling in ZenML. This ensures that our data is not only stored efficiently but also enriched with meaningful, queryable metadata.

Copy CodeCopiedUse a different Browser
@step(enable_cache=True)
def load_data() -> Annotated[DatasetBundle, "raw_dataset"]:
   data = load_breast_cancer()
   return DatasetBundle(
       data.data, data.target, data.feature_names,
       stats={"source": "sklearn.datasets.load_breast_cancer"},
   )


@step
def split_and_scale(
   bundle: DatasetBundle,
   test_size: float = 0.2,
   random_state: int = 42,
) -> Tuple[
   Annotated[np.ndarray, "X_train"],
   Annotated[np.ndarray, "X_test"],
   Annotated[np.ndarray, "y_train"],
   Annotated[np.ndarray, "y_test"],
]:
   X_tr, X_te, y_tr, y_te = train_test_split(
       bundle.X, bundle.y, test_size=test_size,
       random_state=random_state, stratify=bundle.y,
   )
   scaler = StandardScaler().fit(X_tr)
   X_tr, X_te = scaler.transform(X_tr), scaler.transform(X_te)
   log_metadata(metadata={"train_size": len(X_tr), "test_size": len(X_te)})
   return X_tr, X_te, y_tr, y_te


@step
def train_candidate(
   X_train: np.ndarray,
   y_train: np.ndarray,
   model_type: str = "random_forest",
   n_estimators: int = 100,
   max_depth: int = 5,
) -> Annotated[Any, "candidate_model"]:
   if model_type == "random_forest":
       m = RandomForestClassifier(n_estimators=n_estimators,
                                  max_depth=max_depth, random_state=42)
   elif model_type == "gradient_boosting":
       m = GradientBoostingClassifier(n_estimators=n_estimators,
                                      max_depth=max_depth, random_state=42)
   else:
       m = LogisticRegression(max_iter=2000, random_state=42)
   m.fit(X_train, y_train)
   log_metadata(metadata={
       "model_type": model_type,
       "hyperparameters": {"n_estimators": n_estimators, "max_depth": max_depth},
   })
   return m

We define core pipeline steps for loading data, splitting it, scaling features, and training model candidates. We ensure that data loading is cached for efficiency while logging key metadata during preprocessing and training. This forms the backbone of our pipeline, where each model is trained independently with its respective configuration.

Copy CodeCopiedUse a different Browser
@step
def evaluate_candidate(
   model: Any,
   X_test: np.ndarray,
   y_test: np.ndarray,
   label: str,
) -> Annotated[Dict[str, Any], "metrics"]:
   preds = model.predict(X_test)
   probs = (model.predict_proba(X_test)[:, 1]
            if hasattr(model, "predict_proba") else preds)
   metrics: Dict[str, Any] = {
       "accuracy": float(accuracy_score(y_test, preds)),
       "f1":       float(f1_score(y_test, preds)),
       "roc_auc":  float(roc_auc_score(y_test, probs)),
       "label":    label,
   }
   log_metadata(metadata=metrics)
   return metrics


@step
def select_best(
   metrics_list: List[Dict[str, Any]],
   models: List[Any],
) -> Annotated[Any, "production_model"]:
   best_idx = max(range(len(metrics_list)),
                  key=lambda i: metrics_list[i]["roc_auc"])
   best = metrics_list[best_idx]


   ctx = get_step_context()
   try:
       ctx.model.log_metadata({"chosen_candidate": best,
                               "candidate_index": best_idx})
   except Exception as e:
       print(f"  (model metadata log skipped: {e})")


   log_metadata(metadata={
       "winning_metrics": {k: v for k, v in best.items() if k != "label"},
   })
   print(f"\n  Best candidate: {best['label']}  →  "
         f"ROC AUC = {best['roc_auc']:.4f}\n")
   return models[best_idx]

We evaluate each trained model using multiple performance metrics and log the results. We then implement a selection mechanism that identifies the best-performing model based on ROC AUC. Additionally, we attach relevant metadata to the model version, enabling traceability and informed decision-making.

Copy CodeCopiedUse a different Browser
SEARCH_SPACE = [
   {"model_type": "random_forest",     "n_estimators": 50,  "max_depth": 3},
   {"model_type": "random_forest",     "n_estimators": 200, "max_depth": 7},
   {"model_type": "gradient_boosting", "n_estimators": 100, "max_depth": 3},
   {"model_type": "logistic",          "n_estimators": 1,   "max_depth": 1},
]


PRODUCTION_MODEL = Model(
   name="breast_cancer_classifier",
   description="Best model from in-pipeline hyperparameter search",
   tags=["tutorial", "advanced"],
)


@pipeline(model=PRODUCTION_MODEL, enable_cache=True)
def training_pipeline(test_size: float = 0.2):
   bundle = load_data()


   models, metrics = [], []
   for i, cfg in enumerate(SEARCH_SPACE):
       m = train_candidate(
           X_train, y_train, **cfg,
           id=f"train_{i}_{cfg['model_type']}",
       )
       s = evaluate_candidate(
           m, X_test, y_test,
           label=f"{cfg['model_type']}(n={cfg['n_estimators']},d={cfg['max_depth']})",
           id=f"eval_{i}",
       )
       models.append(m)
       metrics.append(s)


   select_best(metrics, models)


print("\n" + "=" * 70 + "\n  RUNNING TRAINING PIPELINE\n" + "=" * 70)
run_obj = training_pipeline()


print("\n" + "=" * 70 + "\n  INSPECTING THE RUN\n" + "=" * 70)
client = Client()
run = client.get_pipeline_run(run_obj.id)


print(f"\nPipeline:   {run.pipeline.name}")
print(f"Run name:   {run.name}")
print(f"Status:     {run.status}")
print(f"Step runs:  {len(run.steps)}")
for name, step_run in run.steps.items():
   print(f"  • {name:35s} status={step_run.status}")


print("\nRun-level metadata (aggregated from steps):")
for k, v in (run.run_metadata or {}).items():
   short = str(v)
   print(f"  {k}: {short[:80]}{'…' if len(short) > 80 else ''}")


print("\n" + "-" * 70 + "\n  MODEL CONTROL PLANE\n" + "-" * 70)
try:
   mv = client.get_model_version(PRODUCTION_MODEL.name, "latest")
except Exception:
   mv = client.list_model_versions(model_name_or_id=PRODUCTION_MODEL.name)[0]


print(f"Model:           {mv.model.name}")
print(f"Version:         {mv.name} (number={mv.number})")
linked = list(mv.data_artifact_ids.keys()) if hasattr(mv, "data_artifact_ids") else []
print(f"Linked outputs:  {linked or '(see dashboard)'}")
if mv.run_metadata:
   print("Version metadata:")
   for k, v in dict(mv.run_metadata).items():
       print(f"  {k}: {str(v)[:80]}")


print("\n" + "-" * 70 + "\n  RELOADING ARTIFACTS DIRECTLY\n" + "-" * 70)
prod_artifact = client.get_artifact_version("production_model")
prod_model = prod_artifact.load()
print(f"Loaded model class:   {type(prod_model).__name__}")
print(f"Artifact metadata:    {dict(prod_artifact.run_metadata) if prod_artifact.run_metadata else '{}'}"[:120])


X_test_arr = client.get_artifact_version("X_test").load()
y_test_arr = client.get_artifact_version("y_test").load()
acc = accuracy_score(y_test_arr, prod_model.predict(X_test_arr))
print(f"Sanity-check accuracy on stored X_test: {acc:.4f}")


ds_artifact = client.get_artifact_version("raw_dataset")
print(f"\nraw_dataset auto-extracted metadata:")
for k, v in (ds_artifact.run_metadata or {}).items():
   print(f"  {k}: {v}")


print("\n" + "=" * 70 + "\n  RE-RUNNING — STEPS SHOULD BE CACHED\n" + "=" * 70)
training_pipeline()


print("""
✅ Tutorial complete.


What just happened:
 • Custom materializer serialized a domain object + auto-extracted metadata.
 • Fan-out: 4 candidates trained + evaluated as 8 distinct step runs.
 • Fan-in: select_best joined them and promoted the winner.
 • Model Control Plane created a versioned 'breast_cancer_classifier'.
 • Every artifact, metric, and hyperparameter was logged and queryable.
 • Second run hit the cache — zero recomputation.


Explore further from this same Python session:
 Client().list_pipeline_runs()
 Client().list_model_versions(model_name_or_id="breast_cancer_classifier")
 Client().list_artifact_versions(name="metrics")
""")

We define the full pipeline, execute it, and inspect the results using the ZenML Client API. We perform a fan-out over multiple configurations, followed by a fan-in step to select the best model. Finally, we demonstrate artifact reuse, metadata inspection, and caching behavior by re-running the pipeline without redundant computation.

In conclusion, we constructed a robust, production-style ML pipeline that demonstrates the full power of ZenML’s orchestration capabilities. We observed how custom materializers enrich artifacts with meaningful metadata, how multiple model candidates can be trained and evaluated in parallel, and how the best model is automatically selected and versioned. We also explored how to inspect pipeline runs, retrieve artifacts directly without recomputation, and verify model performance using stored data. Also, we saw caching in action during a re-run, confirming that redundant computations are avoided. This workflow provides a strong foundation for building scalable, maintainable, and reproducible machine learning systems in real-world scenarios.


Check out the Full Codes with Notebook here. Also, feel free to follow us on Twitter and don’t forget to join our 130k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.

Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us

The post How to Build an End-to-End Production Grade Machine Learning Pipeline with ZenML, Including Custom Materializers, Metadata Tracking, and Hyperparameter Optimization appeared first on MarkTechPost.

Credit: Source link

ShareTweetSendSharePin

Related Posts

Meta Boosts Equity Offering to  Billion, Raising Its AI Bet
AI & Technology

Meta Boosts Equity Offering to $98 Billion, Raising Its AI Bet

October 29, 2026
AI-Made Political Ads Jump Ahead of Midterms
AI & Technology

AI-Made Political Ads Jump Ahead of Midterms

October 29, 2026
SoFi Says It’s Well Positioned for Economic Uncertainty
AI & Technology

SoFi Says It’s Well Positioned for Economic Uncertainty

October 29, 2026
Defense Contractor Anduril Will Build American-Made Laptop With Qualcomm
AI & Technology

Defense Contractor Anduril Will Build American-Made Laptop With Qualcomm

October 29, 2026
Next Post
GameStop's 'Crazy Idea' To Buy eBay Isn't So Crazy, But It Is Risky

GameStop's 'Crazy Idea' To Buy eBay Isn't So Crazy, But It Is Risky

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Search

No Result
View All Result
Nanbeige Releases Nanbeige4.3-33B, a 33B Open Agentic Coding Model

Nanbeige Releases Nanbeige4.3-33B, a 33B Open Agentic Coding Model

October 25, 2026
Tesla shows off sports car that can fly through the air

Tesla shows off sports car that can fly through the air

October 24, 2026
Scott Bessent says prices will come down after Iran war: ‘we will get to the other side’

Scott Bessent says prices will come down after Iran war: ‘we will get to the other side’

October 23, 2026

About

Learn more

Our Services

Legal

Privacy Policy

Terms of Use

Bloggers

Learn more

Article Links

Contact

Advertise

Ask us anything

©2020- TradePoint.io - All rights reserved!

Tradepoint.io, being just a publishing and technology platform, is not a registered broker-dealer or investment adviser. So we do not provide investment advice. Rather, brokerage services are provided to clients of Tradepoint.io by independent SEC-registered broker-dealers and members of FINRA/SIPC. Every form of investing carries some risk and past performance is not a guarantee of future results. “Tradepoint.io“, “Instant Investing” and “My Trading Tools” are registered trademarks of Apperbuild, LLC.

This website is operated by Apperbuild, LLC. We have no link to any brokerage firm and we do not provide investment advice. Every information and resource we provide is solely for the education of our readers. © 2020 Apperbuild, LLC. All rights reserved.

No Result
View All Result
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop

© 2023 - TradePoint.io - All Rights Reserved!