{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":5048,"databundleVersionId":868335,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import train_test_split\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport matplotlib.pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:24.503246Z","iopub.execute_input":"2025-10-26T13:10:24.503497Z","iopub.status.idle":"2025-10-26T13:10:32.920486Z","shell.execute_reply.started":"2025-10-26T13:10:24.503477Z","shell.execute_reply":"2025-10-26T13:10:32.919708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"KAGGLE_BASE_DIR = '/kaggle/input/state-farm-distracted-driver-detection'\nDATA_DIR = os.path.join(KAGGLE_BASE_DIR, 'imgs')\nCSV_PATH = os.path.join(KAGGLE_BASE_DIR, 'driver_imgs_list.csv')\nIMAGE_HEIGHT = 224\nIMAGE_WIDTH = 224\nBATCH_SIZE = 32\nRANDOM_SEED = 42 \n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nNUM_WORKERS = 2 \n\nprint(f\"Using device: {DEVICE}\")\nprint(f\"Data directory: {DATA_DIR}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:32.921926Z","iopub.execute_input":"2025-10-26T13:10:32.922323Z","iopub.status.idle":"2025-10-26T13:10:33.007194Z","shell.execute_reply.started":"2025-10-26T13:10:32.922303Z","shell.execute_reply":"2025-10-26T13:10:33.006513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(CSV_PATH)\n\nunique_drivers = df['subject'].unique()\nprint(f\"Total number of unique drivers: {len(unique_drivers)}\")\n\n# Random shuffle of drivers \nnp.random.seed(RANDOM_SEED)\nnp.random.shuffle(unique_drivers)\n\ntrain_drivers, temp_drivers = train_test_split(unique_drivers, test_size=0.2, random_state=RANDOM_SEED)\nval_drivers, test_drivers = train_test_split(temp_drivers, test_size=0.5, random_state=RANDOM_SEED)\n\nprint(f\"\\nTraining drivers ({len(train_drivers)}): {train_drivers}\")\nprint(f\"Validation drivers ({len(val_drivers)}): {val_drivers}\")\nprint(f\"Test drivers ({len(test_drivers)}): {test_drivers}\")\n\ntrain_df = df[df['subject'].isin(train_drivers)].copy()\nval_df = df[df['subject'].isin(val_drivers)].copy()\ntest_df = df[df['subject'].isin(test_drivers)].copy()\n\nprint(\"\\nDataset Split Summary : \")\nprint(f\"Training set: {len(train_df)} images\")\nprint(f\"Validation set: {len(val_df)} images\")\nprint(f\"Test set: {len(test_df)} images\")\nprint(f\"Total images accounted for: {len(train_df) + len(val_df) + len(test_df)}\")\n\nassert len(set(train_df['subject']) & set(val_df['subject'])) == 0\nprint(\"\\nNo overlap in drivers between sets.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:33.007960Z","iopub.execute_input":"2025-10-26T13:10:33.008239Z","iopub.status.idle":"2025-10-26T13:10:33.078953Z","shell.execute_reply.started":"2025-10-26T13:10:33.008214Z","shell.execute_reply":"2025-10-26T13:10:33.077946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# transformations for training with data augmentation\ntrain_transforms = transforms.Compose([\n    transforms.Resize((IMAGE_HEIGHT, IMAGE_WIDTH)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(15),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# transformations for validation and test set without augmentation\nval_test_transforms = transforms.Compose([\n    transforms.Resize((IMAGE_HEIGHT, IMAGE_WIDTH)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nprint(\"Transformation pipelines defined.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:33.079912Z","iopub.execute_input":"2025-10-26T13:10:33.080296Z","iopub.status.idle":"2025-10-26T13:10:33.087254Z","shell.execute_reply.started":"2025-10-26T13:10:33.080270Z","shell.execute_reply":"2025-10-26T13:10:33.086584Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DriverDataset(Dataset):\n\n    def __init__(self, df, data_dir, transform=None):\n        self.df = df\n        self.data_dir = data_dir\n        self.transform = transform\n        self.class_to_idx = {classname: i for i, classname in enumerate(sorted(self.df['classname'].unique()))}\n        self.idx_to_class = {i: classname for classname, i in self.class_to_idx.items()}\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # Get the row from the dataframe\n        row = self.df.iloc[idx]\n        \n        img_path = os.path.join(self.data_dir, 'train', row['classname'], row['img'])\n        \n        image = Image.open(img_path).convert(\"RGB\")\n        \n        label = self.class_to_idx[row['classname']]\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        return image, label\n\nprint(\"DriverDataset class defined.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:33.089311Z","iopub.execute_input":"2025-10-26T13:10:33.089528Z","iopub.status.idle":"2025-10-26T13:10:33.104881Z","shell.execute_reply.started":"2025-10-26T13:10:33.089512Z","shell.execute_reply":"2025-10-26T13:10:33.104066Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = DriverDataset(df=train_df, data_dir=DATA_DIR, transform=train_transforms)\nval_dataset = DriverDataset(df=val_df, data_dir=DATA_DIR, transform=val_test_transforms)\ntest_dataset = DriverDataset(df=test_df, data_dir=DATA_DIR, transform=val_test_transforms)\n\ntrain_loader = DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS, pin_memory=True)\nval_loader = DataLoader(dataset=val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\ntest_loader = DataLoader(dataset=test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\n\nprint(f\"DataLoaders created successfully.\")\nprint(f\"Number of batches in train_loader: {len(train_loader)}\")\nprint(f\"Number of batches in val_loader: {len(val_loader)}\")\nprint(f\"Number of batches in test_loader: {len(test_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:33.105690Z","iopub.execute_input":"2025-10-26T13:10:33.106033Z","iopub.status.idle":"2025-10-26T13:10:33.119013Z","shell.execute_reply.started":"2025-10-26T13:10:33.106008Z","shell.execute_reply":"2025-10-26T13:10:33.118287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, labels = next(iter(train_loader))\n\nprint(f\"Shape of a batch of images: {images.shape}\")\nprint(f\"Shape of a batch of labels: {labels.shape}\")\n\nimport torchvision\n\ndef imshow(img, title):\n    img = img.numpy().transpose((1, 2, 0))\n    mean = np.array([0.485, 0.456, 0.406])\n    std = np.array([0.229, 0.224, 0.225])\n    img = std * img + mean\n    img = np.clip(img, 0, 1)\n    plt.imshow(img)\n    plt.title(title)\n    plt.axis('off')\n\nclass_names = train_dataset.idx_to_class\nlabel_names = [class_names[l.item()] for l in labels]\n\nimg_grid = torchvision.utils.make_grid(images[:8], nrow=4)\nplt.figure(figsize=(12, 6))\nimshow(img_grid, title=f\"Sample Augmented Images\\nLabels: {label_names[:8]}\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:33.119736Z","iopub.execute_input":"2025-10-26T13:10:33.119974Z","iopub.status.idle":"2025-10-26T13:10:35.227742Z","shell.execute_reply.started":"2025-10-26T13:10:33.119956Z","shell.execute_reply":"2025-10-26T13:10:35.226648Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision.models as models\nimport time","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:35.229098Z","iopub.execute_input":"2025-10-26T13:10:35.229368Z","iopub.status.idle":"2025-10-26T13:10:35.233912Z","shell.execute_reply.started":"2025-10-26T13:10:35.229345Z","shell.execute_reply":"2025-10-26T13:10:35.233049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResidualBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, stride=1):\n        super(ResidualBlock, self).__init__()\n        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(out_channels)\n            )\n\n    def forward(self, x):\n        identity = self.shortcut(x)\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out += identity\n        out = self.relu(out)\n        return out\n\nclass SEBlock(nn.Module):\n    def __init__(self, channel, reduction=16):\n        super(SEBlock, self).__init__()\n        self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.fc = nn.Sequential(\n            nn.Linear(channel, channel // reduction, bias=False),\n            nn.ReLU(inplace=True),\n            nn.Linear(channel // reduction, channel, bias=False),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        b, c, _, _ = x.size()\n        y = self.avg_pool(x).view(b, c)\n        y = self.fc(y).view(b, c, 1, 1)\n        return x * y.expand_as(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:35.234771Z","iopub.execute_input":"2025-10-26T13:10:35.235079Z","iopub.status.idle":"2025-10-26T13:10:35.285317Z","shell.execute_reply.started":"2025-10-26T13:10:35.235059Z","shell.execute_reply":"2025-10-26T13:10:35.284513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BaselineCNN(nn.Module):\n    def __init__(self, num_classes=10):\n        super(BaselineCNN, self).__init__()\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(32),\n            nn.Conv2d(32, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(32),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(64),\n            nn.Conv2d(64, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(64),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n        )\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Dropout(0.5),\n            nn.Linear(64 * 56 * 56, 512), nn.ReLU(inplace=True),\n            nn.Linear(512, num_classes),\n        )\n    def forward(self, x):\n        x = self.features(x)\n        x = self.classifier(x)\n        return x\n\nclass ResNet(nn.Module):\n    def __init__(self, block, num_classes=10):\n        super(ResNet, self).__init__()\n        self.in_channels = 64\n        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        self.relu = nn.ReLU(inplace=True)\n        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n        \n        self.layer1 = self._make_layer(block, 64, 2, stride=1)\n        self.layer2 = self._make_layer(block, 128, 2, stride=2)\n        self.layer3 = self._make_layer(block, 256, 2, stride=2)\n        self.layer4 = self._make_layer(block, 512, 2, stride=2)\n        \n        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n        self.fc = nn.Linear(512, num_classes)\n\n    def _make_layer(self, block, out_channels, num_blocks, stride):\n        strides = [stride] + [1]*(num_blocks-1)\n        layers = []\n        for stride in strides:\n            layers.append(block(self.in_channels, out_channels, stride))\n            self.in_channels = out_channels\n        return nn.Sequential(*layers)\n\n    def forward(self, x):\n        x = self.relu(self.bn1(self.conv1(x)))\n        x = self.maxpool(x)\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.fc(x)\n        return x\n\nclass SEResidualBlock(ResidualBlock):\n    def __init__(self, in_channels, out_channels, stride=1):\n        super().__init__(in_channels, out_channels, stride)\n        self.se = SEBlock(out_channels)\n    \n    def forward(self, x):\n        identity = self.shortcut(x)\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = self.se(out)  # Apply SE block\n        out += identity\n        out = self.relu(out)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:35.286195Z","iopub.execute_input":"2025-10-26T13:10:35.286509Z","iopub.status.idle":"2025-10-26T13:10:35.312544Z","shell.execute_reply.started":"2025-10-26T13:10:35.286485Z","shell.execute_reply":"2025-10-26T13:10:35.311786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_finetuned_mobilenet(num_classes=10):\n    model = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.DEFAULT)\n    for param in model.parameters():\n        param.requires_grad = False\n    num_features = model.classifier[1].in_features\n    model.classifier[1] = nn.Linear(num_features, num_classes)\n    return model\n\ndef get_finetuned_efficientnet(num_classes=10):\n    model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)\n    for param in model.parameters():\n        param.requires_grad = False\n    num_features = model.classifier[1].in_features\n    model.classifier[1] = nn.Linear(num_features, num_classes)\n    return model\n\ndef get_finetuned_vit(num_classes=10):\n    model = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\n    for param in model.parameters():\n        param.requires_grad = False\n    num_features = model.heads.head.in_features\n    model.heads.head = nn.Linear(num_features, num_classes)\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:35.313412Z","iopub.execute_input":"2025-10-26T13:10:35.313712Z","iopub.status.idle":"2025-10-26T13:10:35.329165Z","shell.execute_reply.started":"2025-10-26T13:10:35.313686Z","shell.execute_reply":"2025-10-26T13:10:35.328491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport time\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\n\n\nEPOCHS_HEAD_ONLY = 5       \nEPOCHS_FULL_FINETUNE = 10  \nLR_HEAD = 1e-3\nLR_FULL = 1e-5             \nSAVE_MODELS_PATH = './saved_models/'\nLOGS_PATH = './training_logs/'\n\n\nos.makedirs(SAVE_MODELS_PATH, exist_ok=True)\nos.makedirs(LOGS_PATH, exist_ok=True)\n\n\nfinetuned_models = {\n    \"FineTuned_MobileNetV2\": get_finetuned_mobilenet(num_classes=10),\n    \"FineTuned_EfficientNet\": get_finetuned_efficientnet(num_classes=10),\n    \"FineTuned_ViT\": get_finetuned_vit(num_classes=10)\n}\n\nprint(f\"Ready to fine-tune {len(finetuned_models)} models.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:35.329989Z","iopub.execute_input":"2025-10-26T13:10:35.330242Z","iopub.status.idle":"2025-10-26T13:10:39.184808Z","shell.execute_reply.started":"2025-10-26T13:10:35.330220Z","shell.execute_reply":"2025-10-26T13:10:39.184053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for model_name, model in finetuned_models.items():\n    print(f\"\\n {model_name}\")\n    model.to(DEVICE)\n    criterion = nn.CrossEntropyLoss()\n    history = {'train_loss': [], 'val_loss': [], 'train_acc': [], 'val_acc': []}\n    best_val_acc = 0.0\n\n    print(\"\\nTraining the classifier head \")\n    optimizer_head = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=LR_HEAD)\n    for epoch in range(EPOCHS_HEAD_ONLY):\n        model.train()\n        for images, labels in train_loader:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            optimizer_head.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer_head.step()\n        print(f\"Head Training Epoch {epoch+1}/{EPOCHS_HEAD_ONLY} complete.\")\n        \n\n    print(\"\\nUnfreezing and finetuning all layers \")\n    for param in model.parameters():\n        param.requires_grad = True\n    optimizer_full = optim.Adam(model.parameters(), lr=LR_FULL)\n\n    for epoch in range(EPOCHS_FULL_FINETUNE):\n        start_time = time.time()\n        model.train()\n        running_loss, running_corrects = 0.0, 0\n        for images, labels in train_loader:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            optimizer_full.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer_full.step()\n            running_loss += loss.item() * images.size(0)\n            _, preds = torch.max(outputs, 1)\n            running_corrects += torch.sum(preds == labels.data)\n        \n        epoch_train_loss = running_loss / len(train_loader.dataset)\n        epoch_train_acc = running_corrects.double() / len(train_loader.dataset)\n        \n        model.eval()\n        running_loss, running_corrects = 0.0, 0\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(DEVICE), labels.to(DEVICE)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n                running_loss += loss.item() * images.size(0)\n                _, preds = torch.max(outputs, 1)\n                running_corrects += torch.sum(preds == labels.data)\n\n        epoch_val_loss = running_loss / len(val_loader.dataset)\n        epoch_val_acc = running_corrects.double() / len(val_loader.dataset)\n\n        history['train_loss'].append(epoch_train_loss)\n        history['train_acc'].append(epoch_train_acc.item())\n        history['val_loss'].append(epoch_val_loss)\n        history['val_acc'].append(epoch_val_acc.item())\n        \n        if epoch_val_acc > best_val_acc:\n            best_val_acc = epoch_val_acc\n            torch.save(model.state_dict(), os.path.join(SAVE_MODELS_PATH, f'best_{model_name}.pth'))\n            \n        end_time = time.time()\n        print(f\"Fine-Tuning Epoch {epoch+1}/{EPOCHS_FULL_FINETUNE} | Train Loss: {epoch_train_loss:.4f} | Val Acc: {epoch_val_acc:.4f} | Time: {end_time-start_time:.2f}s\")\n    \n    history_df = pd.DataFrame(history)\n    history_df.to_csv(os.path.join(LOGS_PATH, f'{model_name}_history.csv'), index=False)\n    print(f\"Fine-tuning finished for {model_name}. Best Val Acc: {best_val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T13:10:39.185554Z","iopub.execute_input":"2025-10-26T13:10:39.185855Z"}},"outputs":[],"execution_count":null}]}