데이터사이언스/PyTorch

PyTorch 5 - 딥러닝 기반 협업필터링

Johnny Yoon 2019. 7. 22. 00:38
728x90
반응형

 

딥러닝 기반 협업필터링

이번 포스팅에서는 신경망을 기반으로 한 협업필터링을 구현해본다.
신경망 기반 협업필터링은, 기존의 MF기반 협업필터링과는 조금 다르다.
MF기반은 벡터끼리의 내적곱을 활용하지만,
신경망 기반은 비선형 활성함수(relu)를 사용하게 된다.

In [43]:
# 패키지 import
from pathlib import Path
import pandas as pd
import numpy as np
In [44]:
data = pd.read_csv('./ml-latest-small/ratings.csv')
data.head()
Out[44]:
  userId movieId rating timestamp
0 1 1 4.0 964982703
1 1 3 4.0 964981247
2 1 6 4.0 964982224
3 1 47 5.0 964983815
4 1 50 5.0 964982931
In [45]:
# 먼저 데이터를 Train과 Validation데이터로 나눈다
np.random.seed(3)
msk = np.random.rand(len(data)) < 0.8
train = data[msk].copy()
val = data[~msk].copy()
print(train.head())
print(val.head())
 
   userId  movieId  rating  timestamp
0       1        1     4.0  964982703
1       1        3     4.0  964981247
2       1        6     4.0  964982224
3       1       47     5.0  964983815
6       1      101     5.0  964980868
    userId  movieId  rating  timestamp
4        1       50     5.0  964982931
5        1       70     3.0  964982400
29       1      543     4.0  964981179
30       1      552     4.0  964982653
32       1      590     4.0  964982546
 

데이터 인코딩

지난번 포스팅과 같은 데이터 인코딩 기법을 사용한다.

In [23]:
# 다음은 Pandas의 컬럼을 범주형의 id로 인코드해주는 함수이다
def proc_col(col, train_col=None):
    """ Encodes a pandas column with continous ids. """
    # Unique한 row를 찾는다 즉 사용자 혹은 영화이다
    if train_col is not None:
        uniq = train_col.unique()
    else:
        uniq = col.unique()
    # 사용자/영화를 인덱스와 매핑해준다
    name2idx = {o:i for i,o in enumerate(uniq)}
    # 그리고 그것을 포맷팅해서 리턴한다
    return name2idx, np.array([name2idx.get(x, -1) for x in col]), len(uniq)
In [24]:
# 다음은 실제로 데이터를 인코딩으로 만들어주는 함수이다
# 위에서 정의해준 proc_col을 사용한다
def encode_data(df, train=None):
    """ Encodes rating data with continous user and movie ids. 
    If train is provided, encodes df with the same encoding as train.
    """
    df = df.copy()
    for col_name in ["userId", "movieId"]:
        train_col = None
        if train is not None:
            train_col = train[col_name]
        _,col,_ = proc_col(df[col_name], train_col)
        df[col_name] = col
        df = df[df[col_name] >= 0]
    return df
In [25]:
# encoding the train and validation data
df_train = encode_data(train)
df_val = encode_data(val, train)
In [26]:
num_users = len(df_train.userId.unique())
num_items = len(df_train.movieId.unique())
print(num_users, num_items)
 
610 8998
 

학습과 검증 함수

학습과 검증 함수 역시 지난번 포스팅과 같은 함수들을 활용한다.

In [27]:
import torch
import torch.nn as nn
import torch.nn.functional as F
In [ ]:
def validation_loss(model, unsqueeze=False):
    model.eval()
    users = torch.LongTensor(df_val.userId.values)
    items = torch.LongTensor(df_val.movieId.values)
    ratings = torch.FloatTensor(df_val.rating.values)
    if unsqueeze:
        ratings = ratings.unsqueeze(1)
    y_hat = model(users, items)
    loss = F.mse_loss(y_hat, ratings)
    print("validation loss {:.3f}".format(loss.item()))
In [36]:
def train_mf(model, epochs=10, lr=0.01, wd=0.0, unsqueeze=False):
    optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)
    model.train()
    for i in range(epochs):
        users = torch.LongTensor(df_train.userId.values)
        items = torch.LongTensor(df_train.movieId.values)
        ratings = torch.FloatTensor(df_train.rating.values)
        if unsqueeze:
            ratings = ratings.unsqueeze(1)
        y_hat = model(users, items)
        loss = F.mse_loss(y_hat, ratings)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        print(loss.item()) 
    validation_loss(model, unsqueeze)
 

신경망 모델

아래는 신경망 기반의 협업필터링 모델이다.
기존 MF모델과 비슷하게 Embedding을 사용하기는 하지만,
히든레이어인 선형모델들 뒤에 비선형 활성함수인 relu들이 붙어있는것을 볼 수 있다.

In [46]:
class NNCollabFiltering(nn.Module):
    def __init__(self, num_users, num_items, emb_size=100, n_hidden=10):
        super(NNCollabFiltering, self).__init__()
        self.user_emb = nn.Embedding(num_users, emb_size)
        self.item_emb = nn.Embedding(num_items, emb_size)
        self.lin1 = nn.Linear(emb_size*2, n_hidden)
        self.lin2 = nn.Linear(n_hidden, 1)
        self.drop1 = nn.Dropout(0.1)
        
    def forward(self, u, v):
        U = self.user_emb(u)
        V = self.item_emb(v)
        x = F.relu(torch.cat([U, V], dim=1))
        x = self.drop1(x)
        x = F.relu(self.lin1(x))
        x = self.lin2(x)
        return x
model = NNCollabFiltering(num_users, num_items, emb_size=100)
 

학습 결과

학습시킨 결과를 보면, 기존 MF기반의 모델과 성능이 비슷한 것을 볼 수 있다.

In [47]:
train_mf(model, epochs=15, lr=0.05, wd=1e-6, unsqueeze=True)
 
17.30870246887207
8.484821319580078
2.169651985168457
3.6679458618164062
4.415868759155273
1.8358207941055298
1.1234016418457031
1.9264729022979736
2.612520694732666
2.5729236602783203
1.926349401473999
1.1305603981018066
0.8068538308143616
1.227549433708191
1.6704723834991455
validation loss 1.477
In [48]:
train_mf(model, epochs=10, lr=0.01, wd=1e-6, unsqueeze=True)
 
1.4282337427139282
0.789276659488678
0.7630568146705627
0.9304841160774231
1.0099833011627197
0.9555293321609497
0.8410912752151489
0.7404245138168335
0.7024803757667542
0.7326859831809998
validation loss 0.852
In [49]:
train_mf(model, epochs=10, lr=0.001, wd=1e-6, unsqueeze=True)
 
0.7903790473937988
0.7576255798339844
0.7346392273902893
0.7147817015647888
0.70457923412323
0.6963374018669128
0.6945264935493469
0.6938334107398987
0.6983810067176819
0.7018088698387146
validation loss 0.765

참고자료

아래의 원본 소스를 기반으로 번역 및 개인 의견을 덧붙인 코드입니다.

https://github.com/yanneta/pytorch-tutorials

728x90
반응형

'데이터사이언스 > PyTorch' 카테고리의 다른 글

PyTorch 4 - 협업필터링  (0) 2019.07.14
PyTorch 3 - 데이터 로더, 신경망  (0) 2019.07.13
PyTorch 2 - 로지스틱 회귀  (0) 2019.07.11
PyTorch 1 - 텐서  (0) 2019.07.09