Проектування архітектури ML-пайплайну
ML-пайплайн — послідовність кроків, що відтворюються, від сирих даних до працюючої моделі в production. Без формальної архітектури ML-проект перетворюється на ноутбуки на ноутбуці data scientist'а, які «працюють у мене». Правильна архітектура робить моделі відтворюваними, версійними та масштабованими.
Компоненти Production ML-пайплайна
Кожен production ML-пайплайн складається з декількох етапів, що незалежно розгортаються:
Raw Data → [Data Ingestion] → [Feature Engineering] → [Training] → [Evaluation] → [Registry] → [Serving]
↑ ↓
[Feature Store] [Monitoring]
Data Ingestion Layer: отримання даних із джерел (S3, бази даних, стрімінг). Ідемопотентність — перезапуск не створює дублікати. Партиціонування за датою для інкрементальних перерахунків.
Feature Engineering Layer: трансформації відтворюються та протестовані. Поділ на online features (обчислюються в реальному часі) та batch features (передраховані). Feature store – єдине джерело правди.
Training Layer: гіперпараметричний пошук, крос-валідація, логування експериментів. Checkpoint'и для тривалих навчань.
Evaluation Layer: автоматичне порівняння з baseline та champion моделлю. Відмова у реєстрації при деградації метрик.
Model Registry: версіонування моделей, метадані, статуси (staging/production/archived).
Serving Layer: inference-сервіс з моніторингом latency, throughput, drift.
Вибір оркестратора
| Завдання | Рекомендований інструмент |
|---|---|
| ML-пайплайни (прості) | Apache Airflow |
| ML-пайплайни (нативні) | Kubeflow Pipelines, ZenML |
| Data engineering | Prefect, Dagster |
| Експерименти | MLflow, W&B |
| Feature store | Feast, Hopsworks |
Типова реалізація із ZenML
from zenml import step, pipeline
from zenml.steps import Output
@step
def data_ingestion(
source_path: str,
start_date: str,
end_date: str
) -> Output(data=pd.DataFrame):
"""Загрузка данных за период. Идемпотентно."""
return load_from_s3(source_path, start_date, end_date)
@step
def feature_engineering(
data: pd.DataFrame
) -> Output(features=pd.DataFrame, feature_metadata=dict):
"""Трансформации. Те же трансформации применяются при инференсе."""
transformer = FeatureTransformer()
features = transformer.fit_transform(data)
# Сохраняем артефакт трансформера для serving
return features, transformer.get_metadata()
@step
def model_training(
features: pd.DataFrame,
hyperparams: dict
) -> Output(model=Any, metrics=dict):
model = XGBClassifier(**hyperparams)
X, y = split_features_target(features)
model.fit(X, y)
metrics = evaluate_model(model, X, y)
return model, metrics
@step
def model_evaluation(
model: Any,
metrics: dict,
baseline_metrics: dict
) -> Output(passed=bool):
"""Не пропускаем модель в registry если хуже baseline."""
return metrics["f1"] > baseline_metrics["f1"] * 0.99
@pipeline
def training_pipeline(source: str, start_date: str, end_date: str, hyperparams: dict):
data = data_ingestion(source, start_date, end_date)
features, feature_metadata = feature_engineering(data)
model, metrics = model_training(features, hyperparams)
passed = model_evaluation(model, metrics, load_baseline_metrics())
if passed:
register_model(model, metrics, feature_metadata)
Feature Store - запобігання Training-Serving Skew
Training-serving skew - основне джерело деградації ML-моделей в production. Причина: фічі вважаються по-різному під час навчання та інференсі. Feature store вирішує це: одна кодова база визначає фічу, вона використовується і під час навчання, і при сервінгу.
from feast import FeatureStore, Entity, FeatureView, Field
from feast.types import Float64, Int64
# Определение один раз
customer_stats = FeatureView(
name="customer_stats",
entities=["customer_id"],
ttl=timedelta(days=1),
schema=[
Field(name="total_purchases_7d", dtype=Float64),
Field(name="avg_order_value", dtype=Float64),
Field(name="days_since_last_purchase", dtype=Int64),
],
source=customer_stats_batch_source,
)
# При обучении
training_df = store.get_historical_features(
entity_df=entity_df,
features=["customer_stats:total_purchases_7d", "customer_stats:avg_order_value"]
).to_df()
# При инференсе — те же фичи, те же вычисления
online_features = store.get_online_features(
features=["customer_stats:total_purchases_7d"],
entity_rows=[{"customer_id": "12345"}]
).to_dict()
Data Versioning та відтворюваність
Кожен запуск пайплайну має бути відтворений через 6 місяців. Вимоги:
- Версіонування даних: DVC або Delta Lake (time travel)
- Версіонування коду: git commit hash у метаданих моделі
- Версіонування оточення: Docker image digest
- Версіонування конфігурації: параметри в YAML, не в коді
# experiment_config.yaml — все параметры в одном месте
data:
source: s3://bucket/data/
start_date: "2024-01-01"
end_date: "2024-12-31"
version: "v2.3"
features:
categorical_encoding: "ordinal"
numerical_scaling: "standard"
handle_missing: "median"
model:
type: "lgbm"
n_estimators: 500
learning_rate: 0.05
max_depth: 6
num_leaves: 31
CI/CD для ML-пайплайнів
# .github/workflows/ml-pipeline.yml
on:
push:
paths:
- 'pipelines/**'
- 'features/**'
jobs:
test-and-train:
steps:
- name: Unit tests для feature engineering
run: pytest tests/features/ -v
- name: Integration test на subset данных
run: python run_pipeline.py --mode=test --data-fraction=0.01
- name: Full training run
if: github.ref == 'refs/heads/main'
run: python run_pipeline.py --mode=full
- name: Model evaluation gate
run: python evaluate_model.py --fail-on-degradation
Моніторинг пайплайну
Метрики, які слід відстежувати: час виконання кожного кроку, обсяг оброблених даних, distribution вхідних даних (data drift), метрики моделі на validation set. Алерти при: падінні кроку, аномальній зміні метрик даних, деградації model metrics нижче threshold.
Терміни проектування
Тиждень 1–2: Аудит існуючого ML-стеку, вибір інструментів, проектування архітектури
Тиждень 3–4: Реалізація базового пайплайну для однієї моделі
Місяць 2: Feature store, model registry, evaluation gate
Місяць 3: CI/CD, моніторинг, документація. Перенесення другої моделі на нову архітектуру







