{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":132393,"databundleVersionId":15879982}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-05T04:17:04.615697Z","iopub.execute_input":"2026-03-05T04:17:04.616001Z","iopub.status.idle":"2026-03-05T04:17:05.739216Z","shell.execute_reply.started":"2026-03-05T04:17:04.615970Z","shell.execute_reply":"2026-03-05T04:17:05.738385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport tarfile\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\nfrom PIL import Image\nfrom tqdm import tqdm\n\n# --- 1. Paths & Configurations ---\nINPUT_DIR = \"/kaggle/input/competitions/hkustgz-aiaa-5032-hw2-spring-2026\"\n# (Note: Update INPUT_DIR to your dataset path if it is slightly different in your notebook)\nTRAIN_CSV = f\"{INPUT_DIR}/trainval.csv\"\nTEST_CSV = f\"{INPUT_DIR}/test_for_student.csv\"\nTAR_FILE = f\"{INPUT_DIR}/video_frames_30fpv_320p.tgz\"\nEXTRACT_DIR = \"/kaggle/working/frames\"\n\nEPOCHS = 5\nBATCH_SIZE = 32\nLR = 1e-4\n\n# --- 2. Extract Data ---\n# Kaggle sometimes doesn't auto-extract .tgz files, so we do it manually to the working directory.\nif not os.path.exists(EXTRACT_DIR):\n    print(\"Extracting video frames... This might take a minute.\")\n    os.makedirs(EXTRACT_DIR, exist_ok=True)\n    with tarfile.open(TAR_FILE, 'r:gz') as tar:\n        tar.extractall(path=EXTRACT_DIR)\n    print(\"Extraction complete.\")\n\n# Find the exact subfolder inside the extracted directory\nextracted_folders = os.listdir(EXTRACT_DIR)\n# Depending on how the archive was created, images might be inside a parent folder\nDATA_DIR = os.path.join(EXTRACT_DIR, extracted_folders[0]) if len(extracted_folders) == 1 else EXTRACT_DIR\n\n# --- 3. Custom Dataset ---\nclass VideoFrameDataset(Dataset):\n    def __init__(self, csv_file, img_dir, transform=None, is_test=False):\n        self.df = pd.read_csv(csv_file)\n        self.img_dir = img_dir\n        self.transform = transform\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        video_id = self.df.iloc[idx]['Id']\n        vid_path = os.path.join(self.img_dir, video_id)\n        \n        # Get all frames for the video and pick the middle one (baseline strategy)\n        frames = sorted(os.listdir(vid_path))\n        mid_idx = len(frames) // 2 \n        img_name = frames[mid_idx]\n        \n        img_path = os.path.join(vid_path, img_name)\n        image = Image.open(img_path).convert(\"RGB\")\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        if self.is_test:\n            return image, video_id\n        else:\n            label = self.df.iloc[idx]['Category']\n            return image, label\n\n# --- 4. Transforms ---\ntransform_train = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2), # Basic augmentation\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ntransform_test = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# Loaders\ntrain_dataset = VideoFrameDataset(TRAIN_CSV, DATA_DIR, transform=transform_train)\ntest_dataset = VideoFrameDataset(TEST_CSV, DATA_DIR, transform=transform_test, is_test=True)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\n# --- 5. Model Setup ---\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n# Rule check: ONLY ImageNet-1K pretrained weights are allowed\nmodel = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\nmodel.fc = nn.Linear(model.fc.in_features, 10) # 10 classes (0-9)\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=LR)\n\n# --- 6. Training Loop ---\nprint(\"Starting training...\")\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n    \n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\"):\n        images, labels = images.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n        \n    epoch_acc = 100. * correct / total\n    print(f\"Epoch [{epoch+1}/{EPOCHS}] Loss: {running_loss/len(train_loader):.4f} | Accuracy: {epoch_acc:.2f}%\")\n\n# --- 7. Inference & Submission ---\nprint(\"Generating predictions on the test set...\")\nmodel.eval()\npredictions = []\n\nwith torch.no_grad():\n    for images, video_ids in tqdm(test_loader, desc=\"Testing\"):\n        images = images.to(device)\n        outputs = model(images)\n        _, predicted = outputs.max(1)\n        \n        for vid, pred in zip(video_ids, predicted):\n            predictions.append({\"Id\": vid, \"Category\": pred.item()})\n\n# Save to submission.csv\nsub_df = pd.DataFrame(predictions)\nsub_df.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(\"Saved submission.csv successfully! You can now submit this file.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-05T04:18:36.267386Z","iopub.execute_input":"2026-03-05T04:18:36.268141Z","iopub.status.idle":"2026-03-05T04:22:01.175371Z","shell.execute_reply.started":"2026-03-05T04:18:36.268101Z","shell.execute_reply":"2026-03-05T04:22:01.174377Z"}},"outputs":[],"execution_count":null}]}