본문으로 건너뛰기

01. MLflow 기본과 로컬 실험 기록

✎ 이 문서 수정하기 GitHub에서 수정

학습 설정과 결과를 같은 기준으로 기록한다. Azure 구독 없이 로컬 Python에서 MLflow를 익힌다.

MLflow 기능과 구성과 MLOps에서의 역할을 참고하고, 여기서는 실험 기록을 코드로 확인한다.

준비 조건과 이번 편의 결과

Python 3.11과 PowerShell을 사용한다. 새 폴더에 아래 파일을 만들면 서로 다른 C의 run 세 개와 검증 보고서, 모델이 남는다. AML 플러그인과 workspace는 필요하지 않다.

실험 기록의 단위

개념이 실습에서의 의미예시
Experiment비교 실행의 그룹mlflow-wine-study
Run한 학습 실행C=0.1인 학습 한 번
Parameter실행 설정C, 분할 시드, 데이터 지문
Metric결과 수치검증 정확도, 검증 log loss
Artifact파일 결과분류 보고서, 학습 코드
MLflow Model모델, 입출력, 환경 정보model 경로의 Pipeline
Registered Model이름과 버전으로 관리하는 모델wine-logistic-study의 특정 버전

Tracking URI는 기록 대상이지 학습 컴퓨터를 정하는 값이 아니다. 로컬에서 AML로 옮길 때도 이 둘을 구분한다.

모델을 아티팩트로 올리는 것과 MLflow 모델로 기록하는 것도 다르다. log_artifact()는 파일을 올리고, mlflow.sklearn.log_model()은 모델 파일에 더해 MLmodel, 의존성, 입출력 signature 같은 정보를 묶는다.

실습 환경과 파일 구성

명령은 Python 3.11이 설치된 Windows PowerShell 기준이다. 실습 폴더에서 가상환경 실행 파일을 직접 호출한다.

New-Item -ItemType Directory -Path mlflow-aml-lab
Set-Location .\mlflow-aml-lab
py -3.11 -m venv .venv
New-Item -ItemType Directory -Path src

이 편에서 만드는 파일은 requirements.txt와 src\train.py다. 뒤의 편에서도 같은 폴더를 사용하며 필요한 파일을 추가한다.

mlflow-aml-lab
  requirements.txt
  src
    train.py

패키지 버전

AML 모델 연동을 위해 공식 모델 로깅 문서의 기준에 맞춰 MLflow를 2.16.2로 고정한다. MLflow 3.x 예제는 섞지 않는다.

모델 변환 문서에는 name= 예시도 있지만, 여기서는 2.16.2 기준 artifact_path=를 쓴다. 문서 예제를 섞을 때는 호출 인자와 버전을 함께 확인한다.

requirements.txt:

mlflow==2.16.2
scikit-learn==1.5.1
pandas==2.2.3
numpy==1.26.4
sqlalchemy==2.0.43
.\.venv\Scripts\python.exe -m pip install -r .\requirements.txt
.\.venv\Scripts\python.exe -m pip check

SQLite용 SQLAlchemy도 고정한다. 나머지 의존성은 설치 시점에 결정되므로 같은 환경을 재현하려면 pip freeze 결과를 보관한다.

.\.venv\Scripts\python.exe -m pip freeze | Set-Content -Encoding utf8 .\requirements.local.lock.txt

버전 고정은 예제 실행 기준이며 서버 운영의 보안 권고나 최신 버전 기준은 아니다. UI는 로컬 루프백에서만 연다.

원격 기록만 필요하면 mlflow-skinny를 검토할 수 있다. SQLite, 로컬 서버, UI를 쓰는 이 실습은 전체 mlflow를 설치한다.

로컬에서 한 번의 학습 기록

데이터와 평가 기준

데이터를 학습 60%, 검증 20%, 테스트 20%로 나눈다. 표본 수는 정수로 조정되며, 시드와 분할은 고정하고 C만 바꾼다.

C는 Logistic Regression 규제 강도와 반대로 움직인다. 검증 정확도를 우선하고 동률이면 log loss가 낮은 후보를 선택하며, 테스트 데이터는 선택에 쓰지 않는다.

StandardScaler와 분류기를 Pipeline으로 묶어 학습 데이터에만 전처리를 맞춘 뒤 검증과 테스트에 재사용한다.

학습 코드

src\train.py:

import argparse
import hashlib
import platform
from importlib.metadata import version
from time import perf_counter

import mlflow
import mlflow.sklearn
from mlflow.models import infer_signature
from sklearn.datasets import load_wine
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, log_loss
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler


def load_data():
    dataset = load_wine(as_frame=True)
    fingerprint = hashlib.sha256(
        dataset.frame.to_csv(index=False).encode("utf-8")
    ).hexdigest()
    X_train, X_holdout, y_train, y_holdout = train_test_split(
        dataset.data,
        dataset.target,
        test_size=0.4,
        random_state=42,
        stratify=dataset.target,
    )
    X_val, X_test, y_val, y_test = train_test_split(
        X_holdout,
        y_holdout,
        test_size=0.5,
        random_state=42,
        stratify=y_holdout,
    )
    return X_train, X_val, X_test, y_train, y_val, y_test, fingerprint


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--C", type=float, required=True)
    parser.add_argument("--autolog", action="store_true")
    args = parser.parse_args()
    if args.C <= 0:
        parser.error("--C must be greater than zero")

    X_train, X_val, _, y_train, y_val, _, fingerprint = load_data()
    model = Pipeline([
        ("scale", StandardScaler()),
        ("classifier", LogisticRegression(
            C=args.C, max_iter=1000, random_state=42
        )),
    ])

    if args.autolog:
        mlflow.sklearn.autolog(log_models=False, log_datasets=False)

    with mlflow.start_run() as run:
        mlflow.set_tags({
            "study": "mlflow-aml",
            "model_type": "scaled-logistic-regression",
        })
        mlflow.log_params({
            "C": args.C,
            "split_seed": 42,
            "dataset": "sklearn.load_wine",
            "dataset_sha256": fingerprint,
            "train_rows": len(X_train),
            "validation_rows": len(X_val),
        })

        started = perf_counter()
        model.fit(X_train, y_train)
        fit_seconds = perf_counter() - started
        predictions = model.predict(X_val)
        probabilities = model.predict_proba(X_val)
        mlflow.log_metrics({
            "validation_accuracy": accuracy_score(y_val, predictions),
            "validation_log_loss": log_loss(y_val, probabilities),
            "fit_seconds": fit_seconds,
        })
        mlflow.log_dict(
            classification_report(
                y_val, predictions, output_dict=True, zero_division=0
            ),
            "reports/validation.json",
        )
        mlflow.log_artifact(__file__, artifact_path="source")
        mlflow.log_dict(
            {
                "python": platform.python_version(),
                "packages": {
                    name: version(name)
                    for name in [
                        "mlflow", "scikit-learn", "pandas", "numpy",
                        "cloudpickle",
                    ]
                },
            },
            "environment.json",
        )
        mlflow.sklearn.log_model(
            sk_model=model,
            artifact_path="model",
            signature=infer_signature(X_train, model.predict(X_train)),
            input_example=X_train.head(3),
        )
        print(f"tracking_uri={mlflow.get_tracking_uri()}")
        print(f"run_id={run.info.run_id}")


if __name__ == "__main__":
    main()

Tracking URI와 experiment 이름은 코드에 고정하지 않는다. 로컬은 환경 변수, AML Job은 서비스 실행 문맥을 사용한다.

with mlflow.start_run()은 실행 상태를 기록해 예외와 정상 완료를 구분한다. C 값마다 별도 run을 만든다.

세 번의 실행

모든 명령은 실습 폴더의 루트에서 실행한다. 상대 SQLite 경로를 사용하므로 작업 폴더가 바뀌면 다른 DB를 읽게 된다.

$env:MLFLOW_TRACKING_URI = "sqlite:///mlflow.db"
$env:MLFLOW_REGISTRY_URI = $env:MLFLOW_TRACKING_URI
$env:MLFLOW_EXPERIMENT_NAME = "mlflow-wine-study"

foreach ($c in @("0.01", "0.1", "1.0")) {
    .\.venv\Scripts\python.exe .\src\train.py --C $c
    if ($LASTEXITCODE -ne 0) { throw "Training failed for C=$c" }
}

실행 메타데이터는 SQLite에, 보고서와 모델은 기본 아티팩트 경로 mlruns에 남는다. DB만 복사하면 모델 파일은 이동하지 않는다.

UI에서 확인할 항목

같은 폴더에서 별도 PowerShell 창을 열고 실행한다.

.\.venv\Scripts\mlflow.exe ui --backend-store-uri sqlite:///mlflow.db --host 127.0.0.1 --port 5000

브라우저에서 http://127.0.0.1:5000을 연다. UI 종료는 해당 터미널의 Ctrl+C로 처리한다.

확인 위치남아 있어야 할 내용
Experimentmlflow-wine-study 아래 세 번의 실행
Parameters서로 다른 C, 같은 데이터 지문과 분할 시드
Metrics검증 정확도, 검증 log loss, 학습 시간
Artifactsreports/validation.json, source/train.py, environment.json
Modelmodel 아래 MLmodel과 모델 파일 및 환경 정보

Signature는 기대하는 열과 타입을, 입력 예시는 호출 형태를 설명한다. 데이터 품질이나 업무상 정확성을 보장하지는 않는다.

기록 API를 고르는 기준

메트릭과 파일 기록 문서는 기록의 타입에 따라 API를 나눈다. 보고서를 출력한 것과 구조화된 메트릭으로 저장한 것은 다르다.

남길 정보사용할 API이번 예제와의 연결
실행 설정log_param(), log_params()C, 분할 시드, 데이터 지문
숫자로 비교할 결과log_metric(), log_metrics()검증 정확도와 log loss
단계별로 변하는 숫자log_metric()의 stepepoch별 손실 등을 기록할 때 확장
JSON 또는 YAML 보고서log_dict()클래스별 검증 보고서
기존 파일log_artifact(), log_artifacts()학습 코드와 추가 결과 파일
이미지와 그래프log_image(), log_figure()혼동행렬이나 시각화로 확장
실행을 분류할 정보set_tag(), set_tags()이번 학습을 구분하는 study tag

AML Job의 print()는 진단 로그일 뿐 메트릭 차트에 자동 반영되지 않는다. 오류와 진행 상황은 로그에, 비교 수치는 MLflow 메트릭에 기록한다.

Autologging을 더했을 때의 차이

수동 기록을 확인한 뒤 자동 기록을 추가한다.

.\.venv\Scripts\python.exe .\src\train.py --C 0.1 --autolog

--autolog는 mlflow.sklearn.autolog()를 켠다. 이 예제에서는 모델의 자동 기록을 끄고, 같은 log_model()에서 signature와 입력 예시를 기록한다. 자동 기록과 수동 기록으로 같은 모델을 중복해서 남기지 않기 위해서다.

자동 기록 항목은 프레임워크와 버전에 따라 다르며 평가 기준, 데이터 이용 조건, 선택 이유까지 남기지는 않는다. 검증 메트릭과 데이터 지문은 직접 기록한다.

확인할 결과

세 번의 기본 실행과 --autolog 실행은 별도 run이다. 설정은 Parameters, 수치는 Metrics, 보고서와 코드는 Artifacts에서 확인하며 다음 편에서 코드로 조회한다.

오류 확인

증상확인할 부분
mlflow를 import하지 못함.venv 안의 Python으로 설치하고 실행했는지
SQLAlchemy 관련 import 오류고정한 requirements.txt와 실제 설치 버전이 같은지
기록이 다른 DB에 나타남현재 작업 폴더와 MLFLOW_TRACKING_URI
--C 0 실행이 중단됨양수만 받도록 한 입력 검사이며 정상 동작
UI 포트를 사용할 수 없음이미 사용 중인 포트인지 확인하고 다른 로컬 포트를 선택

정리와 생각할 점

UI는 실행한 터미널에서 Ctrl+C로 종료한다. 다음 편에서 사용할 DB와 아티팩트는 남긴다.

  • 학습 성공과 모델 기록 성공을 같은 것으로 보고 있지 않은가.
  • DB 파일만 남기고 mlruns를 잃으면 어떤 기록을 다시 읽을 수 없는가.
  • 자동 기록 외에 데이터와 평가 조건을 설명할 정보는 무엇인가.

참고 자료

시리즈 목차, 다음: 02. 실행 비교와 메트릭 및 아티팩트 조회

마지막 수정 일자