{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Here is a sample code for gru4rec using recbole. \nI made it based on the following code from a past competition\nhttps://www.kaggle.com/code/astrung/recbole-lstm-sequential-for-recomendation-tutorial\n\nI think you can get a better score if you change the training data or epoch size, etc. Enjoy!","metadata":{}},{"cell_type":"code","source":"!pip install polars","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:23.288741Z","iopub.execute_input":"2022-12-21T07:03:23.289112Z","iopub.status.idle":"2022-12-21T07:03:36.756007Z","shell.execute_reply.started":"2022-12-21T07:03:23.289033Z","shell.execute_reply":"2022-12-21T07:03:36.754855Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install recbole","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:36.758426Z","iopub.execute_input":"2022-12-21T07:03:36.759129Z","iopub.status.idle":"2022-12-21T07:03:47.995223Z","shell.execute_reply.started":"2022-12-21T07:03:36.759089Z","shell.execute_reply":"2022-12-21T07:03:47.994029Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import tqdm\nimport polars as pl\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport random\nimport os \nimport h5py\nimport sys\nimport gc\n\nfrom matplotlib import pyplot as plt\nimport pyarrow.parquet as pq","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:47.997318Z","iopub.execute_input":"2022-12-21T07:03:47.998053Z","iopub.status.idle":"2022-12-21T07:03:48.710056Z","shell.execute_reply.started":"2022-12-21T07:03:47.998008Z","shell.execute_reply":"2022-12-21T07:03:48.709067Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 1. Create atomic file","metadata":{}},{"cell_type":"code","source":"# # extract only last 0.5 week for train to reduce memory\n\n# WEEK=0.5\n\n# import datetime\n# train = pl.read_parquet('/kaggle/input/otto-full-optimized-memory-footprint/train.parquet')\n\n# print (f\"before={datetime.datetime.fromtimestamp(train['ts'].min())} - {datetime.datetime.fromtimestamp(train['ts'].max())}\")\n\n# week1 = datetime.timedelta(weeks=WEEK)\n# last_week = (datetime.datetime.fromtimestamp(train['ts'].max()) - week1).timestamp()\n# last_train = train.filter(pl.col('ts') > last_week)\n\n# print (f\"after={datetime.datetime.fromtimestamp(last_train['ts'].min())} - {datetime.datetime.fromtimestamp(last_train['ts'].max())}\")\n\n# del train\n# gc.collect()","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:48.712749Z","iopub.execute_input":"2022-12-21T07:03:48.713141Z","iopub.status.idle":"2022-12-21T07:03:48.717927Z","shell.execute_reply.started":"2022-12-21T07:03:48.713104Z","shell.execute_reply":"2022-12-21T07:03:48.716856Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"last_train = pl.read_parquet('/kaggle/input/otto-train-and-test-data-for-local-validation/test.parquet')\ntest = pl.read_parquet('/kaggle/input/otto-full-optimized-memory-footprint/test.parquet')\n\ndf = pl.concat([last_train, test])\n\ndf = df.sort(['session', 'aid', 'ts'])\ndf = df.with_columns((pl.col('ts') * 1e9).alias('ts'))\ndf = df.rename({'session': 'session:token', 'aid': 'aid:token', 'ts': 'ts:float'})","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:48.719428Z","iopub.execute_input":"2022-12-21T07:03:48.719769Z","iopub.status.idle":"2022-12-21T07:03:51.829973Z","shell.execute_reply.started":"2022-12-21T07:03:48.719735Z","shell.execute_reply":"2022-12-21T07:03:51.828983Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir /kaggle/working/recbox_data\ndf['session:token', 'aid:token', 'ts:float'].write_csv('/kaggle/working/recbox_data/recbox_data.inter', sep='\\t')\n\ndel df, last_train, test\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:51.834262Z","iopub.execute_input":"2022-12-21T07:03:51.834677Z","iopub.status.idle":"2022-12-21T07:03:57.066908Z","shell.execute_reply.started":"2022-12-21T07:03:51.834637Z","shell.execute_reply":"2022-12-21T07:03:57.065716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. Create dataset and train model with Recbole\n\nFor anyone need instruction document, please check this link: https://recbole.io/docs/user_guide/usage/use_modules.html","metadata":{}},{"cell_type":"code","source":"import logging\nfrom logging import getLogger\nfrom recbole.config import Config\nfrom recbole.data import create_dataset, data_preparation\nfrom recbole.model.sequential_recommender import GRU4Rec\nfrom recbole.trainer import Trainer\nfrom recbole.utils import init_seed, init_logger\n\nfrom recbole.utils.case_study import full_sort_topk","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:57.068548Z","iopub.execute_input":"2022-12-21T07:03:57.068939Z","iopub.status.idle":"2022-12-21T07:03:59.101617Z","shell.execute_reply.started":"2022-12-21T07:03:57.068900Z","shell.execute_reply":"2022-12-21T07:03:59.100153Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"MAX_ITEM = 20\n\nparameter_dict = {\n    'data_path': '/kaggle/working/',\n    'USER_ID_FIELD': 'session',\n    'ITEM_ID_FIELD': 'aid',\n    'TIME_FIELD': 'ts',\n    'user_inter_num_interval': \"[5,Inf)\",\n    'item_inter_num_interval': \"[5,Inf)\",\n    'load_col': {'inter': ['session', 'aid', 'ts']},\n#    'train_neg_sample_args': None,\n    'epochs': 10,\n    'stopping_step':3,\n    'loss_type':'BPR',\n    'eval_batch_size': 1024,\n    #'train_batch_size': 1024,\n#    'enable_amp':True,\n    'MAX_ITEM_LIST_LENGTH': MAX_ITEM,\n    'eval_args': {\n        'split': {'RS': [9, 1, 0]},\n        'group_by': 'user',\n        'order': 'TO',\n        'mode': 'full'}\n}\n\nconfig = Config(model='GRU4Rec', dataset='recbox_data', config_dict=parameter_dict)\n\n# init random seed\ninit_seed(config['seed'], config['reproducibility'])","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:59.103447Z","iopub.execute_input":"2022-12-21T07:03:59.104454Z","iopub.status.idle":"2022-12-21T07:03:59.304388Z","shell.execute_reply.started":"2022-12-21T07:03:59.104415Z","shell.execute_reply":"2022-12-21T07:03:59.303286Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset = create_dataset(config)","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:03:59.309536Z","iopub.execute_input":"2022-12-21T07:03:59.311991Z","iopub.status.idle":"2022-12-21T07:06:29.588876Z","shell.execute_reply.started":"2022-12-21T07:03:59.311948Z","shell.execute_reply":"2022-12-21T07:06:29.587847Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset splitting\ntrain_data, valid_data, test_data = data_preparation(config, dataset)","metadata":{"execution":{"iopub.status.busy":"2022-12-21T07:06:29.592348Z","iopub.execute_input":"2022-12-21T07:06:29.592817Z","iopub.status.idle":"2022-12-21T07:08:17.637906Z","shell.execute_reply.started":"2022-12-21T07:06:29.592765Z","shell.execute_reply":"2022-12-21T07:08:17.636863Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# model loading and initialization\nmodel = GRU4Rec(config, train_data.dataset).to(config['device'])\n\n# trainer loading and initialization\ntrainer = Trainer(config, model)\n\n# model training\nbest_valid_score, best_valid_result = trainer.fit(train_data, valid_data)\n#best_valid_score, best_valid_result = trainer.fit(train_data)","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2022-12-21T07:08:17.642562Z","iopub.execute_input":"2022-12-21T07:08:17.645373Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import gc\ndel trainer, train_data, valid_data, test_data","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. Create recommendation result from trained model\n\nI note document here for any one want to customize it: https://recbole.io/docs/user_guide/usage/case_study.html","metadata":{}},{"cell_type":"code","source":"# https://qiita.com/fufufukakaka/items/e03df3a7299b2b8f99cf\nfrom typing import List, Tuple\nimport numpy as np\nimport torch\n\nfrom pydantic import BaseModel\nfrom recbole.data import create_dataset\nfrom recbole.data.dataset.sequential_dataset import SequentialDataset\nfrom recbole.data.interaction import Interaction\nfrom recbole.model.sequential_recommender.sine import SINE\nfrom recbole.utils import get_model, init_seed\n\nclass ItemHistory(BaseModel):\n    sequence: List[str]\n    topk: int\n\nclass RecommendedItems(BaseModel):\n    score_list: List[float]\n    item_list: List[str]\n\n\ndef pred_user_to_item(item_history: ItemHistory):\n    item_history_dict = item_history.dict()\n    item_sequence = item_history_dict[\"sequence\"]\n    item_length = len(item_sequence)\n    pad_length = MAX_ITEM  # pre-defined by recbole\n\n    padded_item_sequence = torch.nn.functional.pad(\n        torch.tensor(dataset.token2id(dataset.iid_field, item_sequence)),\n        (0, pad_length - item_length),\n        \"constant\",\n        0,\n    )\n\n    input_interaction = Interaction(\n        {\n            \"aid_list\": padded_item_sequence.reshape(1, -1),\n            \"item_length\": torch.tensor([item_length]),\n        }\n    )\n    scores = model.full_sort_predict(input_interaction.to(model.device))\n    scores = scores.view(-1, dataset.item_num)\n    scores[:, 0] = -np.inf  # pad item score -> -inf\n    topk_score, topk_iid_list = torch.topk(scores, item_history_dict[\"topk\"])\n\n    predicted_score_list = topk_score.tolist()[0]\n    predicted_item_list = dataset.id2token(\n        dataset.iid_field, topk_iid_list.tolist()\n    ).tolist()\n\n    recommended_items = {\n        \"score_list\": predicted_score_list,\n        \"item_list\": predicted_item_list,\n    }\n    return recommended_items","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#test = pl.read_parquet('../input/otto-train-and-test-data-for-local-validation/test.parquet')\ntest = pl.read_parquet('../input/otto-full-optimized-memory-footprint/test.parquet')\n\nimport pandas as pd\nimport numpy as np\n\nfrom collections import defaultdict\n\n#sample_sub = pd.read_csv('../input/otto-recommender-system//sample_submission.csv')\n\nsession_types = ['clicks', 'carts', 'orders']\ntest_session_AIDs = test.to_pandas().reset_index(drop=True).groupby('session')['aid'].apply(list)\ntest_session_types = test.to_pandas().reset_index(drop=True).groupby('session')['type'].apply(list)\n\ndel test\ngc.collect()\n\nlabels = []\n\ntype_weight_multipliers = {0: 1, 1: 6, 2: 3}\nfor AIDs, types in zip(test_session_AIDs, test_session_types):\n    if len(AIDs) >= 20:\n        # if we have enough aids (over equals 20) we don't need to look for candidates! we just use the old logic\n        weights=np.logspace(0.1,1,len(AIDs),base=2, endpoint=True)-1\n        aids_temp=defaultdict(lambda: 0)\n        for aid,w,t in zip(AIDs,weights,types): \n            aids_temp[aid]+= w * type_weight_multipliers[t]\n            \n        sorted_aids=[k for k, v in sorted(aids_temp.items(), key=lambda item: -item[1])]\n        labels.append(sorted_aids[:20])\n    else:\n        AIDs = list(dict.fromkeys(AIDs))\n        item = ItemHistory(sequence=AIDs, topk=20)\n        try:\n            nns = [ int(v) for v in pred_user_to_item(item)['item_list']]\n        except:\n            nns = []\n\n        for word in nns:\n            if len(AIDs) == 20:\n                break\n            if int(word) not in AIDs:\n                AIDs.append(word)\n\n        labels.append(AIDs[:20])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_as_strings = [' '.join([str(l) for l in lls]) for lls in labels]\npredictions = pd.DataFrame(data={'session_type': test_session_AIDs.index, 'labels': labels_as_strings})","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_as_strings = [' '.join([str(l) for l in lls]) for lls in labels]\n\npredictions = pd.DataFrame(data={'session_type': test_session_AIDs.index, 'labels': labels_as_strings})\n\nprediction_dfs = []\n\nfor st in session_types:\n    modified_predictions = predictions.copy()\n    modified_predictions.session_type = modified_predictions.session_type.astype('str') + f'_{st}'\n    prediction_dfs.append(modified_predictions)\n\nsubmission = pd.concat(prediction_dfs).reset_index(drop=True)\nsubmission.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}