Practice: Online A/B Test
새로운 알고리즘/모델을 실 서비스에 적용하기 전에 다각도로 실험을 반복해서 검증하고 확인하는 게 중요하다. 과거의 이력 (LOG) 데이터로 오프라인 테스트를 통과하면 다시 서비스 적용 전에 온라인 테스트를 거친다. 오프라인 테스트는 모델 자체의 적합도를 검증하는 과정과 이를 통한 서비스의 성능 (e.g., 광고에서는 CTR이나 매출 등)을 시뮬레이션하는 과정으로 나뉜다. 모델 적합도 검사는 보통의 데이터 과학에서 수행하는 학습과 검증/테스트 데이터를 분리해서 강건하고 정확한 모델을 구축하는 것이고, 성능 시뮬레이션은 가상의 환경에서 LOG를 replay 하며 신규 모델이 기존보다 더 나은지를 체크한다. 오프라인 시뮬레이션은 많은 제약이 있다. 실제 환경을 완벽하게 모사하지 못할 뿐만 아니라, LOG 기록은 기존 모델에 의존적인 흔적, 즉 서빙 바이어스 (serving bias)가 있어서 중립적으로 새로운 모델을 평가/검증하는 건 불가능하다. 그래서 완전히 새로운 데이터로 하는 온라인 테스트가 필요하다.
온라인 테스트도 크게 2단계로 나눈다. 다른 곳에서도 이렇ㄱ 진행하는지는 모르겠으나 카카오의 광고 랭킹 시스템은 온라인 테스트를 2단계로 나눈다. 첫째 테스트는 오프라인의 적합성 평가와 유사하게 실시간 로그 데이터를 온라인 모델 학습기로 공급(fedd)해서 모델을 업데이트한다. 이때 오프라인 테스트에서 사용한 다양한 모델 적합도 지표 (RIG, CTR calibration, AUC 등)가 오프라인에서와 비슷하게 또는 더 낫게 나오는지를 확인한다. 우리는 이를 제로 버킷 (Zero bucket) 테스트라 부른다. 이유는 다음의 A/B 테스트를 설명하면 자연스레 알게 된다. 정확도 1%, 아니 0.1%가 아쉽지만 오프라인에서 1~2%의 향상을 보였던 모델이 온라인 테스트에서는 유의미하지 않은 경우가 많았다. 온라인에서도 유효하려면 오프라인 테스트에서 5% 정도의 향상이 필요했다. 중요한 변화 (MUST)가 아니면 — 제로 버킷은 서비스에 영향을 주지 않기 때문에 웬만하면 진행하지만 — 오프라인 테스트에서 (5% 미만인 경우) 종종 반려한다.
제로 버킷에서 큰 문제가 없다면 흔히 A/B 테스트 또는 버킷 테스트라 부르는 온라인 테스트를 진행한다. A/B 테스트의 개괄은 IF카카오 2019의 자료에 넣었던 아래 그림을 가져왔다. A/B 테스트라 부르는 이유는 실시간으로 들어오는 트래픽을 대조군 (A)과 실험군 (B)으로 나눠서 성능을 비교하기 때문이다. 대조군/실험군의 트래픽 묶음을 버킷 (Bucket)이라 해서 버킷 테스트라고 불리기도 한다. 대조군인 A 버킷은 현재 시스템으로 그대로 서빙하고, 실험군 B버킷은 새로운 모델/시스템으로 서빙한다. 때론 양쪽 버킷에 같은 모델을 사용하는 A/A 테스트도 진행하는데 이는 서빙 바이어스나 모델 자체의 변동성의 효과가 있는지를 확인하기 위함이다. 예를 들어, B 버킷이 A 버킷보다 5% 정도 성능이 좋았는데, 알고 보니 트래픽의 변동성이나 모델의 안정성 등의 이유로 +/- 10% 정도의 성능의 편차가 확인됐다면 앞의 B버킷이 현재보다 더 낫다고 결론짓기 어렵다. 그래서 서비스 환경에서의 다양한 변동성에도 robust한지를 확인하기 위해서 하나의 모델을 A/A 또는 B/B 버킷에 동시에 투입해서 결과를 확인한다. (여유가 되면 == 개별 버킷에 충분한 트래픽이 할당되면)
흔히 A/B 테스트라 하면 하나의 대조군과 하나의 실험군으로 나눠서 진행하는 걸로 오해할 수 있는데, 트래픽의 양과 질에 따라서 여러 개의 버킷을 구성해서 여러 방법론을 동시에 검증할 수도 있다. 그래서 모델1은 버킷 B에, 모델2는 버킷 C에, 모델3은 버킷 D에 등으로 여러 버킷을 동시에 운영하면서 가장 성능이 좋은 모델을 선택한다. 버킷 분리로 인해서 현재 운영 중인 전체 시스템에 발생할 수 있는 부작용을 최소화하기 위해서 처음에는 5~10%의 트래픽을 실험군 B로 할당하고, 실험군의 성능이 더 좋으면 2~30%, 최종적으로 50%까지 늘려가면서 실험을 진행한다. A/B/C/D/… 멀티 버킷이라면 성능이 나쁜 것부터 제외하면서 남은 버킷에 트래픽을 몰아주면 된다. 초기 5~10%는 최소 이 정도는 돼야 하지 않을까라는 경험적 수치일 뿐이다. 트래픽의 양질에 따라서 버킷의 사이즈는 달리 설정할 수 있다. 단일 서비스에 균질의 트래픽이 하루에 수억, 수십억이 밀려온다면 1% 또는 그 이하로 설정해도 검증에 문제가 없다. 하지만 트래픽 양이 적거나 트래픽의 성격과 변동성이 심하다면 처음부터 2~30% 이상을 실험군으로 할당해야 할 수도 있다. (그때그때 달라요.) 초기에 실험군을 보수적으로 잡는 이유는 검증이 덜/안 된 모델에 처음부터 많은 트래픽을 할당해서 발생할 수 있는 전체 시스템의 불안정성을 최소화하기 위함이다. 예를 들어, 새로운 모델에 50% 트래픽을 할당했는데 미처 발견 못한 버그로 인해서 장애가 발생한다면 50%의 트래픽을 허비하게 되고 나아가 50%의 사용자의 만족도를 해치게 된다. 그래서 처음에는 보수적으로 버킷을 설정해서 모델의 성능뿐만 아니라, 안정성까지도 검증하고 차츰 그 양을 늘린다.
버킷의 트래픽은 임의 (random)으로 할당한다. 이때 크게 두 가지 방법이 있다. 트래픽마다 랜덤으로 A 또는 B 버킷에 할당할 수도 있고, 아니면 애초에 사용자 군을 임의로 A/B로 분리한 후에 해당 사용자가 접속하면 대상 버킷에 배정하기도 한다. 후자의 경우 특정 사용자가 계속 실험군에 속해서 이익/불이익을 당할 수도 있기 때문에 주기적으로 재할당 (re-assign)한다. 서비스에 따라서 하루 단위로 이뤄지기도 하고, 매시간마다 재할당이 이뤄지고 하고, 더 짧거나 더 긴 주기로 상황에 맞게 결정하면 된다. 어떤 방법을 사용하든 LOG에는 어느 버킷에 할당됐는지를 기록해서 사후 분석에 이용한다. (물론 Pivot과 같은 도구를 사용해서 실시간으로 모니터링하는 게 최선이다.)
그리고 전체 트래픽 (또는 사용자)의 5~10% 정도를 랜덤 버킷 (Random Bucket)이라는 특별한 버킷으로 구분한다. 랜덤 버킷은 특별한 규칙이나 알고리즘의 도움 없이 전체 콘텐츠 중에서 랜덤으로 선택해서 추천한다. 간혹 알고리즘이나 시스템의 성능을 평가하면서 이 랜덤 버킷과 성능을 비교하는 것을 목격하는데 이는 잘못됐다. 아무런 인위적인 조치가 없을 때 그 트래픽들이 어떻게 작동하는지를 보기 위함이지, 성능을 비교하기 위함이 아니다. 물론 내 알고리즘이 랜덤보다 못하다면 이건 심각한 문제가 있다. 앞서 서빙 바이어스를 언급했는데, 랜덤 버킷을 통해서 서빙 바이어스가 없는, 즉 unbiased 데이터를 모은다. Unbiased 데이터를 이용해서 학습함으로써 예측 모델의 bias를 다소 해소한다. 그리고 Exploration의 한 방법으로 랜덤 버킷을 종종 활용하는데, 신규 등록된 콘텐츠 (Cold-start)는 이전 기록이 없어서 기존 모델로는 추천할 수 없다. (신규 콘텐츠는 X만큼 기본값을 준다와 같은 휴리스틱을 만들 수는 있다.) 랜덤 버킷은 임의로 콘텐츠를 선택하기 때문에 신규 콘텐츠도 추천될 가능성이 있다. 이렇게 수집된 데이터는 다시 학습모델로 들어가서 다음부터는 정상적인 과정을 거쳐 추천된다.
흔히 이런 실험군/대조군 모형에서는 분산분석 ANOVA로 통계적 유의성 (null-hypothesis, statistical significance)를 검증해서 실험군이 더 나음을 확정 짓는 것이 원칙이다. 그런데 실제 서비스에서는 이런 통계적 유의성을 검증하는 게 말처럼 쉽지가 않다. 아무것도 없는 상태 (랜덤 버킷)과 비교하면 통계적으로 유의미한 차이를 보이겠지만, 이미 성숙한 시스템에서 새로운 알고리즘을 적용한다고 해서 극적으로 (통계적으로 유의미할 정도로) 성능이 향상되지 않는다. 물론 통게적 유의성을 가지면 최선이겠지만… 그래서 여러 현실 지표에서 조금의 개선이라도 있으면 실험군의 신규 모델을 전체 시스템에 적용하는 게 일반적이다. 단순히 매출이나 CTR의 상승뿐만 아니라, 서빙 속도나 장애 가능성, 전반적인 사용자 만족도 (정성적?) 등을 종합적으로 검토해서 실험군을 메인 버킷으로 지정한다. 짧게는 몇 시간의 운영으로 이를 판단하기도 하지만, 중요한 시스템이라면 일주일에서 한 달 정도 꾸준히 경과를 보면서 서서히 늘려가는 편이다. (기존) 모델의 에이징 현상으로 인해서 기존 모델을 전혀 변경 없이 그냥 재학습 하는 것만으로도 성능 차이를 보이기도 하기 때문에 성급한 결과 판단은 금물이다. 실험군을 메인으로 설정할 때 여건이 되면 (특히, 성능 차이가 확연하지 않다면) 기존 모델도 5~10% 계속 남겨서 계속 모니터링하는 것도 방법이다.