RecSys Competition - OTTO

Multi-Objective Recommender System

[Kaggle] OTTO – Multi-Objective Recommender System#

The goal of this competition is to predict e-commerce clicks, cart additions, and orders. You’ll build a multi-objective recommender system based on previous events in a user session.

Metric#

Recall@20 기반이며, 점수는 3가지 타입을 모두 고려한다.

score=0.10Rclicks+0.30Rcarts+0.60Rordersscore = 0.10 \cdot R_{clicks} + 0.30 \cdot R_{carts} + 0.60 \cdot R_{orders}
Rtype=iN{predicted aids}i,type{ground truth aids}i,typeiNmin(20,{ground truth aids}i,type)R_{type} = \frac{\sum_{i}^{N} | \{ \text{predicted aids} \}_{i, type} \cap \{ \text{ground truth aids} \}_{i, type} | }{\sum_{i}^{N} \min{( 20, | \{ \text{ground truth aids} \}_{i, type} | )}}

Data#

OTTO 데이터는 session 기반의 데이터로 구성이 되어 있다.

  • session - the unique session id
  • events - the time ordered sequence of events in the session
    • aid - the article id (product code) of the associated event
    • ts - the Unix timestamp of the event
    • type - the event type, i.e., whether a product was clicked, added to the user’s cart, or ordered during the session

Features of training datasets:

  • 12,899,779 sessions
  • 1,855,603 items
  • 216,716,096 events
  • 194,720,954 clicks
  • 16,896,191 carts
  • 5,098,951 orders

Polars:

기존 Pandas를 사용하여 데이터 처리를 하였으나, 속도와 메모리의 한계로 Polars로 변경하였다.
Polars의 장점은 Rust 언어로 구성 되어 있어 가볍고 다중 스레드 지원이 되어 빠르다는 장점이 있었다.

UltraGCN#

Ultra Simplification of Graph Convolutional Networks for Recommendation

Model#

Session 기반 task임으로 session 기반 모델인 SR-GNN을 이용하려고 하였으나 학습 속도가 느려 더 가벼운 모델을 찾게 되었다. (SR-GNN은 session을 RNN을 통해 순차적으로 방문하여 예측한다. RNN으로 학습 속도에 제한을 받게 된다.)

GCN 계열 중에서 가벼운 모델을 찾아 진행하였다. 최근 모델인 UltraGCN을 사용하였다.

  • GCN

    H(l+1)=σ(D~12A~D~12H(l)W(l))  H^{(l+1)} = \sigma\left({\tilde{D}}^{-\frac{1}{2}}\tilde{A}{\tilde{D}}^{-\frac{1}{2}}H^{(l)}W^{(l)} \right)
  • LightGCN

    GCN에서 Linear Layer(WW)가 제거되고 CF 목적으로 사용된다.

    H(l+1)=(D~12A~D~12)H(l)  H^{(l+1)} = ({\tilde{D}}^{-\frac{1}{2}}\tilde{A}{\tilde{D}}^{-\frac{1}{2}}) H^{(l)}
  • UltraGCN

    위의 LightGCN Layer를 무수히 쌓았다고 가정하고, 아래와 같이 근사한다.
    (위의 Layer 반복 역시 Self-loop와 비슷한 역할을 한다고 하여 제거한다.)

    eu=iN(u)βu,iei, βu,i=1dudu+1di+1  e_u = \sum_{i\in{\cal N(u)}}\beta_{u,i}e_i,\ \beta_{u,i}=\frac{1}{d_u}\sqrt{\frac{d_u+1}{d_i+1}}

이를 통해 Layer를 반복하는 과정을 제거함으로 학습 효율을 높였다고 한다. 단순히 Layer 반복을 제거하는 것 외에 Graph structure 데이터를 사전에 계산하여 활용하였다.

Methods#

하지만, 학습은 Ultra한 것 같지만 과정은 그렇게 Ultra하지 못하였다.
모델 내부 연산과정을 무한번 반복하였다 가정하고, 외부로 가져오기 때문이다.

이 과정에서 Item 전체 혹은 User 전체에 대한 β\beta를 구해야한다. 여기서 Item 혹은 User의 수가 매우 많다면, 메모리 문제로 제대로된 β\beta를 구하기 어려워진다. 단일 row or column 으로 계산을 해야 하는데, 이 역시 매우 많은 User 혹은 Item 수로 시간이 매우 많이 걸리게 된다.

특히나 Kaggle과 같은 제한된 Cloud에서는 사용이 어려운 점이 있었다.

메모리 문제로 해당 데이터 처리를 위해 특정 세션 수만큼 나누어 분할하여 처리를 해야만 했다. (학습도 동일)

User-Item matrix는 분할 처리가 가능하였으나, Item-Item matrix를 만드는 과정 자체가 처리가 불가능한 부분이 있어, Item-Item matrix는 사용하지 못하였다.

Session embeddings#

아차차.. 위의 내용을 보면 User-Item matrix를 구한다. 하지만, OTTO는 Session-based task이다. 세션이 가지는 성격은 해당 세션이 유지되는 동안 어떤 아이템을 선호하고 장바구니에 담거나 구매버튼을 누르거나 얼마나 해당 페이지에 머무는지 등이 있다. 이 문제를 해결하기 위해 간단한 접근으로 세션이 선호하는 아이템 세트를 하나의 특성으로 보고 학습을 진행하였다.

세션마다 선호하는 아이템 총 개수도 다르고 중복된 것도 있다.

처리 방법은 아래와 같다.

  1. 해당 세션 내에서 아이템 접근 수를 계산한다. (클릭, 장바구니 담기, 구매 모두 포함)
  2. 접근 수가 가장 많은 아이템을 가장 앞에 오게하여 10개Item Id를 담은 리스트를 생성한다.
    1. 아이템 수가 10개 이상이거나 적을 수 있다. 이상인 경우는 상위 선호 10개 아이템을 이용하고, 적은 경우는 나머지에 Item Id들에 0을 채워 10개의 리스트를 만들었다.
    2. 기존 Item Id가 0부터 시작하여 empty Item Id를 만들기 위해 기존의 Item Id들에 1을 더했다.

위의 방법에서 구매 혹은 장바구니 담기에 Weight를 주어 아이템 접근 수를 전략적으로 할 수 있었지만 시간 여건상 할 수 없었다.

여기서 Item Id를 Session vector로 embedding 하는 과정에서 Item Id를 그대로 사용하는 것 보다는 Item ebeddings를 활용하여 session vector를 생성하도록 하였다.

Session특정 유형으로 분석하길 원하여 일반적인 Linear Layer들 보다는 VAE를 활용하여 비슷한 유형들은 비슷한 latent space에 mapping 되길 원하였다.

아래는 VAE 방식을 활용한 session embedding 코드이다. rasbt의 코드를 참조하여 진행했다.

def user_embeds_as_vae(self, user_favors):
# https://github.com/rasbt/deeplearning-models/blob/master/pytorch_ipynb/autoencoder/ae-var.ipynb    
    x = self.item_embeds(user_favors)  # Session의 선호 Item 10개가 포함되어 있다.
    x = self.user_linear(x.flatten(start_dim=1))  # Embeddings를 Liear Layer을 활용하여 변환
    x = self.batch_norm(x)  # Dataset 마다 경향이 달라 필수적으로 요구 되었다.
    x = F.dropout(x, p=0.3)
    x = F.leaky_relu(x, negative_slope=0.0001)        
    x = self.hidden_1(x)        
    x = F.leaky_relu(x, negative_slope=0.0001)        
    z_mean = self.z_mean(x)        
    z_log_var = self.z_log_var(x)        
     
    # Sample epsilon from standard normal distribution        
    eps = torch.randn(z_mean.size(0), z_mean.size(1)).to(z_mean)        
    z = z_mean + eps * torch.exp(z_log_var/2.)         
    return z

Training#

성능이 부족하다면 일종의 Candidator 역할, 충분하다면 그 자체로 이용하려 하였다.

학습 과정 중 데이터세트가 나누어져 있기 때문에 셔플을 사용하더라도 전체에 대하여 적용되는 것이 아니기 때문에 데이터세트간 경향이 달라 데이터세트가 변경되면 학습이 불안정해지는 경우가 존재하여, batch_norm layer를 추가하여 진행했다.

하나의 세션이 여러개의 아이템을 선호하도록 하기 때문에 Negative sample과 Positive sample간 중복을 고려하여, Negative sample에 큰 coefficient를 주지 않았다.

마지막 날에야 정상적으로 학습이 되면서 어느정도의 Recall@20 (약 0.5)을 보여주었지만, GPU 사용시간 초과로 제출은 하지 못하였다.

Summary#

  1. 데이터가 비교적 간단하였지만, 그 양이 방대하여 처리에 어려움이 있다. (실사용 데이터는 더 어려울 것 같다.)
  2. Session-based 방식의 유용성
    (개인정보 X, 특정 Id 추론 X, 하지만 개인 특성이 없기 때문에 Content-based 방식에서는 어려움이 있을 것으로 보인다.)
  3. UltraGCN은 기존 모델들의 Layer가 깊어짐에 따라 structure data 활용에 제한이 있는 점을 잘 파악하고, 모델 구조를 초 단순화하여 학습에 이점을 가져왔다. 하지만, 이점만큼 사전 structure data 처리 과정에 어려움이 있다.
  4. UltraGCN은 User-based 사용하여 (Encoder of VAE 기반) Session-based 방식으로 변경하여 task를 진행했다.
  5. 모델 성능이 충분한 경우 확률 (or logit) 값에 threshold 주어 클릭, 장바구니 담기, 구매를 구분하려고 하였고, 부족한 경우 2 stage의 Candidator로 사용할 계획이었지만, 참여기간과 학습시간 부족으로 아쉽게 마무리 하지는 못했다.
  6. 추천 시스템을 경험할 수 있는 좋은 기회였다.

Reference#


The equation of User embeddings of UltraGCN

If eu=limleu(l+1)=limleu(l),Then,eu=1du+1eu+iN(u)1(du+1)(di+1)ei(11du+1)eu=iN(u)1(du+1)(di+1)eieu=iN(u)(du+1)du(du+1)(di+1)eieu=iN(u)du+1dudi+1eieu=iN(u)βu,iei, βu,i=1dudu+1di+1\begin{aligned}
&\text{If } e_u = \lim_{l\rarr \infin}e_u^{(l+1)} = \lim_{l\rarr \infin}e_u^{(l)},\\
&\text{Then},\\
&e_u = \frac{1}{d_u+1}e_u + \sum_{i\in{\cal N(u)}}\frac{1}{\sqrt{(d_u+1)(d_i+1)}}e_i\\
&\left(1 - \frac{1}{d_u+1}\right)e_u = \sum_{i\in{\cal N(u)}}\frac{1}{\sqrt{(d_u+1)(d_i+1)}}e_i\\
&e_u = \sum_{i\in{\cal N(u)}}\frac{(d_u+1)}{d_u\sqrt{(d_u+1)(d_i+1)}}e_i\\
&e_u = \sum_{i\in{\cal N(u)}}\frac{\sqrt{d_u+1}}{d_u\sqrt{d_i+1}}e_i\\
&e_u = \sum_{i\in{\cal N(u)}}\beta_{u,i}e_i,\ \beta_{u,i}=\frac{1}{d_u}\sqrt{\frac{d_u+1}{d_i+1}}
\end{aligned}