실험 추적과 모델 레지스트리
이 장에서 배울 것
이번 장에서는 여러 번의 모델 학습 결과를 체계적으로 기록하고, 운영에 올릴 모델을 모델 레지스트리로 관리하는 방법을 배웁니다.
핵심은 단순합니다. 실험을 많이 하는 것보다 어떤 실험이 왜 선택되었는지 남기는 것이 더 중요합니다. 운영 환경에서는 "정확도가 높았다"만으로는 부족합니다. 어떤 데이터로 학습했는지, 어떤 코드 버전이었는지, 어떤 기준으로 기존 모델보다 낫다고 판단했는지 설명할 수 있어야 합니다.
이 장을 끝내면 다음을 할 수 있습니다.
실험 추적에 남겨야 할 정보를 구체적으로 정의합니다.
정확도, F1, AUC, RMSE, 지연 시간, 비용을 비즈니스 목표와 연결합니다.
후보, 스테이징, 프로덕션, 보관 단계의 모델 레지스트리 흐름을 설계합니다.
MLflow Tracking과 Model Registry로 실험 비교와 모델 등록을 수행합니다.
감사와 회고를 위한 실험 리포트를 작성합니다.

왜 이 주제가 중요한가
모델 개발 초기에는 노트북 파일 하나와 model_final.pkl 하나로도 충분해 보입니다. 하지만 운영이 시작되면 질문이 달라집니다.
현재 서비스 중인 모델은 어떤 실험에서 나왔는가?
그 모델은 어떤 데이터 버전으로 학습했는가?
이전 모델보다 정말 나은가?
성능은 좋아졌지만 응답 시간이 느려진 것은 아닌가?
장애가 나면 어떤 버전으로 되돌릴 수 있는가?
이 질문에 답하지 못하면 모델 운영은 감에 의존하게 됩니다. 실험 추적은 학습 과정의 기록이고, 모델 레지스트리는 운영 가능한 모델의 이력 관리 장치입니다. MLflow는 실험의 파라미터, 메트릭, 아티팩트를 기록하고, 등록된 모델의 버전·태그·alias를 관리할 수 있는 대표적인 오픈소스 도구입니다.1
지난 장에서 우리는 데이터와 모델을 버전으로 묶어야 한다고 배웠습니다. 이번 장은 그 위에 "여러 실험 중 무엇을 선택할 것인가"라는 의사결정 층을 올립니다.
핵심 개념 설명
실험, 실행, 아티팩트
실험 추적에서 자주 나오는 세 단어가 있습니다.
실험(Experiment)은 하나의 문제를 풀기 위한 작업 묶음입니다. 예를 들어 "고객 이탈 예측 모델 개선"이 하나의 실험이 될 수 있습니다.
실행(Run)은 학습 코드를 한 번 돌린 기록입니다. 같은 실험 안에서 하이퍼파라미터를 바꾸거나 데이터 버전을 바꾸면 실행이 여러 개 생깁니다.
아티팩트(Artifact)는 실행 중 생성된 파일입니다. 학습된 모델 파일, 혼동 행렬 이미지, 피처 중요도 그래프, 검증 리포트, 환경 파일 등이 여기에 속합니다.
운영 관점에서 한 번의 실행에는 최소한 다음 정보가 남아야 합니다.
| 구분 | 예시 | 이유 |
|---|---|---|
| 파라미터 | learning_rate, max_depth, batch_size | 같은 조건으로 재학습하기 위해 |
| 메트릭 | accuracy, F1, AUC, RMSE, latency_ms | 성능과 운영 품질을 비교하기 위해 |
| 아티팩트 | model.pkl, confusion_matrix.png, eval_report.json | 결과를 검토하고 재사용하기 위해 |
| 코드 커밋 | git commit SHA | 어떤 코드가 모델을 만들었는지 알기 위해 |
| 데이터 버전 | DVC hash, snapshot ID, table version | 어떤 데이터로 학습했는지 알기 위해 |
| 실행 환경 | Python, 라이브러리 버전, Docker 이미지 | 환경 차이로 인한 재현 실패를 줄이기 위해 |
| 태그 | team, task, owner, baseline, approved | 검색과 협업을 쉽게 하기 위해 |
여기서 중요한 점은 "많이 기록하기"가 아닙니다. 나중에 재현, 비교, 감사, 롤백에 필요한 정보를 빠짐없이 남기는 것입니다.
메트릭은 비즈니스 목표와 연결되어야 한다
모델 선택 기준은 문제의 목적에 따라 달라집니다.
분류 문제에서 정확도(Accuracy)는 전체 예측 중 맞힌 비율입니다. 데이터가 균형적일 때 이해하기 쉽습니다. 하지만 사기 거래처럼 양성 사례가 적은 문제에서는 정확도만 보면 위험합니다. 대부분을 "정상"이라고 예측해도 정확도가 높게 나올 수 있기 때문입니다.
F1은 정밀도와 재현율의 균형을 봅니다. 놓치면 안 되는 양성 사례가 있고, 동시에 오탐도 줄여야 할 때 유용합니다.
AUC는 여러 임계값에서 모델이 양성과 음성을 얼마나 잘 구분하는지 봅니다. 신용 평가, 랭킹, 위험 점수화처럼 점수 순위가 중요한 문제에 자주 쓰입니다.
회귀 문제에서는 RMSE를 많이 씁니다. 예측 오차가 클수록 더 크게 벌점을 주기 때문에, 큰 오차가 비즈니스 손실로 이어지는 가격 예측이나 수요 예측에 적합합니다.
운영에서는 여기에 지연 시간과 비용이 추가됩니다. 예를 들어 새 모델의 AUC가 0.01 좋아졌지만 P99 지연 시간이 80ms에서 400ms로 늘었다면 실시간 추천 서비스에는 부적합할 수 있습니다. GPU 비용이 두 배가 되었는데 매출 개선이 작다면 배포하지 않는 것이 합리적일 수 있습니다.
따라서 모델 선택 기준은 다음처럼 정의해야 합니다.
운영 후보 등록 기준
- val_f1이 현재 프로덕션 모델보다 0.01 이상 높을 것
- test_auc가 0.85 이상일 것
- P99 추론 지연 시간이 150ms 이하일 것
- 월 추론 비용 증가율이 20% 이하일 것
- 필수 데이터 슬라이스에서 성능 하락이 없을 것좋은 실험 추적은 "가장 높은 숫자"를 찾는 일이 아니라, 운영에 적합한 균형점을 찾는 일입니다.
모델 레지스트리의 역할
모델 레지스트리는 모델을 버전 단위로 등록하고, 각 버전의 상태와 메타데이터를 관리하는 중앙 저장소입니다. MLflow Model Registry는 등록 모델, 모델 버전, 태그, 설명, 모델 URI, alias 등을 통해 모델 생애주기를 관리합니다.2
운영에서는 다음 네 단계를 많이 사용합니다.
후보(Candidate)
실험 결과가 기본 기준을 통과해 운영 검토 대상이 된 모델입니다.스테이징(Staging)
배포 전 검증 단계입니다. 통합 테스트, 입력 스키마 테스트, 섀도 테스트, 성능 비교를 수행합니다.프로덕션(Production)
실제 사용자 요청을 처리하는 모델입니다. 장애 대응과 모니터링의 대상입니다.보관(Archived)
더 이상 기본으로 사용하지 않지만, 감사와 롤백을 위해 보존하는 모델입니다.
중요한 것은 이 단계를 꼭 특정 도구의 stage 기능으로 구현해야 한다는 뜻은 아닙니다. MLflow는 과거에 Staging, Production, Archived 같은 고정 stage를 많이 사용했지만, MLflow 2.9 이후 stage 관련 기능은 점차 사용 중단(deprecation) 방향으로 안내되고 있습니다. 최신 흐름에서는 model alias와 model version tag를 이용해 더 유연하게 상태와 배포 대상을 표현하는 방식이 권장됩니다.2
예를 들어 다음처럼 설계할 수 있습니다.
alias
candidate: 자동 평가 기준을 통과한 후보 모델alias
staging: 스테이징 환경에서 검증 중인 모델alias
production: 운영 트래픽을 받는 모델tag
validation_status=pending|passed|failed: 검증 상태tag
approved_by=...: 승인자tag
rollback_target=true: 롤백 후보 여부
상태 전환 규칙도 미리 정해야 합니다.
후보 등록: 자동 평가 기준 통과 시
candidatealias 부여스테이징 전환: 통합 테스트 대상이 되면
stagingalias 부여프로덕션 전환: 담당자 승인과 배포 테스트 통과 후
productionalias 전환보관: 더 이상 기본으로 사용하지 않는 모델에
lifecycle=archived태그 부여롤백: 장애 발생 시 검증된 이전 버전으로
productionalias를 되돌림
alias의 장점은 운영 코드가 버전 번호를 직접 알 필요가 없다는 점입니다. 예를 들어 서빙 코드는 models:/iris-classifier@production만 바라보고, 어떤 버전이 실제 프로덕션인지는 레지스트리에서 전환합니다.
쉬운 비유 또는 사례
실험 추적은 요리 연구 노트와 비슷합니다. 같은 파스타를 만들더라도 면의 종류, 삶는 시간, 소스 비율, 불 조절을 기록하지 않으면 맛있는 결과를 다시 만들 수 없습니다.
모델 레지스트리는 식당의 공식 메뉴판에 가깝습니다. 주방에서 수십 가지 레시피를 시도할 수는 있지만, 손님에게 나가는 메뉴는 검증되고 승인된 레시피여야 합니다. 새 레시피가 더 맛있어도 조리 시간이 두 배라면 점심 피크 시간에는 쓰기 어렵습니다.
AI 시스템도 같습니다. 연구 단계에서는 다양한 실험이 필요합니다. 하지만 운영 단계에서는 "이 모델이 왜 공식 모델인가"를 설명할 수 있어야 합니다.
직접 해보는 실습
이번 실습은 MLflow로 실험을 기록하고, 기준 모델보다 나은 후보만 레지스트리에 등록하는 흐름입니다. 실제 서비스 수준의 서버 구성은 PostgreSQL 같은 백엔드 저장소와 S3 또는 MinIO 같은 아티팩트 저장소를 분리하는 것이 좋지만, 여기서는 로컬에서 흐름을 익힙니다.3
1단계: 설치와 UI 실행
pip install mlflow scikit-learn pandas
mlflow ui --port 5000브라우저에서 http://127.0.0.1:5000에 접속합니다. 로컬 파일 저장소만으로도 실험 추적 흐름을 익힐 수 있습니다. 다만 여러 사람이 함께 쓰는 레지스트리는 파일 기반 저장소보다 데이터베이스 기반 백엔드와 별도 아티팩트 저장소를 쓰는 편이 안전합니다.
2단계: 학습 코드 작성
train.py를 만듭니다.
import json
import mlflow
import mlflow.sklearn
from mlflow.models import infer_signature
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import train_test_split
mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("iris-classification")
X, y = load_iris(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
params = {
"n_estimators": 120,
"max_depth": 4,
"random_state": 42,
}
data_version = "iris-v1"
git_commit = "local-dev"
with mlflow.start_run(run_name="rf-candidate") as run:
mlflow.log_params(params)
mlflow.log_param("data_version", data_version)
mlflow.log_param("git_commit", git_commit)
mlflow.set_tags({
"task": "classification",
"owner": "ml-team",
"lifecycle": "candidate",
})
model = RandomForestClassifier(**params)
model.fit(X_train, y_train)
pred = model.predict(X_test)
metrics = {
"accuracy": accuracy_score(y_test, pred),
"f1_macro": f1_score(y_test, pred, average="macro"),
"latency_ms_p99": 20.0,
"estimated_cost_per_1k": 0.01,
}
mlflow.log_metrics(metrics)
with open("eval_report.json", "w", encoding="utf-8") as f:
json.dump(metrics, f, indent=2)
mlflow.log_artifact("eval_report.json")
signature = infer_signature(X_test, model.predict(X_test))
mlflow.sklearn.log_model(
sk_model=model,
name="model",
input_example=X_test.head(3),
signature=signature,
)
print("run_id:", run.info.run_id)
print(metrics)실행합니다.
python train.pyMLflow UI에서 파라미터, 메트릭, 태그, 아티팩트가 기록되었는지 확인합니다. input_example과 signature를 함께 남기면 모델이 기대하는 입력 형태를 확인할 수 있어, 서빙 단계의 타입 오류를 줄이는 데 도움이 됩니다.2
3단계: 기준을 통과한 모델만 등록
register_if_better.py를 만듭니다.
import mlflow
from mlflow.tracking import MlflowClient
mlflow.set_tracking_uri("http://127.0.0.1:5000")
client = MlflowClient()
experiment = client.get_experiment_by_name("iris-classification")
if experiment is None:
raise RuntimeError("experiment not found")
runs = client.search_runs(
experiment_ids=[experiment.experiment_id],
order_by=["metrics.f1_macro DESC"],
max_results=1,
)
if not runs:
raise RuntimeError("no runs found")
best_run = runs[0]
best_f1 = best_run.data.metrics["f1_macro"]
latency = best_run.data.metrics["latency_ms_p99"]
model_name = "iris-classifier"
quality_gate_passed = best_f1 >= 0.95 and latency <= 150
if quality_gate_passed:
model_uri = f"runs:/{best_run.info.run_id}/model"
result = mlflow.register_model(model_uri, model_name)
client.set_model_version_tag(
name=model_name,
version=result.version,
key="validation_status",
value="pending",
)
client.set_model_version_tag(
name=model_name,
version=result.version,
key="decision",
value="registered because f1>=0.95 and latency<=150ms",
)
client.set_registered_model_alias(
name=model_name,
alias="candidate",
version=result.version,
)
print("registered", model_name, "version", result.version)
else:
print("not registered: quality gate failed")실행합니다.
python register_if_better.py이제 UI의 Models 메뉴에서 등록된 모델을 확인합니다. 이후 검증이 끝나면 candidate alias를 staging, 최종 승인 후 production으로 바꾸는 흐름을 만들 수 있습니다. MLflow에서는 등록 모델을 이름과 버전 또는 alias 기반 URI로 불러올 수 있습니다.2
예를 들어 운영 코드에서는 다음처럼 특정 alias를 바라보게 할 수 있습니다.
import mlflow.pyfunc
model = mlflow.pyfunc.load_model("models:/iris-classifier@production")이렇게 하면 새 버전이 프로덕션으로 승인되어도 서빙 코드를 매번 고칠 필요가 없습니다. 레지스트리의 production alias만 새 버전을 가리키게 하면 됩니다.
흔한 실수와 오류 분석
실수 1: 메트릭만 기록한다
f1=0.96만 남기면 부족합니다. 어떤 데이터 버전, 어떤 코드, 어떤 환경에서 나온 값인지 모르면 재현할 수 없습니다. 메트릭은 반드시 파라미터, 데이터 버전, 코드 커밋, 환경 정보와 함께 기록합니다.
실수 2: 최고 성능 모델을 무조건 배포한다
오프라인 성능이 높아도 운영에 부적합할 수 있습니다. 지연 시간, 메모리 사용량, 추론 비용, 특정 사용자 그룹의 성능 하락을 함께 봐야 합니다.
실수 3: 실패 실험을 삭제한다
실패 실험도 자산입니다. 어떤 파라미터가 효과가 없었는지, 어떤 데이터 조합이 문제였는지 남겨야 같은 실수를 반복하지 않습니다. 단, 개인정보나 민감 데이터가 아티팩트에 섞이지 않았는지는 반드시 확인합니다.
실수 4: 레지스트리를 파일 저장소처럼 쓴다
모델 레지스트리는 단순히 모델 파일을 모아두는 폴더가 아닙니다. 승인 상태, 버전, 설명, 태그, 배포 이력, 롤백 근거를 관리하는 운영 장치입니다.
실수 5: 프로덕션 모델을 덮어쓴다
운영 모델은 덮어쓰지 않습니다. 새 모델은 새 버전으로 등록하고, 검증 후 alias나 배포 설정을 전환합니다. 그래야 장애가 났을 때 이전 버전으로 되돌릴 수 있습니다.
실수 6: 도구의 stage 이름과 실제 환경을 혼동한다
Staging이라는 이름이 있다고 해서 반드시 스테이징 환경에 배포된 것은 아닙니다. 반대로 alias production이 있다고 해서 실제 트래픽 라우팅이 자동으로 바뀌는 것도 아닙니다. 레지스트리의 상태 표현, 배포 파이프라인, 실제 서빙 인프라가 어떻게 연결되는지 문서화해야 합니다.
요약
실험 추적은 모델 학습의 기억 장치입니다. 파라미터, 메트릭, 아티팩트, 코드 커밋, 데이터 버전, 실행 환경, 태그를 남겨야 실험을 재현하고 비교할 수 있습니다.
모델 레지스트리는 운영 모델의 생애주기 관리 장치입니다. 후보, 스테이징, 프로덕션, 보관 단계를 통해 모델이 실험 결과에서 운영 자산으로 이동하는 과정을 통제합니다. MLflow처럼 특정 도구를 사용할 때는 고정 stage에만 의존하기보다 alias와 tag를 이용해 현재 조직의 배포 흐름에 맞게 상태를 표현하는 것이 안전합니다.
운영에서 중요한 것은 "가장 높은 정확도"가 아닙니다. 비즈니스 목표, 지연 시간, 비용, 안정성, 감사 가능성을 함께 만족하는 모델을 선택하는 것입니다.
퀴즈
실험 추적에서 파라미터와 메트릭의 차이는 무엇인가?
데이터 버전과 코드 커밋을 함께 기록해야 하는 이유는 무엇인가?
정확도가 높은 모델이 운영 배포에 적합하지 않을 수 있는 이유를 두 가지 쓰라.
모델 레지스트리의 Staging 단계에서는 어떤 검증을 해야 하는가?
실패한 실험을 남겨야 하는 이유는 무엇인가?
체크리스트
모든 실행에 하이퍼파라미터와 기본값을 기록했다.
학습 데이터 버전과 검증 데이터 버전을 기록했다.
코드 커밋 또는 이미지 태그를 기록했다.
Python과 주요 라이브러리 버전을 남겼다.
전체 메트릭뿐 아니라 중요한 데이터 슬라이스별 메트릭을 확인했다.
지연 시간과 비용을 모델 선택 기준에 포함했다.
기준 모델보다 나은 경우에만 후보 모델로 등록한다.
스테이징에서 입력 스키마, 통합 테스트, 섀도 테스트를 수행한다.
프로덕션 전환 승인자와 승인 근거를 남긴다.
이전 프로덕션 모델을 보관하고 롤백 경로를 유지한다.
레지스트리의 alias/tag와 실제 배포 환경의 연결 방식을 문서화했다.
다음 장 연결
이번 장에서는 실험 결과를 기록하고, 운영 후보 모델을 레지스트리로 관리했습니다. 하지만 이 과정을 사람이 매번 수동으로 실행하면 빠뜨리는 단계가 생깁니다.
다음 장에서는 데이터 검증, 학습, 평가, 등록, 승인 요청을 하나의 흐름으로 묶는 파이프라인 orchestration을 다룹니다. 이제 관심사는 "좋은 실험을 남기는 법"에서 "좋은 실험과 검증을 반복 가능하게 실행하는 법"으로 이동합니다.
참고
1MLflow - Open Source AI Platform for Agents, LLMs & Models
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.