본문으로 건너뛰기

09. 모델 학습과 검증 II

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

8장의 파이프라인은 돌아가지만 단계마다 같은 데이터를 다시 내려받고, 학습이 어떻게 흘렀는지는 끝난 뒤 로그를 뒤져야 알며, 어느 모델을 넘길지는 사람이 기억해야 한다. 9장은 이 불편을 PVC, TensorBoard, MLflow로 푼다. 책은 신분증 탐지(YOLO)에 PVC와 TensorBoard를, 영화 추천(MovieLens)에 객체 저장소와 MLflow를 쓰는데, 도구가 갈린 이유는 데이터 모양과 묻는 질문이 달라서다.

반복 다운로드를 PVC 하나로 묶는다

컴포넌트마다 다른 Pod에서 돌고 Pod 사이에는 공유 디스크가 없다. 그래서 8장에서는 다운로드, 학습, 검증이 같은 데이터를 S3에서 세 번 받았다. PersistentVolumeClaim(PVC)은 크기와 접근 방식을 정한 저장 공간 요청이고, StorageClass가 실제 볼륨을 만들어 붙인다. 파이프라인 맨 앞에서 PVC를 만들고 모든 단계가 /data에 마운트하면 다운로드는 한 번으로 끝난다. YOLO는 images/와 labels/ 폴더 구조를 파일 경로로 기대하므로, 업로드와 다운로드로만 파일을 다루는 객체 저장소보다 파일시스템이 맞는다.

대신 챙길 것이 생긴다. ReadWriteOnce는 한 노드에서만 마운트되므로 노드가 여럿이면 다음 단계가 다른 노드에 배치되는 순간 막힌다. KFP는 PVC 안의 데이터 흐름을 모르기 때문에 단계 순서를 .after()로 직접 적어야 하고, 캐시도 PVC 안의 파일을 모른다. DeletePVC는 마지막 단계가 성공해야 실행되어 실패한 실행의 PVC는 남는다.

PVC가 객체 저장소를 대체하지는 않는다. 실행 뒤에도 남아야 할 데이터셋 버전, 모델, 평가 결과는 객체 저장소에 두고, PVC는 한 실행 동안 여러 단계가 같은 파일을 빠르게 나눠 쓰는 작업 공간으로 쓴다. 파일 몇 개짜리 표 데이터를 쓰는 추천 예제가 S3의 parquet를 바로 읽는 이유다. PVC가 지워지면 안에 무엇이 있었는지 알 수 없으므로 원본 URI와 분할 시드는 실행 기록에 따로 남긴다.

책 코드와 실제 저장소의 차이: PVC 파이프라인

책의 리스팅 9.1~9.9는 저자 저장소 object-detection-project의 PVC 파이프라인(커밋 c6afb11)과 거의 같고, 그대로 돌리면 여러 곳에서 막힌다.

  • PVC 이름을 고정 문자열로 넘겨 두 번째 실행부터 이름이 겹치고, PVC 생성과의 순서도 보장되지 않는다. 실행마다 새 이름을 만들고 생성 단계의 출력을 넘기도록 고쳤다.
  • 이미지와 라벨 목록을 위치로 짝지어 570쌍 전부가 다른 파일끼리 묶였다.
  • 책은 학습 로그를 마운트 지점에 쓴다고 설명하지만 코드는 KFP 출력 경로에 쓴다. PVC에 로그가 없으니 TensorBoard가 볼 것이 없다.
  • 검증 단계만 캐시를 꺼서, 재실행하면 다운로드와 분할이 건너뛰어지고 새 PVC가 빈 채로 학습이 실패한다.
  • 학습 이미지가 지금은 설치되지 않는 시스템 패키지와 버전 없는 torch에 기대므로, 검증과 같은 Ultralytics CPU 이미지로 맞췄다.

TensorBoard는 한 번의 학습 안을 본다

TensorBoard는 학습 코드가 남긴 이벤트 파일을 읽어 epoch별 손실, 학습률, 검증 지표를 그린다. 학습 Pod와 TensorBoard Pod가 같은 PVC를 마운트하면 학습 도중에도 곡선이 갱신된다. 책이 TensorBoard를 PVC 바로 뒤에 둔 이유다. 여기서 볼 것은 손실이 줄고 있는지, 학습률 스케줄이 의도대로 움직이는지, 학습 손실은 주는데 검증 손실이 느는 과적합 조짐이 있는지다.

그 검증 지표가 배포 뒤 성능을 대표하지는 않는다. YOLO는 매 epoch val 분할로 best.pt를 고르고, 저자의 검증 컴포넌트도 같은 val을 다시 잰다. 이번 실행에서 두 값이 mAP50 0.995, mAP50-95 0.964로 같았고 test는 어디서도 쓰이지 않았다.

책은 Kubeflow 대시보드의 TensorBoards 메뉴로 인스턴스를 띄우지만, KFP만 설치한 환경에는 이 메뉴가 없어 PVC를 읽기 전용으로 붙인 Deployment로 대신했다. 여러 실행을 나란히 놓고 고르는 일은 MLflow의 몫이다. 이벤트 파일은 PVC와 함께 사라지므로 나중에도 볼 지표는 MLflow에도 남긴다.

추천 지표는 기준선 옆에 놓아야 판단 근거가 된다

추천 파이프라인은 7장에서 parquet로 나눈 MovieLens 평점 표를 S3에서 바로 읽는다. 데이터 검사, 사용자마다 보지 않은 영화 10편을 평점 0으로 더하는 negative sampling, 행렬 분해 학습, 검증, staging 등록 순으로 진행된다. 학습 직전의 데이터 검사는 준비 파이프라인이 바뀌어도 잘못된 입력을 받지 않게 하는 계약이다. 그런데 책 리스팅 9.10은 user_id, item_id 열을 찾고 실제 데이터는 userId, movieId라서 그대로 돌리면 실패한다.

저자 코드의 지표는 보이는 것보다 후하다. RMSE를 배치의 평균 예측과 평균 정답으로 계산해 0.20이 나왔지만, 평점 하나하나로 계산하면 1.08이었다. 학습 데이터 평균 평점을 모든 영화에 똑같이 예측해도 1.05다. precision@50과 recall@50은 사용자가 이미 평점을 남긴 val 행 안에서만 순위를 매기는데, 사용자의 87%는 val 행이 50개보다 적어 높게 나올 수밖에 없다. 보지 않은 영화 전체로 순위를 매기자 모델의 Recall@10은 0.0016 이하였고, 많이 평가된 순서대로 추천하는 인기순 기준선은 0.099였다.

분할도 평가를 후하게 만든다. 7장의 무작위 분할에서 val 평점의 98.8%가 같은 사용자의 가장 늦은 train 평점보다 이른 시각이었다. 미래 행동으로 과거를 맞히는 평가라서 취향 변화처럼 서비스에서 어려운 부분이 빠진다. 시각 기준으로 자르거나 사용자별 마지막 몇 개를 남겨야 서비스에 가깝다.

콜드 스타트

임베딩 모델은 학습에서 본 사용자와 영화만 안다. 무작위 분할이라 val에 처음 보는 사용자는 없었지만, 영화 쪽은 635행이 학습에 없던 영화였고 그 임베딩은 초기화된 값 그대로다. 추천 상위에 평점 수가 적은 낯선 영화가 몰린 것도 이 때문이다. 신규 사용자와 신규 영화에는 인기순이나 장르 같은 다른 신호로 답하고, 어떤 경로로 답했는지도 기록해야 한다.

MLflow는 실행 사이의 계보를 잇는다

TensorBoard가 한 실행 안을 본다면 MLflow는 실행 사이를 잇는다. 학습 컴포넌트는 입력 인자를 파라미터로, epoch별 손실을 지표로, 모델을 입출력 시그니처와 함께 기록하고 run_id를 KFP 출력으로 낸다. 검증과 등록은 다른 Pod에서 돌지만 같은 run_id로 이어져 기록이 한 줄로 남는다. 데이터셋 경로와 코드 버전은 저절로 남지 않으므로 태그로 함께 적어야 모델에서 데이터까지 거슬러 올라갈 수 있다.

모델 파일은 누가 업로드하는가

이 환경의 MLflow 서버는 아티팩트 위치를 s3://mlflow-artifacts로 지정하고 프록시를 쓰지 않는다. 서버는 올릴 경로만 알려 주고 모델 파일은 학습 Pod가 S3에 직접 올린다. 그래서 Pod에 S3 주소와 키가 없으면 메타데이터 기록은 성공하고 모델 업로드만 실패한다. 프록시 모드를 쓰면 학습 코드는 MLflow 주소만 알면 되지만 큰 파일이 전부 서버 한 곳을 지난다.

모델을 클래스 코드 없이 불러올 수 있다는 말에도 조건이 붙는다. mlflow.pytorch는 모델 클래스를 파이썬 바이트코드째 직렬화한다. Python 3.9 컴포넌트에서 저장한 모델이 Python 3.12에서는 열리지 않았다. 서빙 이미지는 모델과 함께 기록되는 python_env.yaml의 버전을 따라야 한다.

등록과 별칭은 “지금 무엇을 쓰는가"를 정한다

모델 레지스트리는 등록 모델 이름 아래 버전을 쌓고, staging이나 prod 같은 별칭이 특정 버전을 가리키게 한다. 서빙은 버전 번호 대신 별칭으로 모델을 찾고, 롤백은 별칭을 이전 버전으로 옮기는 일이 된다. 저자의 등록 단계는 새 run의 지표를 현재 staging 버전과 비교해 기준을 넘으면 별칭을 옮긴다.

구조는 합리적이지만 그대로 쓰기는 어렵다. 첫 실행에서는 staging이 없어 AttributeError가 나고, 파라미터 대신 하드코딩한 모델 이름은 여러 사람이 같은 서버를 쓰면 서로의 버전을 덮는다. 더 근본적으로 비교 지표가 추천 품질을 대표하지 못해서, 이번 실습에서는 상수 예측보다 못한 모델이 staging에 올랐다. 책이 찾는 prod 별칭은 파이프라인이 만들지 않으므로 사람이 검토해 옮기는 절차로 남는다.

실습

책 코드로 직접 실행한 순서와 결과 화면은 9장 실습 기록에 따로 모았다.

정리

PVC는 한 실행 동안 데이터를 어디에 두는지를, TensorBoard는 한 번의 학습 안에서 무슨 일이 있었는지를, MLflow는 어떤 실행이 어떤 결과를 냈고 지금 무엇을 쓰는지를 다룬다. 이 연결은 쉽게 끊긴다. 캐시는 PVC를 모르고, 등록 자동화는 지표가 틀려도 충실하게 등록한다. 10장은 별칭으로 모델을 찾아 서빙하므로 누가 별칭을 옮기는지가 곧 배포 절차가 된다.

이전: 08. 학습과 검증 I, 시리즈 목차, 다음: 10. 추론과 서빙

마지막 수정 일자