{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":38760,"databundleVersionId":4493939,"sourceType":"competition"},{"sourceId":4461402,"sourceType":"datasetVersion","datasetId":2611514},{"sourceId":4474043,"sourceType":"datasetVersion","datasetId":2601572}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install recbole","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-30T09:13:11.754981Z","iopub.execute_input":"2025-11-30T09:13:11.755140Z","execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl\nimport pandas as pd\nimport torch\nimport gc\nfrom tqdm import tqdm\nimport logging\nfrom logging import getLogger\nfrom recbole.config import Config\nfrom recbole.data import create_dataset, data_preparation\nfrom recbole.data.interaction import Interaction\nfrom recbole.model.sequential_recommender import SASRec\nfrom recbole.trainer import Trainer\nfrom recbole.utils import init_seed, init_logger","metadata":{"trusted":true,"execution":{"execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 1. DATA PREPARATION (FIXED)\n# ===========================\nprint(\"Loading data...\")\ntrain = 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\n# ✅ FIX: Chỉ dùng train data để train model\ndf = train.clone()\n\n# ✅ FIX: Sắp xếp ĐÚNG - chỉ theo session và timestamp\ndf = df.sort(['session', 'ts'])\n\n# Convert timestamp to nanoseconds\ndf = df.with_columns((pl.col('ts') * 1e9).alias('ts'))\n\n# Rename columns theo format RecBole\ndf = df.rename({\n    'session': 'session:token', \n    'aid': 'aid:token', \n    'ts': 'ts:float'\n})\n\n# Save data\n!mkdir -p /kaggle/working/recbox_data\ndf.select(['session:token', 'aid:token', 'ts:float']).write_csv(\n    '/kaggle/working/recbox_data/recbox_data.inter', \n    separator='\\t'\n)\n\nprint(f\"✅ Data prepared: {len(df)} interactions\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 2. MODEL CONFIGURATION (IMPROVED)\n# ===========================\nMAX_ITEM = 20  # Giữ nguyên theo yêu cầu\n\nparameter_dict = {\n    'data_path': '/kaggle/working/',\n    'USER_ID_FIELD': 'session',\n    'ITEM_ID_FIELD': 'aid',\n    'TIME_FIELD': 'ts',\n    \n    # ✅ FIX: Giảm filtering để giữ nhiều data hơn\n    'user_inter_num_interval': \"[2,Inf)\",\n    'item_inter_num_interval': \"[3,Inf)\",\n    \n    'load_col': {'inter': ['session', 'aid', 'ts']},\n    'train_neg_sample_args': None,\n    \n    # ✅ FIX: Tăng epochs và early stopping\n    'epochs': 50,\n    'stopping_step': 5,\n    \n    # ✅ ADD: Hyperparameters quan trọng\n    'hidden_size': 64,\n    'inner_size': 256,\n    'n_layers': 2,\n    'n_heads': 2,\n    'hidden_dropout_prob': 0.5,\n    'attn_dropout_prob': 0.5,\n    'learning_rate': 0.001,\n    'train_batch_size': 2048,\n    'eval_batch_size': 1024,\n    \n    'MAX_ITEM_LIST_LENGTH': MAX_ITEM,\n    'eval_args': {\n        'split': {'RS': [8, 1, 1]},  # ✅ FIX: Thêm test set để evaluate\n        'group_by': 'user',\n        'order': 'TO',\n        'mode': 'full'\n    }\n}\n\n# Initialize config\nconfig = Config(model='SASRec', dataset='recbox_data', config_dict=parameter_dict)\ninit_seed(config['seed'], config['reproducibility'])\ninit_logger(config)\nlogger = getLogger()\n\nc_handler = logging.StreamHandler()\nc_handler.setLevel(logging.INFO)\nlogger.addHandler(c_handler)\n\nlogger.info(config)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 3. DATASET & MODEL TRAINING\n# ===========================\nprint(\"\\nCreating dataset...\")\ndataset = create_dataset(config)\nlogger.info(dataset)\n\nprint(\"\\nPreparing data splits...\")\ntrain_data, valid_data, test_data = data_preparation(config, dataset)\n\nprint(\"\\nInitializing model...\")\nmodel = SASRec(config, train_data.dataset).to(config['device'])\nlogger.info(model)\n\nprint(\"\\nTraining model...\")\ntrainer = Trainer(config, model)\nbest_valid_score, best_valid_result = trainer.fit(train_data, valid_data)\n\n# ✅ ADD: Evaluate model (trên valid data thay vì test)\nprint(\"\\n\" + \"=\"*50)\nprint(\"EVALUATION RESULTS\")\nprint(\"=\"*50)\nprint(f\"Best validation score: {best_valid_score}\")\nprint(f\"Best validation result: {best_valid_result}\")\n\n# Nếu muốn evaluate trên test data, cần load model với weights_only=False\ntry:\n    # Evaluate trực tiếp không load lại model\n    test_result = trainer.evaluate(test_data, load_best_model=False)\n    logger.info(f\"Test results (current model): {test_result}\")\nexcept Exception as e:\n    print(f\"⚠️ Cannot evaluate on test data: {e}\")\n    print(\"Continuing with best validation results...\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 4. PREDICTION (OPTIMIZED)\n# ===========================\ndef get_predictions_batch(session_ids, model, dataset, top_k=20):\n    \"\"\"\n    Batch prediction với error handling tốt hơn\n    \"\"\"\n    results = {}\n    batch_data = []\n    \n    for external_session_id in session_ids:\n        try:\n            internal_session_id = dataset.token2id(dataset.uid_field, str(external_session_id))\n        except (KeyError, ValueError):\n            results[external_session_id] = \"\"\n            continue\n        \n        # Lấy interaction history\n        inter_feat = dataset.inter_feat\n        session_mask = (inter_feat[dataset.uid_field] == internal_session_id)\n        session_indices = session_mask.nonzero(as_tuple=True)[0]\n        \n        if len(session_indices) == 0:\n            results[external_session_id] = \"\"\n            continue\n        \n        # Lấy item IDs\n        internal_item_ids = inter_feat[dataset.iid_field][session_indices].cpu().tolist()\n        max_len = dataset.config['MAX_ITEM_LIST_LENGTH']\n        internal_item_ids = internal_item_ids[-max_len:]\n        \n        if len(internal_item_ids) == 0:\n            results[external_session_id] = \"\"\n            continue\n        \n        # Padding\n        padded_items = internal_item_ids + [0] * (max_len - len(internal_item_ids))\n        \n        batch_data.append({\n            'session_id': external_session_id,\n            'internal_session_id': internal_session_id,\n            'padded_items': padded_items,\n            'item_length': len(internal_item_ids),\n            'original_items': internal_item_ids\n        })\n    \n    if len(batch_data) == 0:\n        return results\n    \n    # Tạo batch tensors\n    item_list_field = dataset.iid_field + '_list'\n    \n    batch_interaction = Interaction({\n        dataset.uid_field: torch.tensor([d['internal_session_id'] for d in batch_data], dtype=torch.long),\n        item_list_field: torch.tensor([d['padded_items'] for d in batch_data], dtype=torch.long),\n        'item_length': torch.tensor([d['item_length'] for d in batch_data], dtype=torch.long),\n    })\n    \n    # Batch prediction\n    model.eval()\n    try:\n        with torch.no_grad():\n            batch_interaction = batch_interaction.to(model.device)\n            scores = model.full_sort_predict(batch_interaction)\n            \n            for idx, data in enumerate(batch_data):\n                session_id = data['session_id']\n                internal_item_ids = data['original_items']\n                \n                session_scores = scores[idx].clone()\n                \n                # Loại bỏ items đã tương tác\n                for item_id in internal_item_ids:\n                    if item_id < len(session_scores):\n                        session_scores[item_id] = -float('inf')\n                \n                # Get top-k\n                k = min(top_k, len(session_scores))\n                top_k_indices = torch.topk(session_scores, k=k).indices.cpu().tolist()\n                \n                # Convert to external IDs\n                external_item_ids = [dataset.id2token(dataset.iid_field, idx) for idx in top_k_indices]\n                results[session_id] = \" \".join(external_item_ids)\n    \n    except RuntimeError as e:\n        print(f\"⚠️ CUDA Error in batch: {e}\")\n        print(f\"Falling back to single prediction...\")\n        \n        # Fallback: xử lý từng session\n        for data in batch_data:\n            session_id = data['session_id']\n            try:\n                single_interaction = Interaction({\n                    dataset.uid_field: torch.tensor([data['internal_session_id']], dtype=torch.long),\n                    item_list_field: torch.tensor([data['padded_items']], dtype=torch.long),\n                    'item_length': torch.tensor([data['item_length']], dtype=torch.long),\n                })\n                \n                with torch.no_grad():\n                    single_interaction = single_interaction.to(model.device)\n                    session_scores = model.full_sort_predict(single_interaction)[0]\n                    \n                    for item_id in data['original_items']:\n                        if item_id < len(session_scores):\n                            session_scores[item_id] = -float('inf')\n                    \n                    k = min(top_k, len(session_scores))\n                    top_k_indices = torch.topk(session_scores, k=k).indices.cpu().tolist()\n                    external_item_ids = [dataset.id2token(dataset.iid_field, idx) for idx in top_k_indices]\n                    results[session_id] = \" \".join(external_item_ids)\n            except Exception as e2:\n                print(f\"❌ Error for session {session_id}: {e2}\")\n                results[session_id] = \"\"\n    \n    return results","metadata":{"trusted":true,"execution":{"execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 5. GENERATE SUBMISSION\n# ===========================\nprint(\"\\n\" + \"=\"*50)\nprint(\"GENERATING SUBMISSION\")\nprint(\"=\"*50)\n\ntest_session_ids = test['session'].unique().to_list()\nprint(f\"Total sessions to predict: {len(test_session_ids)}\")\n\nsubmission_data = []\nbatch_size = 64  # Tăng batch size cho nhanh hơn\n\nprint(f\"Processing in batches of {batch_size}...\")\n\nfor i in tqdm(range(0, len(test_session_ids), batch_size)):\n    batch_sessions = test_session_ids[i:i+batch_size]\n    \n    predictions = get_predictions_batch(batch_sessions, model, dataset, top_k=20)\n    \n    for session_id in batch_sessions:\n        predicted_aids = predictions.get(session_id, \"\")\n        \n        submission_data.append({\n            'session_type': f'{session_id}_clicks', \n            'labels': predicted_aids\n        })\n        submission_data.append({\n            'session_type': f'{session_id}_carts', \n            'labels': predicted_aids\n        })\n        submission_data.append({\n            'session_type': f'{session_id}_orders', \n            'labels': predicted_aids\n        })\n    \n    # Clear cache định kỳ\n    if (i // batch_size) % 10 == 0 and torch.cuda.is_available():\n        torch.cuda.empty_cache()\n        gc.collect()\n\n# Save submission\ndf_submission = pd.DataFrame(submission_data)\ndf_submission.to_csv('submission.csv', index=False)\n\nprint(\"\\n\" + \"=\"*50)\nprint(\"✅ COMPLETED!\")\nprint(\"=\"*50)\nprint(f\"📊 Total rows: {len(df_submission)}\")\nprint(f\"📋 Sample rows:\")\nprint(df_submission.head(9))\nprint(f\"\\n💾 File saved: submission.csv\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-11-30T09:14:02.855Z"}},"outputs":[],"execution_count":null}]}