更差的模型上了生产
한국어 원문으로 표시합니다.
목표
모델 버전과 별칭(champion·challenger)을 갖춘 작은 레지스트리를 세우고, 지표가 떨어지면 승격을 막는 관문을 만들고, 관문을 무시한 승격을 되돌리고, 마지막으로 입력 분포가 움직였는지를 숫자로 만듭니다.
왜 중요한가
모델을 배포한다는 것은 파일을 복사하는 일이 아니라 어느 버전을 가리킬지 정하는 일 입니다. 운영이 버전 번호를 직접 들고 있으면 되돌리기가 배포 설정 변경이 되지만, 별칭을 한 겹 두면 되돌리기는 이름이 가리키는 곳을 바꾸는 일이 됩니다. 그래서 레지스트리는 버전·별칭·이력 셋을 함께 둡니다. 승격 관문은 여기에 '자동으로 가되 아무 때나 가지는 않는다' 를 더하는 장치입니다 — 재학습이 끝났다고 바로 운영으로 가면, 어느 주에는 더 나쁜 모델이 조용히 올라갑니다. 관문이 막았는데도 사람이 밀어붙이는 일은 실제로 일어나므로, 막는 것만큼이나 막았다는 사실과 밀어붙였다는 사실을 함께 남기는 것이 중요합니다.
단계
- 지금 운영에 나가 있는 모델을 버전 1로 등록합니다.
- champion 별칭이 버전 1을 가리키게 합니다.
- 재학습 결과를 버전 2로 등록하고 challenger 로 세웁니다.
- 승격 관문을 코드로 만들어 판정을 남깁니다.
- 별칭이 움직인 이력을 감사 기록으로 남깁니다.
- 관문을 무시한 승격을 재현하고 그 사실을 기록에 남깁니다.
- 되돌리고 얼마나 잃었는지 적습니다.
- 입력 분포가 얼마나 이동했는지를 재서 재학습 판단에 붙입니다.
참고
- 작업은 전부
/root/registry아래에서 합니다. 먼저mkdir -p /root/registry를 하세요. - 재료는
/opt/fixtures/mlops에 있습니다. 학습기의--out이 모델 파일을 써 줍니다. - 감사 기록은 덧붙이기입니다. 이미 쓴 줄을 고치면 이력이 아니라 현재 상태가 됩니다.
- 흔한 실수 — 되돌릴 때 버전 2를 registry.json 에서 지우는 것입니다. 별칭만 옮기세요.
- 실습 파드에는 볼륨이 없어 세션이 끝나면
/root가 사라집니다. 60분을 넘을 것 같으면 미리+시간으로 연장하세요.
지금 나가 있는 모델을 버전 1로 등록한다
lr 0.1, epochs 40, seed 7 로 학습기를 돌려 모델 파일을 만들고 /root/registry/registry.json 에 model 을 "churn-classifier" 로, versions 의 첫 항목으로 version 1 을 등록하세요. 각 버전은 version·run_id·params·metrics·model_path·model_sha256·data_sha256·created_at 를 담습니다. model_sha256 은 model_path 가 가리키는 파일의 해시이고, data_sha256 은 /opt/fixtures/mlops/train.csv 의 해시입니다.
학습기의 --out 옵션이 모델 파일을 써 줍니다. 버전마다 파일을 따로 두세요 — 덮어쓰면 되돌릴 것이 남지 않습니다.
배포 대상을 번호가 아니라 이름으로 가리킨다
/root/registry/aliases.json 에 {"champion": 1} 을 저장하세요. 이 실습에서 쓰는 별칭은 champion 과 challenger 둘뿐입니다.
운영이 버전 번호를 직접 보게 두면 되돌릴 때 배포 설정을 고쳐야 합니다. 별칭을 한 겹 두면 되돌리기가 이 파일 한 줄이 됩니다.
재학습 결과를 도전자로 등록한다
lr 0.3, epochs 30, seed 3 으로 다시 학습시켜 버전 2를 /root/registry/registry.json 에 등록하고 /root/registry/aliases.json 의 challenger 가 2를 가리키게 하세요. 버전 번호는 1부터 1씩 올라가야 하고, champion 은 아직 1이어야 합니다.
재학습이 곧 배포는 아닙니다. 새 버전은 일단 도전자로 세워 두고, 승격은 다음 단계의 관문이 판단합니다.
승격을 사람이 아니라 규칙이 판단하게 한다
/root/registry/gate.py 를 작성해 /root/registry/registry.json 과 /root/registry/aliases.json 을 읽고 /root/registry/gate.json 에 champion_version·challenger_version·champion_metric·challenger_metric·margin·decision·reasons 를 저장하세요. margin 은 0.005 이고, 도전자의 valid_accuracy 가 챔피언 + margin 이상일 때만 decision 이 promote, 아니면 block 입니다. reasons 는 10자 이상인 문장들의 목록입니다.
여유폭을 두는 이유는 측정 잡음 때문입니다. 0.001 차이로 챔피언을 바꾸면 다음 주에 다시 바꾸게 됩니다.
별칭이 움직인 이력을 남긴다
/root/registry/audit.jsonl 에 지금까지 별칭을 세운 일을 한 줄씩 남기세요. 각 줄은 ts·alias·from(처음이면 null)·to·actor·reason(10자 이상)을 담습니다. 기록을 처음부터 재생하면 /root/registry/aliases.json 과 정확히 같은 상태가 나와야 합니다.
감사 기록은 '지금 무엇인가' 가 아니라 '어떻게 여기까지 왔는가' 를 담습니다. from 을 적어 두면 재생했을 때 어긋난 곳이 바로 드러납니다.
관문을 무시한 승격이 무엇을 남기는지 본다
관문이 막았는데도 사람이 champion 을 버전 2로 올린 상황을 재현하세요. /root/registry/aliases.json 의 champion 을 2로 바꾸고, /root/registry/audit.jsonl 에 alias champion, from 1, to 2 인 줄을 추가하되 그 줄에 gate_decision 키로 gate.json 의 판정을 그대로 적으세요.
막는 것만으로는 모자랍니다. 밀어붙인 사람이 무엇을 알고 있었는지가 남아야 다음에 같은 일을 두고 이야기할 수 있습니다.
되돌리고 얼마나 잃었는지 적는다
champion 을 다시 버전 1로 되돌리고 그 변경도 /root/registry/audit.jsonl 에 남기세요(기록은 모두 4줄 이상이 되고 마지막 줄이 champion 2→1 이어야 합니다). 그다음 /root/registry/rollback.json 에 restored_version·bad_version·detected_by(10자 이상)·metric_delta(버전 1의 valid_accuracy − 버전 2의 값, 소수점 넷째 자리 반올림)를 저장하세요.
되돌리기는 별칭을 옮기는 일입니다. 모델 파일을 지우거나 버전을 삭제하면 무슨 일이 있었는지가 함께 사라집니다.
입력 분포가 움직였는지를 숫자로 만든다
/opt/fixtures/mlops/train.csv 를 기준으로 /opt/fixtures/mlops/week2.csv 의 다섯 특징(tenure_months·monthly_fee·support_tickets·late_payments·usage_hours)이 얼마나 이동했는지 재서 /root/registry/drift.json 에 baseline·current·features·max_abs_shift_sigma·alert·retrain_required 를 저장하세요. features 의 각 항목은 train_mean·new_mean·pstdev(기준 데이터의 모집단 표준편차)·shift_sigma((new_mean − train_mean) ÷ pstdev)를 담고, 빈 값이나 숫자가 아닌 값은 세지 않습니다. alert 와 retrain_required 는 max_abs_shift_sigma 가 1.0 을 넘는가입니다.
분포 이동은 계약 위반이 아닙니다 — 값이 전부 허용 범위 안에 있어도 평균이 옮겨 갈 수 있습니다. 그래서 따로 재야 합니다.