분포 이동
고친 사람 github-actions[bot]
분포 이동은 잘 맞던 모델이 운영 중에 조용히 틀리기 시작하는 문제입니다. 모델이 배운 데이터와 지금 들어오는 데이터의 모양이 달라져서 생깁니다. 코드는 한 줄도 안 바뀌었습니다. 세상이 바뀌어서 벌어지는 일이라 오류 메시지가 뜨지 않습니다.
쉽고 빠른 이해
분포 이동은 모델이 배운 세상과 지금의 세상이 어긋나 예측이 빗나가는 문제입니다. 한국어 메일로 주로 학습한 스팸 필터에 어느 날부터 영어 메일이 쏟아지면, 필터는 낯선 메일을 두고 자주 틀립니다.
이 문제에 이름이 붙은 까닭은 출시 전 시험으로는 안 보이기 때문입니다. 시험에 쓰는 데이터도 학습 데이터와 같은 때 모은 것이라 점수가 높게 나옵니다. 떨어진 점수는 출시하고 한참 뒤에야 보입니다.
어떻게 도나:
- 어느 시점까지 모은 데이터로 모델을 학습하고 고정합니다
- 사용자, 시장, 계절 같은 바깥 사정이 바뀝니다
- 모델은 여전히 옛 데이터의 모양을 기준으로 답합니다
- 예측이 조금씩, 또는 한꺼번에 어긋납니다
무엇이 나빠지나 — 모델을 한 번 만들고 끝낼 수 없습니다. 들어오는 데이터를 계속 지켜보고 때마다 다시 학습해야 해서 운영에 품이 듭니다.
상세
이 절은 분포 이동이 무엇인지를 스팸 필터 하나를 예로 들어 보입니다. 먼저 모델이 어떤 가정에 기대어 답하는지 세웁니다. 이어서 그 가정이 깨지는 세 가지 모양을 차례로 보고, 이 문제가 터지는 조건과 알아채는 방법을 적습니다. 끝으로 이웃한 문제들과 가릅니다.
눈길에 들어선 운전자
도심에서만 운전을 배운 사람이 처음으로 눈 덮인 산길에 들어섭니다. 배운 대로 핸들을 돌리고 브레이크를 밟습니다. 바퀴가 미끄러지고 차가 생각보다 멀리 나갑니다. 운전 솜씨가 줄어든 것이 아니라 길이 배울 때와 달라진 것입니다.
모델이 기대는 가정
머신러닝 모델은 답을 아는 예제를 많이 봅니다. 그리고 입력에서 답을 내는 규칙을 익힙니다. 이 과정을 학습이라고 부릅니다.
학습에 쓰는 예제 묶음은 훈련 데이터라고 합니다. 스팸 필터라면 「스팸」이나 「정상」 표시가 붙은 옛 메일 수만 통이 훈련 데이터입니다.
학습을 마친 모델을 서비스에 붙여 새로 들어오는 입력에 답하게 하는 단계를 운영이라고 합니다. 운영에서 모델이 받는 입력은 훈련 데이터에 없던 새 메일입니다. 모델은 새 메일도 옛 메일과 비슷한 모양이리라 가정하고 답합니다.
이 모양을 분포라고 부릅니다. 분포는 값들이 어디에 얼마나 몰려 있는지를 적은 것입니다. 메일로 치면 어떤 낱말이 얼마나 자주 나오는지, 스팸이 전체의 몇 분의 일인지가 분포입니다.
모델이 옛 메일에서 익힌 규칙은 옛 분포 안에서만 확인된 것입니다. 새 메일이 같은 분포에서 나온다면 규칙은 계속 통합니다. 분포 이동은 「새 메일도 같은 분포에서 나온다」는 이 가정이 깨진 상태입니다. 훈련 데이터의 분포와 운영에서 들어오는 데이터의 분포가 달라진 것입니다.
테스트 점수가 못 잡는 까닭
모델을 내놓기 전에는 학습에 쓰지 않은 예제를 따로 떼어 두고 점수를 잽니다. 이 예제 묶음을 테스트 세트라고 합니다. 테스트 점수는 「운영에서도 이 정도 맞힐 것이다」라는 예상으로 쓰입니다.
테스트 세트는 대개 훈련 데이터와 같은 때 모은 메일에서 떼어 냅니다. 둘은 분포가 같습니다. 그래서 테스트 점수는 「옛 분포에서 얼마나 맞히나」만 잽니다.
운영 입력은 출시한 뒤에 들어옵니다. 그 사이 바깥 사정이 바뀌었다면 운영 입력은 다른 분포에서 나온 셈입니다. 아래 그림에서 테스트 세트는 훈련 데이터와 같은 묶음에서 나오고, 운영 입력만 따로 들어옵니다.
flowchart TD
subgraph S1["출시 전에 모은 옛 메일"]
A["옛 메일"] --> B["훈련 데이터"]
A --> C["테스트 세트"]
end
subgraph S2["출시 뒤에 들어오는 새 메일"]
D["운영 입력"]
end
B -->|"학습"| M["모델"]
C -->|"점수를 잰다"| M
D -->|"답을 구한다"| M
테스트 점수가 높아도 분포 이동을 못 잡는 까닭이 이 그림에 있습니다. 테스트 세트는 운영 입력이 오는 쪽을 한 번도 보지 않습니다.
입력의 모양이 바뀔 때
분포가 바뀌는 방식은 크게 셋입니다. 첫째는 입력 쪽이 바뀌는 경우입니다. 모델에 넣는 입력의 칸 하나하나를 특성이라고 합니다. 스팸 필터라면 메일에 든 낱말, 보낸 주소, 첨부 파일이 있는지가 특성입니다.
한국어 메일이 대부분이던 서비스가 해외 사용자를 받기 시작했다고 합시다. 영어 메일이 크게 늘어납니다. 어떤 낱말이 들어 있으면 스팸이라는 관계는 전과 같습니다. 달라진 것은 모델이 드물게 본 메일이 많이 들어온다는 점입니다. 훈련 데이터에 영어 메일이 적었으니 모델은 영어 메일을 두고 자주 틀립니다.
이렇게 특성의 분포만 바뀌고 특성과 답의 관계는 그대로인 경우를 공변량 이동이라고 부릅니다. 공변량은 통계에서 입력 쪽 변수를 부르는 말입니다.
답의 비율이 바뀔 때
둘째는 답의 비율이 바뀌는 경우입니다. 모델이 맞혀야 하는 답을 레이블이라고 합니다. 스팸 필터의 레이블은 「스팸」과 「정상」 둘입니다.
학습 때는 메일 열 통에 한 통이 스팸이었다고 합시다. 모델은 스팸의 생김새와 함께 「스팸은 드물다」는 것도 익힙니다. 그래서 애매한 메일은 정상 쪽으로 기울여 판정합니다.
어느 달 스팸이 몰려 메일 절반이 스팸이 됐습니다. 스팸 한 통 한 통의 생김새는 전과 같습니다. 그래도 모델은 애매한 메일을 여전히 정상 쪽으로 기울이니 놓치는 스팸이 늘어납니다. 이처럼 레이블의 비율이 바뀌는 경우를 레이블 이동이라고 부릅니다.
입력과 답의 관계가 바뀔 때
셋째는 같은 입력에 붙어야 할 답이 바뀌는 경우입니다. 한때 스팸에서만 보이던 표현을 정상 업체도 쓰기 시작했다고 합시다. 전에는 그 표현이 든 메일에 「스팸」이 붙었습니다. 이제는 같은 메일에 「정상」이 붙어야 합니다.
반대 방향도 있습니다. 정상 메일에서만 보이던 표현을 스팸이 흉내 내기 시작하면, 그 표현이 든 메일에 이제는 「스팸」이 붙어야 합니다. 두 경우 모두 입력의 모양은 전과 같습니다. 붙어야 할 답만 바뀌었습니다.
이렇게 특성과 레이블 사이의 관계가 바뀌는 경우를 개념 드리프트라고 부릅니다. 드리프트는 떠내려간다는 뜻입니다. 모델이 배운 「무엇이 스팸인가」라는 개념이 현실에서 조금씩 떠내려간다는 데서 온 이름입니다.
세 경우를 한 표로 모으면 아래와 같습니다. 운영에서는 셋이 한꺼번에 오기도 합니다. 해외 사용자가 늘면서 스팸의 비율과 수법까지 함께 바뀌는 식입니다.
| 갈래 | 바뀌는 것 | 전과 같은 것 | 스팸 필터의 예 |
|---|---|---|---|
| 공변량 이동 | 특성의 분포 | 특성과 레이블의 관계 | 영어 메일이 늘었다 |
| 레이블 이동 | 레이블의 비율 | 레이블마다 메일의 생김새 | 스팸이 절반이 됐다 |
| 개념 드리프트 | 특성과 레이블의 관계 | 특성의 분포일 수 있다 | 같은 표현이 이제 스팸이 아니다 |
바뀌는 빠르기
분포는 바뀌는 빠르기도 여러 가지입니다. 빠르기에 따라 알아채는 때가 달라집니다. 아래 표는 같은 스팸 필터에서 셋을 견줍니다.
| 빠르기 | 스팸 필터의 예 | 알아채는 때 |
|---|---|---|
| 한꺼번에 | 가입 화면이 바뀌어 새 사용자 층이 몰린 날 | 그날부터 틀림이 늘어 비교적 일찍 안다 |
| 조금씩 | 스팸 수법이 몇 달에 걸쳐 바뀐다 | 하루하루 차이가 작아 늦게 안다 |
| 되풀이 | 연말마다 쇼핑 광고 메일이 몰린다 | 한 해 치를 봐야 무늬가 보인다 |
발생 조건
분포 이동이 예측을 어긋나게 하려면 아래 셋이 겹쳐야 합니다.
- 모델을 어느 시점까지 모은 데이터로 학습하고, 그 뒤로 고정해 둡니다
- 운영 입력을 만들어 내는 바깥 사정이 그 시점 뒤로 바뀝니다. 사용자, 시장, 계절, 앞단 시스템이 보내는 값 같은 것들입니다
- 바뀐 부분이 모델이 기대던 특성이나 관계를 건드립니다
앞단 시스템의 변화도 2번에 듭니다. 금액을 원 단위로 보내던 시스템이 천 원 단위로 보내기 시작하면, 모델이 받는 금액 특성의 분포가 하루아침에 바뀝니다.
셋 가운데 하나만 빠져도 예측은 어긋나지 않습니다. 새 데이터로 자주 다시 학습하면 1번이 풀립니다. 바깥이 바뀌어도 모델이 쓰지 않는 특성만 바뀌었다면 3번이 빠집니다.
재현도 이 조건대로 합니다. 1월까지의 메일로 필터를 학습합니다. 같은 때 모은 테스트 세트와 7월 메일로 각각 점수를 잽니다. 두 점수의 차이가 그 반년 사이의 분포 이동이 깎아 먹은 몫입니다.
알아채는 방법
운영에서 점수를 바로 재기 어려운 까닭은 정답이 늦게 오기 때문입니다. 스팸 필터라면 사용자가 스팸 신고를 눌러야 정답이 생깁니다. 대출 심사 모델이라면 빌린 돈을 갚았는지가 정답입니다. 그 답은 몇 달 뒤에야 나옵니다.
그래서 정답을 기다리는 동안에는 입력의 분포를 봅니다. 훈련 데이터의 특성 분포와 요즘 들어온 입력의 특성 분포를 견줍니다. 아래 코드는 메일 백 통씩 두 묶음에서 영어 메일의 비율을 잽니다.
train = ["ko"] * 90 + ["en"] * 10
live = ["ko"] * 40 + ["en"] * 60
train.count("en") / len(train) # 0.1
live.count("en") / len(live) # 0.6
영어 메일의 비율이 0.1 에서 0.6 으로 뛰었습니다. 모델은 이 차이를 오류로 알리지 않습니다. 누군가 이렇게 재고 있어야 보입니다. 입력 분포를 계속 재는 일은 모델 운영의 모니터링이 맡습니다.
입력 분포만 봐서는 개념 드리프트를 못 잡습니다. 입력의 모양은 전과 같고 붙어야 할 답만 바뀌었기 때문입니다. 이 경우는 늦게라도 들어오는 정답으로 점수를 다시 재야 드러납니다.
대응과 그 대가
흔한 대응은 최근 데이터로 모델을 다시 학습하는 재학습입니다. 모델이 기대던 분포를 지금의 분포로 옮겨 오는 일입니다. 대가는 학습을 되풀이하는 비용과, 새 모델을 내놓을 때마다 다시 검증하는 수고입니다.
다시 학습할 때 옛 데이터를 얼마나 남길지도 골라야 합니다. 최근 데이터만 쓰면 지금의 분포를 가깝게 따라갑니다. 대신 연말처럼 해마다 돌아오는 무늬를 잊습니다. 오래된 데이터까지 쓰면 그 반대가 됩니다.
이웃한 문제와 가르기
운영에서 모델이 기대만큼 못 맞히는 문제는 분포 이동 말고도 있습니다. 가르는 기준은 둘입니다. 테스트 점수가 어떻게 나왔나, 그리고 운영 점수가 언제 떨어지나입니다.
과적합은 모델이 훈련 데이터의 우연한 흔들림까지 외워서 새 데이터에서 틀리는 문제입니다. 훈련 점수에 비해 테스트 점수가 낮게 나오므로 출시 전에 드러납니다.
데이터 누수는 예측하는 순간에는 없는 정보가 학습에 섞여 들어간 문제입니다. 그래서 테스트 점수까지 부풀려집니다. 운영 점수는 출시하자마자 떨어집니다.
세 문제를 나란히 놓으면 아래와 같습니다.
| 문제 | 테스트 점수 | 운영 점수가 떨어지는 때 | 고장 난 곳 |
|---|---|---|---|
| 과적합 | 훈련 점수보다 낮다 | 출시 전에 이미 보인다 | 모델이 훈련 데이터를 외웠다 |
| 데이터 누수 | 부풀려져 높다 | 출시하자마자 | 예측할 때 없는 정보가 학습에 섞였다 |
| 분포 이동 | 옛 분포에서는 맞다 | 출시 뒤 바깥이 바뀐 때부터 | 세상이 훈련 데이터에서 멀어졌다 |
표본 편향은 분포 이동과 가깝습니다. 훈련 데이터를 한쪽으로 치우치게 모으면 운영 첫날부터 두 분포가 다릅니다. 시간이 흘러 갈라지는 것이 아니라 처음부터 갈라진 채 출발하는 경우입니다.
관련 항목
분포 이동의 하위 종류
공변량 이동 · 레이블 이동 · 개념 드리프트 · 데이터 드리프트
분포 이동을 알아채는 지표와 검정
모니터링 · 모델 모니터링 · KL 발산 · 콜모고로프-스미르노프 검정 · 카이제곱 검정 · 모집단 안정성 지수 · 히스토그램
분포 이동에 맞서는 대응 수단
재학습 · 온라인 학습 · 도메인 적응 · 중요도 가중치 · 전이 학습
분포 이동과 함께 운영 점수를 떨어뜨리는 문제
과적합 · 데이터 누수 · 표본 편향 · 클래스 불균형 · 피드백 루프 · 일반화
분포 이동이 끼어드는 머신러닝 작업 흐름
머신러닝 · 훈련 데이터 · 테스트 세트 · 특성 · 레이블 · 모델 서빙 · 추론 · MLOps
분포 이동을 설명하는 통계 개념
다른 이름: distribution shift · dataset shift · 데이터셋 이동 · 분포 변화