{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.12"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":92399,"databundleVersionId":11038207,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":16319.002053,"end_time":"2025-03-14T21:48:47.719954","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-03-14T17:16:48.717901","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Import","metadata":{"papermill":{"duration":0.004492,"end_time":"2025-03-14T17:16:51.458667","exception":false,"start_time":"2025-03-14T17:16:51.454175","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport random\nimport time\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom torchvision import transforms\nfrom torchvision.io import read_video\nfrom torchvision.models.video import swin3d_b, Swin3D_B_Weights\n\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import average_precision_score","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-03-24T20:24:50.475546Z","iopub.execute_input":"2025-03-24T20:24:50.475998Z","iopub.status.idle":"2025-03-24T20:24:50.482956Z","shell.execute_reply.started":"2025-03-24T20:24:50.475962Z","shell.execute_reply":"2025-03-24T20:24:50.482002Z"},"papermill":{"duration":9.618747,"end_time":"2025-03-14T17:17:01.081314","exception":false,"start_time":"2025-03-14T17:16:51.462567","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.484166Z","iopub.execute_input":"2025-03-24T20:24:50.484475Z","iopub.status.idle":"2025-03-24T20:24:50.497622Z","shell.execute_reply.started":"2025-03-24T20:24:50.484445Z","shell.execute_reply":"2025-03-24T20:24:50.496781Z"},"papermill":{"duration":0.072581,"end_time":"2025-03-14T17:17:01.158257","exception":false,"start_time":"2025-03-14T17:17:01.085676","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Configuration and Reproducibility","metadata":{"papermill":{"duration":0.003645,"end_time":"2025-03-14T17:17:01.166026","exception":false,"start_time":"2025-03-14T17:17:01.162381","status":"completed"},"tags":[]}},{"cell_type":"code","source":"NUM_FRAMES    = 32              # For Swin3D_B, use 32 frames per clip (clip_len=32)\nBATCH_SIZE    = 4               # Adjust according to your resources\nNUM_EPOCHS    = 5               # Number of training epochs\nLEARNING_RATE = 5e-5 \n\n# Learning rate\nNUM_WORKERS   = 2               # Number of workers for the DataLoader\n\n# Paths to data\nTRAIN_CSV       = \"/kaggle/input/nexar-collision-prediction/train.csv\"\nTEST_CSV        = \"/kaggle/input/nexar-collision-prediction/test.csv\"\nTRAIN_VIDEO_DIR = \"/kaggle/input/nexar-collision-prediction/train/\"\nTEST_VIDEO_DIR  = \"/kaggle/input/nexar-collision-prediction/test/\"","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.499274Z","iopub.execute_input":"2025-03-24T20:24:50.499571Z","iopub.status.idle":"2025-03-24T20:24:50.510094Z","shell.execute_reply.started":"2025-03-24T20:24:50.499544Z","shell.execute_reply":"2025-03-24T20:24:50.509279Z"},"papermill":{"duration":0.009845,"end_time":"2025-03-14T17:17:01.179605","exception":false,"start_time":"2025-03-14T17:17:01.169760","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Custom Dataset Class","metadata":{"papermill":{"duration":0.003558,"end_time":"2025-03-14T17:17:01.187125","exception":false,"start_time":"2025-03-14T17:17:01.183567","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class NexarDataset(Dataset):\n    \"\"\"\n    Custom dataset to load videos from a CSV and directory.\n    - Extracts exactly NUM_FRAMES uniformly.\n    - Uses OpenCV to load videos, converts frames from BGR to RGB, and resizes to a target FRAME_SIZE.\n    - Converts the video to a tensor of shape (T, C, H, W) then applies the transform.\n    - Returns a tensor of shape (C, T, H, W) and the target (for training) or video ID (for testing).\n    \"\"\"\n    def __init__(self, csv_path, video_dir, num_frames=NUM_FRAMES, transform=None, mode=\"train\"):\n        self.df = pd.read_csv(csv_path).reset_index(drop=True)\n        self.video_dir = video_dir\n        self.num_frames = num_frames\n        self.transform = transform  # For Swin3D, this should be the pretrained inference transform.\n        self.mode = mode\n\n    def __len__(self):\n        return len(self.df)\n\n    def _load_video(self, video_id):\n        video_path = os.path.join(self.video_dir, video_id + \".mp4\")\n        cap = cv2.VideoCapture(video_path)\n        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n        # Get uniform indices for NUM_FRAMES frames\n        indices = np.linspace(0, total_frames - 1, self.num_frames, dtype=int)\n        collected_frames = {}\n        frame_id = 0\n        ret = True\n        while ret:\n            ret, frame = cap.read()\n            if not ret:\n                break\n            if frame_id in indices:\n                # Convert BGR to RGB and resize to target dimensions (224x224)\n                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n                frame = cv2.resize(frame, (224, 224))\n                collected_frames[frame_id] = frame\n            frame_id += 1\n        cap.release()\n        # For any missing frames, repeat the last available frame\n        video_frames = [collected_frames[idx] if idx in collected_frames \n                        else (video_frames[-1] if len(video_frames) > 0 \n                              else np.zeros((224, 224, 3), dtype=np.uint8))\n                        for idx in indices]\n        video_array = np.stack(video_frames)  # Shape: (T, H, W, C)\n        return video_array\n\n    def __getitem__(self, idx):\n        video_id = str(self.df.loc[idx, \"id\"]).zfill(5)\n        video_array = self._load_video(video_id)  # (T, H, W, C)\n        # Convert the video array to a tensor: shape (T, H, W, C)\n        video_tensor = torch.from_numpy(video_array.astype(np.float32))\n        # Rearrange to (T, C, H, W)\n        video_tensor = video_tensor.permute(0, 3, 1, 2)\n        # Apply the transform, which expects input shape (T, C, H, W) and outputs (C, T, H, W)\n        if self.transform:\n            video_tensor = self.transform(video_tensor)\n        else:\n            video_tensor = video_tensor / 255.0\n            video_tensor = video_tensor.permute(1, 0, 2, 3)\n        if self.mode == \"train\":\n            target = torch.tensor(float(self.df.loc[idx, \"target\"]), dtype=torch.float32)\n            return video_tensor, target\n        else:\n            return video_tensor, video_id","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.511183Z","iopub.execute_input":"2025-03-24T20:24:50.511479Z","iopub.status.idle":"2025-03-24T20:24:50.526044Z","shell.execute_reply.started":"2025-03-24T20:24:50.511453Z","shell.execute_reply":"2025-03-24T20:24:50.525219Z"},"papermill":{"duration":0.015293,"end_time":"2025-03-14T17:17:01.206170","exception":false,"start_time":"2025-03-14T17:17:01.190877","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Pretrained Transform for Swin3D_B","metadata":{"papermill":{"duration":0.00347,"end_time":"2025-03-14T17:17:01.213442","exception":false,"start_time":"2025-03-14T17:17:01.209972","status":"completed"},"tags":[]}},{"cell_type":"code","source":"weights = Swin3D_B_Weights.KINETICS400_IMAGENET22K_V1\npreprocess = weights.transforms()  # This transform resizes to 256, center crops to 224, normalizes, and permutes to (C, T, H, W)","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.526967Z","iopub.execute_input":"2025-03-24T20:24:50.527222Z","iopub.status.idle":"2025-03-24T20:24:50.542235Z","shell.execute_reply.started":"2025-03-24T20:24:50.527204Z","shell.execute_reply":"2025-03-24T20:24:50.541469Z"},"papermill":{"duration":0.009809,"end_time":"2025-03-14T17:17:01.226998","exception":false,"start_time":"2025-03-14T17:17:01.217189","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train/Validation Split (10% for validation)","metadata":{"papermill":{"duration":0.003533,"end_time":"2025-03-14T17:17:01.234611","exception":false,"start_time":"2025-03-14T17:17:01.231078","status":"completed"},"tags":[]}},{"cell_type":"code","source":"df_train = pd.read_csv(TRAIN_CSV)\ndf_train['time_of_event'] = pd.to_numeric(df_train['time_of_event'], errors='coerce')\ndf_train['time_of_alert']  = pd.to_numeric(df_train['time_of_alert'], errors='coerce')\n\ntrain_df, val_df = train_test_split(\n    df_train, test_size=0.07, stratify=df_train['target']\n)\nprint(f\"Number of training samples: {len(train_df)}\")\nprint(f\"Number of validation samples: {len(val_df)}\")\n\n# Save splits as temporary CSV files for dataset loading\ntrain_df.to_csv(\"train_split.csv\", index=False)\nval_df.to_csv(\"val_split.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.543049Z","iopub.execute_input":"2025-03-24T20:24:50.543318Z","iopub.status.idle":"2025-03-24T20:24:50.594148Z","shell.execute_reply.started":"2025-03-24T20:24:50.543293Z","shell.execute_reply":"2025-03-24T20:24:50.593352Z"},"papermill":{"duration":0.042984,"end_time":"2025-03-14T17:17:01.281428","exception":false,"start_time":"2025-03-14T17:17:01.238444","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DataLoaders","metadata":{"papermill":{"duration":0.003689,"end_time":"2025-03-14T17:17:01.289146","exception":false,"start_time":"2025-03-14T17:17:01.285457","status":"completed"},"tags":[]}},{"cell_type":"code","source":"train_dataset = NexarDataset(csv_path=\"train_split.csv\", video_dir=TRAIN_VIDEO_DIR, num_frames=NUM_FRAMES, transform=preprocess, mode=\"train\")\nval_dataset   = NexarDataset(csv_path=\"val_split.csv\", video_dir=TRAIN_VIDEO_DIR, num_frames=NUM_FRAMES, transform=preprocess, mode=\"train\")\ntest_dataset  = NexarDataset(csv_path=TEST_CSV, video_dir=TEST_VIDEO_DIR, num_frames=NUM_FRAMES, transform=preprocess, mode=\"test\")\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS, pin_memory=True)\nval_loader   = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\ntest_loader  = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.595448Z","iopub.execute_input":"2025-03-24T20:24:50.595664Z","iopub.status.idle":"2025-03-24T20:24:50.610174Z","shell.execute_reply.started":"2025-03-24T20:24:50.595645Z","shell.execute_reply":"2025-03-24T20:24:50.609318Z"},"papermill":{"duration":0.01327,"end_time":"2025-03-14T17:17:01.306199","exception":false,"start_time":"2025-03-14T17:17:01.292929","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Initialize MVIT_V2_S Model","metadata":{"papermill":{"duration":0.003562,"end_time":"2025-03-14T17:17:01.313548","exception":false,"start_time":"2025-03-14T17:17:01.309986","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model = swin3d_b(weights=weights).to(device)\n# Replace the final head layer to output a single value for binary classification.\n# For swin3d_b, model.head is typically a Linear layer.\nin_features = model.head.in_features\nmodel.head = nn.Linear(in_features, 1)\n# Use DataParallel to utilize both GPUs (if available)\nif torch.cuda.device_count() > 1:\n    print(\"Using\", torch.cuda.device_count(), \"GPUs!\")\n    model = nn.DataParallel(model)\nmodel = model.to(device)\nprint(model)","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:24:50.611709Z","iopub.execute_input":"2025-03-24T20:24:50.612037Z","iopub.status.idle":"2025-03-24T20:25:01.701901Z","shell.execute_reply.started":"2025-03-24T20:24:50.612008Z","shell.execute_reply":"2025-03-24T20:25:01.701004Z"},"papermill":{"duration":1.93461,"end_time":"2025-03-14T17:17:03.251982","exception":false,"start_time":"2025-03-14T17:17:01.317372","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training and Evaluation Functions","metadata":{"papermill":{"duration":0.004656,"end_time":"2025-03-14T17:17:03.262324","exception":false,"start_time":"2025-03-14T17:17:03.257668","status":"completed"},"tags":[]}},{"cell_type":"code","source":"criterion = nn.BCEWithLogitsLoss()\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\ndef train_one_epoch(model, dataloader, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    start_time = time.time()\n    for inputs, targets in tqdm(dataloader, desc=\"Training\", leave=False):\n        inputs = inputs.to(device)   # Expected shape: (B, C, T, H, W)\n        targets = targets.to(device).unsqueeze(1)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item() * inputs.size(0)\n    epoch_loss = running_loss / len(dataloader.dataset)\n    epoch_time = time.time() - start_time\n    return epoch_loss, epoch_time\n\ndef evaluate(model, dataloader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    all_preds = []\n    all_targets = []\n    with torch.no_grad():\n        for inputs, targets in tqdm(dataloader, desc=\"Validation\", leave=False):\n            inputs = inputs.to(device)\n            targets = targets.to(device).unsqueeze(1)\n            outputs = model(inputs)\n            loss = criterion(outputs, targets)\n            running_loss += loss.item() * inputs.size(0)\n            preds = torch.sigmoid(outputs).squeeze(1).cpu().numpy()\n            all_preds.extend(preds.tolist())\n            all_targets.extend(targets.cpu().numpy().flatten().tolist())\n    val_loss = running_loss / len(dataloader.dataset)\n    return val_loss, np.array(all_preds), np.array(all_targets)\n\ndef compute_map(df, predictions, thresholds=[0.5, 1.0, 1.5]):\n    \"\"\"\n    Computes mean Average Precision (mAP) over multiple thresholds.\n    df must contain columns 'target', 'time_of_event', and 'time_of_alert'.\n    \"\"\"\n    APs = []\n    for thr in thresholds:\n        valid_idx = df.index[(df['target'] == 0) | ((df['target'] == 1) & ((df['time_of_event'] - df['time_of_alert']) >= thr))]\n        if len(valid_idx) == 0:\n            APs.append(0)\n            continue\n        y_true = df.loc[valid_idx, 'target'].values\n        y_pred = predictions[valid_idx]\n        ap = average_precision_score(y_true, y_pred)\n        APs.append(ap)\n    mean_AP = np.mean(APs)\n    return mean_AP, APs","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:25:01.702834Z","iopub.execute_input":"2025-03-24T20:25:01.703153Z","iopub.status.idle":"2025-03-24T20:25:01.713239Z","shell.execute_reply.started":"2025-03-24T20:25:01.703124Z","shell.execute_reply":"2025-03-24T20:25:01.712387Z"},"papermill":{"duration":0.018398,"end_time":"2025-03-14T17:17:03.285573","exception":false,"start_time":"2025-03-14T17:17:03.267175","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Loop with Validation per Epoch","metadata":{"papermill":{"duration":0.004619,"end_time":"2025-03-14T17:17:03.295296","exception":false,"start_time":"2025-03-14T17:17:03.290677","status":"completed"},"tags":[]}},{"cell_type":"code","source":"train_losses = []\nval_losses = []\nval_mAPs = []\n\nfor epoch in range(NUM_EPOCHS):\n    train_loss, train_time = train_one_epoch(model, train_loader, criterion, optimizer, device)\n    print(f\"Epoch {epoch+1}/{NUM_EPOCHS} - Train Loss: {train_loss:.4f} - Time: {train_time:.2f}s\")\n    \n    val_loss, val_preds, val_targets = evaluate(model, val_loader, criterion, device)\n    \n    # Load validation CSV for mAP computation\n    val_df_eval = pd.read_csv(\"val_split.csv\")\n    val_df_eval['time_of_event'] = pd.to_numeric(val_df_eval['time_of_event'], errors='coerce')\n    val_df_eval['time_of_alert']  = pd.to_numeric(val_df_eval['time_of_alert'], errors='coerce')\n    \n    mean_AP, APs = compute_map(val_df_eval, val_preds, thresholds=[0.5, 1.0, 1.5])\n    print(f\"  Validation Loss: {val_loss:.4f} - mAP: {mean_AP:.4f} | AP per threshold: {APs}\")\n    \n    train_losses.append(train_loss)\n    val_losses.append(val_loss)\n    val_mAPs.append(mean_AP)\n","metadata":{"execution":{"iopub.status.busy":"2025-03-24T20:25:01.714204Z","iopub.execute_input":"2025-03-24T20:25:01.714495Z","execution_failed":"2025-03-24T20:26:11.708Z"},"papermill":{"duration":15374.775043,"end_time":"2025-03-14T21:33:18.075088","exception":false,"start_time":"2025-03-14T17:17:03.300045","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualization of Results","metadata":{"papermill":{"duration":0.075648,"end_time":"2025-03-14T21:33:18.229092","exception":false,"start_time":"2025-03-14T21:33:18.153444","status":"completed"},"tags":[]}},{"cell_type":"code","source":"epochs_range = range(1, NUM_EPOCHS+1)\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(epochs_range, train_losses, label=\"Train Loss\")\nplt.plot(epochs_range, val_losses, label=\"Val Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training and Validation Loss\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(epochs_range, val_mAPs, label=\"Val mAP\", color=\"green\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"mAP\")\nplt.title(\"Validation mAP\")\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"execution_failed":"2025-03-24T20:26:11.708Z"},"papermill":{"duration":0.669263,"end_time":"2025-03-14T21:33:18.973390","exception":false,"start_time":"2025-03-14T21:33:18.304127","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference on Test Set and Submission Generation","metadata":{"papermill":{"duration":0.072006,"end_time":"2025-03-14T21:33:19.118552","exception":false,"start_time":"2025-03-14T21:33:19.046546","status":"completed"},"tags":[]}},{"cell_type":"code","source":"test_dataset = NexarDataset(csv_path=TEST_CSV, video_dir=TEST_VIDEO_DIR, num_frames=NUM_FRAMES, transform=preprocess, mode=\"test\")\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\n\nmodel.eval()\ntest_predictions = []\ntest_video_ids = []\n\nwith torch.no_grad():\n    for inputs, vids in tqdm(test_loader, desc=\"Test Inference\", leave=False):\n        inputs = inputs.to(device)\n        outputs = model(inputs)\n        probs = torch.sigmoid(outputs).squeeze(1).cpu().numpy()\n        test_predictions.extend(probs.tolist())\n        test_video_ids.extend(vids)","metadata":{"execution":{"execution_failed":"2025-03-24T20:26:11.708Z"},"papermill":{"duration":924.736275,"end_time":"2025-03-14T21:48:43.927155","exception":false,"start_time":"2025-03-14T21:33:19.190880","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df = pd.DataFrame({\"id\": test_video_ids, \"score\": test_predictions}).sort_values(\"id\")\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"Submission file generated: submission.csv\")","metadata":{"execution":{"execution_failed":"2025-03-24T20:26:11.708Z"},"papermill":{"duration":0.134365,"end_time":"2025-03-14T21:48:44.168748","exception":false,"start_time":"2025-03-14T21:48:44.034383","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}