콘텐츠로 이동
Study NoteMLflow

4. artifact를 어디까지 붙일 것인가

결론부터
artifact는 “값으로 못 담는 것”의 자리다 — 다만 log_artifacts는 디렉터리를 통째로 올리는 게 아니라 그 안의 내용만 올린다
이 장에서 처음 나오는 말3개
artifact
run에 딸린 파일. 리포트·예측·모델처럼 param·metric으로 못 담는 것을 넣는다.
artifact_path
run의 artifact 루트 아래 하위 폴더 이름. 안 주면 루트에 바로 놓인다.
artifact URI
이 run의 artifact가 저장된 위치. <artifact_location>/<run_id>/artifacts다.

두 함수의 차이가 폴더 구조를 정한다

섹션 제목: “두 함수의 차이가 폴더 구조를 정한다”

이름이 한 글자 차이인데 동작이 다르다.

함수인자하는 일
log_artifact(local_path, artifact_path=None)파일 하나그 파일을 올린다
log_artifacts(local_dir, artifact_path=None)디렉터리그 디렉터리의 내용물을 올린다

log_artifacts가 디렉터리 이름을 버린다는 게 핵심이다. artifacts/research/nxt_daily_o2n_20260830_120331/을 넘기면 run의 artifact 루트에 predictions.parquet, summary.csv, report.html이 바로 놓인다. sshim-trader의 실제 저장 결과가 그렇다.

  • 디렉터리artifacts/mlruns/
    • 디렉터리e9489504b5f64a6fb07a9b7b45b73b55/ run_id
      • 디렉터리artifacts/ 여기가 artifact 루트다
        • predictions.parquet 폴더 이름 없이 바로 놓였다
        • summary.csv
        • daily_net_returns.csv
        • report.html

지금 log_run()은 이렇게 부른다.

for path in artifact_paths:
path = Path(path)
if path.is_dir():
mlflow.log_artifacts(str(path)) # ← 폴더 이름이 사라진다
elif path.is_file():
mlflow.log_artifact(str(path))

호출부가 항상 폴더 하나만 넘기고 있어서(artifact_paths=[out_dir]) 지금은 문제가 없다. 하지만 둘 이상을 넘기는 순간 조용히 덮어쓴다. 연구 산출물 폴더와 백테스트 산출물 폴더에 둘 다 report.html이 있으므로, 나중에 하나만 남는다.

고치는 방법은 한 줄이다. 폴더 이름을 하위 경로로 넘긴다.

if path.is_dir():
mlflow.log_artifacts(str(path), artifact_path=path.name)

이러면 artifacts/nxt_daily_o2n_20260830_120331/predictions.parquet처럼 원래 구조가 보존되고, 여러 폴더를 붙여도 섞이지 않는다. 지금 코드가 이미 out_dir 이름을 run_name으로도 쓰고 있으니 디스크·UI·artifact 세 곳의 이름이 나란히 맞는다.

무엇을 올리고 무엇을 두고 갈 것인가

섹션 제목: “무엇을 올리고 무엇을 두고 갈 것인가”

artifact는 파일이 복사된다. artifacts/research/…/predictions.parquet과 artifacts/mlruns/<run_id>/artifacts/predictions.parquet이 둘 다 디스크를 차지한다. 그래서 판단 기준이 필요하다.

파일올릴까왜
summary.csv · summary.json올린다작고, 런을 열자마자 확인하는 것
report.html올린다UI에서 바로 열린다. 런 목록에서 리포트로 한 번에 간다
daily_net_returns.csv올린다수십 KB. 다른 런과 겹쳐 그릴 때 필요하다
predictions.parquet재고한다종목 × 거래일이라 수십 MB가 될 수 있다. 원본이 이미 폴더에 있다
features_*.parquet올리지 않는다여러 런이 공유하는 캐시다. 런마다 복사하면 배로 늘어난다
데이터셋 원본올리지 않는다어떤 데이터였는지는 log_input으로 참조만 남긴다(10장)

기준은 “6개월 뒤 이 런을 열었을 때 이게 없으면 판단을 못 하는가”다. 원본 폴더가 그대로 남아 있고 out_dir tag가 그 경로를 가리키고 있으니, 큰 파일은 참조만으로 충분한 경우가 많다.

UI의 미리보기 대상도 참고가 된다. HTML·이미지·텍스트 계열은 UI 안에서 바로 열리고, parquet 같은 바이너리는 다운로드 링크만 나온다. report.html을 붙이는 것의 값이 특히 큰 이유다.

값이 아닌 것을 값처럼 남기는 세 함수

섹션 제목: “값이 아닌 것을 값처럼 남기는 세 함수”

파일을 먼저 만들지 않고 바로 artifact로 남기는 짧은 길이 있다.

import mlflow
# 1) 문자열 → 파일
mlflow.log_text(summary.to_string(), "summary.txt")
# 2) DataFrame·dict → 구조화된 표 (UI에 표로 렌더된다)
mlflow.log_table(summary.reset_index(), "summary_table.json")
# 3) matplotlib·plotly figure → 그림
mlflow.log_figure(fig, "equity_curve.html")

log_figure는 matplotlib.figure.Figure와 plotly.graph_objects.Figure를 받는다. sshim-trader는 plotly로 리포트를 만들므로(scripts/research_report.py), build_report가 만드는 figure를 파일로 쓰기 전에 그대로 넘길 수도 있다. 다만 지금은 완성된 report.html을 올리는 쪽이 더 단순하다 — 한 화면에 전부 들어 있고 UI에서 그대로 열린다.

기록만 하고 못 꺼내면 색인이 아니다. artifact는 세 가지 방법으로 되가져온다.

import mlflow
# 특정 run의 파일 하나를 로컬로 내려받는다 (경로를 리턴한다)
local = mlflow.artifacts.download_artifacts(run_id=run_id, artifact_path="summary.csv")
# 텍스트는 파일로 안 내리고 바로 읽는다
text = mlflow.artifacts.load_text(f"runs:/{run_id}/summary.csv")
# 어떤 파일이 붙어 있는지 목록만 본다
files = mlflow.artifacts.list_artifacts(run_id=run_id)

runs:/<run_id>/<경로> 형태가 MLflow의 artifact URI다. 8장에서 모델을 불러올 때 같은 문법이 다시 나온다. CLI로도 같은 일을 한다.

터미널 창
uv run mlflow artifacts list --run-id <run_id>
uv run mlflow artifacts download --run-id <run_id> --artifact-path summary.csv --dst-path /tmp

6장의 search_runs와 묶으면 “holdout Sharpe 상위 3개 런의 daily_net_returns.csv를 전부 내려받아 한 그래프에 겹쳐 그리기” 같은 작업이 스크립트 열 줄이 된다.