{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":106372,"databundleVersionId":12864984,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"print(\"hello world\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:39.383531Z","iopub.execute_input":"2025-07-07T01:12:39.383793Z","iopub.status.idle":"2025-07-07T01:12:39.391328Z","shell.execute_reply.started":"2025-07-07T01:12:39.383769Z","shell.execute_reply":"2025-07-07T01:12:39.390610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset,DataLoader\nimport torchvision\nfrom torchvision import transforms,models\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score\nfrom sklearn.preprocessing import LabelEncoder\n\n# Image processing\nfrom PIL import Image\nimport cv2\nimport os\nfrom tqdm import tqdm\n\n# Set random seeds for reproducibility\nnp.random.seed(42)\ntorch.manual_seed(42)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed(42)\n\n# Check device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(f\"CUDA Version: {torch.version.cuda}\")\n    print(f\"Memory Available: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:39.392709Z","iopub.execute_input":"2025-07-07T01:12:39.392930Z","iopub.status.idle":"2025-07-07T01:12:48.379933Z","shell.execute_reply.started":"2025-07-07T01:12:39.392914Z","shell.execute_reply":"2025-07-07T01:12:48.379125Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(\"/kaggle/input/2025-bamboo-summer-competiton-dl-pr/train.csv\")\ntest_df = pd.read_csv(\"/kaggle/input/2025-bamboo-summer-competiton-dl-pr/test.csv\")\nsample_submission = pd.read_csv(\"/kaggle/input/2025-bamboo-summer-competiton-dl-pr/sample_submission.csv\")\nprint(\"Dataset Overview:\")\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Test samples: {len(test_df)}\")\nprint(f\"Number of classes: {train_df['label'].nunique()}\")\n\n# Display first few rows\nprint(\"\\nTraining data sample:\")\nprint(train_df.head(10))\n\nprint(\"\\nClass distribution:\")\nclass_counts = train_df['label'].value_counts()\nprint(f\"Most common class: {class_counts.index[0]} ({class_counts.iloc[0]} samples)\")\nprint(f\"Least common class: {class_counts.index[-1]} ({class_counts.iloc[-1]} samples)\")\nprint(f\"Average samples per class: {class_counts.mean():.1f}\")\n\n# Visualize class distribution\nplt.figure(figsize=(15, 8))\nplt.subplot(1, 2, 1)\nclass_counts.head(20).plot(kind='bar')\nplt.title('Top 20 Classes Distribution')\nplt.xlabel('Butterfly Species')\nplt.ylabel('Number of Images')\nplt.xticks(rotation=45, ha='right')\n\nplt.subplot(1, 2, 2)\nplt.hist(class_counts.values, bins=20, alpha=0.7, color='skyblue')\nplt.title('Distribution of Samples per Class')\nplt.xlabel('Number of Images per Class')\nplt.ylabel('Number of Classes')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:48.380631Z","iopub.execute_input":"2025-07-07T01:12:48.380960Z","iopub.status.idle":"2025-07-07T01:12:48.984373Z","shell.execute_reply.started":"2025-07-07T01:12:48.380943Z","shell.execute_reply":"2025-07-07T01:12:48.983557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize sample images from different classes\ndef show_sample_images(n_classes=5, n_samples=4):\n    \"\"\"Display sample images from different butterfly classes\"\"\"\n    # Get random classes\n    sample_classes = train_df['label'].value_counts().head(n_classes).index\n    \n    fig, axes = plt.subplots(n_classes, n_samples, figsize=(20, n_classes * 4))\n    fig.suptitle('Sample Images from Different Butterfly Classes', fontsize=16)\n    \n    for i, class_name in enumerate(sample_classes):\n        class_images = train_df[train_df['label'] == class_name]['filename'].values\n        selected_images = np.random.choice(class_images, min(n_samples, len(class_images)), replace=False)\n        \n        for j, img_filename in enumerate(selected_images):\n            img_path = f'/kaggle/input/2025-bamboo-summer-competiton-dl-pr/train/{img_filename}'\n            if os.path.exists(img_path):\n                img = Image.open(img_path)\n                axes[i, j].imshow(img)\n                axes[i, j].set_title(f'{class_name}')\n                axes[i, j].axis('off')\n            else:\n                axes[i, j].text(0.5, 0.5, 'Image not found', \n                               ha='center', va='center', transform=axes[i, j].transAxes)\n                axes[i, j].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\nshow_sample_images()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:48.985647Z","iopub.execute_input":"2025-07-07T01:12:48.985872Z","iopub.status.idle":"2025-07-07T01:12:52.030411Z","shell.execute_reply.started":"2025-07-07T01:12:48.985854Z","shell.execute_reply":"2025-07-07T01:12:52.029412Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_encoder = LabelEncoder()\ntrain_df['label_encoded'] = label_encoder.fit_transform(train_df['label'])\nprint(f\"Number of classes: {len(label_encoder.classes_)}\")\nprint(\"Label mapping (first 10):\")\nfor i in range(10):\n    print(f\"{i}: {label_encoder.classes_[i]}\")\nX_train,X_val,y_train,y_val = train_test_split(\n    train_df['filename'].values,\n    train_df['label_encoded'].values,\n    test_size=0.2,\n    random_state=42,\n    stratify=train_df['label_encoded'].values\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:52.031244Z","iopub.execute_input":"2025-07-07T01:12:52.031591Z","iopub.status.idle":"2025-07-07T01:12:52.049582Z","shell.execute_reply.started":"2025-07-07T01:12:52.031559Z","shell.execute_reply":"2025-07-07T01:12:52.048810Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define data transformations\nIMG_SIZE = 224\n\n# Training transformations with data augmentation\ntrain_transforms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.3),\n    transforms.RandomRotation(degrees=30),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1), scale=(0.9, 1.1)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# Validation/Test transformations (no augmentation)\nval_transforms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nclass ButterflyDataset(Dataset):\n    def __init__(self, filenames, labels, root_dir, transform=None, is_test=False):\n        self.filenames = filenames\n        self.labels = labels\n        self.root_dir = root_dir\n        self.transform = transform\n        self.is_test = is_test\n    \n    def __len__(self):\n        return len(self.filenames)\n    \n    def __getitem__(self, idx):\n        img_path = os.path.join(self.root_dir, self.filenames[idx])\n        \n        try:\n            image = Image.open(img_path).convert('RGB')\n        except Exception as e:\n            print(f\"Error loading image {img_path}: {e}\")\n            # Return a blank image if loading fails\n            image = Image.new('RGB', (IMG_SIZE, IMG_SIZE), color=(0, 0, 0))\n        \n        if self.transform:\n            image = self.transform(image)\n        \n        if self.is_test:\n            return image, self.filenames[idx]\n        else:\n            return image, self.labels[idx]\n\n# Create datasets\ntrain_dataset = ButterflyDataset(X_train, y_train, '/kaggle/input/2025-bamboo-summer-competiton-dl-pr/train', transform=train_transforms)\nval_dataset = ButterflyDataset(X_val, y_val, '/kaggle/input/2025-bamboo-summer-competiton-dl-pr/train', transform=val_transforms)\ntest_dataset = ButterflyDataset(test_df['filename'].values, None, '/kaggle/input/2025-bamboo-summer-competiton-dl-pr/test', \n                               transform=val_transforms, is_test=True)\n\nprint(f\"Dataset sizes:\")\nprint(f\"Train: {len(train_dataset)}\")\nprint(f\"Validation: {len(val_dataset)}\")\nprint(f\"Test: {len(test_dataset)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:52.050509Z","iopub.execute_input":"2025-07-07T01:12:52.050788Z","iopub.status.idle":"2025-07-07T01:12:52.066600Z","shell.execute_reply.started":"2025-07-07T01:12:52.050765Z","shell.execute_reply":"2025-07-07T01:12:52.065928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create data loaders\nBATCH_SIZE = 32\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, \n                         num_workers=4 if torch.cuda.is_available() else 0, pin_memory=True)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, \n                       num_workers=4 if torch.cuda.is_available() else 0, pin_memory=True)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, \n                        num_workers=4 if torch.cuda.is_available() else 0, pin_memory=True)\n\nprint(f\"Number of batches:\")\nprint(f\"Train: {len(train_loader)}\")\nprint(f\"Validation: {len(val_loader)}\")\nprint(f\"Test: {len(test_loader)}\")\n\n# Test data loading\nsample_batch = next(iter(train_loader))\nprint(f\"\\nSample batch shapes:\")\nprint(f\"Images: {sample_batch[0].shape}\")\nprint(f\"Labels: {sample_batch[1].shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:52.067483Z","iopub.execute_input":"2025-07-07T01:12:52.068268Z","iopub.status.idle":"2025-07-07T01:12:53.427756Z","shell.execute_reply.started":"2025-07-07T01:12:52.068236Z","shell.execute_reply":"2025-07-07T01:12:53.426946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ButterflyClassifier(nn.Module):\n    def __init__(self, num_classes=75, dropout_rate=0.5):\n        super(ButterflyClassifier, self).__init__()\n        \n        # Load pre-trained ResNet50\n        self.backbone = models.resnet50(pretrained=True)\n        \n        # Freeze early layers (optional - can be unfrozen later for fine-tuning)\n        for param in list(self.backbone.parameters())[:-20]:\n            param.requires_grad = False\n        \n        # Get the number of features from the last layer\n        num_features = self.backbone.fc.in_features\n        \n        # Replace the classifier with our custom head\n        self.backbone.fc = nn.Sequential(\n            nn.Dropout(dropout_rate),\n            nn.Linear(num_features, 512),\n            nn.ReLU(),\n            nn.BatchNorm1d(512),\n            nn.Dropout(dropout_rate),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.BatchNorm1d(256),\n            nn.Dropout(dropout_rate),\n            nn.Linear(256, num_classes)\n        )\n    \n    def forward(self, x):\n        return self.backbone(x)\n\n# Create model\nmodel = ButterflyClassifier(num_classes=len(label_encoder.classes_))\nmodel = model.to(device)\n\n# Count parameters\ntotal_params = sum(p.numel() for p in model.parameters())\ntrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n\nprint(f\"Model created successfully!\")\nprint(f\"Total parameters: {total_params:,}\")\nprint(f\"Trainable parameters: {trainable_params:,}\")\nprint(f\"Model device: {next(model.parameters()).device}\")\n\n# Print model architecture\nprint(f\"\\nModel architecture:\")\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:53.428803Z","iopub.execute_input":"2025-07-07T01:12:53.429029Z","iopub.status.idle":"2025-07-07T01:12:54.485280Z","shell.execute_reply.started":"2025-07-07T01:12:53.429006Z","shell.execute_reply":"2025-07-07T01:12:54.484544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training configuration\nEPOCHS = 75\nLEARNING_RATE = 1e-3\nWEIGHT_DECAY = 1e-4\n\n# Loss function and optimizer\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY)\n\n# Learning rate scheduler\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, mode='min', factor=0.5, patience=3, verbose=True, min_lr=1e-7\n)\n\n# Early stopping class\nclass EarlyStopping:\n    def __init__(self, patience=15, min_delta=1e-4):\n        self.patience = patience\n        self.min_delta = min_delta\n        self.best_loss = None\n        self.counter = 0\n        self.early_stop = False\n\n    def __call__(self, val_loss):\n        if self.best_loss is None:\n            self.best_loss = val_loss\n        elif self.best_loss - val_loss > self.min_delta:\n            self.best_loss = val_loss\n            self.counter = 0\n        else:\n            self.counter += 1\n            if self.counter >= self.patience:\n                self.early_stop = True\n\n# Training function\ndef train_epoch(model, train_loader, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    correct_predictions = 0\n    total_predictions = 0\n    \n    progress_bar = tqdm(train_loader, desc=\"Training\", leave=False)\n    \n    for data, targets in progress_bar:\n        data, targets = data.to(device), targets.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(data)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        _, predicted = torch.max(outputs.data, 1)\n        total_predictions += targets.size(0)\n        correct_predictions += (predicted == targets).sum().item()\n        \n        progress_bar.set_postfix({\n            'Loss': f'{loss.item():.4f}',\n            'Acc': f'{100 * correct_predictions / total_predictions:.2f}%'\n        })\n    \n    epoch_loss = running_loss / len(train_loader)\n    epoch_acc = correct_predictions / total_predictions\n    return epoch_loss, epoch_acc\n\n# Validation function\ndef validate_epoch(model, val_loader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    correct_predictions = 0\n    total_predictions = 0\n    \n    with torch.no_grad():\n        progress_bar = tqdm(val_loader, desc=\"Validation\", leave=False)\n        \n        for data, targets in progress_bar:\n            data, targets = data.to(device), targets.to(device)\n            outputs = model(data)\n            loss = criterion(outputs, targets)\n            \n            running_loss += loss.item()\n            _, predicted = torch.max(outputs.data, 1)\n            total_predictions += targets.size(0)\n            correct_predictions += (predicted == targets).sum().item()\n            \n            progress_bar.set_postfix({\n                'Loss': f'{loss.item():.4f}',\n                'Acc': f'{100 * correct_predictions / total_predictions:.2f}%'\n            })\n    \n    epoch_loss = running_loss / len(val_loader)\n    epoch_acc = correct_predictions / total_predictions\n    return epoch_loss, epoch_acc\n\n# Print training configuration\nprint(\"Training configuration ready!\")\nprint(f\"Epochs: {EPOCHS}\")\nprint(f\"Learning Rate: {LEARNING_RATE}\")\nprint(f\"Weight Decay: {WEIGHT_DECAY}\")\nprint(f\"Optimizer: {optimizer.__class__.__name__}\")\nprint(f\"Scheduler: {scheduler.__class__.__name__}\")\nprint(\"Early stopping enabled\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:54.487765Z","iopub.execute_input":"2025-07-07T01:12:54.488009Z","iopub.status.idle":"2025-07-07T01:12:54.500305Z","shell.execute_reply.started":"2025-07-07T01:12:54.487979Z","shell.execute_reply":"2025-07-07T01:12:54.499619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training loop\ntrain_losses = []\ntrain_accuracies = []\nval_losses = []\nval_accuracies = []\nlearning_rates = []\n\nbest_val_acc = 0.0\nbest_model_state = None\n\nprint(\"Starting training...\")\nprint(\"=\" * 60)\n\nfor epoch in range(EPOCHS):\n    print(f\"\\nEpoch {epoch+1}/{EPOCHS}\")\n    print(\"-\" * 50)\n    \n    # Training phase\n    train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)\n    \n    # Validation phase\n    val_loss, val_acc = validate_epoch(model, val_loader, criterion, device)\n    \n    # Update learning rate\n    scheduler.step(val_loss)\n    current_lr = optimizer.param_groups[0]['lr']\n    \n    # Store metrics\n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n    val_losses.append(val_loss)\n    val_accuracies.append(val_acc)\n    learning_rates.append(current_lr)\n    \n    # Save best model\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        best_model_state = model.state_dict().copy()\n        print(f\"✅ New best validation accuracy: {val_acc:.4f}\")\n    \n    # Print epoch results\n    print(f\"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f}\")\n    print(f\"Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f}\")\n    print(f\"Learning Rate: {current_lr:.2e}\")\n    print(f\"Best Val Acc: {best_val_acc:.4f}\")\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"Training completed!\")\nprint(f\"Best validation accuracy: {best_val_acc:.4f}\")\n\n# Load best model\nif best_model_state is not None:\n    model.load_state_dict(best_model_state)\n    print(\"Best model loaded!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:12:54.501055Z","iopub.execute_input":"2025-07-07T01:12:54.501303Z","iopub.status.idle":"2025-07-07T01:13:18.600530Z","shell.execute_reply.started":"2025-07-07T01:12:54.501287Z","shell.execute_reply":"2025-07-07T01:13:18.599640Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot training history\nfig, axes = plt.subplots(2, 2, figsize=(15, 10))\n\n# Loss plot\naxes[0, 0].plot(train_losses, label='Train Loss', marker='o')\naxes[0, 0].plot(val_losses, label='Validation Loss', marker='s')\naxes[0, 0].set_title('Training and Validation Loss')\naxes[0, 0].set_xlabel('Epoch')\naxes[0, 0].set_ylabel('Loss')\naxes[0, 0].legend()\naxes[0, 0].grid(True)\n\n# Accuracy plot\naxes[0, 1].plot(train_accuracies, label='Train Accuracy', marker='o')\naxes[0, 1].plot(val_accuracies, label='Validation Accuracy', marker='s')\naxes[0, 1].set_title('Training and Validation Accuracy')\naxes[0, 1].set_xlabel('Epoch')\naxes[0, 1].set_ylabel('Accuracy')\naxes[0, 1].legend()\naxes[0, 1].grid(True)\n\n# Learning rate plot\naxes[1, 0].plot(learning_rates, marker='o', color='red')\naxes[1, 0].set_title('Learning Rate Schedule')\naxes[1, 0].set_xlabel('Epoch')\naxes[1, 0].set_ylabel('Learning Rate')\naxes[1, 0].set_yscale('log')\naxes[1, 0].grid(True)\n\n# Combined accuracy plot\naxes[1, 1].plot(range(1, len(train_accuracies) + 1), \n               [acc * 100 for acc in train_accuracies], \n               label='Train Accuracy', marker='o', linewidth=2)\naxes[1, 1].plot(range(1, len(val_accuracies) + 1), \n               [acc * 100 for acc in val_accuracies], \n               label='Validation Accuracy', marker='s', linewidth=2)\naxes[1, 1].set_title('Accuracy Progress (%)')\naxes[1, 1].set_xlabel('Epoch')\naxes[1, 1].set_ylabel('Accuracy (%)')\naxes[1, 1].legend()\naxes[1, 1].grid(True)\n\nplt.tight_layout()\nplt.show()\n\n# Print final training statistics\nprint(\"Training Summary:\")\nprint(f\"Final train accuracy: {train_accuracies[-1]:.4f} ({train_accuracies[-1]*100:.2f}%)\")\nprint(f\"Final validation accuracy: {val_accuracies[-1]:.4f} ({val_accuracies[-1]*100:.2f}%)\")\nprint(f\"Best validation accuracy: {best_val_acc:.4f} ({best_val_acc*100:.2f}%)\")\nprint(f\"Final learning rate: {learning_rates[-1]:.2e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:13:18.601445Z","iopub.execute_input":"2025-07-07T01:13:18.601719Z","iopub.status.idle":"2025-07-07T01:13:19.766650Z","shell.execute_reply.started":"2025-07-07T01:13:18.601685Z","shell.execute_reply":"2025-07-07T01:13:19.765990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Detailed validation evaluation\ndef evaluate_model(model, data_loader, device, label_encoder):\n    model.eval()\n    all_predictions = []\n    all_targets = []\n    all_probabilities = []\n    \n    with torch.no_grad():\n        for data, targets in tqdm(data_loader, desc=\"Evaluating\"):\n            data, targets = data.to(device), targets.to(device)\n            \n            outputs = model(data)\n            probabilities = torch.softmax(outputs, dim=1)\n            _, predictions = torch.max(outputs, 1)\n            \n            all_predictions.extend(predictions.cpu().numpy())\n            all_targets.extend(targets.cpu().numpy())\n            all_probabilities.extend(probabilities.cpu().numpy())\n    \n    return np.array(all_predictions), np.array(all_targets), np.array(all_probabilities)\n\n# Get predictions and targets\nval_predictions, val_targets, val_probabilities = evaluate_model(model, val_loader, device, label_encoder)\n\n# Calculate accuracy\nval_accuracy = accuracy_score(val_targets, val_predictions)\nprint(f\"Validation Accuracy: {val_accuracy:.4f} ({val_accuracy*100:.2f}%)\")\n\n# Classification report\nclass_names = label_encoder.classes_\nprint(\"\\nClassification Report:\")\nprint(classification_report(val_targets, val_predictions, \n                          target_names=class_names, \n                          labels=range(len(class_names)),\n                          zero_division=0))\n\n# Confusion matrix visualization (for top classes)\ntop_classes_idx = train_df['label'].value_counts().head(10).index\ntop_classes_encoded = [label_encoder.transform([cls])[0] for cls in top_classes_idx]\n\n# Filter predictions and targets for top classes\nmask = np.isin(val_targets, top_classes_encoded)\nfiltered_targets = val_targets[mask]\nfiltered_predictions = val_predictions[mask]\n\nif len(filtered_targets) > 0:\n    cm = confusion_matrix(filtered_targets, filtered_predictions, labels=top_classes_encoded)\n    \n    plt.figure(figsize=(12, 10))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                xticklabels=[class_names[i] for i in top_classes_encoded],\n                yticklabels=[class_names[i] for i in top_classes_encoded])\n    plt.title('Confusion Matrix - Top 10 Classes')\n    plt.xlabel('Predicted')\n    plt.ylabel('Actual')\n    plt.xticks(rotation=45, ha='right')\n    plt.yticks(rotation=0)\n    plt.tight_layout()\n    plt.show()\nelse:\n    print(\"No samples found for top classes in validation set\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:13:19.767593Z","iopub.execute_input":"2025-07-07T01:13:19.767885Z","iopub.status.idle":"2025-07-07T01:13:22.217287Z","shell.execute_reply.started":"2025-07-07T01:13:19.767848Z","shell.execute_reply":"2025-07-07T01:13:22.216407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a new model for final training on full dataset\nprint(\"Creating final model for full dataset training...\")\nfinal_model = ButterflyClassifier(num_classes=len(label_encoder.classes_))\nfinal_model = final_model.to(device)\n\n# Use the same training configuration that worked best\nfinal_optimizer = optim.AdamW(final_model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY)\nfinal_scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    final_optimizer, mode='min', factor=0.5, patience=3, verbose=True, min_lr=1e-7\n)\n\n# Create full training dataset (all training data)\nfull_train_dataset = ButterflyDataset(\n    train_df['filename'].values, \n    train_df['label_encoded'].values, \n    '/kaggle/input/2025-bamboo-summer-competiton-dl-pr/train', \n    transform=train_transforms\n)\n\nfull_train_loader = DataLoader(\n    full_train_dataset, \n    batch_size=BATCH_SIZE, \n    shuffle=True, \n    num_workers=4 if torch.cuda.is_available() else 0, \n    pin_memory=True\n)\n\nprint(f\"Full training dataset size: {len(full_train_dataset)}\")\nprint(f\"Number of batches: {len(full_train_loader)}\")\n\n# Determine optimal number of epochs based on validation performance\n# Use slightly fewer epochs since we don't have validation to monitor\noptimal_epochs = len(train_losses) - 3  # Stop a bit before overfitting\noptimal_epochs = max(optimal_epochs, 75)  # Minimum 15 epochs\noptimal_epochs = min(optimal_epochs, 75)  # Maximum 20 epochs\n\nprint(f\"Training final model for {optimal_epochs} epochs...\")\n\n# Training function for full dataset (no validation)\ndef train_full_dataset(model, train_loader, criterion, optimizer, scheduler, epochs, device):\n    train_losses = []\n    train_accuracies = []\n    \n    print(\"Starting final training on full dataset...\")\n    print(\"=\" * 60)\n    \n    for epoch in range(epochs):\n        print(f\"\\nEpoch {epoch+1}/{epochs}\")\n        print(\"-\" * 50)\n        \n        model.train()\n        running_loss = 0.0\n        correct_predictions = 0\n        total_predictions = 0\n        \n        progress_bar = tqdm(train_loader, desc=\"Training\", leave=False)\n        \n        for batch_idx, (data, targets) in enumerate(progress_bar):\n            data, targets = data.to(device), targets.to(device)\n            \n            optimizer.zero_grad()\n            outputs = model(data)\n            loss = criterion(outputs, targets)\n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item()\n            _, predicted = torch.max(outputs.data, 1)\n            total_predictions += targets.size(0)\n            correct_predictions += (predicted == targets).sum().item()\n            \n            # Update progress bar\n            progress_bar.set_postfix({\n                'Loss': f'{loss.item():.4f}',\n                'Acc': f'{100 * correct_predictions / total_predictions:.2f}%'\n            })\n        \n        epoch_loss = running_loss / len(train_loader)\n        epoch_acc = correct_predictions / total_predictions\n        \n        train_losses.append(epoch_loss)\n        train_accuracies.append(epoch_acc)\n        \n        # Update learning rate based on loss\n        scheduler.step(epoch_loss)\n        current_lr = optimizer.param_groups[0]['lr']\n        \n        # Print epoch results\n        print(f\"Train Loss: {epoch_loss:.4f} | Train Acc: {epoch_acc:.4f}\")\n        print(f\"Learning Rate: {current_lr:.2e}\")\n    \n    print(\"\\n\" + \"=\" * 60)\n    print(\"Final training completed!\")\n    \n    return train_losses, train_accuracies\n\n# Train the final model\nfinal_train_losses, final_train_accuracies = train_full_dataset(\n    final_model, full_train_loader, criterion, final_optimizer, final_scheduler, optimal_epochs, device\n)\n\nprint(f\"Final model training completed!\")\nprint(f\"Final training accuracy: {final_train_accuracies[-1]:.4f} ({final_train_accuracies[-1]*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:13:22.218284Z","iopub.execute_input":"2025-07-07T01:13:22.218529Z","iopub.status.idle":"2025-07-07T01:13:41.883050Z","shell.execute_reply.started":"2025-07-07T01:13:22.218507Z","shell.execute_reply":"2025-07-07T01:13:41.881924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Compare training curves\nplt.figure(figsize=(15, 5))\n\n# Loss comparison\nplt.subplot(1, 3, 1)\nplt.plot(train_losses[:len(final_train_losses)], label='Original Training (with validation)', marker='o')\nplt.plot(final_train_losses, label='Full Dataset Training', marker='s')\nplt.title('Training Loss Comparison')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\n\n# Accuracy comparison\nplt.subplot(1, 3, 2)\nplt.plot([acc * 100 for acc in train_accuracies[:len(final_train_accuracies)]], \n         label='Original Training (with validation)', marker='o')\nplt.plot([acc * 100 for acc in final_train_accuracies], \n         label='Full Dataset Training', marker='s')\nplt.title('Training Accuracy Comparison')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy (%)')\nplt.legend()\nplt.grid(True)\n\n# Final accuracy comparison\nplt.subplot(1, 3, 3)\nmodels = ['With Validation', 'Full Dataset']\naccuracies = [train_accuracies[len(final_train_accuracies)-1] * 100, final_train_accuracies[-1] * 100]\nbars = plt.bar(models, accuracies, color=['skyblue', 'lightgreen'])\nplt.title('Final Training Accuracy')\nplt.ylabel('Accuracy (%)')\nplt.ylim(0, 100)\n\n# Add value labels on bars\nfor bar, acc in zip(bars, accuracies):\n    plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, \n             f'{acc:.1f}%', ha='center', va='bottom')\n\nplt.tight_layout()\nplt.show()\n\nprint(\"\\\\nTraining Comparison:\")\nprint(f\"Original model (with validation): {train_accuracies[len(final_train_accuracies)-1]*100:.2f}%\")\nprint(f\"Final model (full dataset): {final_train_accuracies[-1]*100:.2f}%\")\nimprovement = (final_train_accuracies[-1] - train_accuracies[len(final_train_accuracies)-1]) * 100\nprint(f\"Improvement: {improvement:+.2f} percentage points\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:13:41.884239Z","iopub.execute_input":"2025-07-07T01:13:41.884500Z","iopub.status.idle":"2025-07-07T01:13:42.425713Z","shell.execute_reply.started":"2025-07-07T01:13:41.884476Z","shell.execute_reply":"2025-07-07T01:13:42.424984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate test predictions using the final model trained on full dataset\ndef predict_test_set(model, test_loader, device, label_encoder):\n    model.eval()\n    test_predictions = []\n    test_filenames = []\n    test_probabilities = []\n    \n    with torch.no_grad():\n        for batch_data in tqdm(test_loader, desc=\"Predicting test set\"):\n            if len(batch_data) == 2:  # (images, filenames)\n                images, filenames = batch_data\n                images = images.to(device)\n                \n                outputs = model(images)\n                probabilities = torch.softmax(outputs, dim=1)\n                _, predictions = torch.max(outputs, 1)\n                \n                test_predictions.extend(predictions.cpu().numpy())\n                test_filenames.extend(filenames)\n                test_probabilities.extend(probabilities.cpu().numpy())\n    \n    # Convert predictions to class names\n    predicted_labels = label_encoder.inverse_transform(test_predictions)\n    \n    return test_filenames, predicted_labels, test_probabilities\n\n# Get test predictions using the final model (trained on full dataset)\nprint(\"Generating final test predictions with full dataset model...\")\ntest_filenames, predicted_labels, test_probabilities = predict_test_set(final_model, test_loader, device, label_encoder)\n\nprint(f\"Generated predictions for {len(test_filenames)} test images\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:18:55.093652Z","iopub.execute_input":"2025-07-07T01:18:55.094385Z","iopub.status.idle":"2025-07-07T01:18:58.871067Z","shell.execute_reply.started":"2025-07-07T01:18:55.094363Z","shell.execute_reply":"2025-07-07T01:18:58.870154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create final submission dataframe\nfinal_submission_df = pd.DataFrame({\n    'filename': test_filenames,\n    'label': predicted_labels\n})\n\n# Verify submission format\nprint(\"\\\\nFinal Submission Preview:\")\nprint(final_submission_df.head(10))\nprint(f\"\\\\nSubmission shape: {final_submission_df.shape}\")\nprint(f\"Unique predictions: {final_submission_df['label'].nunique()}\")\n\n# Check for any missing files\nexpected_files = set(test_df['filename'].values)\npredicted_files = set(final_submission_df['filename'].values)\nmissing_files = expected_files - predicted_files\n\nif missing_files:\n    print(f\"\\\\nWarning: Missing predictions for {len(missing_files)} files:\")\n    print(list(missing_files)[:10])  # Show first 10 missing files\nelse:\n    print(\"\\\\n✅ All test files have predictions!\")\n\n# Show prediction distribution\nprint(\"\\\\nPrediction Distribution (Top 10):\")\npred_counts = final_submission_df['label'].value_counts().head(10)\nfor species, count in pred_counts.items():\n    percentage = (count / len(final_submission_df)) * 100\n    print(f\"{species}: {count} ({percentage:.1f}%)\")\n\n# Save final submission\nfinal_submission_df.to_csv('final_submission.csv', index=False)\nprint(\"\\\\n🎯 Final submission saved as 'final_submission.csv'\")\n\n# Summary\nprint(\"\\\\n\" + \"=\" * 60)\nprint(\"🦋 BUTTERFLY CLASSIFICATION SUMMARY\")\nprint(\"=\" * 60)\nprint(f\"📊 Dataset: {len(train_df)} training images, {len(test_df)} test images\")\nprint(f\"🏷️  Classes: {len(label_encoder.classes_)} butterfly species\")\nprint(f\"🧠 Model: ResNet-50 with transfer learning\")\nprint(f\"📈 Best validation accuracy: {best_val_acc:.4f} ({best_val_acc*100:.2f}%)\")\nprint(f\"📈 Final training accuracy: {final_train_accuracies[-1]:.4f} ({final_train_accuracies[-1]*100:.2f}%)\")\nprint(f\"🎯 Predictions generated for: {len(final_submission_df)} test images\")\nprint(f\"💾 Submission file: final_submission.csv\")\nprint(\"=\" * 60)\n\n# Performance tips for future improvements\nprint(\"\\\\n🚀 POTENTIAL IMPROVEMENTS:\")\nprint(\"- Try different model architectures (EfficientNet, Vision Transformer)\")\nprint(\"- Implement test-time augmentation (TTA)\")\nprint(\"- Use ensemble of multiple models\")\nprint(\"- Fine-tune with different learning rates for different layers\")\nprint(\"- Experiment with different data augmentation strategies\")\nprint(\"- Add focal loss for class imbalance if present\")\nprint(\"- Use progressive resizing (start with smaller images, then larger)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:19:02.991816Z","iopub.execute_input":"2025-07-07T01:19:02.992101Z","iopub.status.idle":"2025-07-07T01:19:03.014726Z","shell.execute_reply.started":"2025-07-07T01:19:02.992076Z","shell.execute_reply":"2025-07-07T01:19:03.014061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T01:13:42.472540Z","iopub.status.idle":"2025-07-07T01:13:42.472766Z","shell.execute_reply.started":"2025-07-07T01:13:42.472660Z","shell.execute_reply":"2025-07-07T01:13:42.472670Z"}},"outputs":[],"execution_count":null}]}