기록하는삶
[추천시스템/RecSys] RecBole 라이브러리 본문
https://github.com/RUCAIBox/RecBole
RecBole은 파이토치/파이썬 기반으로 만들어진 라이브러리로 글을 쓰는 시점을 기준으로 78개의 추천 시스템 관련 알고리즘 모델이 구현돼 있다.
라이브러리가 요구하는 형식에 맞춰 데이터와 config file을 만들고나면, 학습과 평가, 간단한 Auto 하이퍼파라미터 튜닝까지 해볼 수 있다. 이번 프로젝트에 이 라이브러리를 활용하며 알게된 사용법에 대해 간단히 정리해본다. 이 글은 git clone을 통해 라이브러리 구조를 그대로 가져와 활용하는 것을 기준으로 작성하였다.
1) Dataset의 구축
예시로 라이브러리가 제공하고 있는 포맷은 ML-100k다. 데이터를 tsv 형태로 원하는 경로에 저장해둔 뒤, 이를 config 파일에서 지정한 방식으로 불러오는 방법을 사용하고 있다. 이때 몇 가지 특이한 점이 있는데 아래와 같다.
먼저 데이터가 저장되어야 하는 경로는 RecBole/dataset/(dataset명)이다. 이때 작성하는 폴더명(dataset명)을 이후 CLI에서 실행할 때 argument로 던지게 되며, 위 예시처럼 파일 명에도 동일한 이름을 사용해야한다. 대신 파일을 구분하기 위해 확장자에 .inter/.user/.item을 사용하게 되는데 이는 그냥 tsv의 확장자를 임의로 변경한 것이라 생각하면 된다.
각각을 열어보면 위와 같이 되어있는데, 첫 row는 "column명:data type"의 형식으로 넣어주며, 이는 data type의 종류로는 token, float, token-seq 등이 있다. token은 categorical 변수, float은 실수형 변수라 생각하면 편하고, token-seq 컬럼의 각 인자들은 " "로 구분되어 있음을 알 수 있다.
2) Config files
좀 특이하고 복잡하게 느껴졌던 것이, 하나의 모델이 학습을 위해 config 파일 여러 개를 불러와 사용한다는 점이었다. 아래의 세 가지+1을 모두 확인해야한다. 모든 yaml 파일은 properties 폴더 안에 있다.
① overall.yaml
들어가보면 모든 모델이 공통적으로 필요로하는 epoch, batchsize, earlystopping patience, metric, optimizer, learning rate, 저장 경로, random seed 등을 지정할 수 있다. train_batch가 2048로 굉장히 큰데, 이후 inference까지 하고 싶다면 환경에 따라 모델이 무거워 CPU가 터질 수 있으니 적당히 조절할 필요가 있어보인다. (나는 256으로 학습한 모델도 문제가 생겨 64로 바꾸었고, 만들어진 모델은 파라미터를 모두 저장하는 pth 파일로 19기가가 넘었다..)
+ "~"로 표시하면 사용하지 않겠다는 의미인데, 나의 경우 CE loss를 사용하기 위해 여기에서 neg_sampling 옵션을 "~"로 지정해주어야 했다. (그렇지 않으면 서로 충돌)
② dataset.yaml
dataset을 생성했다면, 아까 사용한 이름을 그대로 사용해 yaml 파일을 하나 생성해줘야한다. 적당히 나의 상황에 맞게 생성하면 된다. 이 라이브러리가 제공하는 모델들은 sequential, knowledge based 등으로 구분해놓기도 했는데, 모델 종류에 따라 필요한 파라미터를 지정하거나 수정해야하는 경우도 있다.
중요한 부분은 위의 두 부분 정도로 생각했는데, field_separator는 데이터를 아까 tsv 형태로 줬으니 '\t'이고 csv를 사용하고 싶다면 ','로 변경할 수 있는 부분이다. seq_separator는 위의 token_seq 타입의 데이터가 " "로 구분되어 있다는 의미이니, 다른 것으로 구분해두었다면 이 역시 수정이 필요하다.
그 아래 load_col의 경우 .inter 파일에서 위의 네 컬럼을 사용하겠다는 의미이며, 외에도 다른 .user 파일이나 .item 파일에서 다른 컬럼을 사용하고 싶다면, 아래의 예시처럼 추가해줘야 할 수 있다. 이때 사용하는 컬럼 명이 아까 첫 row에 : 앞에 입력해줬던 이름이다.
③ model.yaml
나의 경우 SASRecF 모델을 사용했다. 사용하려는 모델의 파라미터를 상황에 맞게 수정하면 된다.
selected_features에 사용하고 싶은 컬럼을 추가해줄 수 있고, 대부분 모델에 loss_type으로 BPR도 구현이 되어있어 지정할 수 있다.
④ quick_start_config
이 부분은 아마 예시 코드를 그대로 사용할게 아니라면 참고만 하면 될 것 같다.
이렇게 파일 명과 라이브러리가 본래 제공하는 구조대로 yaml 파일을 사용하기 싫다면, 따로 config 파일을 하나로 던져주는 방법도 있는 것 같은데, 해당 CLI 명령어는 아래와 같다.
python run_hyper.py --model=[model_name] --dataset=[data_name] --config_files=xxxx.yaml --params_file=hyper.test
e.g.
python run_hyper.py --model=BPR --dataset=ml-100k --config_files=test.yaml --params_file=hyper.test
prams_file을 던지면 지정한 파라미터 범위에서 자동으로 최적값을 찾아준다. 나는 config_files는 따로 던지지 않고, model_name과 dataset만 지정해주고 나머지는 원래 구조에 맞게 이름을 활용해 만들어 넣었다.
3) Train / Eval / Inference
모델이 돌아가기 시작하면, 사용될 모든 파라미터들이 한 번 출력되고 evaluation까지는 알아서 수행된다. 다만 정답 label이 존재하지 않는 데이터에 대해 evaluation하고 싶은 경우에는, 상황에 맞게 inference하는 코드를 따로 작성해야한다.
# quick_start.py
def load_data_and_model(model_file):
r"""Load filtered dataset, split dataloaders and saved model.
Args:
model_file (str): The path of saved model file.
Returns:
tuple:
- config (Config): An instance object of Config, which record parameter information in :attr:`model_file`.
- model (AbstractRecommender): The model load from :attr:`model_file`.
- dataset (Dataset): The filtered dataset.
- train_data (AbstractDataLoader): The dataloader for training.
- valid_data (AbstractDataLoader): The dataloader for validation.
- test_data (AbstractDataLoader): The dataloader for testing.
"""
checkpoint = torch.load(model_file)
config = checkpoint['config']
init_seed(config['seed'], config['reproducibility'])
init_logger(config)
logger = getLogger()
logger.info(config)
dataset = create_dataset(config)
logger.info(dataset)
train_data, valid_data, test_data = data_preparation(config, dataset)
init_seed(config['seed'], config['reproducibility'])
model = get_model(config['model'])(config, train_data.dataset).to(config['device'])
model.load_state_dict(checkpoint['state_dict'])
model.load_other_parameter(checkpoint.get('other_parameter'))
return config, model, dataset, train_data, valid_data, test_data
© 2022 GitHub, Inc.
Terms
Privacy
Security
Status
Docs
Contact GitHu
위처럼 quick_start.py 내부의 load_data_and_model 함수가 test용 dataset은 제공하며, 각 모델에 생성되어 있는 predict_full_sort 함수를 예측에 활용할 수 있다.
# SASRecF.py
def full_sort_predict(self, interaction):
item_seq = interaction[self.ITEM_SEQ]
item_seq_len = interaction[self.ITEM_SEQ_LEN]
seq_output = self.forward(item_seq, item_seq_len)
test_items_emb = self.item_embedding.weight
scores = torch.matmul(seq_output, test_items_emb.transpose(0, 1)) # [B, item_num]
return scores
모든 대상에 대한 예측 값을 제공하기 때문에 상위 k개의 값을 정렬하여 구하면 된다. 이때 무작정 모든 값을 정렬하게 되면 시간 복잡도가 너무 커지게 되므로, 아래 예시처럼 구현하여 시간 효율을 높일 수 있다.
# run_inference.py 구현 예시
import argparse
import torch
import numpy as np
import pandas as pd
from recbole.quick_start import load_data_and_model
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--model_path', '-m', type=str, default='saved/model.pth', help='name of models')
# python run_inference.py --model_path=/opt/ml/input/RecBole/saved/SASRecF-Apr-07-2022_03-17-16.pth 로 실행
args, _ = parser.parse_known_args()
# model, dataset 불러오기
config, model, dataset, train_data, valid_data, test_data = load_data_and_model(args.model_path)
# device 설정
device = config.final_config_dict['device']
# user, item id -> token 변환 array
user_id2token = dataset.field2id_token['user_id']
item_id2token = dataset.field2id_token['item_id']
# user-item sparse matrix
matrix = dataset.inter_matrix(form='csr')
# user id, predict item id 저장 변수
pred_list = None
user_list = None
model.eval()
for data in test_data:
interaction = data[0].to(device)
score = model.full_sort_predict(interaction)
rating_pred = score.cpu().data.numpy().copy()
batch_user_index = interaction['user_id'].cpu().numpy()
rating_pred[matrix[batch_user_index].toarray() > 0] = 0
ind = np.argpartition(rating_pred, -10)[:, -10:]
arr_ind = rating_pred[np.arange(len(rating_pred))[:, None], ind]
arr_ind_argsort = np.argsort(arr_ind)[np.arange(len(rating_pred)), ::-1]
batch_pred_list = ind[
np.arange(len(rating_pred))[:, None], arr_ind_argsort
]
# 예측값 저장
if pred_list is None:
pred_list = batch_pred_list
user_list = batch_user_index
else:
pred_list = np.append(pred_list, batch_pred_list, axis=0)
user_list = np.append(user_list, batch_user_index, axis=0)
result = []
for user, pred in zip(user_list, pred_list):
for item in pred:
result.append((int(user_id2token[user]), int(item_id2token[item])))
# 데이터 저장
dataframe = pd.DataFrame(result, columns=["user", "item"])
dataframe.sort_values(by='user', inplace=True)
dataframe.to_csv(
"/saved/submission.csv", index=False
)
print('inference done!')
'AI > 추천시스템(RecSys)' 카테고리의 다른 글
[논문 리뷰] BPR: Bayesian Personalized Ranking from Implicit Feedback(2009) (0) | 2022.03.19 |
---|---|
[논문 리뷰] Neural Collaborative Filtering(2017) (0) | 2022.03.19 |
[추천 시스템/RecSys] 딥러닝을 활용한 추천 모델 DeepFM, DIN, BST (0) | 2022.03.17 |
[추천 시스템/RecSys] CAR(Context-aware Recommendation), FM, FFM (0) | 2022.03.15 |
[추천 시스템/RecSys] KNN과 ANN / ANNOY, HNSW, IVF 등의 방법론 (0) | 2022.03.12 |