이 포스팅은 implicit feedback에 대한 Collaborative Filtering 계열 학습에 초점을 둡니다.
Pytorch 는 Dataset 과 Dataloader 클래스를 상속해 커스텀 데이터셋과 데이터로더를 만들 수 있다. 보통 Dataset을 커스텀하여 학습할 데이터를 텐서화하며 데이터로더는 collate_fn 등을 활용해 미니배치상 부가 절차를 수행한다.
implicit feedback 데이터를 학습할 땐 포지티브, 네거티브 샘플링이 필요하며 유저와 아이템의 상호작용이 있으면 포지티브 샘플, 상호작용이 없는 모든 아이템들은 네거티브 샘플이 될 수 있다.
그간 머신러닝 딥러닝 추천 모델을 구축하면서 point-wise, pair-wise loss 를 범용적으로 적용할 수 있는 데이터셋을 디자인하는데 머리를 썼는데 이를 정리하고자 한다.
implicit feedback은 웹 로그 데이터로 쉽게 수집할 수 있으며 웹 접속 세션(시간대)별로 구분하거나 환경에 따라 학습할 시계열만큼 정제할 수 있다.
ml-latest-small 데이터셋을 확인해보자.
유저아이디, 타임스탬프 컬럼으로 정렬한 모습이다. explicit feedback 이지만 평점을 매겼다면 일단 본 것으로 간주(모두 포지티브 샘플)하고 userId, movieId 컬럼만으로 학습 데이터셋을 구성할 수 있다.
explicit 데이터임을 십분 활용해 평점이 1점 이거나, 유저별로 편향을 고려하여 유저가 비교적 낮은 평점을 부여한 영화도 네거티브 샘플로 간주해볼 수 있다.
일단은 모두 포지티브 샘플이라 가정한다.
이 데이터셋의 Id 정보는 정수로 저장되어 있어 그대로 CF 모델을 만들 수도 있지만, 아이템 Id가 unique 사이즈 대비 매우 크기 때문에 sparcity가 학습 불가할 정도로 높아질 수 있어 라벨인코딩을 진행해야 한다.
인코딩 사전 정보는 따로 저장하여 추후 inference 시 인코딩 엔진으로 활용할 수 있다.
uid2idx = {v: k for k, v in enumerate(ratings_df['userId'].unique())}
iid2idx = {v: k for k, v in enumerate(ratings_df['movieId'].unique())}
# 추후 디코딩 용
idx2uid = {k: v for k, v in enumerate(ratings_df['userId'].unique())}
idx2iid = {k: v for k, v in enumerate(ratings_df['movieId'].unique())}
# 인코딩
ratings_df['user_idx'] = ratings_df['userId'].map(uid2idx)
ratings_df['item_idx'] = ratings_df['movieId'].map(iid2idx)
위와 같이 python dict를 활용하여 간단하게 인코딩 할 수 있다.
이 후 학습을 위해 train, valid split 을 진행하는데 이 포스팅에선 세션, 시계열을 나누지 않고 통째로 학습한다. (최근 시계열만 학습하는 방향도 가능하나 그만큼 학습데이터가 줄어들 수 있다. 영화 개봉연도에 따라 최신 영화에 대해서만 학습하는 방향도 있으나 모든 데이터를 학습 후 추론 시 개봉연도 기준으로 topk 를 정제할 수도 있다.)
train, valid set 은 유저별로 아이템을 그룹바이한 상태에서 랜덤하게 추출한다.
valid_df = ratings_df.groupby('user_idx').sample(n=2, random_state=42)
train_df = ratings_df.drop(valid_df.index)
# number of cold-start items
print(len(set(valid_df['item_idx']) - set(train_df['item_idx'])))
위와 같이 valid set 을 나누는 과정에서 cold-start 아이템이 생길 수 있다. CF 계열 모델의 학습데이터가 곧 라벨인 특성 때문으로 평가지표 확인 시 감안해야 한다.
이후 user_idx, item_idx 컬럼으로 구성된 데이터 프레임으로만 pytorch 데이터셋을 구성한다. train_df 에 대한 Dataset 클래스를 구성하며 유저-포지티브 아이템 페어가 나오도록 한다.