머신러닝 모델로 동네생활 신고 업무 자동화하기

Jaeyoon Chun(Aio)
당근 테크 블로그
13 min readJan 13, 2022

안녕하세요, 당근마켓 머신러닝 팀 모델 개발자 Aio(아이오)예요. 2021년에 동네생활 글을 필터링(신고)하는 새로운 모델을 개발하고, 필터링된 글을 자동으로 처리하는 시스템을 적용했는데요. 개발과정에서 치열하게 고민했던 내용들과 자동화 적용으로 얻은 성과를 공유하려고 해요. 이전 모델 개발기가 궁금하시다면 Matthew의 딥러닝으로 동네생활 게시글 필터링하기를 참고해주세요 :)

다음과 같이 글을 써보려고 해요.

  • 개발목표
  • 개발과정: 고민은 꼬리를 물고
  • 적용결과: 두 마리 토끼 잡기
  • 마치며
당근마켓 동네생활 pr 이미지예요.
당근마켓 동네생활

개발목표

동네생활에 필터링이 왜 필요한가요?

당근마켓은 따뜻한 지역 커뮤니티를 만들기 위해 노력하고 있어요. 동네생활은 이러한 당근마켓의 대표 서비스로, 동네 주민만 알 수 있는 동네 이야기, 알짜배기 정보를 나누며 진짜 동네 이웃들과 교감하는 공간으로 만들어 나가고 있어요.

그래서 업체를 홍보하는 글, 구인구직하는 글, 단순 거래하는 글은 동네생활에 어울리지 않아요. 당근마켓에는 이미 이런 글들을 품을 수 있는 다양한 지역 서비스들이 있어요. 새 모델은 동네생활에 어울리지 않는 글들을 골라내어 적절한 서비스로 옮겨줌으로써, 사용자들이 더 의미있는 연결을 만들 수 있도록 돕는다는 목표로 개발되었어요. 더 나아가 “따뜻함, 신뢰와 존중”과 거리가 먼 글은 피드에서 미노출 시켜 사용자 경험을 긍정적으로 만들고자 했어요.

운영을 더 쉽고 빠르게

당근마켓 MAU(Monthly Active User)는 1700만명을 상회하고 있어요. 사용자가 증가하는 만큼, 사용자들에게 좋은 서비스를 제공하기 위한 운영 업무도 같이 증가해요. 이런 상황에서 머신러닝 기술을 도입하면 늘어나는 운영 업무를 효율적으로 해결할 수 있어요.

더 자세히 살펴볼까요. 당근마켓에는 고객서비스를 담당하는 자회사인 당근서비스가 있어요. 당근서비스 업무 중 많은 양을 차지하는 신고 처리에 모델을 도입하면 신고 처리하는데 소요되는 시간을 단축할 수 있어요. 운영자는 단축된 시간만큼 사용자에게 더 중요한 업무에 집중할 수 있을 거예요. 또한 ML팀에서 운영자의 처리 결과를 학습 데이터로 사용하기 때문에 모델의 성능에도 다시 좋은 영향을 미치게 돼요. 그래서 신고 처리의 일정 부분 이상 자동화한다는 목표를 세우고 개발에 착수했어요.

ML팀과 당근서비스의 선순환 도표. ML 모델 개선되면 당근서비스의 신고처리량 감소되고 당근서비스에서 중요 업무에 집중 가능해요. 그러면 다시 ML 모델도 개선돼요.
ML팀과 당근서비스의 선순환

개발 과정: 고민은 꼬리를 물고

글을 필터링 하려면 내용에 따라 이 글이 동네생활 피드에 있어도 되는 글인지 확인해야 돼요. 제재할 글이라면 어떤 항목으로 제재하여 후속 조치를 할지도 분류해야 하구요. 그래서 글을 이해하고 적절하게 분류하는 글모델을 개발하고자 했어요.

저희는 당근서비스 운영자들이 모델의 동작을 이해할 수 있게끔 복잡도를 낮추고 싶었어요. 또한 복잡도가 낮아지면 준-실시간으로 동네생활에 올라오는 글을 예측하기에 용이해 운영 자동화에 도움이 돼요. 그래서 모델 아키텍쳐는 최대한 심플하게 가져가려고 했어요. 오히려 많은 시간을 투자하여 고민한 것은 동네생활 도메인에 적합한, 그리고 실시간으로 쏟아져 들어오는 온라인 데이터에 적합한 모델을 만들 방법이었어요.

데이터 작업

개발 초기에 학습을 위한 데이터를 만들기 위해 다양한 작업을 했어요. 이 글에서는 데이터를 정의한 과정, 데이터 불균형 문제를 해결한 과정을 담아보려고 해요.

분류 문제를 지도 학습으로 풀려면 negative 데이터와 positive 데이터가 있어야 하는데요. 당근마켓 데이터는 어떤 것이 negative고 positive인지 정의되어 있지 않기 때문에 모델 개발자가 직접 데이터를 보며 설계해야 돼요.

동네생활 데이터 Venn diagram. 전체 글 중에 신고된 글이 있고 제재된 글이 있어요. 신고된 글과 제재된 글은 상당 부분 교집합을 가지고 있지만 완전한 포함관계는 아니에요.
동네생활 데이터 Venn diagram

머신러닝 팀은 수작업을 지양하기 때문에, 라벨링을 하지 않고서 positive로 간주할 데이터를 찾아야 했어요. 동네생활 글이 신고가 되면 당근서비스의 운영자들이 신고 내용을 보고 제재하거나 정상글이면 다시 거부해요. 저희는 이렇게 운영자가 신고 처리한 결과값을 이용해 추가 작업을 따로 하지 않으면서 학습 데이터를 확보하고자 했어요. 처리 결과값 중에서 어떤 영역의 글을 positive로 볼지 결정하기 위해 데이터 정의를 변경하며 실험하는 과정도 거쳤어요.

사용할 데이터를 정의하니 이제는 데이터 불균형이 고민되었어요. negative와 positive의 비율이 1:1이면 가장 좋겠지만, 현실의 데이터는 언제나 극심하게 불균형하죠. 아래에서 왼쪽 원형차트는 동네생활에서 제재까지 이어진 글과 아닌 글의 비율을 대략적으로 나타낸 차트예요. 학습에 적절한 양을 판단하기 위해 1:1, 1:3, 1:6 … 등으로 negative sampling을 하고 실험을 돌려서 가장 적절한 비율값을 찾았어요.

불균형한 동네생활 데이터를 나타내는 원형 차트 2개. 정상글의 일부만 제재된 글이고, 제재된 글 안에서도 항목이 많은 것을 보여주고 있어요.
불균형한 동네생활 데이터를 나타내는 차트

positive에 여러 라벨이 존재한다면 positive 라벨 간의 불균형 문제도 생길 수 있어요. 동네생활의 신고 항목은 약 30개예요. 제재된 데이터가 부족한 항목이 많다면 모델이 이 항목들까지 다 예측하는 건 너무 어려운 문제가 돼요. 확인 결과 데이터 양이 상대적으로 많이 부족한 항목들이 있었고 이 항목을 그룹으로 묶어 “기타” 항목으로 만들었어요.

신나는 모델 개발

데이터를 이해하고 모델링에 적합하도록 만드는 데 오랜 시간을 투자하다가, 본격적으로 코딩을 하니 설레기까지 하더라구요 :) 이 파트에서는 모델 간의 비교를 위한 메트릭 설정, 사용한 모델 아키텍쳐와 학습 방법, 파이프라인 구축에 대해서 설명할 거예요.

슬랙 캡쳐본. 캡쳐내용: @싱클레어 데이터 끝내보리기 다짐 스레드
슬랙 캡쳐본. 캡션: 약간 힘들었던 데이터 보는 기간.. 캡쳐 내용: 오늘 기필코 쿼리 다 짜서 데이터 다 뽑아놓고 퇴근할거예요 엉엉
약간 힘들었던 데이터 보는 기간..

모델 학습 실험을 위해 오프라인 메트릭을 정의했어요. 분류 문제를 풀 때는 False Positive(위양성)와 False Negative(위음성) 중 어떤 것을 중요하게 볼지 결정해야 하는데요. 대표적인 예시인 질병 검사 같은 경우는 False Negative를 줄이는 것이 더 중요할 거예요. 병에 걸린 사람을 놓치는 것은 위험하니까요.

하지만 동네생활에서는 False Positive를 줄이는 게 중요하다고 판단했어요. 좋은 글을 쓴 사람을 잘못 제재하면 따뜻함을 지향하는 당근마켓에 대한 신뢰도가 떨어질 것이기 때문이에요. 한편으로 잡아내는 양(Recall)도 포기할 수 없었기 때문에, Recall at Precision를 메인 메트릭으로 삼으면서 Precision 기준을 0.9로 잡았어요. Recall at Precision은 Precision의 한 점(point)에서의 Recall을 보는 수치라서, 더 안정적인 값을 파악하기 위해 AUC-ROC를 그려 구간의 면적을 계산해 참고했어요.

AUC-ROC plot 및 수치. 수치는 0.71 정도예요.
AUC-ROC plot 및 수치

실험을 위한 베이스라인 모델로는 1차로 LSTM, 2차로 vanila transformer을 쌓아 구현하고 성능을 내었어요. 계층적 분류에 좋을 것 같은 복잡한 아키텍쳐도 실험해보았지만, 단순함을 고수하려고 되돌아왔어요. 메인 모델 개발은 transformer 레이어를 쌓아 동네생활 글을 MLM으로 pretrain하고, 분류 task로 finetuning하는 방법을 사용했어요. BERT 방법론을 사용했다고 보시면 돼요. BERT에서 Next Sentence Prediction을 쓰는 것처럼 저희는 해당 글이 어떤 주제로 쓰였는지 주제를 예측하는 task를 사전학습 objective에 추가했어요. 사전학습할 때는 GCP의 TPU을 할당받아 돌렸어요. 깨알 홍보인데, 저희 팀은 필요하다면 GCP에서 TPU 등 필요한 리소스를 마음껏 활용할 수 있답니다.

finetuning의 분류 task를 설계할 때는 깊게 고민하며 실험을 했어요. 저희가 예측해야 되는 것은 두 가지였어요.

  • 이 글이 제재될 글인가?
  • 제재된다면, 어떤 항목으로 제재되어야 할까?

또한 모델이 낸 예측값을 바탕으로 다음 액션을 결정하기 위해서 threshold를 설정하는 게 필요한데요. 이 threshold 설정을 간결하게 만들어야 하는 니즈가 있었고, 결과적으로 아래와 같은 multi-class classification 형태를 가져갔어요.(라벨 개수만큼 output을 낸 후 softmax 통과)

finetuning 분류 task. 분류 head가 어떻게 구성되어 있고 제재인지 아닌지 파악하는 로직을 설명하고 있어요.
finetuning 분류 task

모델은 글이 정상글인지, 아니라면 어떤 항목으로 제재될 글인지 예측해요. 만약 정상글이라면 0번에 치우친 분포가 나타날 것이고, 4번으로 제재될 글이라면 4번에 치우친 분포가 나타날 거예요. 제재항목이 헷갈리는 글이라면 여러 항목에 걸쳐서 고르게 퍼진 분포가 나타날 거구요. threshold는 정상글인지, 제재될 글인지에 대해서 1개만 두도록 했어요.

모델이 특정 threshold 이상 확실하게 예측을 한다면 운영자가 확인하지 않도록 자동화 했어요. 특히 다른 서비스로 옮길 수 있는 글들은 자동으로 처리되는 양이 더 많도록 설계했어요. 어떤 threshold 값을 설정할지는 threshold 마다 메트릭을 뽑아낸 후 수동으로 조정하고 있어요. 장기적으로는 threshold 수동 조정을 자동화할 수 있는 방법을 고민 중이에요.

파이프라인 빌드하기

모델 개발을 하는 한편, 데이터 수집부터, 배포까지 한 번에 연결할 수 있는 파이프라인도 구축했어요. 모델 개발 후 적용만 하면 끝나는 게 아니라 주기적으로 모델을 재학습해서 업데이트 해야 되기 때문이에요. 주기적으로 재학습 해야하는 이유를 정리해보았어요.

  • 새로운 글이 끊임없이 쌓여 데이터가 늘어남
  • 동네생활에 올라오는 글의 패턴은 늘 달라짐
  • 신고 항목 추가/삭제 빈번히 일어남
  • 제재 기준 변경이 있음

예를 들어볼까요. 기존에는 특정 내용의 글들이 A 항목으로 제재 되었는데, 모종의 이유로 기준이 변경되어 더 이상 A가 아닌 B 항목으로 제재된다고 생각해보아요. 이렇게 기준이 변경되면 빠르게 데이터를 변경하여 다시 학습 후 배포해야 올바른 예측을 할 수 있을 거예요.

이런 상황에서 파이프라인 작업이 안 되어 있다면 데이터 수집도 다시 하고, 변환도 따로 하고, 학습도 다시 돌리고, 기존에 배포 된 모델을 저장된 곳에서 직접 불러와서 성능 비교도 해야 되고… 개발자가 해야 하는 액션이 너무 많을 거예요. 이 때문에 저희는 모델 개발과 발 맞추어 파이프라인 작업을 병행했어요.

파이프라인 그래프
파이프라인 그래프

머신러닝 팀에서는 TensorFlow Extended(TFX)를 도입하여 파이프라인을 구축하고 있어요. TFX 라이브러리를 사용하여 각 단계에 맞는 컴포넌트를 개발하고 개발된 컴포넌트들은 kubeflow를 통해 orchestration해요. (kubeflow 파이프라인 운용하기 from Yoon). evaluator 다음에 지정된 모델 버켓으로 모델을 업데이트 하는 pusher, k8s pod를 재시작하여 모델을 배포하는 restarter 컴포넌트가 붙어있기도 해요. 저희는 파이프라인 실행 중 중요 컴포넌트 실행이 끝날 때마다 슬랙으로 알림을 보내 모니터링도 하고 있어요.

파이프라인으로 빌드된 모델을 프로덕션 환경에서 서빙할 때는 TensorFlow Serving을 이용하고 있어요. tf-serving을 이용하면 추가적인 서버 구축 작업이 필요없어서 편리해요. 또한 기본적으로 gRPC, REST API를 모두 지원하고 있고, 인퍼런스 성능향상을 위한 옵션들을 제공하고 있어서 빠르고 안정적으로 모델을 서빙할 수 있었어요.

이렇게 주기적으로 재학습하고 빠르게 배포하는 사이클을 만들었는데도, 여전히 고민되는 문제가 있어요. 재학습 시 현재 버전의 경향성을 잘 유지하면서 새로운 데이터를 학습하는 방법은 무엇일까? 즉, 현재 버전이 잘 예측하는 것은 여전히 잘 예측하도록 하면서, 잘못 예측하는 걸 더 잘하게 만드는 방법은 무엇일까? 이 고민에 대한 답은 앞으로도 계속 해결해 나가려고 해요.

적용 결과: 두 마리 토끼 잡기

모델을 프로덕션에 적용한 후 결과를 트랙킹하며 데이터를 분석했어요. 저희는 분석 결과를 바탕으로 모델 개발 전 목표했던 두 마리 토끼를 잡았다고 결론내릴 수 있었어요. 운영을 자동화 하자. 이를 통해 당근마켓 사용자들이 더 좋은 경험을 얻을 수 있도록 돕자. 아래에서 분석 결과를 설명해볼게요.

신고된 글 처리량. 모델 적용 전후로 수동처리량이 감소한 것을 보여주는 바 그래프예요.
차트 1: 제재된 글 처리량
글 이동 예시. 예시 내용: “동네생활, 동네 00 가게에서 일할 홀 서버 구해요~ 시급 만원이고 일주일에 두번 두시간 정도? 010-xxxx-xxxx으로 연락주세요" 라는 글이 당근알바로 이동된다는 내용이에요.
글 이동 예시

차트 1은 동네생활에서 제재된 글이 어떻게 처리되었는지 보여주는 그래프예요. 모델 적용 전에는 운영자가 대부분 수동으로 제재 했는데요. 적용 후에는 자동으로 처리되는 양이 일정하게 유지되는 것을 확인할 수 있어요. 적용 전후로 수동 처리량은 약 40% 감소했고, 제재한 것뿐만 아니라 거부한 양까지 고려해서 계산하면 22% 정도 감소했어요.

다른 서비스로 글을 이동하는 로직도 각 서비스 팀분들과 협업하여 적용했어요. 구인글을 예시로 들어보면, 동네생활 피드에서는 큰 반응 없이 흘러갔던 글을 구직자들이 많이 이용하는 “당근알바”로 옮겨줌으로써 구인글을 올린 유저가 질 높은 동네 연결을 경험할 수 있게 돕고 있어요.

차트 2: 글이 제재될 때까지 걸린 시간. 적용 전, 후 제재까지 걸린 시간의 평균을 보여주는 바 그래프예요.
차트 2: 글이 제재될 때까지 걸린 시간
차트 3. 특정 항목에서의 사용자 신고량. 모델 적용 전후로 사용자 신고량을 보여주는 시계열 그래프예요
차트 3: 특정 항목에서의 사용자 신고량

차트 2는 동네생활에 적절하지 않은 글이 제재될 때까지 걸린 시간의 평균을 나타낸 결과예요. 사용자가 글을 쓰면 준-실시간성으로 모델이 예측값을 내고 자동으로 처리하기 때문에, 사용자 신고를 통해 운영자가 확인할 때보다 빠르게 처리할 수 있어요. 글이 제재되는 시간은 약 57% 감소했어요.

이렇게 동네생활에 적절하지 않은 글이 더 빠르게 제재되면, 사용자들이 해당 글들을 피드에서 발견하고 신고하는 빈도도 감소할 거라고 생각했어요. 예상대로 사용자 신고 감소 경향이 두드러지는 항목들도 발견할 수 있었어요(차트 3 확인). 해당 항목에서는 모델 적용 전후로 사용자 신고량도 감소했고, 전체 신고 중에 사용자 신고량이 차지하는 비율도 이전보다 47%가량 줄었답니다.

마치며

혹시 2021년 당근마켓 연말 총결산 기사와 Gary가 쓴 풋내기 창업자의 스타트업 창업하기 13화_토론토로 이사를 보셨나요? 당근마켓은 현재진행형으로 성장하고 있고 글로벌로의 확장도 꿈꾸고 있어요. 이런 당근마켓에서 머신러닝 팀은 우리의 기술로 이웃과의 연결을 고도화한다라는 가슴 뛰는 미션을 세웠답니다.

아직도 당근마켓에는 머신러닝 모델을 도입할 영역이 많이 남아 있어요. 위에서 써내려 온 것처럼 계속 고민해 나갈 재미있는 문제들도 곳곳에 있구요. 팀원들끼리 “당근마켓은 머신러닝 엔지니어의 놀이터 같은 곳이다” 라고 얘기하곤 해요. 이 글을 읽으면서 문제를 고민하는 재미를 느끼신 분, 내 역량을 마음껏 뽐내며 임팩트 있는 일을 하고 싶은 분, 계셨나요? 저희 팀으로 초대 드리고 싶어요! 많은 관심 부탁드려요 :)

당근마켓과 함께 할 멋진 머신러닝 엔지니어를 찾고 있어요
당근마켓 ML팀을 소개합니다!

긴 글 읽어주셔서 감사합니다!

--

--