{"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"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":4120,"databundleVersionId":44256}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport subprocess\n\nrepo_path = '/kaggle/working/CTR'\nif not os.path.exists(repo_path):\n    print(\"Đang tải mã nguồn từ Github...\")\n    subprocess.run([\"git\", \"clone\", \"https://github.com/w4ngg/CTR.git\", repo_path])\n    \nprint(\"✅ Tải mã nguồn thành công! Sẵn sàng.\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport hashlib\n\nshard_dir = '/kaggle/working/shards'\nos.makedirs(shard_dir, exist_ok=True)\n\ncsv_path = '/kaggle/input/competitions/avazu-ctr-prediction/train.gz'\nCHUNK_SIZE = 1_000_000\nVOCAB_SIZE = 500_000   # <--- ĐIỂM VÀNG (Không tràn RAM)\n\ncat_features = ['C1', 'site_id', 'site_domain', 'site_category', \n                'app_id', 'app_domain', 'app_category', \n                'device_id', 'device_ip', 'device_model', 'device_type', \n                'device_conn_type', 'C14', 'C15', 'C16', 'C17', 'C18', 'C19', 'C20', 'C21']\n\n# Chỉ cắt file nếu thư mục chưa có đủ 40 file\nif len(os.listdir(shard_dir)) < 40:\n    print(\"🔪 Đang tiền xử lý (Hashing) và cắt nhỏ data. Bước này chạy khá lâu, vui lòng đợi...\")\n    chunk_iterator = pd.read_csv(csv_path, chunksize=CHUNK_SIZE)\n    for i, chunk in enumerate(chunk_iterator):\n        for col in cat_features:\n            chunk[col] = chunk[col].astype(str).apply(\n                lambda x: int(hashlib.md5(f\"{col}_{x}\".encode('utf-8')).hexdigest(), 16) % VOCAB_SIZE\n            )\n        cols_to_keep = cat_features + ['click']\n        shard_path = os.path.join(shard_dir, f'part_{i:03d}.csv')\n        chunk[cols_to_keep].to_csv(shard_path, index=False)\n        print(f\" Đã xử lý và tạo xong {shard_path}\")\n    print(\"✅ Chuẩn bị dữ liệu hoàn tất!\")\nelse:\n    print(\"✅ Dữ liệu đã được cắt nhỏ từ trước!\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport sys\nimport glob\nimport random\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom sklearn.metrics import roc_auc_score, log_loss\n\nrepo_path = '/kaggle/working/CTR'\nos.chdir(repo_path)\nsys.path.insert(0, repo_path)\nfrom model import NAFI \n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"🚀 Đang chạy trên: {DEVICE}\")\n\nVOCAB_SIZE = 500_000\nBATCH_SIZE = 4096\ncat_features = ['C1', 'site_id', 'site_domain', 'site_category', \n                'app_id', 'app_domain', 'app_category', \n                'device_id', 'device_ip', 'device_model', 'device_type', \n                'device_conn_type', 'C14', 'C15', 'C16', 'C17', 'C18', 'C19', 'C20', 'C21']\n\n# Khởi tạo Mô hình\nfield_dims = [VOCAB_SIZE] * len(cat_features)\nmodel = NAFI(field_dims=field_dims, embed_dim=16).to(DEVICE)\noptimizer = optim.Adam(model.parameters(), lr=0.001)\ncriterion = nn.BCELoss()\n\nall_shards = sorted(glob.glob('/kaggle/working/shards/part_*.csv'))\ntrain_shards = all_shards[:35]\nval_shards = all_shards[35:]\n\nepochs = 3\nbest_logloss = 999.0 # Biến để tìm ra epoch xịn nhất\n\nprint(\"🔥 Bắt đầu Pipeline Huấn luyện (Global Shuffle)...\")\nfor epoch in range(epochs):\n    random.shuffle(train_shards)\n    model.train()\n    total_loss = 0\n    total_batches = 0\n    \n    print(f\"\\n--- Đang chạy Epoch {epoch+1}/{epochs} ---\")\n    for shard_idx, shard_file in enumerate(train_shards):\n        chunk = pd.read_csv(shard_file)\n        X_chunk = torch.tensor(chunk[cat_features].values, dtype=torch.long)\n        y_chunk = torch.tensor(chunk['click'].values, dtype=torch.float32).unsqueeze(1)\n        \n        dataset = torch.utils.data.TensorDataset(X_chunk, y_chunk)\n        loader = torch.utils.data.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)\n        \n        for X_batch, y_batch in loader:\n            X_batch, y_batch = X_batch.to(DEVICE), y_batch.to(DEVICE)\n            optimizer.zero_grad()\n            \n            # ĐÃ FIX LỖI PYTORCH BROADCASTING (Loss kẹt 17)\n            preds = model(X_batch).view(-1)\n            targets = y_batch.view(-1)\n            \n            loss = criterion(preds, targets)\n            loss.backward()\n            optimizer.step()\n            total_loss += loss.item()\n            total_batches += 1\n            \n        # THEO DÕI TIẾN ĐỘ \n        print(f\"   Đã Train xong Shard {shard_idx+1}/{len(train_shards)}\")\n            \n    print(f\"✅ Xong TRAIN Epoch {epoch+1} | Loss: {total_loss/total_batches:.4f}\")\n    \n    # ĐÁNH GIÁ (VALIDATION)\n    model.eval()\n    val_y_true, val_y_pred = [], []\n    with torch.no_grad():\n        for shard_file in val_shards:\n            chunk = pd.read_csv(shard_file)\n            X_chunk = torch.tensor(chunk[cat_features].values, dtype=torch.long)\n            y_chunk = torch.tensor(chunk['click'].values, dtype=torch.float32).unsqueeze(1)\n            \n            dataset = torch.utils.data.TensorDataset(X_chunk, y_chunk)\n            loader = torch.utils.data.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=False)\n            \n            for X_batch, y_batch in loader:\n                X_batch = X_batch.to(DEVICE)\n                preds = model(X_batch).view(-1)\n                val_y_true.extend(y_batch.view(-1).numpy().tolist())\n                val_y_pred.extend(preds.cpu().numpy().tolist())\n                \n    if len(val_y_true) > 0:\n        auc_score = roc_auc_score(val_y_true, val_y_pred)\n        logloss_score = log_loss(val_y_true, val_y_pred)\n        print(f\"🏆 KẾT QUẢ VAL Epoch {epoch+1} | AUC: {auc_score:.4f} | LogLoss: {logloss_score:.4f}\")\n        \n        # 👉 CÔNG NGHỆ LƯU MODEL DÙNG LÂU DÀI\n        if logloss_score < best_logloss:\n            best_logloss = logloss_score\n            # Lưu lại \"não\" của con AI vào file .pth\n            torch.save(model.state_dict(), '/kaggle/working/nafi_best_model.pth')\n            print(\"   💾 Đã lưu lại bộ Trọng số (Weights) tốt nhất!\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# CELL 4: DỰ ĐOÁN TẬP TEST KAGGLE\n# ==========================================\nimport gc \nimport hashlib \nimport pandas as pd\nimport torch\n\ntest_path = '/kaggle/input/competitions/avazu-ctr-prediction/test.gz'\nsubmission_path = '/kaggle/working/submission.csv'\nmodel_weights_path = '/kaggle/working/nafi_best_model.pth'\n\n# 👉 TỰ NẠP LẠI NÃO MODEL TỪ FILE LƯU TRỮ .PTH (Phục vụ cho mấy tháng sau)\nif os.path.exists(model_weights_path):\n    model.load_state_dict(torch.load(model_weights_path))\n    print(f\"🧠 Đã nạp lại bộ não mô hình từ file: {model_weights_path}\")\n\nprint(\"🚀 Đang chạy dự đoán trên tập Test để nộp bài...\")\n\nwith open(submission_path, 'w') as f:\n    f.write('id,click\\n')\n\nmodel.eval()\n\n# Ép kiểu 'id': str chống lỗi mất đuôi\ntest_iterator = pd.read_csv(test_path, chunksize=1_000_000, dtype={'id': str})\n\nwith torch.no_grad(): \n    for batch_idx, chunk in enumerate(test_iterator):\n        \n        # ĐÃ SỬA LỖI ĐỤNG ĐỘ INDEX CỦA PYTORCH \n        for col in cat_features:\n            chunk[col] = chunk[col].astype(str).apply(\n                lambda x: int(hashlib.md5(f\"{col}_{x}\".encode('utf-8')).hexdigest(), 16) % VOCAB_SIZE\n            )\n        \n        X_chunk = torch.tensor(chunk[cat_features].values, dtype=torch.long)\n        \n        dataset = torch.utils.data.TensorDataset(X_chunk)\n        loader = torch.utils.data.DataLoader(dataset, batch_size=4096, shuffle=False)\n        \n        chunk_preds = []\n        for X_batch in loader:\n            X_batch = X_batch[0].to(DEVICE)\n            preds = model(X_batch).view(-1).cpu().numpy().tolist()\n            chunk_preds.extend(preds)\n        \n        sub_chunk = pd.DataFrame({'id': chunk['id'], 'click': chunk_preds})\n        sub_chunk.to_csv(submission_path, mode='a', header=False, index=False)\n        \n        print(f\" Đã dự đoán và lưu xong Test Chunk {batch_idx+1}\")\n        \n        del chunk, X_chunk, dataset, loader, chunk_preds, sub_chunk\n        torch.cuda.empty_cache()\n        gc.collect()\n\nprint(\"=\"*60)\nprint(f\"✅ HOÀN TẤT! File nộp bài đã được tạo: {submission_path}\")\nprint(\"=\"*60)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}