Written by seoo2001


Local에 Git Clone 하기

설치 방법은 pip, git clone 등 다양함 일단 git clone으로 해봤음

https://github.com/RUCAIBox/RecBole

apt-get install git
git clone https://github.com/RUCAIBox/RecBole.git
cd RecBole
rm -r .git # 깃 설정 삭제 (Optional)

requirements 다운

# 방법 1
python setup.py install 

# 방법 2
pip install -r requirements.txt

# 일단 1번으로 진행했음. 에러 발생하면, 2번 진행

# 추가 설치
pip install xgboost # 없으면 error 발생
pip install lightgbm # 없으면 error 발생

데이터셋 설정

interaction 데이터셋, 유저, 아이템 정보 등으로 구성.

파일 형식은 tab으로 구분된 csv 파일.

# 데이터셋 생성 예시
import pandas as pd
train_df = pd.read_csv('../data/train/train_ratings.csv')
genres_df = pd.read_csv('../data/train/genres.tsv', sep='\t')
directors_df = pd.read_csv('../data/train/directors.tsv', sep='\t')
writers_df = pd.read_csv('../data/train/writers.tsv', sep='\t')
years_df = pd.read_csv('../data/train/years.tsv', sep='\t')
titles_df = pd.read_csv('../data/train/titles.tsv', sep='\t')

# merge dataframes
unique_movie = train_df['item'].unique()
genres_df = genres_df.groupby('item')['genre'].apply(lambda x: ' '.join(x)).reset_index()
directors_df = directors_df.groupby('item')['director'].apply(lambda x: ' '.join(x)).reset_index()
writers_df = writers_df.groupby('item')['writer'].apply(lambda x: ' '.join(x)).reset_index()

unique_movie = pd.DataFrame(unique_movie, columns=['item'])
unique_movie = pd.merge(unique_movie, genres_df, on='item', how='left')
unique_movie = pd.merge(unique_movie, directors_df, on='item', how='left')
unique_movie = pd.merge(unique_movie, writers_df, on='item', how='left')
unique_movie = pd.merge(unique_movie, titles_df, on='item', how='left')
unique_movie = pd.merge(unique_movie, years_df, on='item', how='left')

# column name 바꾸기
unique_movie.columns = ['item_id:token', 'genre:token_seq', 'director:token_seq', 'writer:token', 'title:token_seq', 'year:token']
unique_movie.to_csv('../data/train/movie_info.tsv', sep='\t', index=False)

# inter
train_df = pd.read_csv('../data/train/train_ratings.csv')
train_df.columns = ['user_id:token', 'item_id:token', 'timestamp:float']

train_df['label:float'] = 1 # label 1로 추가 (Optional)
train_df.to_csv('../data/train/interactions.tsv', sep='\t', index=False)

interactions.tsv -> interaction 파일, 파일명 데이터셋이름.inter 로 수정 movie_info.tsv -> item 파일, 파일명 데이터셋이름.item 으로 수정

데이터셋 관련 추가 자료는 링크 참고

데이터셋 저장 저장 위치 Recbole/dataset/데이터셋이름/데이터셋이름.inter Recbole/dataset/데이터셋이름/데이터셋이름.item

image-20241210184946223

  • 데이터 설정 파일 수정 파일 위치: RecBole/recbole/properties/dataset/데이터셋이름.yaml (데이터셋이름은 위에서 저장한 이름과 동일하게)
field_separator: "\t"  # 데이터 구분자
seq_separator: " "              # (str) Separator inside the sequence features.

# 필드 정의
load_col:
   inter: [user_id, item_id, timestamp]  # interaction 데이터의 컬럼
   item: [item_id, genre, director, writer, title, year] # 선택적: 아이템 부가 정보

# 필드 타입 명시
USER_ID_FIELD: user_id
ITEM_ID_FIELD: item_id
TIME_FIELD: timestamp  # 시간 정보가 있는 경우
# LABEL_FIELD: label              # (str) Expected field name of the generated labels for point-wise dataLoaders. 


# 평가 설정
metrics: ["Recall"]  # 평가 메트릭
topk: [10]  # Top-K 추천 설정
valid_metric: Recall@10

NEG_PREFIX: neg_

# training settings
train_batch_size: 2048
eval_batch_size: 4096

# negative sampling settings
train_neg_sample_args: 
   distribution: uniform    # 균등 분포로 샘플링
   sample_num: 5           # positive sample 1개당 5개의 negative samples
   dynamic: True          # 매 epoch마다 새로운 negative samples 생성

# validation, test 네거티브 샘플링 되는건지 모르겠음. 고쳐줘
valid_neg_sample_args:
   distribution: uniform
   sample_num: 5

test_neg_sample_args:
   distribution: uniform
   sample_num: 5


학습

Recbole/run_model.py 파일 생성

# 실행: python3 run_model.py
# 실행 방법은 다른 방법도 많음.
from recbole.config import Config
from recbole.quick_start import run_recbole

config_dict = {
    'model': 'RaCT',
    'dataset': 'ml',
    'loss_type': 'BCEWithLogitsLoss',
    'train_neg_sample_args': {'distribution': 'uniform', 'sample_num': 5},
    'eval_args': {
        'split': {'RS': [0.8, 0.1, 0.1]},
        'group_by': 'user',
        'order': 'RO',
        'mode': {'valid': 'full', 'test': 'full'},
        'metrics': ['Recall'],
        'topk': [10],
        'valid_metric': 'Recall@10'

    },
    # 여기도 negative sampling 되는건지 모르겠음.
    'eval_neg_sample_args': {'distribution': 'uniform', 'sample_num': 100},
}

run_recbole(config_dict=config_dict)

학습이 되면 모델이 Recbole/saved/ 경로에 .pth 형식으로 저장됨.


모델 테스트

Recbole/run_inference.py 파일 생성

# 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/RaCT-ml-150.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 = score.cpu().data.numpy().reshape(len(batch_user_index), -1)
        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:
            user_token = user_id2token[user]
            item_token = item_id2token[item]
            if user_token != '[PAD]' and item_token != '[PAD]':
                result.append((int(user_token), int(item_token)))
            else:
                result.append((int(user_token), 2571)) # [PAD] 토큰이 들어있는 경우 예외처리. 2571이 중복으로 들어가는 경우도 있어서, 수정 필요함.
            
    # 데이터 저장
    dataframe = pd.DataFrame(result, columns=["user", "item"])
    dataframe.sort_values(by='user', inplace=True)
    dataframe.to_csv(
        "./submission.csv", index=False
    ) # 파일 저장 경로는 적절하게 설정!
    print('inference done!')

추가 팁

설정 사항은 다음의 우선순위를 따름. The priority of the configuration methods is: Command Line > Parameter Dicts > Config Files > Default Settings

BPR, NCF, PureSVD, RaCT, MultiVAE, MultiDAE, EASE 등 써보면 될듯.

모델마다 이상한 오류 많았음. 부스팅 모델 쓰려면 negative sampling을 직접해서 데이터셋에 0, 1로 라벨링된 상태로 넣거나, negative sampling 되도록 고쳐야됨.

참고 블로그 https://mingchin.tistory.com/420 https://hoon-bari.github.io/RS/Recbole

Docs(https 보안연결 안됨, 고급 -> 안전하지 않음으로 이동 클릭) https://recbole.io/docs/

  • 추가 오류나, 팁, 개선내용 댓글로 추가하기