부스트캠퍼로서의 마지막 프로젝트
최근 나의 티스토리 포스팅 대부분을 차지했던 부스트캠프가 끝났다. 8월에 만났던 팀원들과 마지막 프로젝트까지 함께한 만큼 주제에 대해 다함께 오랜 시간 고민했고, 공통적으로 이루고 싶었던 목표는 "기술적 챌린지를 통해 사용자 경험 개선"이었다.
데이터셋 및 주제 선정
팀 소개서 작성->기업 매칭을 거쳐 업스테이지와 연계해 비즈니스적 가치를 창출하는 AI 프로덕트를 개발하는 것을 목표로 삼았다.
팀의 공통 관심사였던 콘텐츠•문화 도메인 내에서 사용자의 실제 로그 데이터를 찾아야 했고, 학습용 데이터는 플레이리스트와 유저 간의 상호작용 로그가 기록된 kaggle의 Spotify Dataset을 선정했다. 유저와 트랙 간의 상호작용 로그를 수집한 데이터셋이다.
음악 청취 상호작용 데이터를 기반으로 비즈니스적 가치를 창출할 수 있도록 "자영업자의 가게 무드에 최적화된 end-to-end 음악 추천 솔루션"을 고안해 기획, 개발 및 배포까지 완성했다.
Roles & Responsibilities
역할 |
Data Engineering |
LightGCN Modeling, Frontend Development |
MLOps |
Bi-Encoder Modeling, Explainable Recommendation System |
Backend Development, FM model Test |
나는 프로젝트에서 상호작용 데이터 모델링과 프론트엔드 개발을 맡았다.
AI Tech인만큼 이번 포스팅에는 모델링 과정에서 고민했던 점과 직면했던 문제와 해결 과정에 대해 이야기해보려고 한다.
모델링 진행 목차는 다음과 같다.
0. EDA
1. 목표 수립, 모델의 조건
2. 실험 과정
3. 마주한 문제 상황, 해결 과정
4. 얻은 깨달음
EDA
Long-Tail Problem

- 상호작용 횟수가 일부의 유저와 아이템들에게 쏠리는 현상이다.
- 유명한 아이템만 추천되어 편향이 강화되는 Echo Chamber가 우려되기에, 인기가 없는 아이템들도 추천될 수 있도록 모델링 해야 한다.
높은 희소성
- 사용자는 2819058개의 아이템 중 평균 810개의 아이템과 상호작용을 이룬다.
- 데이터 밀도: 0.000031339818 (인터랙션 수 / (user 수 * item 수))
목표 수립
- 프로젝트 초반에 수립했던 학습 목표는 웹서비스에 적재된 유저의 로그 데이터를 기반으로 개인화된 추천을 제공하는 모델을 개발하는 것이었다. 웹서비스에서 얻을 수 있는 로그 데이터를 직접 결정할 수 있는 상황이었기에, 한 번의 클릭으로 수집한 선호/비선호 데이터를 활용해야 한다.
- 상호작용 로그 데이터를 기반으로 유저, 아이템의 임베딩을 학습하는 two-tower model을 개발하는 것을 목표로 설정했다.
모델의 조건
- Data: 유저 ID와 해당 유저가 청취한 트랙의 상호작용 데이터를 가지고 있는 Spotify Dataset
- 기획: 온보딩 단계, 플레이리스트 완성 단계에서 유저의 선호(pos)/비선호(neg) 데이터 수집
서비스에서 수집된 유저의 선호, 비선호 데이터를 함께 고려해서 표현력 높은 representation을 생성하는 모델들을 실험하고, 가장 적합한 모델을 찾아야 했다. 무엇보다 희소성이 높은 데이터임을 가장 크게 고려했다.
실험 과정
- BPR-CF 실험
- 상호작용을 다루는 가장 대표적인 모델인 협업필터링에 positive 아이템과 negative 아이템을 모두 모델링할 수 있는 Bayesian Personalized Ranking으로 실험을 진행했다.유저/아이템 임베딩을 가우시안 초기화 후, epoch=10/embedding_dim=64/lr=0.01으로 대조학습을 진행했다.
- LightGCN 실험
- GNN 모델은 희소한 데이터에 강건하다(다른 모델들에 비해..). 그 중에서도 학습, 추론 시간이 비교적 가벼운 LightGCN에 대한 실험을 진행했다. positive graph, negative graph를 독립적으로 구축해 표현력이 높은 임베딩을 생성했다.
precision@10 | recall@10 | ndcg@10 | inference time | hyperparams | |
LightGCN | 0.07150564 | 0.04085309 | 0.08954724 | 6분 29초 | epoch=328(early stop) / embedding_dim=64 / lr=0.001 / n_layers=3 |
FM | 0.006 | 0.001 | 0.005 | 4시간 30분 | epoch=100 / embedding_dim=10 / lr=0.001 |
CF | 0.0362 | 0.0031 | 0.0363 | 2시간 57분 | epoch=10 / embedding_dim=64 / lr=0.01 |
마주한 문제 상황, 해결 과정
- 부정 아이템을 반영하는 방법으로 scipy의 인접행렬은 유저 행과 상호작용이 있는 아이템들은 positive라고 해석하고 1로 채우기 때문에, negative 상호작용은 -1로 채우면 어떨까? 하는 아이디어를 구상했다.
Task: 위 접근이 정상적으로 학습되지 않았던 이유는 인접행렬 그래프를 반환하는 getSparseGraph() 함수의 정규화 과정 때문이었다.
- ${0,1,-1}$로 이루어진 희소행렬을 scipy의 .tolil() 함수로 변환해 adj_mat에 반영할 때 문제가 발생한다. adj_mat은 보통 0/1로 구성된 인접 행렬로 가정한 후 제곱 계산을 통해 정규화한다.
- 정규화 과정:
- $D^{-0.5}$
- $(m+n) \times (m+n)$의 대각행렬 D를 정규화해 유저와 아이템의 상호작용 count의 편차에서 오는 불균형을 해소한다.
- 따라서 정규화를 진행해야 하는 차수 행렬 Graph에는 ${0,1}$의 0 혹은 긍정 상호작용 값들만 들어가고, 부정 상호작용은 별도의 그래프로 처리해야 한다.
Action: 부정 상호작용을 불러오는 단계에서는 긍정 상호작용에서 그래프를 분리해 부정 상호작용이 있는 (uid,iid)에 -1을 할당하는 방식으로 변경해 BPR Sampling 단계에서 pos item과 neg item을 서로 다른 그래프에서 가져오도록 변경했다.
참고로, 부정 상호작용은 메시지 패싱이랑은 관련이 없는 항목이기 때문에 정규화 단계에서 고려할 필요 없다.
Result: positive, negative 그래프를 독립적으로 구축해서 학습하는 데에 성공했고, 최종적으로 0.195786의 precision@10 값을 도출하는 성과를 달성했다.
문제 해결 과정에서 어떤 지점을 달성하고, 어떠한 깨달음을 얻었는가?
- 각 노드마다 상호작용 횟수에 따라 연산의 횟수가 달라지기 때문에 정규화 과정이 필요하고, 정규화를 하기 위해서는 실수 값만 다뤄야 한다는 것을 깨달아 적용할 수 있었다.
- 머릿속으로만 생각한 아이디어를 실제 코드로 옮길 때는 단순 구현에 그쳐서는 안된다. 그 대신 수학적인 공식 등 고려할 것이 많기 때문에 기본적인 원리와 이론을 완벽하게 이해하고 있어야 삽질 시간이 줄어들 것이다.
'AI' 카테고리의 다른 글
KL Divergence - 두 확률분포의 차이 계산하기 (4) | 2024.12.16 |
---|---|
[부스트캠프 AI Tech] RecSys Level 02 - Movie Recommendation KPT 회고 (0) | 2024.11.30 |
[부스트캠프 AI Tech] RecSys Level 01 KPT 회고 (0) | 2024.09.29 |
[Python] 자연어처리 - TfidfVectorizer (0) | 2024.07.30 |
[RecSys] Latent Factor 알아보기 (0) | 2024.03.26 |