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