← posts

Data Drift를 발견하는 법

,

그리스의 철학자 헤라클레이토스는 “같은 강물에 두 번 발을 담글 수 없다”라는 말을 남겼다고 합니다. 강물이 끊임없이 흐르기 때문에 어떤 사람이 똑같은 자리에 서서 두번째로 발을 넣는다 한들 그건 처음과 같은 강물이 아니라는 뜻이죠. 마찬가지로 현실 세계의 데이터도 끊임없이 변화합니다. 일반적으로 ML 모델을 프로덕션 단계로 보내기 전에 우리가 원하는 수준의 성능을 보이는지를 확실히 하고 넘어가지만, 문제는 그 이후에 데이터가 변할 가능성이 크다는 것이고, 대부분의 경우 변한다는 것입니다. 모델은 적용되는 그 순간부터 학습에 사용했던 데이터와 다른 데이터를 맞닥뜨리게 됩니다. 운이 좋다면 한동안은 잘 돌아가겠지만 시간이 더 지나면 높은 확률로 테스트 결과와는 다른 성능을 보이게 될 겁니다.

한 문장으로 짚고 넘어가면, 오늘 이 글에서 다룰 것은

데이터나 변수 간 관계 변화로 인한 프로덕션 모델의 성능 저하

라는 현상(Drift)입니다.

특정 사건으로 인해 사람들의 소비가 변화하면서 모델의 예측과 동떨어지게 됨 - 이미지 출처

이런 현상을 부르는 말은 뭐 model drift, concept drift, data drift 등으로 다양한 것 같습니다. 보통 데이터의 통계적인 특성이 변화하는 걸 일반적인 용어로 concept drift로 일컫는 것 같고, 아래에서 조금 더 자세히 다루겠지만 여러 개념으로 쪼개서 각각 다른 이름을 붙이기도 하는 듯합니다.

아무튼 이 글은 주로 drift가 발생했을 때 이를 발견할 수 있는 방법들을 다룰 건데요. 들어가기 전에 그걸 왜 발견해야 돼? 라는 생각을 할 수도 있겠습니다. 어차피 필연적으로 데이터가 변화하는 거라면 그냥 최신 데이터에 맞게 계속 모델을 다시 학습시키면 되는 거 아닌가?

물론 그냥 때마다 모델 다시 학습시키도 가능한 해결책이 될 수 있겠습니다. 하지만 모델 학습이나 데이터 획득에 큰 비용이 드는 경우, 시간이 지났다는 이유만으로 그 리소스를 들여서 무조건 재학습을 하는 것은 비효율적인 선택이라고 할 수 있습니다. 적절한 주기를 찾는 것도 하나의 큰 과제일 수 있겠고요.

또한 모델과 상호작용하여 데이터의 변화가 일어났다면, 그 변화의 양상을 전혀 모르는 채로 단순히 처음 모델링을 할 때의 프로세스를 처음부터 다시 거치는 것만으로는 문제가 해결되지 않거나, 헤매게 될 수도 있습니다. 예를 들어 사기 거래 여부를 분류하는 모델을 만들었고, 해당 모델이 제법 성능이 좋아서 그 분류 결과를 통해 사기꾼들이 거래하지 못하도록 조치를 취했다고 칩시다. 이런 영역의 문제점은 나쁜 사람들은 보통 즉각적으로 반응을 한다는 것입니다. 만약 모델이 사기라고 분류한 케이스 중 대다수가 백 만원 이상의 고액 결제건이었다면, 앞으로 사기를 치고 싶은 사람들은 안 걸리게 십 만원씩 쪼개서 거래를 해볼까? 라고 생각할 수 있습니다. 우리가 만약 건당 결제 금액그 데이터의 분포 변화를 감지하고 어떻게 바뀌었는지 분석할 수 없다면 모델을 개선하는 데 어려움이 있겠죠 (사실 제가 맨날 겪는 일임).

결론적으로 데이터의 변화가 어떤 변수에서 어떤 방향으로 일어났는지 발견하고, 그게 현재 모델의 성능에 어떤 방식으로 영향을 끼치고 있는지 파악해야 보다 효과적이면서도 비용을 낭비하지 않는 해결책을 찾아낼 수 있습니다.

Drift의 종류

크게 2가지 기준으로 구분해볼 수 있습니다.

  1. 개념적인 구분
    • Drift를 간단한 공식으로 나타낸다면, 어떤 방식으로든 타겟 변수(y)와 사용한 피쳐(X)의 관계의 분포가 변화한다는 것을 뜻함
      • 즉 결합확률분포의 변화 : Pt(X,y)!=Pt+(X,y)P_{t}(X,y)!= P_{t+}(X,y)
    • 이걸 쪼개보면,
      • P(X)P(X)의 변화 ( Covariate drift, Data drift ) : 인풋 피쳐의 분포가 변하는 경우
        • ex) 이탈율 예측을 하는데 예전에는 2-30대가 주로 이용하던 상품인데 트렌드의 변화 등으로 인해 요즘은 3-40대로 타겟 고객이 변화했을 때. 인풋 중 하나인 ‘나이’의 분포가 변화함

이미지 출처
- P(y)P(y) 의 변화 ( Label drift, Prior probability shift ): 타겟 변수의 분포가 변하는 경우
- P(y∣X)P(y|X) 의 변화 (Concept drift, Conditional change, Posterior class shift) : 인풋과 타겟 변수의 관계가 변하는 경우 - 가장 발견하고 규명하기 어려운 종류의 변화
-

이미지 출처
- 이런 것들을 개념적으로 쪼개 보는 이유는 다음과 같이 데이터의 변화가 반드시 모델의 결정경계를 건드리지는 않는 경우도 있기 때문!
-

이미지 출처

  1. 양상에 따른 구분
    • 다음 그림을 보면 쉽게 이해됨

이미지 출처

Drift가 발생하는 이유?

다음은 흔히 언급되는 drift를 유발하는 원인들입니다.

어떻게 발견하는가

기본적으로는 비교 대상인 분포 2개의 데이터가 있어야 합니다. 즉 내가 비교하려고 하는 분포(Tested distribution)이 있고, 그게 뭐에 비해서 달라진 것이냐? 를 보는 근거 분포(Reference distribution)를 나누어 볼 수 있는데요. 보통 tested distribution이 아마 더 최신의 데이터일 것이고, reference distribution은 보다 이전의 데이터이거나(ex.전주 동일 요일) 학습에 사용했던 데이터 등 과거의 데이터를 의미합니다.

시간이 흐름에 따라 매번 이전의 데이터와 비교를 하겠다면 sliding window로 일정 시간 텀마다 비교할 수 있겠죠? 그렇다면 그 주기를 정하는 것도 중요한 문제입니다(ex. 1분 전? 어제? 전주? 전달?). 타이밍을 줄일수록 빠르게 drift를 발견할 수 있겠지만 노이즈도 많아지기 때문입니다. 이 가게에 원래 금요일 밤에는 30대 직장인이 많이 방문하는데, 어제(목요일 밤)과 분포가 다르다고 문제가 생겼다고 볼 수는 없듯이요.

한달에 한번, 전주의 같은 요일과 비교하기 - 이미지 출처

어쨌든 이런 것들을 정했다고 치고, 이제 일정 주기마다 데이터를 비교해서 drift가 발생할 경우 나에게 얼럿을 주는 시스템을 상상해 봅시다. 이런 시스템이라면 결국 drift의 정도를 하나의 단일한 값으로 나타내주고 그 값에 대해 이 정도를 넘으면 경고다! 라는 임계값(threshold)를 정할 필요가 있겠죠.

분포 간 통계적 차이를 계산해보자

간단한 접근으로, 각각의 피쳐에 대해 단변량으로 접근해서 하나하나 모니터링할 수 있겠습니다. 이때는 각각의 drift 정도를 수치화한 다음 하나의 값으로 집계하고, 이때 피쳐 중요도에 따라 가중치를 둬도 좋을 듯합니다.

따라서 두 개의 데이터를 놓고 그 분포가 얼마나 떨어져 있는지 거리를 수치화하는 다음과 같은 방법들이 필요하겠습니다.

이미지 출처
- 누적분포함수를 그림으로 그려보면 저 검은색 선인 둘 사이의 거리 D를 통계량으로 테스트함
- Dn,m=sup⁡∣F1,n(x)−F2,m(x)∣D_{n,m} = \sup \mid F_{1,n}(x) - F_{2,m}(x) \mid
- 쉽게 쓰면 그냥 두 누적분포의 최대 차이라는 뜻
- 영가설은 두 데이터의 분포 간 차이가 없다 (p값이 유의수준 이하일 경우 기각되어 차이가 있다)
- from scipy.stats import ks_2samp 로 쓸 수 있음

이미지 출처
- 카테고리컬한 분포 또는 bin으로 나눈 데이터에 쓰기 편한 지표고 구현도 그냥 하면 될 듯. 다음과 같이 알려진 공식도 있음
- PSI<0.1\text{PSI} < 0.1 : 변화 없다
- PSI<0.2\text{PSI} < 0.2 : 약간 변화했다
- PSI≥0.2\text{PSI} \ge 0.2 : 심하다 너 경고

다만 이런 단변량적 시야에는 한계가 몇 가지 있습니다.

모델의 오차를 모니터링하자

단변량 접근 말고 통째로 모델 단위로 접근하는 방법을 생각해 봅시다. 모델 학습에 비용이 크게 들지 않는 상황이라면 비교 대상인 두 개의 데이터를 놓고, 따로 모델을 학습시켜서 얼마나 두 개의 모델이 동의하지 않는지(성능이든 피쳐 중요도든)를 수치화해볼 수 있습니다. 하지만 모델 재학습을 최대한 효율적으로 하려고 drift를 사전에 발견하려는 게 아닌가? 싶으니 이건 그렇게 좋은 방법이 아닐 수도 있겠어요.

아니면 시간의 흐름에 따라 모델의 틀림 자체를 모니터링할 수도 있겠죠. 다음 방법들은 scikit-multiflow 로 쉽게 써볼 수 있습니다.

오차 모니터링 예시 - 이미지 출처

다만, 이렇게 모델 단위로 drift를 발견해도, 결국 뭐가 어떻게 문제인지 알아내고 싶다면 각각의 피쳐로 drill down해서 보고 분포의 차이를 명확히 검증해 봐야 하는 경우가 많을 것 같다는 생각도 드네요.

차원 축소 후 데이터를 복구해보자

또다른 아이디어로는 데이터셋을 PCA 등으로 차원 축소한 다음 복구를 해볼 수도 있습니다. 데이터가 어떻게든 많이 바뀌었다면(PCA가 배운 분산의 형태에서 많이 떨어지게 되었다면) 복구 데이터의 오차가 커질 것이라고 가정하여, 원래 데이터포인트와 복구된 데이터 간의 유클리디언 거리를 drift 점수로 쓰는 것입니다.

모델 성능 예측과 모니터링에 쓰이는 NannyML 패키지 공식 문서에 PCA를 사용한 방법으로 다변량 drift를 발견한 예시가 있어서 가져와봤습니다.

별로 현실적인 예시는 아니지만 이런 식으로 두 개의 피쳐의 관계가 휙 돌아가(?) 버렸다면 분명 drift가 일어난 건 맞는데, 위와 우측에 그려진 각각의 단변량 분포를 보면 각각의 피쳐 내에서는 분포가 바뀐 것처럼 보이지가 않습니다. 이럴 때 앞서 언급한 PCA 방법을 쓰면, 다음과 같이 복구 오차가 증가하는 것을 볼 수 있습니다.


참고한 글들


Comments