{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":38760,"databundleVersionId":4493939,"sourceType":"competition"},{"sourceId":4436180,"sourceType":"datasetVersion","datasetId":2597726},{"sourceId":4461402,"sourceType":"datasetVersion","datasetId":2611514},{"sourceId":4474043,"sourceType":"datasetVersion","datasetId":2601572}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# OTTO - GRU4Rec Simple Pipeline\n\n**Optimized for 30GB RAM**\n\nSimple and clean implementation using RecBole + Polars","metadata":{}},{"cell_type":"code","source":"# Install RecBole\n!pip install recbole polars -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:32:40.532214Z","iopub.execute_input":"2025-12-01T08:32:40.53248Z","iopub.status.idle":"2025-12-01T08:33:58.481637Z","shell.execute_reply.started":"2025-12-01T08:32:40.53246Z","shell.execute_reply":"2025-12-01T08:33:58.480928Z"}},"outputs":[],"execution_count":null},{"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":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:33:58.483019Z","iopub.execute_input":"2025-12-01T08:33:58.483252Z","iopub.status.idle":"2025-12-01T08:34:00.129595Z","shell.execute_reply.started":"2025-12-01T08:33:58.483228Z","shell.execute_reply":"2025-12-01T08:34:00.129053Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Data Loading & Preparation","metadata":{}},{"cell_type":"code","source":"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([train, test])\n#df = pl.read_parquet('../input/otto-train-and-test-data-for-local-validation/test.parquet')\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":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:34:20.669039Z","iopub.execute_input":"2025-12-01T08:34:20.66945Z","iopub.status.idle":"2025-12-01T08:34:22.520096Z","shell.execute_reply.started":"2025-12-01T08:34:20.66943Z","shell.execute_reply":"2025-12-01T08:34:22.519477Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Đường dẫn thư mục\ndirectory = \"/kaggle/working/recbox_data/\"\n\n# Kiểm tra và tạo thư mục\nif not os.path.exists(\"/kaggle/working/recbox_data/\"):\n    os.makedirs(directory)\n    # print(f\"Đã tạo thư mục: {directory} \")\nelse:\n    print(f\"Thư mục đã tồn tại: \")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:35:56.73597Z","iopub.execute_input":"2025-12-01T08:35:56.736546Z","iopub.status.idle":"2025-12-01T08:35:56.740466Z","shell.execute_reply.started":"2025-12-01T08:35:56.736524Z","shell.execute_reply":"2025-12-01T08:35:56.739794Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df.columns)\n\n# Convert the relevant columns to a Pandas DataFrame\npandas_df = df[['session:token', 'aid:token', 'ts:float']].to_pandas()\n\n# Save to a tab-separated file using Pandas\npandas_df.to_csv(\n    '/kaggle/working/recbox_data/recbox_data.inter',\n    sep='\\t',\n    index=False\n)\n\n# Clean up memory\ndel df, pandas_df\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:36:07.682775Z","iopub.execute_input":"2025-12-01T08:36:07.683449Z","iopub.status.idle":"2025-12-01T08:36:36.714707Z","shell.execute_reply.started":"2025-12-01T08:36:07.683426Z","shell.execute_reply":"2025-12-01T08:36:36.713991Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Model Configuration","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":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:36:42.549727Z","iopub.execute_input":"2025-12-01T08:36:42.550395Z","iopub.status.idle":"2025-12-01T08:36:59.03452Z","shell.execute_reply.started":"2025-12-01T08:36:42.550369Z","shell.execute_reply":"2025-12-01T08:36:59.033975Z"}},"outputs":[],"execution_count":null},{"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': 30,\n    'stopping_step':3,\n    \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'])\n\n# logger initialization\ninit_logger(config)\nlogger = getLogger()\n\n# Create handlers\nc_handler = logging.StreamHandler()\nc_handler.setLevel(logging.INFO)\nlogger.addHandler(c_handler)\n\n# write config info into log\nlogger.info(config)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:37:07.918683Z","iopub.execute_input":"2025-12-01T08:37:07.919549Z","iopub.status.idle":"2025-12-01T08:37:08.311049Z","shell.execute_reply.started":"2025-12-01T08:37:07.919519Z","shell.execute_reply":"2025-12-01T08:37:08.310555Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Training","metadata":{}},{"cell_type":"code","source":"# # === Initialize config ===\n# config = Config(model='GRU4Rec', dataset='recbox_data', config_dict=parameter_dict)\n\n# # === Initialize random seed and logger ===\n# init_seed(config['seed'], config['reproducibility'])\n# init_logger(config)\n# logger = getLogger()\n\n# # === Create handler để log ra màn hình ===\n# c_handler = logging.StreamHandler()\n# c_handler.setLevel(logging.INFO)\n# logger.addHandler(c_handler)\n\n# # === Print config để kiểm tra ===\n# logger.info(config)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T07:54:52.61298Z","iopub.execute_input":"2025-12-01T07:54:52.613325Z","iopub.status.idle":"2025-12-01T07:54:53.029259Z","shell.execute_reply.started":"2025-12-01T07:54:52.613296Z","shell.execute_reply":"2025-12-01T07:54:53.028525Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Create dataset ===\ndataset = create_dataset(config)\nlogger.info(dataset)\n\nprint(f\"\\nDataset Statistics:\")\nprint(f\"  Users (Sessions): {dataset.user_num:,}\")\nprint(f\"  Items: {dataset.item_num:,}\")\nprint(f\"  Interactions: {dataset.inter_num:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:37:14.720305Z","iopub.execute_input":"2025-12-01T08:37:14.720849Z","iopub.status.idle":"2025-12-01T08:39:26.596085Z","shell.execute_reply.started":"2025-12-01T08:37:14.72083Z","shell.execute_reply":"2025-12-01T08:39:26.595495Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Dataset splitting ===\ntrain_data, valid_data, test_data = data_preparation(config, dataset)\nprint(f\"\\nData Splits:\")\nprint(f\"  Train batches: {len(train_data)}\")\nprint(f\"  Valid batches: {len(valid_data)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:42:10.322917Z","iopub.execute_input":"2025-12-01T08:42:10.323536Z","iopub.status.idle":"2025-12-01T08:42:10.344505Z","shell.execute_reply.started":"2025-12-01T08:42:10.323515Z","shell.execute_reply":"2025-12-01T08:42:10.343563Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = GRU4Rec(config, train_data.dataset).to(config['device'])\nlogger.info(model)\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":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:42:01.678938Z","iopub.status.idle":"2025-12-01T08:42:01.679228Z","shell.execute_reply.started":"2025-12-01T08:42:01.679104Z","shell.execute_reply":"2025-12-01T08:42:01.679117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from 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":{"iopub.status.busy":"2025-12-01T05:03:27.971601Z","iopub.execute_input":"2025-12-01T05:03:27.972521Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Clean Up Memory","metadata":{}},{"cell_type":"code","source":"# Free memory\nimport gc\ndel trainer, train_data, valid_data, test_data\ngc.collect()\n\nprint(\"✅ Memory freed!\")","metadata":{},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_user_to_item(item)['item_list']\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"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},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Summary\n\n**GRU4Rec Simple Pipeline Complete!**\n\n✅ Data loaded with Polars (memory efficient)  \n✅ RecBole format conversion  \n✅ GRU4Rec model trained  \n✅ Optimized for <30GB RAM  \n\n**Model saved to:** `/kaggle/working/`\n\n**Next steps:**\n- Use trained model for inference\n- Generate predictions for test set\n- Create submission file","metadata":{}}]}