{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":92399,"databundleVersionId":11038207,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Import","metadata":{}},{"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\nimport torchvision\nfrom torchvision import transforms\nfrom torchvision.models.video import mvit_v2_s, MViT_V2_S_Weights\n\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import average_precision_score","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:34.959815Z","iopub.execute_input":"2025-03-23T14:50:34.960074Z","iopub.status.idle":"2025-03-23T14:50:44.072621Z","shell.execute_reply.started":"2025-03-23T14:50:34.960051Z","shell.execute_reply":"2025-03-23T14:50:44.071735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.073508Z","iopub.execute_input":"2025-03-23T14:50:44.073866Z","iopub.status.idle":"2025-03-23T14:50:44.154220Z","shell.execute_reply.started":"2025-03-23T14:50:44.073844Z","shell.execute_reply":"2025-03-23T14:50:44.153479Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Configuration and Reproducibility","metadata":{}},{"cell_type":"code","source":"NUM_FRAMES    = 16              # For MVIT_V2_S, use exactly 16 frames\nFRAME_SIZE    = (224, 224)      # Expected resolution: 224x224\nBATCH_SIZE    = 12               # Adjust according to available resources\nNUM_EPOCHS    = 4               # Number of training epochs\nLEARNING_RATE = 1e-4            # Learning rate\nNUM_WORKERS   = 2               # Number of workers for DataLoader\n\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":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.155255Z","iopub.execute_input":"2025-03-23T14:50:44.155613Z","iopub.status.idle":"2025-03-23T14:50:44.289010Z","shell.execute_reply.started":"2025-03-23T14:50:44.155579Z","shell.execute_reply":"2025-03-23T14:50:44.288051Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Custom Dataset Class","metadata":{}},{"cell_type":"code","source":"class NexarDataset(Dataset):\n    \"\"\"\n    Custom dataset for loading videos from a CSV and folder.\n    - Extracts exactly NUM_FRAMES frames uniformly.\n    - Converts frames from BGR to RGB and resizes to FRAME_SIZE.\n    - Applies a transform (if provided) on each frame.\n    - Returns a tensor of shape (C, T, H, W) and the target (for \"train\" mode).\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\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        # Compute 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 from BGR to RGB and resize\n                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n                frame = cv2.resize(frame, FRAME_SIZE)\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((FRAME_SIZE[1], FRAME_SIZE[0], 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)\n        # Apply transformation on each frame\n        if self.transform:\n            # Each frame becomes a tensor of shape (C, H, W)\n            video_tensor = torch.stack([self.transform(frame) for frame in video_array])\n        else:\n            video_tensor = torch.from_numpy(video_array.astype(np.float32) / 255.0)\n            video_tensor = video_tensor.permute(0, 3, 1, 2)\n        # Rearrange dimensions to (C, T, H, W)\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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.290058Z","iopub.execute_input":"2025-03-23T14:50:44.290340Z","iopub.status.idle":"2025-03-23T14:50:44.303585Z","shell.execute_reply.started":"2025-03-23T14:50:44.290319Z","shell.execute_reply":"2025-03-23T14:50:44.302644Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Transformations","metadata":{}},{"cell_type":"code","source":"# Basic transformation: resize and normalize\ntransform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.ToTensor(),  # Convert to [0,1]\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.304541Z","iopub.execute_input":"2025-03-23T14:50:44.304762Z","iopub.status.idle":"2025-03-23T14:50:44.318395Z","shell.execute_reply.started":"2025-03-23T14:50:44.304742Z","shell.execute_reply":"2025-03-23T14:50:44.317582Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train/Validation Split (10% for validation)","metadata":{}},{"cell_type":"code","source":"df_train = pd.read_csv(TRAIN_CSV)\n#df_train = df_train.head(150)\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.05, 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":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.320457Z","iopub.execute_input":"2025-03-23T14:50:44.320684Z","iopub.status.idle":"2025-03-23T14:50:44.383151Z","shell.execute_reply.started":"2025-03-23T14:50:44.320662Z","shell.execute_reply":"2025-03-23T14:50:44.382501Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DataLoaders","metadata":{}},{"cell_type":"code","source":"train_dataset = NexarDataset(csv_path=\"train_split.csv\", video_dir=TRAIN_VIDEO_DIR, num_frames=NUM_FRAMES, transform=transform, mode=\"train\")\nval_dataset   = NexarDataset(csv_path=\"val_split.csv\", video_dir=TRAIN_VIDEO_DIR, num_frames=NUM_FRAMES, transform=transform, mode=\"train\")\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)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.384082Z","iopub.execute_input":"2025-03-23T14:50:44.384384Z","iopub.status.idle":"2025-03-23T14:50:44.392555Z","shell.execute_reply.started":"2025-03-23T14:50:44.384352Z","shell.execute_reply":"2025-03-23T14:50:44.391848Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Initialize MVIT_V2_S Model","metadata":{}},{"cell_type":"code","source":"weights = MViT_V2_S_Weights.KINETICS400_V1\nmodel = mvit_v2_s(weights=weights).to(device)\n# Replace the last layer in the head to output a single value (binary classification)\nin_features = model.head[-1].in_features\nmodel.head[-1] = nn.Linear(in_features, 1)\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":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:44.393382Z","iopub.execute_input":"2025-03-23T14:50:44.393705Z","iopub.status.idle":"2025-03-23T14:50:46.422686Z","shell.execute_reply.started":"2025-03-23T14:50:44.393673Z","shell.execute_reply":"2025-03-23T14:50:46.421771Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training and Evaluation Functions","metadata":{}},{"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 the mean Average Precision (mAP) over multiple thresholds.\n    df must contain columns 'target', 'time_of_event', and 'time_of_alert'.\n    \"\"\"\n    from sklearn.metrics import average_precision_score\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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:46.423501Z","iopub.execute_input":"2025-03-23T14:50:46.423731Z","iopub.status.idle":"2025-03-23T14:50:46.434977Z","shell.execute_reply.started":"2025-03-23T14:50:46.423711Z","shell.execute_reply":"2025-03-23T14:50:46.434246Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Loop with Validation per Epoch","metadata":{}},{"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 the 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":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:50:46.436067Z","iopub.execute_input":"2025-03-23T14:50:46.436436Z","iopub.status.idle":"2025-03-23T14:53:25.452013Z","shell.execute_reply.started":"2025-03-23T14:50:46.436406Z","shell.execute_reply":"2025-03-23T14:53:25.450342Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualization of Results","metadata":{}},{"cell_type":"code","source":"epochs_range = range(1, NUM_EPOCHS+1)\n\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(epochs_range, train_losses, label=\"Train Loss\")\nplt.plot(epochs_range, val_losses, label=\"Validation 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=\"Validation mAP\", color=\"green\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"mAP\")\nplt.title(\"Validation mAP\")\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:53:25.452897Z","iopub.status.idle":"2025-03-23T14:53:25.453339Z","shell.execute_reply":"2025-03-23T14:53:25.453128Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference on Test Set and Submission Generation","metadata":{}},{"cell_type":"code","source":"test_dataset = NexarDataset(csv_path=TEST_CSV, video_dir=TEST_VIDEO_DIR, num_frames=NUM_FRAMES, transform=transform, 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":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:53:25.454551Z","iopub.status.idle":"2025-03-23T14:53:25.454984Z","shell.execute_reply":"2025-03-23T14:53:25.454791Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-23T14:53:25.456035Z","iopub.status.idle":"2025-03-23T14:53:25.456513Z","shell.execute_reply":"2025-03-23T14:53:25.456304Z"}},"outputs":[],"execution_count":null}]}