MLOps κ°μ
MLOps κ°μ¶
1. MLOpsλ?¶
MLOps(Machine Learning Operations)λ λ¨Έμ λ¬λ λͺ¨λΈμ κ°λ°, λ°°ν¬, μ΄μμ μλννκ³ ν¨μ¨μ μΌλ‘ κ΄λ¦¬νκΈ° μν μ€λ¬΄ λ°©λ²λ‘ μ λλ€.
κΈ°μ‘΄ ML νλ‘μ νΈμ λ¬Έμ μ :
- λͺ¨λΈμ΄ λ
ΈνΈλΆμμλ§ μλ
- μ€ν μ¬ν λΆκ°λ₯
- λ°°ν¬ ν μ±λ₯ μ ν κ°μ§ μ΄λ €μ
- μλ μ¬νμ΅ νμ
MLOpsμ λͺ©ν:
- μλνλ ML νμ΄νλΌμΈ
- μ¬ν κ°λ₯ν μ€ν
- μ§μμ μΈ λͺ¨λΈ λͺ¨λν°λ§
- μλ μ¬νμ΅ λ° λ°°ν¬
1.1 MLOpsμ ν΅μ¬ μμΉ¶
"""
MLOpsμ ν΅μ¬ μμΉ
"""
# 1. μ¬νμ± (Reproducibility)
# - λμΌν λ°μ΄ν°μ μ½λλ‘ λμΌν κ²°κ³Όλ₯Ό μ»μ μ μμ΄μΌ ν¨
experiment_config = {
"data_version": "v1.2.0",
"code_version": "git:abc123",
"random_seed": 42,
"hyperparameters": {"lr": 0.001, "epochs": 100}
}
# 2. μλν (Automation)
# - μλ μμ
μ΅μν, νμ΄νλΌμΈμΌλ‘ μλν
pipeline_stages = [
"data_validation",
"feature_engineering",
"model_training",
"model_evaluation",
"model_deployment"
]
# 3. λͺ¨λν°λ§ (Monitoring)
# - λͺ¨λΈ μ±λ₯κ³Ό λ°μ΄ν° νμ§ μ§μ κ°μ
monitoring_metrics = {
"model_accuracy": 0.95,
"prediction_latency_p99": "50ms",
"data_drift_score": 0.02
}
# 4. λ²μ κ΄λ¦¬ (Versioning)
# - λ°μ΄ν°, μ½λ, λͺ¨λΈ λͺ¨λ λ²μ κ΄λ¦¬
versions = {
"data": "s3://bucket/data/v2.0/",
"model": "models/classifier_v3.2.1",
"config": "configs/production.yaml"
}
2. DevOps vs MLOps¶
2.1 μ ν΅μ DevOps¶
κ°λ°μ β μ½λ μμ± β λΉλ β ν
μ€νΈ β λ°°ν¬ β λͺ¨λν°λ§
β β
βββββββββββ νΌλλ°± ββββββββ
2.2 MLOpsμ μΆκ° μμ¶
λ°μ΄ν° κ³Όνμ β λ°μ΄ν° β νΌμ² μμ§λμ΄λ§ β λͺ¨λΈ νμ΅ β κ²μ¦ β λ°°ν¬ β λͺ¨λν°λ§
β β
β β λ°μ΄ν° λ리ννΈ κ°μ§ βββββββββββββ
β β λͺ¨λΈ μ±λ₯ μ ν κ°μ§ βββββββββββββ
ββββββββββββββββ μ¬νμ΅ νΈλ¦¬κ±° βββββββββββββββββ
2.3 μ£Όμ μ°¨μ΄μ ¶
"""
DevOps vs MLOps μ°¨μ΄μ
"""
# DevOps: μ½λ μ€μ¬
devops_artifacts = {
"source": "application_code",
"build": "docker_image",
"test": "unit_tests, integration_tests",
"deploy": "kubernetes_manifests"
}
# MLOps: λ°μ΄ν° + μ½λ + λͺ¨λΈ
mlops_artifacts = {
"data": "training_data, validation_data",
"features": "feature_definitions, feature_store",
"code": "training_scripts, serving_code",
"model": "model_weights, model_metadata",
"experiments": "hyperparameters, metrics, artifacts"
}
# MLOps κ³ μ μ κ³Όμ
mlops_challenges = [
"λ°μ΄ν° νμ§ κ΄λ¦¬",
"νΌμ² μΌκ΄μ± μ μ§",
"λͺ¨λΈ λ²μ κ΄λ¦¬",
"A/B ν
μ€νΈ",
"λ리ννΈ κ°μ§",
"λͺ¨λΈ ν΄μ κ°λ₯μ±"
]
| κ΅¬λΆ | DevOps | MLOps |
|---|---|---|
| μ£Όμ μ°μΆλ¬Ό | μ ν리μΌμ΄μ μ½λ | λ°μ΄ν° + λͺ¨λΈ + μ½λ |
| ν μ€νΈ | μ λ/ν΅ν© ν μ€νΈ | + λ°μ΄ν° κ²μ¦, λͺ¨λΈ κ²μ¦ |
| λ°°ν¬ λ¨μ | 컨ν μ΄λ/μλΉμ€ | λͺ¨λΈ + μλΉ μΈνλΌ |
| λͺ¨λν°λ§ | μμ€ν λ©νΈλ¦ | + λͺ¨λΈ μ±λ₯, λ°μ΄ν° λ리ννΈ |
| λ‘€λ°± | μ΄μ λ²μ λ°°ν¬ | λͺ¨λΈ λ²μ λ‘€λ°± + λ°μ΄ν° κ³ λ € |
3. MLOps μ±μλ λ 벨¶
3.1 Googleμ MLOps μ±μλ λͺ¨λΈ¶
Level 0: μλ νλ‘μΈμ€
βββ μ£ΌνΌν° λ
ΈνΈλΆμμ μ€ν
βββ μλμΌλ‘ λͺ¨λΈ λ°°ν¬
βββ λͺ¨λν°λ§ μμ
βββ μ¬νμ΅ νΈλ¦¬κ±° μμ
Level 1: ML νμ΄νλΌμΈ μλν
βββ μλνλ νμ΅ νμ΄νλΌμΈ
βββ μ€ν μΆμ
βββ λͺ¨λΈ λ μ§μ€νΈλ¦¬
βββ κΈ°λ³Έ λͺ¨λν°λ§
Level 2: CI/CD νμ΄νλΌμΈ
βββ μλ ν
μ€νΈ (μ½λ + λ°μ΄ν° + λͺ¨λΈ)
βββ μ§μμ νμ΅ (CT)
βββ μλ μ¬λ°°ν¬
βββ μμ ν λͺ¨λν°λ§ λ° μλ¦Ό
3.2 μ±μλλ³ νΉμ§¶
"""
MLOps μ±μλ λ λ²¨λ³ κ΅¬ν
"""
# Level 0: μλ ML
class Level0_ManualML:
"""
- λ°μ΄ν° κ³Όνμκ° λ
ΈνΈλΆμμ μ€ν
- μμ§λμ΄κ° μλμΌλ‘ λͺ¨λΈ λ°°ν¬
- λλ¬Έ λ°°ν¬ (λΆκΈ°λ³/μ°κ°)
"""
def train(self):
# λ
ΈνΈλΆμμ μ€ν
model = train_model(data)
model.save("model.pkl")
# μλμΌλ‘ μλ²μ 볡μ¬
# Level 1: ML νμ΄νλΌμΈ
class Level1_Pipeline:
"""
- μλνλ νμ΅ νμ΄νλΌμΈ
- μ€ν μΆμ (MLflow)
- λͺ¨λΈ λ μ§μ€νΈλ¦¬
"""
def train_pipeline(self):
with mlflow.start_run():
data = load_data()
model = train_model(data)
mlflow.log_metrics(evaluate(model))
mlflow.sklearn.log_model(model, "model")
# Level 2: CI/CD/CT
class Level2_CICDCT:
"""
- μ½λ λ³κ²½ μ μλ ν
μ€νΈ
- λ°μ΄ν° λ³κ²½ μ μλ μ¬νμ΅
- μ±λ₯ μ ν μ μλ λ‘€λ°±
"""
def continuous_training(self):
if detect_drift() or new_data_available():
trigger_training_pipeline()
if model_performance_degraded():
rollback_to_previous_version()
3.3 μ±μλ 체ν¬λ¦¬μ€νΈ¶
# mlops_maturity_checklist.yaml
level_0:
- λ
ΈνΈλΆ κΈ°λ° μ€ν
- μλ λͺ¨λΈ λ°°ν¬
- λ¬Έμν μμ
level_1:
- μλνλ νμ΅ νμ΄νλΌμΈ
- μ€ν μΆμ μμ€ν
(MLflow/W&B)
- λͺ¨λΈ λ²μ κ΄λ¦¬
- κΈ°λ³Έ λͺ¨λν°λ§
level_2:
- CI/CD for ML
- μλνλ ν
μ€νΈ (λ°μ΄ν°/λͺ¨λΈ)
- μ§μμ νμ΅ (Continuous Training)
- λ리ννΈ κ°μ§ λ° μλ¦Ό
- A/B ν
μ€νΈ μΈνλΌ
- νΌμ² μ€ν μ΄
4. MLOps λꡬ μνκ³¶
4.1 μΉ΄ν κ³ λ¦¬λ³ λꡬ¶
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MLOps λꡬ μνκ³ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β [μ€ν μΆμ ] [νμ΄νλΌμΈ] [νΌμ² μ€ν μ΄] β
β - MLflow - Kubeflow - Feast β
β - Weights & Biases - Airflow - Tecton β
β - Neptune - Prefect - Hopsworks β
β - Comet ML - Dagster β
β β
β [λͺ¨λΈ λ μ§μ€νΈλ¦¬] [μλΉ] [λͺ¨λν°λ§] β
β - MLflow Registry - TorchServe - Evidently β
β - Vertex AI - Triton - Grafana β
β - SageMaker - TFServing - Prometheus β
β - Neptune - BentoML - WhyLabs β
β - Seldon β
β β
β [λ°μ΄ν° λ²μ ] [λΌλ²¨λ§] [μΈνλΌ] β
β - DVC - Label Studio - Kubernetes β
β - Delta Lake - Labelbox - Docker β
β - LakeFS - Prodigy - Terraform β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
4.2 μ£Όμ λꡬ λΉκ΅¶
"""
MLOps λꡬ λΉκ΅
"""
# μ€ν μΆμ λꡬ
experiment_tracking = {
"MLflow": {
"type": "μ€νμμ€",
"features": ["μ€ν μΆμ ", "λͺ¨λΈ λ μ§μ€νΈλ¦¬", "μλΉ"],
"deployment": "self-hosted / managed",
"best_for": "μν°νλΌμ΄μ¦, 컀μ€ν°λ§μ΄μ§"
},
"Weights & Biases": {
"type": "μμ© (λ¬΄λ£ ν°μ΄ μμ)",
"features": ["μ€ν μΆμ ", "νμ΄νΌνλΌλ―Έν° νλ", "μν°ν©νΈ"],
"deployment": "SaaS / self-hosted",
"best_for": "λ₯λ¬λ, νμ
, μκ°ν"
},
"Neptune": {
"type": "μμ© (λ¬΄λ£ ν°μ΄ μμ)",
"features": ["μ€ν μΆμ ", "λ©νλ°μ΄ν° κ΄λ¦¬"],
"deployment": "SaaS",
"best_for": "λκ·λͺ¨ μ€ν κ΄λ¦¬"
}
}
# μλΉ λꡬ
serving_tools = {
"TorchServe": {
"supported": ["PyTorch"],
"features": ["REST/gRPC", "λ°°μΉ μΆλ‘ ", "A/B ν
μ€νΈ"],
"best_for": "PyTorch λͺ¨λΈ"
},
"Triton": {
"supported": ["PyTorch", "TensorFlow", "ONNX", "TensorRT"],
"features": ["λ©ν°λͺ¨λΈ", "GPU μ΅μ ν", "λμ λ°°μΉ"],
"best_for": "κ³ μ±λ₯ μΆλ‘ , λ©ν° νλ μμν¬"
},
"TFServing": {
"supported": ["TensorFlow"],
"features": ["REST/gRPC", "λ²μ κ΄λ¦¬"],
"best_for": "TensorFlow λͺ¨λΈ"
}
}
4.3 ν΄λΌμ°λ κ΄λ¦¬ν μλΉμ€¶
"""
ν΄λΌμ°λ MLOps νλ«νΌ
"""
cloud_platforms = {
"AWS SageMaker": {
"components": [
"SageMaker Studio", # κ°λ° νκ²½
"SageMaker Pipelines", # νμ΄νλΌμΈ
"Model Registry", # λͺ¨λΈ κ΄λ¦¬
"SageMaker Endpoints", # μλΉ
"Model Monitor" # λͺ¨λν°λ§
]
},
"Google Vertex AI": {
"components": [
"Workbench", # κ°λ° νκ²½
"Vertex Pipelines", # νμ΄νλΌμΈ
"Model Registry", # λͺ¨λΈ κ΄λ¦¬
"Prediction", # μλΉ
"Model Monitoring" # λͺ¨λν°λ§
]
},
"Azure ML": {
"components": [
"Azure ML Studio", # κ°λ° νκ²½
"Azure Pipelines", # νμ΄νλΌμΈ
"Model Registry", # λͺ¨λΈ κ΄λ¦¬
"Managed Endpoints", # μλΉ
"Data Drift" # λͺ¨λν°λ§
]
}
}
5. MLOps μν€ν μ² ν¨ν΄¶
5.1 κΈ°λ³Έ μν€ν μ²¶
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MLOps κΈ°λ³Έ μν€ν
μ² β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββ βββββββββββββββ βββββββββββββββ β
β β Data ββββββΆβ Feature ββββββΆβ Model β β
β β Lake β β Store β β Training β β
β βββββββββββ βββββββββββββββ ββββββββ¬βββββββ β
β β β
β βΌ β
β βββββββββββ βββββββββββββββ ββββββββββββββββ β
β β Monitor βββββββ Model βββββββ Model β β
β β /Alert β β Serving β β Registry β β
β ββββββ¬βββββ βββββββββββββββ ββββββββββββββββ β
β β β
β βββββββββββ Retrain Trigger ββββββββββββββββββΆ Training β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
5.2 μ½λ μμ¶
"""
MLOps νμ΄νλΌμΈ κΈ°λ³Έ ꡬ쑰
"""
from typing import Dict, Any
class MLOpsPipeline:
"""κΈ°λ³Έ MLOps νμ΄νλΌμΈ"""
def __init__(self, config: Dict[str, Any]):
self.config = config
self.experiment_tracker = None
self.model_registry = None
self.feature_store = None
def data_ingestion(self):
"""λ°μ΄ν° μμ§ λ° κ²μ¦"""
raw_data = self.load_data(self.config["data_source"])
validated_data = self.validate_data(raw_data)
return validated_data
def feature_engineering(self, data):
"""νΌμ² μμ§λμ΄λ§"""
features = self.feature_store.get_features(
entity_ids=data["entity_ids"],
feature_list=self.config["features"]
)
return features
def train(self, features):
"""λͺ¨λΈ νμ΅"""
with self.experiment_tracker.start_run():
model = self.train_model(features)
metrics = self.evaluate(model)
self.experiment_tracker.log_metrics(metrics)
return model
def register(self, model):
"""λͺ¨λΈ λ±λ‘"""
if self.passes_quality_gate(model):
self.model_registry.register(
model=model,
stage="staging"
)
def deploy(self, model_version: str):
"""λͺ¨λΈ λ°°ν¬"""
model = self.model_registry.load(model_version)
self.serving_endpoint.update(model)
def monitor(self):
"""λͺ¨λΈ λͺ¨λν°λ§"""
if self.detect_drift():
self.trigger_retraining()
6. μμνκΈ°¶
6.1 첫 MLOps νλ‘μ νΈ¶
"""
MLOps μμνκΈ°: MLflowλ‘ μ€ν μΆμ
"""
import mlflow
import mlflow.sklearn
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
# MLflow μ€μ
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("iris-classification")
# λ°μ΄ν° μ€λΉ
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.2, random_state=42
)
# MLflow μ€ν μμ
with mlflow.start_run(run_name="random-forest-v1"):
# νμ΄νΌνλΌλ―Έν° λ‘κΉ
params = {"n_estimators": 100, "max_depth": 5}
mlflow.log_params(params)
# λͺ¨λΈ νμ΅
model = RandomForestClassifier(**params, random_state=42)
model.fit(X_train, y_train)
# νκ° λ° λ©νΈλ¦ λ‘κΉ
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
mlflow.log_metric("accuracy", accuracy)
# λͺ¨λΈ μ μ₯
mlflow.sklearn.log_model(model, "model")
print(f"Run ID: {mlflow.active_run().info.run_id}")
print(f"Accuracy: {accuracy:.4f}")
6.2 λ‘컬 νκ²½ μ€μ ¶
# MLflow μλ² μμ
mlflow server \
--backend-store-uri sqlite:///mlflow.db \
--default-artifact-root ./mlruns \
--host 0.0.0.0 \
--port 5000
# λΈλΌμ°μ μμ νμΈ
# http://localhost:5000
μ°μ΅ λ¬Έμ ¶
λ¬Έμ 1: MLOps μ±μλ νκ°¶
νμ¬ νμ ML νλ‘μΈμ€λ₯Ό νκ°νκ³ μ±μλ λ 벨μ κ²°μ νμΈμ.
λ¬Έμ 2: λꡬ μ ν¶
λ€μ μν©μ μ ν©ν MLOps λꡬλ₯Ό μ ννμΈμ: - μκ·λͺ¨ ν, PyTorch μ¬μ©, μμ° μ ν - λκ·λͺ¨ ν, λ©ν° νλ μμν¬, κ³ μ±λ₯ νμ
μμ½¶
| κ°λ | μ€λͺ |
|---|---|
| MLOps | ML λͺ¨λΈμ κ°λ°, λ°°ν¬, μ΄μ μλν |
| DevOps vs MLOps | MLOpsλ λ°μ΄ν°μ λͺ¨λΈ κ΄λ¦¬κ° μΆκ°λ¨ |
| μ±μλ Level 0 | μλ νλ‘μΈμ€, λ ΈνΈλΆ κΈ°λ° |
| μ±μλ Level 1 | μλνλ νμ΄νλΌμΈ, μ€ν μΆμ |
| μ±μλ Level 2 | CI/CD/CT, μ§μμ νμ΅ |
| ν΅μ¬ λꡬ | MLflow, W&B, Kubeflow, Feast, Triton |