Skip to content

필사 모드: MLflow 완벽 가이드: 실험 추적부터 Model Registry, 프로덕션 배포까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

MLflow란?

MLflow는 ML 라이프사이클을 관리하는 오픈소스 플랫폼입니다. 네 가지 핵심 컴포넌트로 구성됩니다:

  • MLflow Tracking: 실험 파라미터, 메트릭, 아티팩트 기록
  • MLflow Projects: 재현 가능한 ML 코드 패키징
  • MLflow Models: 다양한 프레임워크의 모델을 통일된 형식으로 패키징
  • MLflow Model Registry: 모델 버전 관리 및 배포 워크플로우

설치 및 서버 설정

기본 설치

# pip 설치
pip install mlflow

# 추가 프레임워크 지원
pip install mlflow[extras]  # sklearn, tensorflow, pytorch 등

# 서버 시작 (로컬)
mlflow server --host 0.0.0.0 --port 5000

# PostgreSQL + S3 백엔드로 프로덕션 서버
mlflow server \
  --backend-store-uri postgresql://mlflow:password@localhost:5432/mlflow \
  --default-artifact-root s3://mlflow-artifacts/ \
  --host 0.0.0.0 --port 5000

Docker Compose로 배포

# docker-compose.yml
services:
  mlflow:
    image: ghcr.io/mlflow/mlflow:v3.15.1
    ports:
      - '5000:5000'
    environment:
      - MLFLOW_BACKEND_STORE_URI=postgresql://mlflow:password@postgres:5432/mlflow
      - MLFLOW_DEFAULT_ARTIFACT_ROOT=s3://mlflow-artifacts/
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
    command: >
      mlflow server
      --backend-store-uri postgresql://mlflow:password@postgres:5432/mlflow
      --default-artifact-root s3://mlflow-artifacts/
      --host 0.0.0.0 --port 5000
    depends_on:
      - postgres

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: mlflow
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mlflow
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

처음 5분: 서버를 띄우고 첫 Run을 남기기

이 글의 코드는 MLflow 3.15.1을 기준으로 정리했습니다. 2.x에서 3.x로 넘어오며 바뀐 인자가 있어, 버전을 먼저 맞추는 편이 빠릅니다.

터미널에 mlflow server만 쳐도 서버는 뜹니다. MLflow 3.7.0부터 SQLite가 기본 백엔드 스토어가 되면서, 인자를 하나도 주지 않으면 실행한 디렉터리에 sqlite:///mlflow.db가 자동으로 생깁니다. --backend-store-uri를 반드시 붙여야 하는 건 아니라는 뜻입니다. 대신 공식 문서는 동시성이 높은 프로덕션 배포라면 PostgreSQL이나 MySQL을 고려하라고 안내합니다. 백엔드 스토어가 지원하는 방언은 sqlite, postgresql, mysql, mssql 네 가지입니다.

현재 CLI 문서에는 mlflow server만 있습니다. 오래된 글에 자주 등장하는 mlflow ui는 명령어 목록에서 찾을 수 없습니다. 제거된 버전이 문서에 명시돼 있지는 않으니 단정하진 않겠지만, 손에 익은 명령이 있다면 mlflow server 쪽으로 옮겨 두는 편이 안전합니다.

# 인자 없이 — MLflow 3.7.0 이상이면 ./mlflow.db 가 자동 생성된다
mlflow server

# 저장 위치를 명시하고 싶을 때
mlflow server \
  --backend-store-uri sqlite:///mlflow.db \
  --artifacts-destination ./mlartifacts \
  --host 127.0.0.1 --port 5000

# 클라이언트 쪽 (스크립트를 실행하는 셸)
export MLFLOW_TRACKING_URI=http://127.0.0.1:5000
export MLFLOW_EXPERIMENT_NAME=iris-classification

브라우저로 5000 포트를 열면 UI가 보입니다. 처음에는 Default 실험 하나뿐이고 Run 목록은 비어 있습니다. 스크립트를 돌렸는데도 목록이 계속 비어 있다면, 대개 클라이언트가 서버를 보고 있지 않은 것입니다. 클라이언트는 mlflow.set_tracking_uri() 호출이나 MLFLOW_TRACKING_URI 환경변수로 서버 주소를 알아내는데, 이 환경변수의 기본값은 None입니다. 아무것도 설정하지 않으면 기록이 서버로 가지 않습니다. 실험 이름은 MLFLOW_EXPERIMENT_NAME, 레지스트리 주소는 MLFLOW_REGISTRY_URI로 따로 지정합니다.

아티팩트 경로도 첫날 헷갈리는 지점입니다. --serve-artifacts는 기본으로 켜져 있습니다. 클라이언트가 스토리지에 직접 붙는 대신 트래킹 서버를 경유한다는 뜻이고, 그래서 UI의 아티팩트 URI가 mlflow-artifacts:/로 시작합니다. 실제 저장 위치는 --artifacts-destination으로 정합니다. 클라이언트가 스토리지에 직접 접근하게 하려면 --no-serve-artifacts를, 반대로 아티팩트 프록시 전용 서버를 따로 세우려면 --artifacts-only를 씁니다. 이 셋을 구분해 두면 학습 로그는 남는데 모델 파일만 안 올라가는 상황에서 어디를 볼지 바로 좁혀집니다.

실험 추적 (Tracking)

기본 사용법

import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score, precision_score

# 트래킹 서버 설정
mlflow.set_tracking_uri("http://localhost:5000")

# 실험 생성/설정
mlflow.set_experiment("iris-classification")

# 데이터 준비
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 실험 실행
with mlflow.start_run(run_name="rf-baseline"):
    # 파라미터 기록
    params = {
        "n_estimators": 100,
        "max_depth": 5,
        "min_samples_split": 2,
        "random_state": 42
    }
    mlflow.log_params(params)

    # 모델 학습
    model = RandomForestClassifier(**params)
    model.fit(X_train, y_train)

    # 예측 및 메트릭
    y_pred = model.predict(X_test)
    metrics = {
        "accuracy": accuracy_score(y_test, y_pred),
        "f1_macro": f1_score(y_test, y_pred, average="macro"),
        "precision_macro": precision_score(y_test, y_pred, average="macro")
    }
    mlflow.log_metrics(metrics)

    # 태그
    mlflow.set_tag("model_type", "random_forest")
    mlflow.set_tag("dataset", "iris")

    # 모델 저장 (MLflow 3부터 artifact_path= 대신 name=)
    mlflow.sklearn.log_model(
        model,
        name="model",
        registered_model_name="iris-classifier"
    )

    # 커스텀 아티팩트 (그래프, 보고서 등)
    import matplotlib.pyplot as plt
    from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

    cm = confusion_matrix(y_test, y_pred)
    fig, ax = plt.subplots()
    ConfusionMatrixDisplay(cm).plot(ax=ax)
    fig.savefig("confusion_matrix.png")
    mlflow.log_artifact("confusion_matrix.png")

    print(f"Run ID: {mlflow.active_run().info.run_id}")
    print(f"Metrics: {metrics}")

하이퍼파라미터 튜닝 추적

import optuna
import mlflow

def objective(trial):
    params = {
        "n_estimators": trial.suggest_int("n_estimators", 50, 500),
        "max_depth": trial.suggest_int("max_depth", 2, 20),
        "min_samples_split": trial.suggest_int("min_samples_split", 2, 10),
        "min_samples_leaf": trial.suggest_int("min_samples_leaf", 1, 5),
    }

    with mlflow.start_run(nested=True, run_name=f"trial-{trial.number}"):
        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)

        return accuracy

# Optuna 스터디 실행
with mlflow.start_run(run_name="hyperparameter-tuning"):
    study = optuna.create_study(direction="maximize")
    study.optimize(objective, n_trials=50)

    # 최적 결과 기록
    mlflow.log_params(study.best_params)
    mlflow.log_metric("best_accuracy", study.best_value)
    mlflow.set_tag("best_trial", study.best_trial.number)

PyTorch 모델 추적

import torch
import torch.nn as nn
import mlflow.pytorch

class SimpleNet(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_dim, output_dim)

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

with mlflow.start_run(run_name="pytorch-model"):
    model = SimpleNet(4, 32, 3)
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    criterion = nn.CrossEntropyLoss()

    mlflow.log_params({
        "hidden_dim": 32,
        "learning_rate": 0.001,
        "optimizer": "Adam",
        "epochs": 100
    })

    for epoch in range(100):
        # 학습 로직...
        loss = criterion(model(X_tensor), y_tensor)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 에폭별 메트릭 기록
        mlflow.log_metric("train_loss", loss.item(), step=epoch)

    # PyTorch 모델 저장
    mlflow.pytorch.log_model(model, "model")

Run 하나가 실제로 남기는 것

mlflow.start_run()으로 열리는 Run은 네 종류의 기록을 담습니다. 파라미터, 메트릭, 태그, 그리고 아티팩트입니다. 파라미터는 문자열로 저장되고, 메트릭은 숫자이며 step을 함께 넘기면 시간축을 가진 곡선이 됩니다.

start_run()이 받는 인자는 run_id, experiment_id, run_name, nested, parent_run_id, tags, description, log_system_metrics입니다. 튜닝 코드에서 쓴 nested=True는 부모 Run 안에 자식 Run을 만드는 스위치인데, 자식에는 mlflow.parentRunId 시스템 태그가 자동으로 붙습니다. UI에서 트리로 접히는 것도 이 태그 덕분입니다.

MLflow 3에서 눈에 띄게 달라진 부분은 모델 로깅입니다. 첫째, mlflow.sklearn.log_model()mlflow.pyfunc.log_model()의 인자가 name=으로 바뀌었습니다. 문서는 artifact_path=가 deprecated이니 name=을 쓰라고 명시하고 있고, 이 변경은 MLflow 3.0부터입니다. 둘째, mlflow.start_run() 컨텍스트 없이도 log_model()을 호출할 수 있습니다. 반환되는 ModelInfo에는 model_id가 들어 있고, 이 값으로 models:/ 형태의 URI를 만들어 다시 불러올 수 있습니다. 셋째, mlflow.sklearn.log_model()serialization_format 기본값이 skops입니다. cloudpickle을 전제로 로딩 환경을 맞춰 두었다면 확인이 필요합니다.

import mlflow
from mlflow.models import infer_signature

signature = infer_signature(model_input=X_train, model_output=model.predict(X_train))

# MLflow 3: start_run() 없이도 로깅되고, ModelInfo 를 돌려받는다
info = mlflow.sklearn.log_model(
    model,
    name="model",                 # artifact_path= 는 deprecated
    signature=signature,
    input_example=X_train[:2],
    registered_model_name="iris-classifier",
)

print(info.model_id)
loaded = mlflow.pyfunc.load_model(f"models:/{info.model_id}")

infer_signature(model_input=None, model_output=None, params=None)는 입출력 스키마를 추론해 모델과 함께 저장합니다. 시그니처가 붙어 있으면 서빙 단계에서 컬럼 수나 타입이 어긋났을 때 예측이 조용히 이상해지는 대신 요청 단계에서 걸러집니다.

기록이 쌓이면 mlflow.search_runs()로 꺼냅니다. 이 함수는 pandas DataFrame을 돌려줍니다. 실험 결과를 그대로 표로 받아 정렬하고 groupby 할 수 있다는 뜻입니다. 컬럼 이름은 저장 계층의 접두사를 그대로 따라갑니다.

run_id  status  start_time  params.n_estimators  params.max_depth  metrics.accuracy  tags.model_type

처음에는 필터 없이 한 번 호출해 .columns를 찍어 보고 그다음에 원하는 열만 고르는 순서가 편합니다.

Model Registry

모델 등록 및 버전 관리

from mlflow import MlflowClient

client = MlflowClient()

# 모델 등록 (log_model에서 registered_model_name 사용 시 자동 등록)
# 또는 수동 등록:
result = client.create_registered_model(
    name="iris-classifier",
    description="Iris 꽃 분류 모델"
)

# 특정 실행의 모델을 버전으로 등록
model_version = client.create_model_version(
    name="iris-classifier",
    source=f"runs:/{run_id}/model",
    run_id=run_id,
    description="RandomForest baseline v1"
)

print(f"Model Version: {model_version.version}")

Alias를 활용한 배포 관리

# MLflow 2.x에서는 Alias 사용 (Stage는 deprecated)
client = MlflowClient()

# 프로덕션 alias 설정
client.set_registered_model_alias(
    name="iris-classifier",
    alias="champion",
    version=3
)

# 도전자 모델 설정
client.set_registered_model_alias(
    name="iris-classifier",
    alias="challenger",
    version=5
)

# Alias로 모델 로드
champion_model = mlflow.pyfunc.load_model("models:/iris-classifier@champion")
challenger_model = mlflow.pyfunc.load_model("models:/iris-classifier@challenger")

# A/B 테스트
champion_pred = champion_model.predict(X_test)
challenger_pred = challenger_model.predict(X_test)

print(f"Champion accuracy: {accuracy_score(y_test, champion_pred)}")
print(f"Challenger accuracy: {accuracy_score(y_test, challenger_pred)}")

모델 태그 활용

# 모델 버전에 태그 추가
client.set_model_version_tag(
    name="iris-classifier",
    version=3,
    key="validation_status",
    value="approved"
)

client.set_model_version_tag(
    name="iris-classifier",
    version=3,
    key="approved_by",
    value="data-science-lead"
)

# 태그로 모델 검색
from mlflow import search_model_versions

approved_versions = search_model_versions(
    "name='iris-classifier' AND tag.validation_status='approved'"
)

모델 서빙

MLflow 내장 서빙

# 로컬 REST API 서빙
mlflow models serve \
  -m "models:/iris-classifier@champion" \
  --port 8080 \
  --env-manager local

# 테스트 요청
curl -X POST http://localhost:8080/invocations \
  -H "Content-Type: application/json" \
  -d '{"inputs": [[5.1, 3.5, 1.4, 0.2]]}'

서빙 명령의 기본값

mlflow models serve는 기본값이 은근히 많이 물립니다. -p/--port는 5000, -h/--host는 127.0.0.1, -w/--workers는 1, -t/--timeout은 60초입니다. 호스트 기본값이 루프백이라 컨테이너 안에서 그대로 띄우면 밖에서 붙지 않고, 워커가 하나라 부하 테스트 숫자가 기대보다 낮게 나옵니다.

환경 관리자는 --env-manager로 고릅니다. 유효한 값은 local, virtualenv, uv, conda이고 기본값은 virtualenv입니다. 기본값이 virtualenv라는 것은 서빙을 띄울 때마다 격리 환경을 새로 만든다는 뜻이라, 첫 기동이 예상보다 오래 걸립니다. 이미 의존성이 맞춰진 이미지 안이라면 --env-manager local이 가장 빠르고, 재현성을 챙기면서 속도도 원한다면 uv를 써 볼 만합니다.

/invocations 엔드포인트가 받는 페이로드 키는 dataframe_split, dataframe_records, instances, inputs, params 다섯 가지입니다. 다섯 개 모두 현재 유효하며 deprecated 표시가 붙은 것은 없습니다.

mlflow models serve \
  -m "models:/iris-classifier@champion" \
  --host 0.0.0.0 --port 8080 \
  --workers 4 \
  --env-manager local

FastAPI 커스텀 서빙

from fastapi import FastAPI
import mlflow.pyfunc
import numpy as np

app = FastAPI()

# 모델 로드 (서버 시작 시 1회)
model = mlflow.pyfunc.load_model("models:/iris-classifier@champion")

@app.post("/predict")
async def predict(features: list[list[float]]):
    predictions = model.predict(np.array(features))
    return {
        "predictions": predictions.tolist(),
        "model_version": "champion"
    }

@app.get("/health")
async def health():
    return {"status": "healthy", "model": "iris-classifier@champion"}

실험 비교 및 분석

MLflow UI에서 비교

# 실험 검색 (CLI)
mlflow runs list --experiment-id 1

# 메트릭 기반 검색
mlflow runs list \
  --experiment-id 1 \
  --filter "metrics.accuracy > 0.95" \
  --order-by "metrics.accuracy DESC"

Python API로 분석

import mlflow
import pandas as pd

# 실험의 모든 실행 조회
runs = mlflow.search_runs(
    experiment_ids=["1"],
    filter_string="metrics.accuracy > 0.9",
    order_by=["metrics.accuracy DESC"],
    max_results=10
)

# DataFrame으로 분석
print(runs[["run_id", "params.n_estimators", "params.max_depth", "metrics.accuracy"]])

# 최적 실행 찾기
best_run = runs.iloc[0]
print(f"Best run: {best_run.run_id}, Accuracy: {best_run['metrics.accuracy']}")

검색 문법: 여기서 대부분 한 번은 막힌다

mlflow.search_runs()와 UI 검색창은 같은 필터 문법을 씁니다. 짧은 문법인데, 처음 쓰면 반드시 한 번은 걸리는 규칙이 있습니다.

접두사대상
metrics.숫자 메트릭metrics.accuracy > 0.72
params.하이퍼파라미터 (문자열 저장)params.n_estimators = "100"
tags.사용자/시스템 태그tags.environment IS NOT NULL
datasets.데이터셋 정보datasets.name = "iris"
attributes.Run 자체의 속성attributes.status = "FINISHED"

attributes.로 접근할 수 있는 항목은 status, user_id, run_name, run_id, start_time, end_time입니다.

막히는 지점은 세 가지입니다. 첫째, AND는 지원되지만 OR는 지원되지 않습니다. 조건 두 개를 or로 묶고 싶으면 쿼리를 두 번 던져 DataFrame 단계에서 합치는 수밖에 없습니다. 둘째, 파라미터는 전부 문자열로 저장되므로 숫자처럼 보여도 큰따옴표로 감싸야 합니다. 셋째, LIKE는 대소문자를 구분하고 ILIKE는 구분하지 않습니다. IS NULLIS NOT NULL은 파라미터와 태그에만 쓸 수 있습니다.

runs = mlflow.search_runs(
    experiment_ids=["1"],
    filter_string='metrics.accuracy > 0.72 AND metrics.loss <= 0.15',
    order_by=["metrics.accuracy DESC"],
)

# 파라미터는 문자열 — 따옴표가 없으면 원하는 결과가 나오지 않는다
exact = mlflow.search_runs(filter_string='params.n_estimators = "100"')

# OR 가 없으므로 두 번 조회해서 붙인다
import pandas as pd

merged = pd.concat([
    mlflow.search_runs(filter_string='tags.model_type = "random_forest"'),
    mlflow.search_runs(filter_string='tags.model_type = "xgboost"'),
]).drop_duplicates(subset="run_id")

프로덕션 체크리스트

□ 백엔드 스토어를 PostgreSQL/MySQL로 설정
□ 아티팩트 스토어를 S3/GCS/MinIO로 설정
□ 인증/권한 설정 (OIDC, Basic Auth)
□ 자동 실험 기록 (autolog) 설정
□ Model Registry alias 규칙 정립
□ CI/CD에서 모델 검증 자동화
□ 모델 서빙 헬스체크 설정
□ 실험 정리 정책 (오래된 실행 아카이브)

실패 사례와 함정

증상부터 적습니다. 실제로 마주치는 순서가 그렇습니다.

증상: 학습은 끝났는데 Run이 계속 RUNNING 상태다. 진단: autolog와 수동 Run이 겹쳤습니다. 활성 Run이 없을 때 mlflow.autolog()는 Run을 스스로 만들고 학습이 끝나면 스스로 닫습니다. 그런데 이미 열려 있는 Run이 있으면, 문서 설명대로 그 Run에 기록은 하되 학습이 끝나도 자동으로 닫지는 않습니다. 블록 없이 start_run()을 호출했다면 mlflow.end_run()을 직접 불러야 합니다.

증상: 파라미터 서치를 50번 돌렸는데 자식 Run이 5개뿐이다. 진단: sklearn autolog는 파라미터 서치 estimator에 대해 부모 Run 하나와 자식 Run 여러 개를 만드는데, 자식 개수가 max_tuning_runs로 제한됩니다. 기본값이 5입니다. 나머지 시도는 개별 Run으로 아예 기록되지 않습니다. 전부 보려면 이 값을 올리세요. autolog가 지원하는 플레이버는 Keras/TensorFlow, LightGBM, Paddle, PySpark, PyTorch, scikit-learn, Spark, statsmodels, XGBoost입니다.

증상: log_model()에서 deprecation 경고가 뜬다. 진단: artifact_path=를 쓰고 있습니다. MLflow 3.0부터 name=으로 바뀌었고, 문서에도 artifact_path=는 deprecated이니 name=을 쓰라고 적혀 있습니다. 경고 단계라 당장 깨지지는 않지만, 새로 쓰는 코드는 name=으로 통일하는 편이 낫습니다.

증상: mlflow.register_model() 호출이 몇 분 동안 돌아오지 않는다. 진단: 이 함수는 await_registration_for 인자를 갖고 있고 기본값이 300초입니다. 모델 버전이 준비될 때까지 최대 5분을 기다린다는 뜻입니다. CI 파이프라인이 이유 없이 5분 늘어났다면 대개 여기입니다.

증상: 예전 튜토리얼의 Stage 코드가 경고를 내거나 동작하지 않는다. 진단: 문서는 Model Stage가 deprecated이며 향후 메이저 릴리스에서 제거될 예정이라고 안내합니다. 대상 API는 transition_model_version_stage()입니다. 제거 버전은 아직 발표되지 않았지만, 지금 새로 짜는 코드라면 alias와 태그로 옮기는 게 맞습니다. 옛 Production 스테이지에 대응하는 이름으로 문서가 예시로 드는 것이 champion입니다. alias를 붙일 때는 client.set_registered_model_alias(), 읽을 때는 client.get_model_version_by_alias(), 뗄 때는 client.delete_registered_model_alias()를 씁니다. 모델을 부를 때 models:/iris-classifier@champion 형태의 URI를 쓰면 버전 번호를 코드에 박지 않아도 됩니다.

증상: 여러 명이 동시에 학습을 돌리자 기록이 간헐적으로 실패한다. 진단: 백엔드 스토어가 SQLite인지 먼저 확인하세요. SQLite는 파일 락 기반이라 동시 쓰기가 몰리면 대기하거나 실패합니다. 공식 문서의 안내는 명확합니다. 동시성이 높은 프로덕션 배포라면 PostgreSQL이나 MySQL을 고려하라는 것입니다. 혼자 쓰는 노트북이면 SQLite로 충분하고, 팀이 붙으면 옮길 시점입니다.

# autolog 가 Run 을 닫아 주는 경우와 아닌 경우
mlflow.autolog()

model.fit(X_train, y_train)          # 활성 Run 없음 -> 만들고 닫아 준다

with mlflow.start_run(run_name="manual"):
    model.fit(X_train, y_train)      # 여기에 기록되고, 블록을 나갈 때 닫힌다

run = mlflow.start_run(run_name="leaky")
model.fit(X_train, y_train)          # 기록은 되지만 닫히지 않는다
mlflow.end_run()                     # 직접 닫아야 한다

언제 MLflow를 쓰지 않나

도입 글이 잘 안 적는 부분이라 따로 둡니다.

노트북 하나로 끝나는 일회성 분석이라면 MLflow는 과합니다. 기준을 하나 잡자면 이렇습니다. 같은 코드를 파라미터만 바꿔 세 번 이상 돌릴 생각이 없다면 아직 이릅니다. 반대로 지난주에 돌린 그 설정이 뭐였는지 되짚는 질문이 한 번이라도 나왔다면 그때가 도입 시점입니다.

MLflow가 하지 않는 일도 분명히 해 두는 편이 낫습니다.

  • 오케스트레이션을 하지 않습니다. 스케줄링, 재시도, 의존성 그래프는 Airflow나 Argo 같은 도구의 몫입니다.
  • 피처 스토어가 아닙니다. 피처를 계산해 주지도, 학습과 서빙 사이의 정합성을 보장해 주지도 않습니다.
  • 데이터 버전 관리 도구가 아닙니다. Run에 데이터셋 정보를 붙일 수는 있지만 데이터 자체의 스냅샷을 떠 주지는 않습니다.
  • 프로덕션 모니터링 도구가 아닙니다. 배포된 모델의 드리프트나 지연 시간은 별도 관측 스택이 필요합니다.

정리하면 MLflow는 무엇을 어떤 설정으로 돌렸고 결과가 어땠는지를 남기는 장부입니다.

참고 자료

아래 링크는 2026-08-16 확인 기준입니다. 본문의 인자와 기본값은 MLflow 3.15.1 문서를 따랐고, 버전이 다르면 달라질 수 있습니다. 정확한 API는 사용 중인 버전의 문서에서 확인하세요.


퀴즈

📝 확인 퀴즈 (6문제)

Q1. MLflow의 네 가지 핵심 컴포넌트는?

Tracking, Projects, Models, Model Registry

Q2. mlflow.log_params와 mlflow.log_metrics의 차이점은?

log_params는 학습 하이퍼파라미터(문자열)를 기록하고, log_metrics는 성능 지표(숫자)를 기록합니다. 메트릭은 step 파라미터로 에폭별 추적이 가능합니다.

Q3. MLflow 2.x에서 모델 배포 관리에 사용하는 개념은?

Alias (예: @champion, @challenger). Stage는 deprecated되었습니다.

Q4. nested=True 파라미터는 언제 사용하나요?

하이퍼파라미터 튜닝처럼 부모 실행 안에서 여러 자식 실행을 기록할 때 사용합니다.

Q5. 아티팩트 스토어로 S3를 사용하는 이유는?

모델 파일, 그래프 등 대용량 아티팩트를 확장 가능한 객체 스토리지에 저장하여 팀 간 공유와 버전 관리가 용이합니다.

Q6. mlflow.autolog()의 장점과 단점은?

장점: 코드 수정 없이 자동으로 파라미터/메트릭/모델을 기록. 단점: 불필요한 정보가 많이 기록될 수 있고, 커스텀 메트릭은 별도 기록 필요.

현재 단락 (1/322)

MLflow는 ML 라이프사이클을 관리하는 오픈소스 플랫폼입니다. 네 가지 핵심 컴포넌트로 구성됩니다:

작성 글자: 0원문 글자: 15,277작성 단락: 0/322