MLflow κΈ°μ΄
MLflow κΈ°μ΄¶
1. MLflow κ°μ¶
MLflowλ λ¨Έμ λ¬λ λΌμ΄νμ¬μ΄ν΄μ κ΄λ¦¬νκΈ° μν μ€νμμ€ νλ«νΌμ λλ€. μ€ν μΆμ , λͺ¨λΈ ν¨ν€μ§, λ°°ν¬λ₯Ό ν΅ν©μ μΌλ‘ μ§μν©λλ€.
1.1 MLflowμ 4κ°μ§ μ»΄ν¬λνΈ¶
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MLflow μ»΄ν¬λνΈ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Tracking β β Projects β β Models β β
β β β β β β β β
β β - μ€ν μΆμ β β - μ¬ν κ°λ₯νβ β - λͺ¨λΈ ν¬λ§· β β
β β - λ©νΈλ¦ β β νλ‘μ νΈ β β - λ€μν β β
β β - νλΌλ―Έν° β β - μμ‘΄μ± κ΄λ¦¬β β νλ μ΄λ² β β
β β - μν°ν©νΈ β β β β β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Model Registry β β
β β β β
β β - λͺ¨λΈ λ²μ κ΄λ¦¬ - μ€ν
μ΄μ§ μ ν - λͺ¨λΈ μ€λͺ
β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1.2 μ€μΉ λ° μ€μ ¶
# MLflow μ€μΉ
pip install mlflow
# μΆκ° μμ‘΄μ± (μ ν)
pip install mlflow[extras] # λͺ¨λ μΆκ° κΈ°λ₯
pip install mlflow[sklearn] # scikit-learn μ§μ
pip install mlflow[pytorch] # PyTorch μ§μ
# λ²μ νμΈ
mlflow --version
2. MLflow Tracking¶
2.1 κΈ°λ³Έ κ°λ ¶
"""
MLflow Tracking κΈ°λ³Έ κ°λ
"""
# ν΅μ¬ μ©μ΄
mlflow_concepts = {
"Experiment": "κ΄λ ¨ μ€νλ€μ κ·Έλ£Ή (μ: 'churn-prediction')",
"Run": "νλμ νμ΅ μ€ν (νλΌλ―Έν°, λ©νΈλ¦, μν°ν©νΈ ν¬ν¨)",
"Parameters": "μ
λ ₯ μ€μ (learning_rate, epochs λ±)",
"Metrics": "μΆλ ₯ κ²°κ³Ό (accuracy, loss λ±)",
"Artifacts": "νμΌ (λͺ¨λΈ, κ·Έλν, λ°μ΄ν° λ±)",
"Tags": "μ€νμ λν λ©νλ°μ΄ν°"
}
2.2 첫 λ²μ§Έ μ€ν¶
"""
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, precision_score, recall_score, f1_score
# λ°μ΄ν° μ€λΉ
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.set_experiment("iris-classification")
# μ€ν μμ
with mlflow.start_run(run_name="random-forest-baseline"):
# 1. νλΌλ―Έν° λ‘κΉ
params = {
"n_estimators": 100,
"max_depth": 5,
"random_state": 42
}
mlflow.log_params(params)
# 2. λͺ¨λΈ νμ΅
model = RandomForestClassifier(**params)
model.fit(X_train, y_train)
# 3. μμΈ‘
y_pred = model.predict(X_test)
# 4. λ©νΈλ¦ λ‘κΉ
metrics = {
"accuracy": accuracy_score(y_test, y_pred),
"precision": precision_score(y_test, y_pred, average='macro'),
"recall": recall_score(y_test, y_pred, average='macro'),
"f1": f1_score(y_test, y_pred, average='macro')
}
mlflow.log_metrics(metrics)
# 5. λͺ¨λΈ λ‘κΉ
mlflow.sklearn.log_model(model, "model")
# 6. νκ·Έ μΆκ°
mlflow.set_tag("model_type", "RandomForest")
mlflow.set_tag("developer", "ML Team")
# μ€ν μ 보 μΆλ ₯
run = mlflow.active_run()
print(f"Run ID: {run.info.run_id}")
print(f"Experiment ID: {run.info.experiment_id}")
print(f"Metrics: {metrics}")
2.3 νλΌλ―Έν° λ° λ©νΈλ¦ λ‘κΉ ¶
"""
λ€μν λ‘κΉ
λ°©λ²
"""
import mlflow
import numpy as np
with mlflow.start_run():
# λ¨μΌ νλΌλ―Έν°
mlflow.log_param("learning_rate", 0.001)
# λ€μ€ νλΌλ―Έν°
mlflow.log_params({
"batch_size": 32,
"epochs": 100,
"optimizer": "adam"
})
# λ¨μΌ λ©νΈλ¦
mlflow.log_metric("accuracy", 0.95)
# λ€μ€ λ©νΈλ¦
mlflow.log_metrics({
"precision": 0.93,
"recall": 0.91,
"f1": 0.92
})
# μ€ν
λ³ λ©νΈλ¦ (νμ΅ κ³‘μ )
for epoch in range(100):
train_loss = 1.0 / (epoch + 1) + np.random.random() * 0.1
val_loss = 1.0 / (epoch + 1) + np.random.random() * 0.15
mlflow.log_metric("train_loss", train_loss, step=epoch)
mlflow.log_metric("val_loss", val_loss, step=epoch)
# νκ·Έ (κ²μ κ°λ₯ν λ©νλ°μ΄ν°)
mlflow.set_tag("data_version", "v2.0")
mlflow.set_tag("experiment_type", "baseline")
# λ€μ€ νκ·Έ
mlflow.set_tags({
"feature_set": "full",
"preprocessing": "standardized"
})
2.4 μν°ν©νΈ λ‘κΉ ¶
"""
μν°ν©νΈ λ‘κΉ
"""
import mlflow
import matplotlib.pyplot as plt
import pandas as pd
import json
with mlflow.start_run():
# 1. νμΌ λ‘κΉ
# λ¨μΌ νμΌ
with open("config.json", "w") as f:
json.dump({"key": "value"}, f)
mlflow.log_artifact("config.json")
# λλ ν 리 μ 체
mlflow.log_artifacts("./outputs", artifact_path="results")
# 2. κ·Έλν λ‘κΉ
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
ax.set_title("Training Curve")
mlflow.log_figure(fig, "training_curve.png")
plt.close()
# 3. DataFrame λ‘κΉ
(CSV)
df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
df.to_csv("data.csv", index=False)
mlflow.log_artifact("data.csv")
# 4. λμ
λ리λ₯Ό JSONμΌλ‘
results = {"accuracy": 0.95, "model": "RF"}
mlflow.log_dict(results, "results.json")
# 5. ν
μ€νΈ λ‘κΉ
mlflow.log_text("This is a log message", "log.txt")
3. MLflow UI¶
3.1 μλ² μμ¶
# λ‘컬 μλ² μμ (κΈ°λ³Έ)
mlflow ui
# ν¬νΈ μ§μ
mlflow ui --port 5000
# νΈμ€νΈ μ§μ (μΈλΆ μ μ νμ©)
mlflow ui --host 0.0.0.0 --port 5000
# λ°±μλ μ μ₯μ μ§μ
mlflow server \
--backend-store-uri sqlite:///mlflow.db \
--default-artifact-root ./mlruns \
--host 0.0.0.0 \
--port 5000
3.2 Tracking URI μ€μ ¶
"""
Tracking URI μ€μ λ°©λ²
"""
import mlflow
# λ°©λ² 1: μ½λμμ μ€μ
mlflow.set_tracking_uri("http://localhost:5000")
# λ°©λ² 2: νκ²½ λ³μ
# export MLFLOW_TRACKING_URI=http://localhost:5000
# λ°©λ² 3: νμΌ κΈ°λ° (κΈ°λ³Έκ°)
mlflow.set_tracking_uri("file:///path/to/mlruns")
# νμ¬ μ€μ νμΈ
print(mlflow.get_tracking_uri())
3.3 UI κΈ°λ₯ νμ©¶
"""
UIμμ νμ©ν μ μλ κΈ°λ₯λ€
"""
# 1. μ€ν λΉκ΅λ₯Ό μν ꡬ쑰νλ λ‘κΉ
experiments_to_compare = [
{"n_estimators": 50, "max_depth": 3},
{"n_estimators": 100, "max_depth": 5},
{"n_estimators": 200, "max_depth": 10}
]
for params in experiments_to_compare:
with mlflow.start_run():
mlflow.log_params(params)
# νμ΅ λ° νκ°
accuracy = train_and_evaluate(params)
mlflow.log_metric("accuracy", accuracy)
# 2. κ²μ κ°λ₯ν νκ·Έ μΆκ°
with mlflow.start_run():
mlflow.set_tags({
"model_type": "RandomForest",
"feature_version": "v2",
"data_split": "stratified"
})
# 3. μ€ν κ²μ (API)
runs = mlflow.search_runs(
experiment_names=["iris-classification"],
filter_string="metrics.accuracy > 0.9 and params.max_depth = '5'",
order_by=["metrics.accuracy DESC"]
)
print(runs[["run_id", "params.n_estimators", "metrics.accuracy"]])
4. κΈ°λ³Έ μ¬μ© μμ ¶
4.1 scikit-learn λͺ¨λΈ¶
"""
scikit-learn λͺ¨λΈ μ 체 μμ
"""
import mlflow
import mlflow.sklearn
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# λ°μ΄ν° λ‘λ
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42
)
# μ€ν μ€μ
mlflow.set_experiment("wine-classification")
# νμ΄νΌνλΌλ―Έν° 그리λ
param_grid = [
{"n_estimators": 50, "learning_rate": 0.1, "max_depth": 3},
{"n_estimators": 100, "learning_rate": 0.05, "max_depth": 5},
{"n_estimators": 200, "learning_rate": 0.01, "max_depth": 7}
]
for params in param_grid:
with mlflow.start_run(run_name=f"gb-n{params['n_estimators']}"):
# νλΌλ―Έν° λ‘κΉ
mlflow.log_params(params)
mlflow.log_param("test_size", 0.2)
# νμ΄νλΌμΈ μμ±
pipeline = Pipeline([
("scaler", StandardScaler()),
("classifier", GradientBoostingClassifier(**params, random_state=42))
])
# κ΅μ°¨ κ²μ¦
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5)
mlflow.log_metric("cv_mean", cv_scores.mean())
mlflow.log_metric("cv_std", cv_scores.std())
# μ΅μ’
νμ΅
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
# λ©νΈλ¦ λ‘κΉ
from sklearn.metrics import accuracy_score, precision_score, recall_score
mlflow.log_metrics({
"test_accuracy": accuracy_score(y_test, y_pred),
"test_precision": precision_score(y_test, y_pred, average='macro'),
"test_recall": recall_score(y_test, y_pred, average='macro')
})
# Confusion Matrix μκ°ν
fig, ax = plt.subplots(figsize=(8, 6))
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax)
ax.set_xlabel('Predicted')
ax.set_ylabel('Actual')
ax.set_title('Confusion Matrix')
mlflow.log_figure(fig, "confusion_matrix.png")
plt.close()
# Feature Importance
classifier = pipeline.named_steps['classifier']
fig, ax = plt.subplots(figsize=(10, 6))
importance = classifier.feature_importances_
indices = np.argsort(importance)[::-1]
ax.barh(range(len(importance)), importance[indices])
ax.set_yticks(range(len(importance)))
ax.set_yticklabels([wine.feature_names[i] for i in indices])
ax.set_title('Feature Importance')
mlflow.log_figure(fig, "feature_importance.png")
plt.close()
# λͺ¨λΈ λ‘κΉ
mlflow.sklearn.log_model(pipeline, "model")
print(f"Params: {params}")
print(f"Test Accuracy: {accuracy_score(y_test, y_pred):.4f}")
4.2 PyTorch λͺ¨λΈ¶
"""
PyTorch λͺ¨λΈ MLflow μΆμ
"""
import mlflow
import mlflow.pytorch
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import numpy as np
# λ°μ΄ν° μ€λΉ
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
X_train_t = torch.FloatTensor(X_train)
y_train_t = torch.LongTensor(y_train)
X_test_t = torch.FloatTensor(X_test)
y_test_t = torch.LongTensor(y_test)
train_dataset = TensorDataset(X_train_t, y_train_t)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
# λͺ¨λΈ μ μ
class SimpleNN(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.2)
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
# μ€ν μ€μ
mlflow.set_experiment("pytorch-classification")
# νμ΄νΌνλΌλ―Έν°
params = {
"hidden_dim": 64,
"learning_rate": 0.001,
"epochs": 50,
"batch_size": 32
}
with mlflow.start_run():
mlflow.log_params(params)
# λͺ¨λΈ μ΄κΈ°ν
model = SimpleNN(20, params["hidden_dim"], 2)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=params["learning_rate"])
# νμ΅
model.train()
for epoch in range(params["epochs"]):
total_loss = 0
for batch_X, batch_y in train_loader:
optimizer.zero_grad()
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
mlflow.log_metric("train_loss", avg_loss, step=epoch)
# κ²μ¦ (λ§€ 10 μν)
if (epoch + 1) % 10 == 0:
model.eval()
with torch.no_grad():
outputs = model(X_test_t)
_, predicted = torch.max(outputs, 1)
accuracy = (predicted == y_test_t).float().mean().item()
mlflow.log_metric("val_accuracy", accuracy, step=epoch)
model.train()
# μ΅μ’
νκ°
model.eval()
with torch.no_grad():
outputs = model(X_test_t)
_, predicted = torch.max(outputs, 1)
test_accuracy = (predicted == y_test_t).float().mean().item()
mlflow.log_metric("test_accuracy", test_accuracy)
# λͺ¨λΈ λ‘κΉ
mlflow.pytorch.log_model(model, "model")
print(f"Test Accuracy: {test_accuracy:.4f}")
5. Autologging¶
5.1 μλ λ‘κΉ μ€μ ¶
"""
MLflow Autologging
"""
import mlflow
# μ 체 νλ μμν¬ μλ λ‘κΉ
mlflow.autolog()
# νΉμ νλ μμν¬λ§
mlflow.sklearn.autolog()
mlflow.pytorch.autolog()
mlflow.tensorflow.autolog()
mlflow.xgboost.autolog()
mlflow.lightgbm.autolog()
# μλ λ‘κΉ
λΉνμ±ν
mlflow.autolog(disable=True)
5.2 Autologging μμ¶
"""
sklearn autologging μμ
"""
import mlflow
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
# Autologging νμ±ν
mlflow.sklearn.autolog(
log_input_examples=True, # μ
λ ₯ μμ λ‘κΉ
log_model_signatures=True, # λͺ¨λΈ μκ·Έλμ² λ‘κΉ
log_models=True, # λͺ¨λΈ λ‘κΉ
log_datasets=True, # λ°μ΄ν°μ
μ 보 λ‘κΉ
silent=False # λ‘κΉ
λ©μμ§ μΆλ ₯
)
# λ°μ΄ν° μ€λΉ
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.set_experiment("autolog-demo")
# λͺ¨λΈ νμ΅ (μλμΌλ‘ run μμ± λ° λ‘κΉ
)
model = RandomForestClassifier(n_estimators=100, max_depth=5)
model.fit(X_train, y_train)
# μλμΌλ‘ λ‘κΉ
λλ νλͺ©:
# - νλΌλ―Έν°: n_estimators, max_depth, ...
# - λ©νΈλ¦: training_score, ...
# - μν°ν©νΈ: model, feature_importance, ...
6. λͺ¨λΈ λ‘λ λ° μμΈ‘¶
6.1 μ μ₯λ λͺ¨λΈ λ‘λ¶
"""
μ μ₯λ λͺ¨λΈ λ‘λ λ°©λ²
"""
import mlflow
import mlflow.sklearn
# λ°©λ² 1: Run IDλ‘ λ‘λ
model = mlflow.sklearn.load_model("runs:/RUN_ID/model")
# λ°©λ² 2: μν°ν©νΈ κ²½λ‘λ‘ λ‘λ
model = mlflow.sklearn.load_model("file:///path/to/mlruns/0/run_id/artifacts/model")
# λ°©λ² 3: Model Registryμμ λ‘λ (λ€μ λ μ¨μμ μμΈν)
model = mlflow.sklearn.load_model("models:/MyModel/Production")
# λ°©λ² 4: pyfuncμΌλ‘ λ‘λ (νλ μμν¬ λ¬΄κ΄)
model = mlflow.pyfunc.load_model("runs:/RUN_ID/model")
# μμΈ‘
predictions = model.predict(X_test)
6.2 μ΅κ·Ό μ€ν κ²°κ³Ό μ‘°ν¶
"""
μ€ν κ²°κ³Ό μ‘°ν
"""
import mlflow
from mlflow.tracking import MlflowClient
client = MlflowClient()
# μ€ν μ‘°ν
experiment = client.get_experiment_by_name("iris-classification")
print(f"Experiment ID: {experiment.experiment_id}")
# μ€ν κ²μ
runs = client.search_runs(
experiment_ids=[experiment.experiment_id],
filter_string="metrics.accuracy > 0.9",
order_by=["metrics.accuracy DESC"],
max_results=5
)
for run in runs:
print(f"Run ID: {run.info.run_id}")
print(f" Accuracy: {run.data.metrics.get('accuracy')}")
print(f" Params: {run.data.params}")
# μ΅κ³ μ±λ₯ λͺ¨λΈ λ‘λ
best_run = runs[0]
best_model = mlflow.sklearn.load_model(f"runs:/{best_run.info.run_id}/model")
μ°μ΅ λ¬Έμ ¶
λ¬Έμ 1: κΈ°λ³Έ μ€ν μΆμ ¶
Titanic λ°μ΄ν°μ μ μ¬μ©νμ¬ μμ‘΄ μμΈ‘ λͺ¨λΈμ νμ΅νκ³ , MLflowλ‘ μ€νμ μΆμ νμΈμ.
# ννΈ
import mlflow
from sklearn.datasets import fetch_openml
titanic = fetch_openml("titanic", version=1, as_frame=True)
# μ μ²λ¦¬ ν λͺ¨λΈ νμ΅
# mlflowλ‘ νλΌλ―Έν°, λ©νΈλ¦ λ‘κΉ
λ¬Έμ 2: νμ΄νΌνλΌλ―Έν° λΉκ΅¶
μλ‘ λ€λ₯Έ νμ΄νΌνλΌλ―Έν°λ‘ 5κ° μ΄μμ μ€νμ μ€ννκ³ , MLflow UIμμ λΉκ΅νμΈμ.
μμ½¶
| κΈ°λ₯ | λ©μλ | μ€λͺ |
|---|---|---|
| μ€ν μ€μ | mlflow.set_experiment() |
μ€ν κ·Έλ£Ή μ§μ |
| μ€ν μμ | mlflow.start_run() |
μ μ€ν μμ |
| νλΌλ―Έν° | mlflow.log_param(s)() |
μ λ ₯ νλΌλ―Έν° λ‘κΉ |
| λ©νΈλ¦ | mlflow.log_metric(s)() |
μΆλ ₯ λ©νΈλ¦ λ‘κΉ |
| μν°ν©νΈ | mlflow.log_artifact(s)() |
νμΌ λ‘κΉ |
| λͺ¨λΈ | mlflow.sklearn.log_model() |
λͺ¨λΈ μ μ₯ |
| μλ λ‘κΉ | mlflow.autolog() |
μλ μΆμ νμ±ν |