{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":446788,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":362853,"modelId":383719}],"dockerImageVersionId":31041,"isInternetEnabled":false,"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":"2025-06-23T16:07:14.693994Z","iopub.execute_input":"2025-06-23T16:07:14.694215Z","iopub.status.idle":"2025-06-23T16:07:53.710905Z","shell.execute_reply.started":"2025-06-23T16:07:14.694191Z","shell.execute_reply":"2025-06-23T16:07:53.709991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms, models\nimport pandas as pd\nimport numpy as np\nimport os\nfrom PIL import Image\nfrom tqdm import tqdm\n\n# --- 1. Configuration for this Independent Test Script ---\nclass Config:\n    # --- Dataset Paths (pointing to your Kaggle data) ---\n    DATA_ROOT = '/kaggle/input/cassava-leaf-disease-classification' \n    TEST_CSV = os.path.join(DATA_ROOT, 'sample_submission.csv') # Path to sample_submission.csv for test image IDs\n    TEST_IMAGES_DIR = os.path.join(DATA_ROOT, 'test_images') # Path to test_images folder\n\n    # --- Model Loading Path ---\n    # This is the path to your pre-trained ASDA model weights\n    # IMPORTANT: If you uploaded your model as a separate Kaggle dataset,\n    # adjust this path accordingly, e.g., '/kaggle/input/your-model-dataset-name/resnet50_asda_best_model.pth'\n    BEST_MODEL_ASDA_PATH = os.path.join(\"/kaggle/input/resnetasda2/pytorch/default/1\", \"resnet50_asda_best_model.pth\") \n\n    # --- Inference Parameters ---\n    IMAGE_SIZE = 384\n    BATCH_SIZE_INFERENCE = 64 # Larger batch size for efficient inference\n    NUM_CLASSES = 5 # Number of disease classes (0, 1, 2, 3, 4)\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    # --- Feature Map Sizes for ASDA (must match what model was trained with) ---\n    # These sizes are crucial for correctly rebuilding the ASDA module in your classifier\n    RESNET_FM_SIZES = {\n        'layer1': (96, 96), \n        'layer2': (48, 48), \n        'layer3': (24, 24), \n        'layer4': (12, 12)\n    }\n\nprint(f\"Using device: {Config.DEVICE}\")\nprint(f\"Loading model weights from: {Config.BEST_MODEL_ASDA_PATH}\")\nprint(f\"Loading test images from: {Config.TEST_IMAGES_DIR}\")\n\n# --- 2. Custom Dataset for Test (Reads from TEST_IMAGES_DIR) ---\nclass TestDataset(Dataset): # Renamed for clarity in this independent script\n    def __init__(self, image_ids, img_dir, transform=None):\n        self.image_ids = image_ids # image_ids is expected to be a list here\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_ids)\n\n    def __getitem__(self, idx):\n        img_name = self.image_ids[idx]\n        img_path = os.path.join(self.img_dir, img_name)\n        img = Image.open(img_path).convert('RGB')\n        if self.transform:\n            img = self.transform(img)\n        return img, img_name # Return image tensor and its ID for submission\n\n# --- 3. Data Transforms for Inference ---\n# Use transforms consistent with validation/test phase during training\ninference_transforms = transforms.Compose([\n    transforms.Resize((Config.IMAGE_SIZE, Config.IMAGE_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), # ImageNet means and stds\n])\n\n# --- 4. ASDA Module Definition (MUST EXACTLY MATCH TRAINED MODEL'S DEFINITION) ---\nclass ASDA(nn.Module):\n    def __init__(self, channel, input_H, input_W, reduction_ratio=4):\n        super(ASDA, self).__init__()\n        self.input_H = input_H\n        self.input_W = input_W\n        self.conv_3x3 = nn.Conv2d(channel, channel // 2, kernel_size=3, padding=1, bias=False)\n        self.conv_5x5 = nn.Conv2d(channel, channel // 2, kernel_size=5, padding=2, bias=False)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv_1x1_reduce = nn.Conv2d(channel, 1, kernel_size=1, bias=False) \n        self.adaptive_pool = nn.AdaptiveAvgPool2d((4, 4)) \n        self.fc_spatial1 = nn.Linear(4 * 4, (4 * 4) // reduction_ratio, bias=False)\n        self.fc_spatial2 = nn.Linear((4 * 4) // reduction_ratio, input_H * input_W, bias=False)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        b, c, h, w = x.size()\n        f_3x3 = self.relu(self.conv_3x3(x))\n        f_5x5 = self.relu(self.conv_5x5(x))\n        f_local = torch.cat([f_3x3, f_5x5], dim=1) \n        f_spatial_pre = self.conv_1x1_reduce(f_local) \n        f_pooled = self.adaptive_pool(f_spatial_pre) \n        f_pooled = f_pooled.view(b, -1) \n        f_linear = self.relu(self.fc_spatial1(f_pooled))\n        spatial_weights = self.fc_spatial2(f_linear).view(b, 1, self.input_H, self.input_W) \n        spatial_weights = self.sigmoid(spatial_weights)\n        return x * spatial_weights.expand_as(x)\n\n# --- 5. CCIA Module Definition (MUST EXACTLY MATCH TRAINED MODEL'S DEFINITION if used) ---\n# This class needs to be defined even if the loaded model only uses ASDA, \n# because ResNet50_Classifier might reference it in its __init__ (even if use_ccia is False).\nclass CCIA(nn.Module):\n    def __init__(self, channel, reduction=16):\n        super(CCIA, self).__init__()\n        self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.fc1 = nn.Linear(channel, channel // reduction, bias=False)\n        self.relu = nn.ReLU(inplace=True)\n        self.fc2 = nn.Linear(channel // reduction, channel, bias=False)\n        self.channel_interaction_conv = nn.Conv1d(1, 1, kernel_size=3, padding=1, bias=False) \n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        b, c, _, _ = x.size()\n        y = self.avg_pool(x).view(b, c) \n        y = self.fc1(y)\n        y = self.relu(y)\n        y = self.fc2(y) \n        y = y.unsqueeze(1) \n        y = self.channel_interaction_conv(y) \n        y = y.squeeze(1) \n        y = self.sigmoid(y).view(b, c, 1, 1) \n        return x * y.expand_as(x) \n\n# --- 6. ResNet50_Classifier Model Definition (MUST EXACTLY MATCH TRAINED MODEL'S DEFINITION) ---\nclass ResNet50_Classifier(nn.Module):\n    # This definition must match the exact __init__ and forward of the model you *trained*\n    # Even the use_ccia/use_asda flags passed here must reflect how it was trained.\n    def __init__(self, num_classes=Config.NUM_CLASSES, use_ccia=False, use_asda=False, weights_init_type='random'):\n        super(ResNet50_Classifier, self).__init__()\n        \n        # In this inference script, we primarily create the model structure to load weights.\n        # So, weights=None is typically used here, as we load state_dict later.\n        self.resnet = models.resnet50(weights=None) \n        \n        self.use_ccia = use_ccia\n        self.use_asda = use_asda\n\n        self.resnet.fc = nn.Identity() \n\n        if self.use_ccia:\n            self.ccia_layer1 = CCIA(channel=256) \n            self.ccia_layer2 = CCIA(channel=512) \n            self.ccia_layer3 = CCIA(channel=1024) \n            self.ccia_layer4 = CCIA(channel=2048) \n        \n        if self.use_asda:\n            self.asda_layer1 = ASDA(channel=256, input_H=Config.RESNET_FM_SIZES['layer1'][0], input_W=Config.RESNET_FM_SIZES['layer1'][1])\n            self.asda_layer2 = ASDA(channel=512, input_H=Config.RESNET_FM_SIZES['layer2'][0], input_W=Config.RESNET_FM_SIZES['layer2'][1])\n            self.asda_layer3 = ASDA(channel=1024, input_H=Config.RESNET_FM_SIZES['layer3'][0], input_W=Config.RESNET_FM_SIZES['layer3'][1])\n            self.asda_layer4 = ASDA(channel=2048, input_H=Config.RESNET_FM_SIZES['layer4'][0], input_W=Config.RESNET_FM_SIZES['layer4'][1])\n        \n        self.fc = nn.Linear(2048, num_classes) \n\n    def forward(self, x):\n        x = self.resnet.conv1(x)\n        x = self.resnet.bn1(x)\n        x = self.resnet.relu(x)\n        x = self.resnet.maxpool(x)\n\n        x = self.resnet.layer1(x)\n        if self.use_ccia: x = self.ccia_layer1(x)\n        if self.use_asda: x = self.asda_layer1(x) \n\n        x = self.resnet.layer2(x)\n        if self.use_ccia: x = self.ccia_layer2(x)\n        if self.use_asda: x = self.asda_layer2(x)\n        \n        x = self.resnet.layer3(x)\n        if self.use_ccia: x = self.ccia_layer3(x)\n        if self.use_asda: x = self.asda_layer3(x)\n\n        x = self.resnet.layer4(x)\n        if self.use_ccia: x = self.ccia_layer4(x)\n        if self.use_asda: x = self.asda_layer4(x)\n\n        x = self.resnet.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.fc(x)\n        return x\n\n# --- Main Inference Logic ---\nif __name__ == \"__main__\":\n    print(\"\\n--- Starting Inference on Test Set ---\")\n\n    # Load test image IDs from sample_submission.csv\n    submission_df_template = pd.read_csv(Config.TEST_CSV)\n    test_image_ids = submission_df_template['image_id'].tolist()\n    \n    # Create the dataset for inference on the TEST set\n    test_dataset = TestDataset(\n        image_ids=test_image_ids,\n        img_dir=Config.TEST_IMAGES_DIR, # CRUCIAL: Pointing to the TEST image directory\n        transform=inference_transforms # Use inference transforms\n    )\n\n    test_loader = DataLoader(\n        test_dataset,\n        batch_size=Config.BATCH_SIZE_INFERENCE,\n        shuffle=False, # No shuffling for consistent order\n        num_workers=os.cpu_count() // 2,\n        pin_memory=True\n    )\n    print(f\"Total test samples for inference: {len(test_dataset)}\")\n    print(f\"Total batches for inference: {len(test_loader)}\")\n\n    # Instantiate the model (must match your trained model's architecture)\n    # If your best model used both CCIA and ASDA, set both to True here.\n    # weights_init_type='random' is used because we're loading specific weights, not ImageNet.\n    model_for_inference = ResNet50_Classifier(\n        num_classes=Config.NUM_CLASSES, \n        use_ccia=False,  # Set this based on how your best model was trained (True if it included CCIA)\n        use_asda=True,   # Set this based on how your best model was trained (True if it included ASDA)\n        weights_init_type='random' # Match what you chose for training (random vs imagenet)\n    )\n\n    # Load the trained model weights\n    if not os.path.exists(Config.BEST_MODEL_ASDA_PATH):\n        print(f\"Error: Best model weights not found at {Config.BEST_MODEL_ASDA_PATH}.\")\n        print(\"Please ensure your trained model is correctly mounted as a Kaggle dataset or saved in /kaggle/working/.\")\n        exit() # Exit if model not found\n    \n    model_for_inference.load_state_dict(torch.load(Config.BEST_MODEL_ASDA_PATH, map_location=Config.DEVICE))\n    model_for_inference.to(Config.DEVICE)\n    model_for_inference.eval() # Set model to evaluation mode for inference\n\n    all_predictions = []\n    all_image_ids_from_loader = [] # Collect image IDs from the loader to ensure correct order\n\n    # Perform inference\n    with torch.no_grad(): # Disable gradient calculation for inference\n        for inputs, img_ids in tqdm(test_loader, desc=\"Predicting on test set\"):\n            inputs = inputs.to(Config.DEVICE)\n            outputs = model_for_inference(inputs)\n            _, predicted = torch.max(outputs.data, 1)\n            \n            all_predictions.extend(predicted.cpu().numpy())\n            all_image_ids_from_loader.extend(img_ids) # Collect image IDs from the DataLoader\n\n    # Create the final submission DataFrame\n    # IMPORTANT: Ensure the order of image_ids matches the original submission_df_template.\n    # The DataLoader provides images in a consistent order based on the initial list,\n    # so matching `test_image_ids` with `all_predictions` directly should be correct.\n    submission_df = pd.DataFrame({\n        'image_id': test_image_ids, # Use the original order from the template CSV\n        'label': all_predictions\n    })\n\n    # Save the submission file to /kaggle/working/\n    # This path is where Kaggle expects your submission.csv\n    submission_file_path = os.path.join('/kaggle/working', 'submission.csv') \n    submission_df.to_csv(submission_file_path, index=False)\n\n    print(f\"\\nSubmission file saved to: {submission_file_path}\")\n    print(\"First 5 rows of generated submission.csv:\")\n    print(submission_df.head())\n\n    print(\"\\nInference on test set complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-23T16:30:27.216008Z","iopub.execute_input":"2025-06-23T16:30:27.216781Z","iopub.status.idle":"2025-06-23T16:30:29.731037Z","shell.execute_reply.started":"2025-06-23T16:30:27.216746Z","shell.execute_reply":"2025-06-23T16:30:29.730066Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}