지난주 모델이 더 좋았는데 아무도 못 찾는다 · 자동으로 가되 아무 때나 가지는 않게 · 이론
막는 것과 남기는 것
한 줄 요약
재학습은 자동으로 가되 승격은 관문을 통과해야 하고, 관문이 막은 것을 사람이
밀어붙였다면 그 사실까지 기록에 남아야 한다.
왜 이게 필요했나
재학습 파이프라인을 만들고 나면 유혹이 하나 생긴다. 학습이 끝났으니 그대로 배포까지
이어 버리는 것이다. 대부분의 주에는 아무 일도 일어나지 않는다. 문제는 아무 일도
일어나지 않는 것이 성공했다는 뜻이 아니라는 점이다. 상류 데이터가 한 번
망가진 주에도 파이프라인은 똑같이 초록불을 켜고, 더 나쁜 모델이 조용히 운영으로
올라간다.
구글이 정리한 MLOps 성숙도 문서는 이 지점을 분명히 구분한다. 학습 파이프라인을
자동화하는 것(연속 학습)과 그 산출물을 운영에 올리는 것(연속 배포)은 다른 단계이고,
후자에는 모델 검증 단계가 반드시 끼어 있어야 한다
([MLOps: Continuous delivery and automation pipelines in machine learning](https://cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning)).
어떻게 동작하나
관문은 생각보다 단순한 규칙 몇 줄이다. 어려운 것은 규칙을 정하는 일이 아니라
규칙이 읽을 숫자가 믿을 만하게 준비돼 있는가다. 그래서 앞 모듈들이 먼저 온다.
입력 champion 별칭이 가리키는 버전의 평가 지표 challenger 별칭이 가리키는 버전의 평가 지표 데이터 계약 검증 결과규칙 challenger >= champion + margin 이고 계약 검증 통과출력 promote | block + 왜 그렇게 판단했는지여유폭(margin)을 두는 이유는 측정 잡음 때문이다. 같은 데이터라도 씨앗이 다르면
정확도가 소수점 셋째 자리에서 흔들린다. 여유폭이 0 이면 그 흔들림만으로 챔피언이
바뀌고, 다음 주에 또 바뀐다. 배포가 잦아지는 것이 아니라 무의미하게 잦아진다.
MLflow 는 이 판단의 결과를 버전 태그로 붙이는 방식을 문서에서 예로 든다. 검증을
기다리는 버전에 validation_status: pending, 통과한 버전에 approved 를 붙이는
식이다([MLflow Model Registry](https://mlflow.org/docs/latest/ml/model-registry/)).
관문의 판정을 태그로 남기면 사람이 아니라 자동화가 그 조건으로 다음 단계를 고를 수 있다.
관문이 읽는 숫자가 어디서 오는지도 정해 두어야 한다. 평가 분할을 매번 새로 뽑으면
지표가 흔들려 관문이 무의미해지고, 반대로 고정된 분할만 계속 쓰면 그 분할에
과적합된 모델이 관문을 통과하게 된다. 그래서 대개 고정 분할을 기준으로 삼되
주기적으로 새 분할을 추가해 두 숫자를 함께 본다.
그리고 되돌리기. 앞 모듈에서 별칭을 둔 이유가 여기서 회수된다. 되돌리기는champion 이 가리키는 버전을 바꾸는 일이고, 그 변경은 감사 기록에 한 줄이 된다.
기록에 이전 값(from)까지 적어 두면 처음부터 재생했을 때 지금 상태가 나와야 하고,
나오지 않으면 어딘가에서 기록되지 않은 변경이 있었다는 뜻이다. 이 성질 하나가
"누가 언제 무엇을 바꿨는가" 를 추측이 아니라 검산으로 만든다.
관문의 판정은 사람이 읽을 수 있어야 한다. block 이라는 한 단어만 남으면 다음
사람은 왜 막혔는지 알 수 없어 관문을 의심하게 되고, 의심받는 관문은 곧 꺼진다.
그래서 판정 옆에 비교한 두 숫자와 여유폭, 그리고 어떤 조건에서 걸렸는지를 문장으로
함께 남긴다.
현장에서 만나는 모습
관문을 만들고 나면 반드시 우회 요청이 온다. 데모가 내일이고, 고객이 기다리고 있고,
이번 것은 확실하다고 한다. 이때 관문을 끄는 대신 **무시할 수 있게 하되 무시한
사실을 남기도록** 설계하는 편이 오래간다. 막을 수 없는 것을 막으려 하면 사람들은
관문 바깥에 새 경로를 만들고, 그러면 기록조차 남지 않는다.
두 번째로 흔한 것은 관문이 보는 지표가 하나뿐인 경우다. 전체 정확도만 보면 소수
집단에서 크게 나빠진 모델이 통과한다. 지표를 늘리는 일은 쉽지만, 늘린 지표마다
"얼마나 나빠지면 막을 것인가" 를 정해야 해서 합의가 필요하다. 그 합의를 코드에
적어 두는 것이 관문의 진짜 값어치다.
세 번째는 되돌린 뒤의 침묵이다. 되돌리기까지는 잘하는데 왜 그 버전이 나빴는지를
아무도 적지 않아서, 두 달 뒤 비슷한 파라미터로 같은 실수를 반복한다.
네 번째는 관문을 통과한 뒤의 관찰이다. 승격은 끝이 아니라 시작이라, 새 챔피언이
운영에서 실제로 어떻게 하는지를 며칠 지켜볼 창이 필요하다. 그래서 많은 팀이
도전자를 일부 트래픽에만 태워 보고 나서 별칭을 옮긴다. 이때도 판단의 근거는
같은 자리에 적는다 — 관문이 본 숫자와 관찰 기간에 본 숫자가 다른 곳에 흩어져
있으면, 되돌릴지 말지를 결정하는 회의에서 두 사람이 다른 표를 들고 나온다.
다음 실습에서 할 것
버전과 별칭을 갖춘 작은 레지스트리를 세우고, 재학습 결과를 도전자로 등록하고,
여유폭을 가진 승격 관문을 코드로 만든다. 그다음 관문이 막은 승격을 사람이
밀어붙인 상황을 재현하고, 되돌린 뒤 감사 기록을 처음부터 재생해 지금 상태와
맞는지 검산한다. 마지막으로 입력 분포가 얼마나 움직였는지를 숫자로 만든다.