본문 바로가기
ML&DL/Recommender System

[Recommender System] Bayesian Personalized Ranking (BPR) 구현

by 거북이주인장 2024. 9. 28.

오늘은 implicit feedback 데이터에서 활용도가 높은 bayesian personalized loss을 활용한 BPR을 pytorch을 사용하여 구현해본다.

BPR에 관한 자세한 설명은 아래 포스팅에서 했으니 참고하기 바란다.

https://steady-programming.tistory.com/50

 

[Recommender System / Paper review] #12 BPR: Bayesian Personalized Ranking from Implicit Feedback

논문 링크(5317회 인용) Summary 개인화된 랭킹 자체를 output으로 만들기 위한 parameter learning 방법을 제시한다. explicit feedback이 아닌, implicit feedback을 이용하며 결측치를 단순히 negative feedback으로 간

steady-programming.tistory.com

고려 사항

BPR의 핵심은 유저가 선호를 표현한 positive item과 선호를 표현하지 않은 negative item을 사용하여 이 아이템간의 ranking 자체를 모델링하는 방법이다. 처음에 BPR의 논문을 읽고 정리하는데는 큰 어려움이 없었으나, 막상 구현하려니까 negative item을 어떻게 처리할지가 감이 잘 안 왔다. 즉, 전체 아이템 중에 유저가 선호를 표현한 positive item은 소수이고 대다수가 negative item일텐데, implicit feedback이니까 이 negative을 다 포함해야하나? 라는 고민이 들었다.

 

그러던 도중, 아래 페이퍼를 발견하였다.

https://steady-programming.tistory.com/95

 

[Recommender System / Paper review] #37 Revisiting Negative Sampling vs. Non-sampling in Implicit Recommendation

논문 링크(29회 인용)개인적으로 추천시스템의 여러 모델을 리뷰하고 pytorch 또는 numpy로 구현하는 프로젝트를 진행하고 있다. (PR은 언제나 환영!)https://github.com/bohyunshin/recommender GitHub - bohyunshin/re

steady-programming.tistory.com

implicit feedback에서 negative item을 샘플링할지 하지 않을지, 한다면 어떤 방법으로 샘플링해야할지에 대한 서베이 논문이다. 이 페이퍼를 통해 negative item을 모두 사용하지 않는다면 기본적인 uniform 샘플링부터 gan을 활용한 샘플링까지 다양한 방법론이 있다는 것을 알게 되었다. 또한, 몇 개의 negative item을 샘플링하는게 좋을지에 대한 실험 결과도 있었다. 논문에서는 샘플링하는 negative item의 개수가 많아질수록 metric이 상승함을 보였다.

 

물론, 샘플링하는 negative item의 수는 무작정 늘리는게 좋아보이지는 않았다.

 

어느정도 샘플링 개수가 많아지면 그 성능이 크게 달라지지 않았기 때문이다. 이를 기반으로 bpr을 구현한 다른 github repository을 찾아봤는데, 우선 아래의 문제점이 있었다.

  1. negative item을 샘플링하는 코드가 생략되어 있고 전처리 파일을 바로 모델에 사용한다.
  2. negative item을 샘플링하는 코드가 있는 레포는 uniform sampling밖에 없었고, 샘플링 개수도 1개로 고정되어 있었다.

위의 사전 조사를 바탕으로, 직접 구현할 때는 raw 데이터에서 negative item을 샘플링하는 로직을 uniform sampling으로 우선 구현하고, 다양한 샘플링 방법과 다수의 negative item을 샘플링하는 방향은 추후에 추가하기로 했다. (다수의 negative item을 샘플링하는 것은 뒤에도 언급하겠지만, pytorch의 DataLoader에서 구현하기가 조금 어려워서 뒤로 미뤘다.)

Loss function

 

논문에서는 위의 Posterior Probability가 제시되고, 이 사후 확률을 최대화하는 모델 파라미터를 찾는 방향으로 학습이 진행된다. 구현을 다하고 이 부분을 캐치하지 못했는데, 논문에서는 손실 함수가 아니라 사후 확률 관점에서 식이 제시되어 있었다. 따라서, 최소화하고 싶으면 위의 사후 확률에 `-`을 붙여야 한다. 그런데, 구현할 때 `-`을 붙이지 않아서 값이 자꾸 -무한대로 튀는 것이었다. 한참을 들여다보고, 정말로 gradient가 구해지는 것이 맞는지, sgd가 실행되는게 맞는지 디버깅한 후에, 사후 확률을 손실 함수로 착각했다는 것을 깨달았다. 손실 함수는 아래와 같이 정의했다.

https://github.com/bohyunshin/recommender/blob/master/recommender/loss/custom.py#L5

 

recommender/recommender/loss/custom.py at master · bohyunshin/recommender

Implementation of various recommender algorithm. Contribute to bohyunshin/recommender development by creating an account on GitHub.

github.com

import torch
import torch.nn.functional as F


def bpr_loss(pred, params, regularization):
    logprob = F.logsigmoid(pred).sum()
    penalty = torch.tensor(0., requires_grad=True)
    for param in params:
        penalty = penalty + param.data.norm(dim=1).pow(2).sum() * regularization
    return -logprob + penalty

 

`pred` 값은 손실 함수의 $\hat{x}_{uij} = \hat{x}_{ui} - \hat{x}_{uj}$로, 이 값은 pytorch 모델에서 `forward` 메써드를 통해 얻는다. 그 값을 얻고 custom 손실 함수를 작성하는 것이다. `params`은 pytorch로 정의한 모델에서 `model.parameters()` 객체로, `generator`이다. 해당 값은 for 문을 통해 접근하며, 파라미터에 대한 penalty로 L2 norm의 제곱합을 더하는 부분이 있다.

 

가장 중요한 부분이 마지막 줄인데, `logprob - penalty`가 아니라, `-logprob + penalty`으로 반환을 한다.

 

custom 손실 함수이기 때문에 `loss.backward()`을 했을 때, gradient가 계산되지 않은 것이 걱정 되었다. `penalty`을 정의할 때, `requires_grad` 값을 True로 정의하고, 디버깅을 통해 중간에 계산되는 값들의 grad가 계산되는지 확인하였다.

Uniform negative sampler

서두에서 언급했듯이, 모든 negative item을 사용할 것이 아니기 때문에 이 중 일부를 샘플링한다. 더 고도화된 샘플링 기법은 나중에 구현하는 것으로 하고 처음에는 가장 기본적인 uniform sampler을 구현한다.

https://github.com/bohyunshin/recommender/blob/master/recommender/data_loader/uniform_negative_sampling_dataset.py

 

recommender/recommender/data_loader/uniform_negative_sampling_dataset.py at master · bohyunshin/recommender

Implementation of various recommender algorithm. Contribute to bohyunshin/recommender development by creating an account on GitHub.

github.com

import numpy as np
from torch.utils.data import Dataset


class UniformNegativeSamplingDataset(Dataset):
    def __init__(self, X, user_items):
        self.X = X
        self.user_items = user_items

    def __len__(self):
        return self.X.shape[0]

    def __getitem__(self, idx):
        u,i = self.X[idx]
        j = np.random.randint(self.user_items.shape[1]) # sample only ONE negative sample
        while self.user_items[u].toarray().reshape(-1)[j] == 1:
            j = np.random.randint(self.user_items.shape[1])
        return u, i, j

 

크게 복잡한 것은 없다. `__getitem__` 메써드에서 `user_items`의 값이 0이 나올 때까지 sampling을 한다. 이때, 이 과정이 한번만 수행되므로 1개만 샘플링하는 것이다. 사실, 필자가 구현하고 싶은 것은 argument로 샘플링하고 싶은 개수를 받은 후에 그만큼 negative sampling하는 것이었다. 그런데, pytorch의 Dataset 클래스의 `__getitem__`은 하나의 데이터만 리턴하는 방식인 것 같았다. 그래서 배치를 사후적으로 처리하기 위해 `collate_fn`을 고려하기도 했는데.. 문제가 조금 복잡해져서 우선 1개를 샘플링하는 것으로 구현하고 todo로 남겨두었다.

Model

이럴 때 pytorch는 참 유용하고 고마운 도구임이 느껴진다. 모델을 정의하고 학습하기가 정말 편리하기 때문이다. (필자는 이거를 깨달은지 얼마 안 되었다... 아직 pytorch newbie..) 아래와 같이 BPR을 정의하자.

https://github.com/bohyunshin/recommender/blob/master/recommender/model/bpr.py

 

recommender/recommender/model/bpr.py at master · bohyunshin/recommender

Implementation of various recommender algorithm. Contribute to bohyunshin/recommender development by creating an account on GitHub.

github.com

from torch import nn

from model.torch_model_base import TorchModelBase


class BayesianPersonalizedRanking(TorchModelBase):
    def __init__(self, num_users, num_items, num_factors):
        super().__init__()

        self.embed_user = nn.Embedding(num_users, num_factors)
        self.embed_item = nn.Embedding(num_items, num_factors)

        nn.init.xavier_normal_(self.embed_user.weight)
        nn.init.xavier_normal_(self.embed_item.weight)

    def forward(self, user_idx, pos_item_idx, neg_item_idx):
        embed_user = self.embed_user(user_idx)  # batch_size * num_factors
        embed_pos_item = self.embed_item(pos_item_idx)  # batch_size * num_factors
        embed_neg_item = self.embed_item(neg_item_idx)  # batch_size * num_factors
        output = (embed_user * (embed_pos_item - embed_neg_item)).sum(axis=1)  # batch_size * 1
        return output

 

import하는 `TorchModelBase`는 레포에 직접 들어가서 확인해보기 바란다.

 

BPR은 $\hat{x}_{uij}$을 어떻게 정의하느냐에 따라서 또 달라지는데, 이 클래스는 $\hat{x}_{uij} = \hat{a}_u^T \hat{b}_i^{T} - \hat{a}_u^T \hat{b}_j^{T}$로 정의한다. 즉, 유저/아이템 벡터의 내적으로 점수를 구하는, 가장 기본적인 형태를 가져간다. BPR 논문에서 나왔듯이, 사실 이 $\hat{x}_{uij}$는 그 안에 어떠한 추천 모델도 들어갈 수 있기 때문에 높은 유연성을 가진다. 지금은 $\hat{x}_{uij}$을 이렇게 내적의 차로 고정하지만, 나중에는 손실 함수로 BPR을 선택하고, 그 안에 원하는 추천 모델의 점수가 들어가게끔 구현할 필요가 있어 보인다.

Training

이제 모델을 학습해보자.

https://github.com/bohyunshin/recommender/blob/master/recommender/train/bpr.py

 

recommender/recommender/train/bpr.py at master · bohyunshin/recommender

Implementation of various recommender algorithm. Contribute to bohyunshin/recommender development by creating an account on GitHub.

github.com

import os
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)),"../.."))

import argparse
import torch
from torch.utils.data import DataLoader, random_split
from torch import nn, optim
import importlib
import copy

from data_loader.uniform_negative_sampling_dataset import UniformNegativeSamplingDataset
from model.bpr import BayesianPersonalizedRanking
from tools.csr import implicit_to_csr
from loss.custom import bpr_loss
from tools.logger import setup_logger

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--dataset", type=str, required=True)
    parser.add_argument("--batch_size", type=int, default=32)
    parser.add_argument("--lr", type=float, default=1e-2)
    parser.add_argument("--regularization", type=float, default=1e-4)
    parser.add_argument("--epochs", type=int, default=20)
    parser.add_argument("--num_factors", type=int, default=128)
    parser.add_argument("--train_ratio", type=float, default=0.8)
    parser.add_argument("--model_path", type=str, required=True)
    parser.add_argument("--log_path", type=str, required=True)
    parser.add_argument("--patience", type=int, default=5)
    parser.add_argument("--movielens_data_type", type=str, default="ml-latest-small")
    return parser.parse_args()


def main(args):
    logger = setup_logger(args.log_path)
    logger.info(f"selected dataset: {args.dataset}")
    logger.info(f"selected movielens data type: {args.movielens_data_type}")
    preprocessor_module = importlib.import_module(f"recommender.preprocess.{args.dataset}.preprocess_torch").Preprocessor
    preprocessor = preprocessor_module(movielens_data_type=args.movielens_data_type)
    X,y = preprocessor.preprocess()
    shape = (preprocessor.num_users, preprocessor.num_items)
    user_items = implicit_to_csr(X, shape)
    seed = torch.Generator().manual_seed(42)

    dataset = UniformNegativeSamplingDataset(X, user_items)
    train_dataset, validation_dataset = random_split(dataset, [args.train_ratio, 1-args.train_ratio], generator=seed)
    train_dataloader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True)
    validation_dataloader = DataLoader(validation_dataset, batch_size=args.batch_size, shuffle=True)

    # set up pytorch model
    model = BayesianPersonalizedRanking(
        num_users=preprocessor.num_users,
        num_items=preprocessor.num_items,
        num_factors=args.num_factors,
    )
    optimizer = optim.SGD(model.parameters(), lr=args.lr)

    best_loss = float('inf')
    for epoch in range(args.epochs):
        logger.info(f"####### Epoch {epoch} #######")

        model.train()
        # training
        tr_loss = 0.0
        for data in train_dataloader:
            user_id, pos_item_id, neg_item_id = data

            optimizer.zero_grad()
            y_pred = model(user_id, pos_item_id, neg_item_id)
            loss = bpr_loss(y_pred, model.parameters(), args.regularization)
            loss.backward()
            optimizer.step()

            tr_loss += loss.item()

        tr_loss = round(tr_loss / len(train_dataloader), 6)

        model.eval()
        # validation
        with torch.no_grad():
            val_loss = 0.0
            for data in validation_dataloader:
                user_id, pos_item_id, neg_item_id = data

                optimizer.zero_grad()
                y_pred = model(user_id, pos_item_id, neg_item_id)
                loss = bpr_loss(y_pred, model.parameters(), args.regularization)

                val_loss += loss.item()
            val_loss = round(val_loss / len(validation_dataloader), 6)

        logger.info(f"Train Loss: {tr_loss}")
        logger.info(f"Validation Loss: {val_loss}")

        if best_loss > val_loss:
            prev_best_loss = best_loss
            best_loss = val_loss
            best_model_weights = copy.deepcopy(model.state_dict())
            patience = args.patience
            torch.save(model.state_dict(), args.model_path)
            logger.info(f"Best validation: {best_loss}, Previous validation loss: {prev_best_loss}")
        else:
            patience -= 1
            logger.info(f"Validation loss did not decrease. Patience {patience} left.")
            if patience == 0:
                logger.info(f"Patience over. Early stopping at epoch {epoch} with {best_loss} validation loss")
                break

    # Load the best model weights
    model.load_state_dict(best_model_weights)
    logger.info("Load weight with best validation loss")

    torch.save(model.state_dict(), args.model_path)
    logger.info("Save final model")


if __name__ == "__main__":
    args = parse_args()
    main(args)

 

기존에 사용하던 파이프라인과 동일하고, 한가지 다른 점이 있다면 `bpr_loss`을 부럴와서 사용한다는 점이다. `bpr_loss`에서 계산되는 텐서들의 `requires_grad` 값은 True로 확인했으므로, `loss.backward()`을 통해서 gradient을 계산하고 step을 진행한다.

 

movielens 1m 데이터에 대해서 학습을 진행해보자.

 

$ recommender/train/bpr.py \
	--batch_size 32 \
	--lr 0.01 \
	--regularization 0.0001 \
	--epochs 50 \
	--num_factors 128 \
	--train_ratio 0.8 \
	--dataset movielens \
	--patience 5 \
	--log_path log.log \
	--model_path model.pt \
	--movielens_data_type ml-1m

 

아래와 같은 로그를 얻는다.

더보기

2024-09-28 19:34:24,457 - recommender - INFO - selected dataset: movielens
2024-09-28 19:34:24,457 - recommender - INFO - selected movielens data type: ml-1m
2024-09-28 19:34:30,403 - recommender - INFO - ####### Epoch 0 #######
2024-09-28 19:35:55,591 - recommender - INFO - Train Loss: 22.228465
2024-09-28 19:35:55,591 - recommender - INFO - Validation Loss: 22.222329
2024-09-28 19:35:55,603 - recommender - INFO - Best validation: 22.222329, Previous validation loss: inf
2024-09-28 19:35:55,603 - recommender - INFO - ####### Epoch 1 #######
2024-09-28 19:37:16,157 - recommender - INFO - Train Loss: 22.153098
2024-09-28 19:37:16,157 - recommender - INFO - Validation Loss: 21.974817
2024-09-28 19:37:16,162 - recommender - INFO - Best validation: 21.974817, Previous validation loss: 22.222329
2024-09-28 19:37:16,162 - recommender - INFO - ####### Epoch 2 #######
2024-09-28 19:38:32,873 - recommender - INFO - Train Loss: 20.538121
2024-09-28 19:38:32,873 - recommender - INFO - Validation Loss: 17.950076
2024-09-28 19:38:32,877 - recommender - INFO - Best validation: 17.950076, Previous validation loss: 21.974817
2024-09-28 19:38:32,877 - recommender - INFO - ####### Epoch 3 #######
2024-09-28 19:39:54,932 - recommender - INFO - Train Loss: 15.302445
2024-09-28 19:39:54,932 - recommender - INFO - Validation Loss: 13.602234
2024-09-28 19:39:54,939 - recommender - INFO - Best validation: 13.602234, Previous validation loss: 17.950076
2024-09-28 19:39:54,939 - recommender - INFO - ####### Epoch 4 #######
2024-09-28 19:41:18,548 - recommender - INFO - Train Loss: 12.900662
2024-09-28 19:41:18,548 - recommender - INFO - Validation Loss: 12.507457
2024-09-28 19:41:18,554 - recommender - INFO - Best validation: 12.507457, Previous validation loss: 13.602234
2024-09-28 19:41:18,554 - recommender - INFO - ####### Epoch 5 #######
2024-09-28 19:42:41,229 - recommender - INFO - Train Loss: 12.181067
2024-09-28 19:42:41,229 - recommender - INFO - Validation Loss: 12.067953
2024-09-28 19:42:41,235 - recommender - INFO - Best validation: 12.067953, Previous validation loss: 12.507457
2024-09-28 19:42:41,235 - recommender - INFO - ####### Epoch 6 #######
2024-09-28 19:44:02,606 - recommender - INFO - Train Loss: 11.870089
2024-09-28 19:44:02,606 - recommender - INFO - Validation Loss: 11.839314
2024-09-28 19:44:02,612 - recommender - INFO - Best validation: 11.839314, Previous validation loss: 12.067953
2024-09-28 19:44:02,612 - recommender - INFO - ####### Epoch 7 #######
2024-09-28 19:45:23,241 - recommender - INFO - Train Loss: 11.58592
2024-09-28 19:45:23,241 - recommender - INFO - Validation Loss: 11.629331
2024-09-28 19:45:23,246 - recommender - INFO - Best validation: 11.629331, Previous validation loss: 11.839314
2024-09-28 19:45:23,246 - recommender - INFO - ####### Epoch 8 #######
2024-09-28 19:46:41,887 - recommender - INFO - Train Loss: 11.322776
2024-09-28 19:46:41,888 - recommender - INFO - Validation Loss: 11.278913
2024-09-28 19:46:41,894 - recommender - INFO - Best validation: 11.278913, Previous validation loss: 11.629331
2024-09-28 19:46:41,894 - recommender - INFO - ####### Epoch 9 #######
2024-09-28 19:47:57,111 - recommender - INFO - Train Loss: 11.053888
2024-09-28 19:47:57,111 - recommender - INFO - Validation Loss: 11.011622
2024-09-28 19:47:57,116 - recommender - INFO - Best validation: 11.011622, Previous validation loss: 11.278913
2024-09-28 19:47:57,116 - recommender - INFO - ####### Epoch 10 #######
2024-09-28 19:49:13,639 - recommender - INFO - Train Loss: 10.733207
2024-09-28 19:49:13,639 - recommender - INFO - Validation Loss: 10.781224
2024-09-28 19:49:13,644 - recommender - INFO - Best validation: 10.781224, Previous validation loss: 11.011622
2024-09-28 19:49:13,644 - recommender - INFO - ####### Epoch 11 #######
2024-09-28 19:50:30,702 - recommender - INFO - Train Loss: 10.514888
2024-09-28 19:50:30,702 - recommender - INFO - Validation Loss: 10.558061
2024-09-28 19:50:30,708 - recommender - INFO - Best validation: 10.558061, Previous validation loss: 10.781224
2024-09-28 19:50:30,708 - recommender - INFO - ####### Epoch 12 #######
2024-09-28 19:51:49,487 - recommender - INFO - Train Loss: 10.30004
2024-09-28 19:51:49,487 - recommender - INFO - Validation Loss: 10.425422
2024-09-28 19:51:49,493 - recommender - INFO - Best validation: 10.425422, Previous validation loss: 10.558061
2024-09-28 19:51:49,493 - recommender - INFO - ####### Epoch 13 #######
2024-09-28 19:53:05,463 - recommender - INFO - Train Loss: 10.142428
2024-09-28 19:53:05,463 - recommender - INFO - Validation Loss: 10.287086
2024-09-28 19:53:05,468 - recommender - INFO - Best validation: 10.287086, Previous validation loss: 10.425422
2024-09-28 19:53:05,468 - recommender - INFO - ####### Epoch 14 #######
2024-09-28 19:54:22,802 - recommender - INFO - Train Loss: 10.007884
2024-09-28 19:54:22,802 - recommender - INFO - Validation Loss: 10.147149
2024-09-28 19:54:22,807 - recommender - INFO - Best validation: 10.147149, Previous validation loss: 10.287086
2024-09-28 19:54:22,808 - recommender - INFO - ####### Epoch 15 #######
2024-09-28 19:55:41,183 - recommender - INFO - Train Loss: 9.861015
2024-09-28 19:55:41,183 - recommender - INFO - Validation Loss: 10.064527
2024-09-28 19:55:41,204 - recommender - INFO - Best validation: 10.064527, Previous validation loss: 10.147149
2024-09-28 19:55:41,204 - recommender - INFO - ####### Epoch 16 #######
2024-09-28 19:56:54,467 - recommender - INFO - Train Loss: 9.791463
2024-09-28 19:56:54,467 - recommender - INFO - Validation Loss: 10.054267
2024-09-28 19:56:54,473 - recommender - INFO - Best validation: 10.054267, Previous validation loss: 10.064527
2024-09-28 19:56:54,473 - recommender - INFO - ####### Epoch 17 #######
2024-09-28 19:58:12,966 - recommender - INFO - Train Loss: 9.704449
2024-09-28 19:58:12,966 - recommender - INFO - Validation Loss: 9.964985
2024-09-28 19:58:12,973 - recommender - INFO - Best validation: 9.964985, Previous validation loss: 10.054267
2024-09-28 19:58:12,973 - recommender - INFO - ####### Epoch 18 #######
2024-09-28 19:59:27,951 - recommender - INFO - Train Loss: 9.629866
2024-09-28 19:59:27,951 - recommender - INFO - Validation Loss: 9.968628
2024-09-28 19:59:27,951 - recommender - INFO - Validation loss did not decrease. Patience 4 left.
2024-09-28 19:59:27,951 - recommender - INFO - ####### Epoch 19 #######
2024-09-28 20:00:43,889 - recommender - INFO - Train Loss: 9.595219
2024-09-28 20:00:43,889 - recommender - INFO - Validation Loss: 9.97794
2024-09-28 20:00:43,889 - recommender - INFO - Validation loss did not decrease. Patience 3 left.
2024-09-28 20:00:43,890 - recommender - INFO - ####### Epoch 20 #######
2024-09-28 20:01:59,367 - recommender - INFO - Train Loss: 9.556236
2024-09-28 20:01:59,367 - recommender - INFO - Validation Loss: 9.961639
2024-09-28 20:01:59,373 - recommender - INFO - Best validation: 9.961639, Previous validation loss: 9.964985
2024-09-28 20:01:59,373 - recommender - INFO - ####### Epoch 21 #######
2024-09-28 20:03:18,990 - recommender - INFO - Train Loss: 9.551289
2024-09-28 20:03:18,990 - recommender - INFO - Validation Loss: 10.026126
2024-09-28 20:03:18,990 - recommender - INFO - Validation loss did not decrease. Patience 4 left.
2024-09-28 20:03:18,990 - recommender - INFO - ####### Epoch 22 #######
2024-09-28 20:04:37,535 - recommender - INFO - Train Loss: 9.525835
2024-09-28 20:04:37,535 - recommender - INFO - Validation Loss: 9.983868
2024-09-28 20:04:37,535 - recommender - INFO - Validation loss did not decrease. Patience 3 left.
2024-09-28 20:04:37,535 - recommender - INFO - ####### Epoch 23 #######
2024-09-28 20:05:53,576 - recommender - INFO - Train Loss: 9.515424
2024-09-28 20:05:53,576 - recommender - INFO - Validation Loss: 10.000781
2024-09-28 20:05:53,576 - recommender - INFO - Validation loss did not decrease. Patience 2 left.
2024-09-28 20:05:53,576 - recommender - INFO - ####### Epoch 24 #######
2024-09-28 20:07:10,749 - recommender - INFO - Train Loss: 9.513156
2024-09-28 20:07:10,749 - recommender - INFO - Validation Loss: 9.98525
2024-09-28 20:07:10,749 - recommender - INFO - Validation loss did not decrease. Patience 1 left.
2024-09-28 20:07:10,749 - recommender - INFO - ####### Epoch 25 #######
2024-09-28 20:08:24,952 - recommender - INFO - Train Loss: 9.514542
2024-09-28 20:08:24,952 - recommender - INFO - Validation Loss: 10.133966
2024-09-28 20:08:24,952 - recommender - INFO - Validation loss did not decrease. Patience 0 left.
2024-09-28 20:08:24,952 - recommender - INFO - Patience over. Early stopping at epoch 25 with 9.961639 validation loss
2024-09-28 20:08:24,954 - recommender - INFO - Load weight with best validation loss
2024-09-28 20:08:24,960 - recommender - INFO - Save final model

 

train loss / validation loss가 지속적으로 감소하다가 20번째 epoch에서 best validation loss을 기록하고 그 이후 5번의 epoch동안 validation loss가 감소하지 않아서 학습을 종료하였다.

Conclusion

오늘은 BPR을 pytorch로 구현해보고 그 과정에서 negative sampling과 custom loss function에 대해서 심도 있게 생각해보았다. 사용하면 할수록 pytorch는 정말 말도 안 되는 라이브러리인 것 같다. 이렇게 최적화를 떠먹여주는 라이브러리가 있다니.. gpu 상에서 pytorch을 잘 돌리는 것도 실력일 것 같은데 좀 더 많이 연습해야겠다.

BPR을 구현은 했지만 아직 풀어야할 문제가 더 남아있다. 다양한 negative sampling 전략의 결과를 비교해보는 것, 그리고 bpr을 좀 더 general한 손실 함수로 만드는 것. 이렇게 하나하나 문제를 풀어가며 pytorch에 익숙해져 있는 모습을 발견하면 좋겠다.

댓글