BERT 개념이해 #11 MLflow

BERT 개념이해 #11 MLflow #

#2026-03-04


#1 MLflow가 풀려는 문제: 실험이 많아지면 “기억”은 반드시 깨진다

모델을 개선할 때는 거의 항상 같은 패턴이 반복된다. 학습률을 바꾸고, max_length를 바꾸고, dropout을 바꾸고, label_smoothing을 켜보고, classifier hidden size를 조정한다. 처음엔 “이 정도는 머리로 기억되지” 싶지만, 실험이 10개만 넘어가도 바로 헷갈린다. 더 큰 문제는 “결과 숫자”만 기억해도 소용이 없다는 점이다. 어떤 실험이 0.96이 나왔는데, 그때 정확히 어떤 하이퍼파라미터였는지, 어떤 데이터 버전이었는지, 어떤 모델 아티팩트를 썼는지를 같이 알아야 그 실험을 다시 재현하거나 운영에 올릴 수 있다.

MLflow는 이 실험 과정을 “자동으로 기록하고, 비교하고, 다시 꺼내 쓰게” 만드는 도구다. 한마디로 머신러닝 실험의 버전 컨트롤에 가깝다.

#

#2 MLflow의 기본 단위: Experiment와 Run이 “폴더 구조”로 실험을 정리한다

MLflow를 이해하는 가장 쉬운 방법은 폴더 구조를 상상하는 것이다. 큰 폴더 하나가 Experiment다. 예를 들어 “bert-ag-news-optuna” 같은 이름이 Experiment다. 그 안에는 실험을 한 번 실행할 때마다 Run이라는 하위 폴더가 하나씩 생긴다. “trial_0042”, “retrain_20260305_…” 같은 게 Run이다.

그리고 Run 안에는 세 가지가 들어간다. 첫째는 Params, 즉 그 실험의 설정값이다. learning_rate, batch_size, max_length, dropout 같은 값들이 여기 들어간다. 둘째는 Metrics, 즉 결과 숫자다. accuracy, f1, loss 같은 평가 지표가 들어간다. 셋째는 Artifacts, 즉 파일이다. 학습된 모델, tokenizer 파일, ONNX 파일, confusion matrix 이미지 같은 “실험 산출물”이 전부 artifacts로 들어간다.

결국 MLflow는 “한 번의 실험 실행 = Run 하나 = 설정/결과/산출물이 한 세트로 묶인 기록”이라는 구조를 갖는다. 이 구조가 생기는 순간, 실험이 아무리 많아져도 섞이지 않는다.

# MLflow의 4가지 핵심 개념

Experiment (실험 묶음)
  └─ Run (단일 실험 실행)
        ├─ Params  (하이퍼파라미터)   → mlflow.log_params({"lr": 3.5e-5, ...})
        ├─ Metrics (평가 지표)        → mlflow.log_metrics({"f1": 0.96, ...})
        └─ Artifacts (파일)          → mlflow.log_artifact("bert.onnx")

예시:
  Experiment: "bert-ag-news-optuna"
    Run: "trial_0042"
      params: {lr=3.5e-5, dropout=0.3, classifier_hidden=256}
      metrics: {f1=0.9601, accuracy=0.9603}
    Run: "trial_0043"
      params: {lr=2e-5, dropout=0.2, classifier_hidden=128}
      metrics: {f1=0.9487, accuracy=0.9490}

실험에서 진짜 중요한 건 “정확도 0.96” 같은 결과 숫자만이 아니다. 그 숫자가 나온 조건이 중요하다. max_length를 128로 했는지 192로 했는지, label_smoothing을 0.1로 했는지 0으로 했는지, classifier hidden을 256으로 했는지 128으로 했는지에 따라 결과는 달라진다. MLflow의 log_params는 이 조건들을 자동으로 저장한다. 그래서 나중에 “accuracy가 0.95 이상인 run만 보고 싶다” 또는 “dropout이 0.3인 실험들끼리만 비교하고 싶다” 같은 조회가 가능해진다. 즉 파라미터를 ‘기록’하는 순간부터, 실험을 단순한 메모가 아니라 검색 가능한 데이터베이스로 바꾸게 된다.

메트릭은 그냥 최종 점수만 저장할 수도 있지만, 더 중요한 기능은 “학습 과정의 흐름”을 남길 수 있다는 점이다. 예를 들어 eval_steps=200으로 평가를 주기적으로 돌리면, f1이 시간이 지나며 어떻게 올라가는지, 어느 지점에서 과적합이 시작되는지 같은 곡선을 저장할 수 있다. MLflow의 metrics 파일이 “timestamp step value” 형식인 이유가 바로 이거다. 값 하나가 아니라, 스텝에 따른 변화를 기록하도록 설계돼 있다. 그래서 웹 UI에서 실험을 보면 “이 run은 초반에 빨리 올라갔는데 후반에 정체됐네”, “이 run은 천천히 올라가지만 최종은 더 높네” 같은 판단이 가능해진다. 단순히 숫자 비교가 아니라 학습의 성격까지 비교할 수 있게 된다.

실험에서 최고 성능이 나왔다고 해도, 그 모델 파일이 없으면 운영에 못 올린다. 그때의 ONNX 파일이 없으면 서버에서 바로 돌릴 수 없다. 그때의 tokenizer가 없으면 같은 전처리를 재현할 수 없다. 즉 실험은 결국 “파일로 완성”된다. MLflow가 artifacts를 저장해주는 순간, “성능이 좋은 run”은 곧 “바로 배포 가능한 산출물을 가진 run”이 된다. 너의 파이프라인에서 champion이 승격될 때 ONNX 파일을 mlflow.log_artifact(ONNX_PATH)로 저장하는 구조는 딱 이 목적에 맞다. 성능이 좋은 모델이 자동으로 아티팩트까지 묶여서 보관되면, 운영에서 가져다 쓰는 일이 매우 쉬워진다

#

#3 로컬 mlruns/ 구조가 의미하는 것: 서버 없이도 실험 데이터베이스가 생긴다

MLflow는 꼭 중앙 서버가 있어야만 하는 도구가 아니다. tracking URI를 로컬 경로로 지정하면, 모든 실험 기록이 파일로 쌓인다. mlruns/ 디렉토리 아래에 experiment id 폴더가 생기고, 그 안에 run id 폴더들이 생기며, params/metrics/artifacts가 각각 파일로 들어간다.

이게 왜 좋은가 하면, 실험 기록이 “어떤 특수한 시스템 안에 갇히지 않는다.” 그냥 파일이니까 백업도 쉽고, git으로 관리하진 않더라도 스토리지에 복사해두기도 쉽고, 서버 환경이 바뀌어도 그대로 가져갈 수 있다. 작은 프로젝트에서 MLflow를 시작하기에 가장 현실적인 방식이 로컬 파일 기반 저장이다.

MLflow Tracking URI = mlruns/ 디렉토리

bert/data/mlruns/
  ├─ 0/                           ← Experiment ID
  │   └─ meta.yaml                ← 실험 이름, 생성 시각
  └─ 1/
      ├─ meta.yaml
      └─ <run_id>/                ← 각 Run
            ├─ meta.yaml
            ├─ params/
            │   ├─ lr             ← 파일명=파라미터명, 내용=값
            │   └─ dropout
            ├─ metrics/
            │   ├─ f1             ← 각 줄: timestamp step value
            │   └─ accuracy
            └─ artifacts/
                └─ bert.onnx

→ 원격 서버 없이 로컬에서 바로 사용 가능
→ mlflow ui 명령으로 웹 UI 실행

#

#4 HuggingFace Trainer와 report_to=“mlflow"의 의미: ‘기록을 자동화’한다

실험 기록이 귀찮아지면 결국 아무도 안 하게 된다. MLflow가 유용하려면 기록이 자동이어야 한다. HuggingFace Trainer에서 TrainingArguments(report_to=“mlflow”)를 켜면, 평가가 발생할 때마다 Trainer가 자동으로 mlflow.log_metrics를 호출해서 기록한다. 즉 “학습 코드를 깔끔하게 유지한 채로” 실험이 자동으로 축적된다.

이 자동화는 특히 하이퍼파라미터 실험에서 효과가 크다. 사람은 실험을 많이 할수록 기록을 빠뜨리는데, 자동화는 그 실수를 원천 차단한다.

#

#5 Optuna와 MLflow를 같이 쓸 때의 감각: Optuna는 ‘찾고’, MLflow는 ‘남긴다’

Optuna는 “최적의 하이퍼파라미터를 찾는 탐색 엔진”이다. 반면 MLflow는 “그 탐색 과정과 결과를 기록하는 저장소”다. 둘을 같이 쓰면, trial마다 run이 하나씩 남고, 나중에 “왜 이 파라미터 조합이 이겼는지”를 데이터로 확인할 수 있다.

다만 trial이 수백 개가 되면 매 trial마다 로깅이 I/O 병목이 될 수 있다. 그래서 “탐색 중에는 기록을 최소화하고, 최종 선택된 파라미터로 제대로 학습할 때만 MLflow를 강하게 기록한다”는 전략이 실무적으로 자주 쓰인다. 너의 정리처럼 trial에서는 report_to를 끄고, 최종 학습만 report_to=“mlflow"로 남기는 방식은 속도와 재현성의 균형을 맞춘 선택이다.

Optuna: 하이퍼파라미터 탐색 (어떤 파라미터가 최적인가?)
MLflow: 실험 기록 (각 실험을 영구적으로 저장)

함께 사용하는 방식:
  Optuna trial 내부에서 MLflow run 시작
    → trial마다 별도 run으로 기록

  주의:
    Optuna 내부 학습 (trial별 빠른 학습)에서는 MLflow 비활성화
      report_to="none"   ← 속도를 위해 trial 내부는 기록 안 함
    최적 파라미터로 최종 학습 시에만 MLflow 활성화
      report_to="mlflow" ← 최종 결과만 기록

  이유: trial이 수십~수백 개일 때
    → 모든 trial에 MLflow 기록하면 I/O 오버헤드 발생

#

#6 재학습 파이프라인에서 MLflow의 역할: 모델 승격을 ‘감사 가능하게’ 만든다

Champion–Challenger 구조에서 중요한 건 단순히 “새 모델이 더 좋다”가 아니라, “왜 승격됐는지”가 남아야 한다는 것이다. 피드백 데이터가 몇 건이었는지, 학습 데이터 크기가 얼마였는지, challenger의 f1이 얼마였는지, 승격 여부가 무엇이었는지 같은 정보가 기록돼야 한다. 그래야 나중에 운영 이슈가 생겼을 때 “어느 시점에 어떤 근거로 모델이 바뀌었는가”를 추적할 수 있다.

MLflow에 retrain run을 남기면 이 흐름이 한 번에 정리된다. 게다가 승격된 경우 ONNX까지 artifacts로 남기면, “그때 운영에 올라간 실행 파일”까지 같이 보관된다. 이건 단순 실험 기록이 아니라 운영 변경 이력까지 겸하는 역할이다.

#

#7 정리: MLflow는 실험을 ‘데이터화’해서 비교와 재현을 가능하게 만든다

MLflow를 도입하면 실험은 더 이상 메모장이 아니다. Experiment는 실험 묶음이고, Run은 한 번의 실행이며, 그 안에 params(조건), metrics(결과), artifacts(산출물)가 한 세트로 저장된다. 로컬 mlruns/만으로도 바로 시작할 수 있고, 웹 UI로 여러 run을 한눈에 비교할 수 있다. Trainer 연동을 통해 기록을 자동화하면 실험이 쌓일수록 가치가 커진다. Optuna는 최적을 찾고, MLflow는 그 과정을 남겨서 재현 가능하게 만든다. 그리고 재학습 파이프라인에서 MLflow는 승격 기록과 배포 아티팩트를 묶어, 모델 운영을 “추적 가능하고 검증 가능한 과정”으로 만들어준다.

  • MLflow = 머신러닝 실험의 버전 컨트롤
  • 4가지 핵심: ExperimentRunParams / Metrics / Artifacts
  • 저장 방식: mlruns/ 디렉토리에 파일로 저장 (서버 불필요)
  • HuggingFace 연동: TrainingArguments(report_to="mlflow")으로 eval 자동 기록
  • 재학습 파이프라인: Champion 승격 시 ONNX 아티팩트도 함께 저장
  • FastAPI /experiments: mlruns/ 디렉토리를 파싱하여 API로 노출
  • Optuna + MLflow: trial 내부는 report_to="none", 최종 결과만 report_to="mlflow"
  • 이 프로젝트 실험명: bert-ag-news-optuna, bert-ag-news-retrain