{"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":4104,"databundleVersionId":46661,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14774,"databundleVersionId":875431,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport torch\nimport torchvision\nimport cv2 # Still commonly used for image processing with PyTorch\nimport os\nimport matplotlib.pyplot as plt\nfrom torch import nn\nfrom torch.optim import Adam\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm \nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix \nfrom torchvision import transforms","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.349792Z","iopub.execute_input":"2025-10-05T12:22:32.350655Z","iopub.status.idle":"2025-10-05T12:22:32.355201Z","shell.execute_reply.started":"2025-10-05T12:22:32.350619Z","shell.execute_reply":"2025-10-05T12:22:32.354498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.356615Z","iopub.execute_input":"2025-10-05T12:22:32.356894Z","iopub.status.idle":"2025-10-05T12:22:32.376433Z","shell.execute_reply.started":"2025-10-05T12:22:32.356876Z","shell.execute_reply":"2025-10-05T12:22:32.375772Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Loading The DataSet","metadata":{}},{"cell_type":"code","source":"img_size = 224\nbatch_size = 32\ncsv_path = \"/kaggle/input/aptos2019-blindness-detection/train.csv\"\nimg_dir = \"/kaggle/input/aptos2019-blindness-detection/train_images\"\nsave_dir = \"/kaggle/working/preprocessed_images\" ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.37707Z","iopub.execute_input":"2025-10-05T12:22:32.37728Z","iopub.status.idle":"2025-10-05T12:22:32.390853Z","shell.execute_reply.started":"2025-10-05T12:22:32.377264Z","shell.execute_reply":"2025-10-05T12:22:32.390328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(save_dir, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.391442Z","iopub.execute_input":"2025-10-05T12:22:32.391624Z","iopub.status.idle":"2025-10-05T12:22:32.406979Z","shell.execute_reply.started":"2025-10-05T12:22:32.391603Z","shell.execute_reply":"2025-10-05T12:22:32.406247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(csv_path)\ndf[\"id_code\"] = df[\"id_code\"].apply(lambda x: os.path.join(img_dir, x + \".png\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.408332Z","iopub.execute_input":"2025-10-05T12:22:32.408908Z","iopub.status.idle":"2025-10-05T12:22:32.430595Z","shell.execute_reply.started":"2025-10-05T12:22:32.408886Z","shell.execute_reply":"2025-10-05T12:22:32.430001Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. CLAHE Preprocessing ","metadata":{}},{"cell_type":"code","source":"def apply_clahe_and_return_tensor(image_path: str, img_size: int, save_dir: str = None) -> torch.Tensor:\n    \"\"\"\n    Reads an image, applies CLAHE, saves the preprocessed image (optional),\n    and returns it as a normalized PyTorch Tensor.\n\n    Args:\n        image_path: Path to the input image file.\n        img_size: The target height and width for resizing.\n        save_dir: Directory to save the processed image. If None, the image is not saved.\n\n    Returns:\n        A normalized PyTorch Tensor (C x H x W).\n    \"\"\"\n    \n    # 1. Read and resize the image (OpenCV/Numpy)\n    # Using IMREAD_COLOR to ensure 3 channels\n    image = cv2.imread(image_path, cv2.IMREAD_COLOR) \n    if image is None:\n        raise ValueError(f\"Unable to read image at path: {image_path}\")\n        \n    image = cv2.resize(image, (img_size, img_size))\n    \n    # 2. Apply CLAHE (OpenCV/Numpy)\n    # Convert to LAB color space (BGR -> LAB)\n    lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)\n    l, a, b = cv2.split(lab)\n    \n    # Apply CLAHE to the L channel\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))\n    cl = clahe.apply(l)\n    \n    # Merge the LAB channels and convert back to BGR (for saving) or RGB (for Tensor)\n    merged_lab = cv2.merge((cl, a, b))\n    \n    # Convert LAB to RGB for PyTorch/Numpy\n    final_image_rgb = cv2.cvtColor(merged_lab, cv2.COLOR_LAB2RGB)\n    \n    # 3. Save the preprocessed image (Optional)\n    if save_dir:\n        # Save in BGR format as cv2.imwrite expects\n        save_path = os.path.join(save_dir, os.path.basename(image_path))\n        cv2.imwrite(save_path, cv2.cvtColor(merged_lab, cv2.COLOR_LAB2BGR))\n\n    # 4. Convert to PyTorch Tensor and Normalize\n    # The image is currently a NumPy array (H x W x C) with values 0-255.\n    \n    # Convert H x W x C (NumPy) to C x H x W (PyTorch standard)\n    # and convert to float and normalize to 0.0 - 1.0\n    tensor_transform = transforms.Compose([\n        transforms.ToTensor(), # Converts HxWxC (0-255) to CxHxW (0.0-1.0)\n    ])\n    \n    final_tensor = tensor_transform(final_image_rgb)\n    \n    # Alternatively, without 'torchvision.transforms.ToTensor()':\n    # final_tensor = torch.from_numpy(final_image_rgb).permute(2, 0, 1).float() / 255.0\n\n    return final_tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.431142Z","iopub.execute_input":"2025-10-05T12:22:32.431294Z","iopub.status.idle":"2025-10-05T12:22:32.593275Z","shell.execute_reply.started":"2025-10-05T12:22:32.431282Z","shell.execute_reply":"2025-10-05T12:22:32.592473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Preprocessing images and saving to disk...\")\n\n# Iterate through the DataFrame column containing the full paths to the images\nfor image_path in tqdm(df[\"id_code\"], desc=\"Processing Images\"):\n    \n    # Call the function to process the image and save it to the specified directory.\n    # We ignore the returned tensor here, as the goal is just saving to disk.\n    \n    # Note: You need to pass 'img_size' to the function. \n    # Ensure it's defined (e.g., img_size = 224)\n    try:\n        # Assuming your function signature requires img_size\n        apply_clahe_and_return_tensor(image_path, img_size, save_dir)\n    except ValueError as e:\n        # Handle cases where an image might be corrupted or unreadable\n        print(f\"Skipping {image_path}: {e}\")\n\nprint(\"Preprocessing complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:22:32.594097Z","iopub.execute_input":"2025-10-05T12:22:32.594343Z","iopub.status.idle":"2025-10-05T12:29:30.727012Z","shell.execute_reply.started":"2025-10-05T12:22:32.594328Z","shell.execute_reply":"2025-10-05T12:29:30.726247Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Getting the Dataset to the Pytorch DataSet","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torchvision import transforms\nfrom PIL import Image\nimport os\n# Assume 'img_size' is defined globally (e.g., img_size = 224)\n\ndef load_preprocessed_image_pytorch(image_path: str, label: int, img_size: int):\n    \"\"\"\n    Loads, resizes, converts, and normalizes an image for PyTorch.\n\n    Args:\n        image_path: Path to the image file.\n        label: The associated label (kept as is, but can be converted to tensor).\n        img_size: The target dimension for resizing.\n\n    Returns:\n        A tuple: (image_tensor, label_tensor)\n    \"\"\"\n    \n    # 1. Load the image using PIL (The standard PyTorch way, replacing tf.io.read_file/decode_png)\n    try:\n        # Open the image file\n        image = Image.open(image_path).convert('RGB')\n    except Exception as e:\n        print(f\"Error loading image {image_path}: {e}\")\n        # Return dummy data or raise an error depending on your needs\n        return None, None \n\n    # 2. Define the transformations (replaces tf.image.resize and tf.cast/normalization)\n    preprocess = transforms.Compose([\n        # Resize the image to [img_size, img_size]\n        transforms.Resize((img_size, img_size)),\n        # Convert PIL image to a PyTorch Float Tensor (C x H x W) \n        # and normalize to [0.0, 1.0]\n        transforms.ToTensor(), \n        # Note: If you want to use the preprocessed CLAHE images, \n        # you might skip the ToTensor() normalization if you already saved \n        # them as floats, but for typical PNGs, this is correct.\n    ])\n\n    # 3. Apply transformations\n    image_tensor = preprocess(image)\n    \n    # 4. Convert label to PyTorch Tensor (often LongTensor for classification)\n    label_tensor = torch.tensor(label, dtype=torch.long)\n    \n    return image_tensor, label_tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:30:36.566675Z","iopub.execute_input":"2025-10-05T12:30:36.566935Z","iopub.status.idle":"2025-10-05T12:30:36.573147Z","shell.execute_reply.started":"2025-10-05T12:30:36.566916Z","shell.execute_reply":"2025-10-05T12:30:36.572363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 1. Define the Custom Dataset Class (Equivalent to dataset.map) ---\nclass CustomImageDataset(Dataset):\n    \"\"\"\n    A custom PyTorch Dataset to load image paths and labels.\n    \"\"\"\n    def __init__(self, df, save_dir, img_size):\n        # 1. Create the list of paths (Equivalent to image_paths definition)\n        self.image_paths = [os.path.join(save_dir, os.path.basename(path)) \n                            for path in df[\"id_code\"]]\n        \n        # 2. Get the labels (Equivalent to labels definition)\n        self.labels = df[\"diagnosis\"].values\n        \n        self.img_size = img_size\n        \n        # Ensure the number of paths matches the number of labels\n        assert len(self.image_paths) == len(self.labels)\n\n    def __len__(self):\n        \"\"\"Returns the total number of samples.\"\"\"\n        return len(self.labels)\n\n    def __getitem__(self, idx):\n        \"\"\"\n        Loads and preprocesses one sample (image and label) at index idx.\n        This replaces the role of the 'map' function with 'load_preprocessed_image'.\n        \"\"\"\n        image_path = self.image_paths[idx]\n        label = self.labels[idx]\n        \n        # This function should contain your preprocessing (resize, CLAHE, ToTensor)\n        # Using the PyTorch-ready function from the previous response:\n        # Note: You need to ensure load_preprocessed_image_pytorch is defined and available\n        image_tensor, label_tensor = load_preprocessed_image_pytorch(\n            image_path, label, self.img_size\n        )\n\n        # Handle cases where image loading failed\n        if image_tensor is None:\n            # You might implement robust error handling here (e.g., loading the next valid sample)\n            # For simplicity, we'll raise an error or return a placeholder\n            raise RuntimeError(f\"Failed to load sample at index {idx}: {image_path}\")\n            \n        return image_tensor, label_tensor\n\n# --- 2. Instantiate Dataset and DataLoader (Equivalent to shuffle, batch, prefetch) ---\n\n# Assume these variables are defined globally:\n# df: Your Pandas DataFrame\n# save_dir: Directory where processed images are saved\n# img_size: The target image size\n# batch_size: The desired batch size (e.g., batch_size = 32)\n\n# 1. Instantiate the Dataset\npytorch_dataset = CustomImageDataset(df, save_dir, img_size)\n\n# 2. Instantiate the DataLoader\n# This handles shuffling, batching, and parallel loading (prefetch)\ndata_loader = DataLoader(\n    dataset=pytorch_dataset,\n    batch_size=batch_size,\n    shuffle=True,                       # Equivalent to dataset.shuffle(len(df))\n    num_workers=os.cpu_count() // 2,    # Equivalent to num_parallel_calls=tf.data.AUTOTUNE\n    pin_memory=True,                    # Improves transfer speed to GPU (similar to prefetch)\n    # prefetch_factor=2                 # Can be added for explicit prefetching control\n)\n\nprint(f\"PyTorch DataLoader ready with batch size: {batch_size}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:31:26.285624Z","iopub.execute_input":"2025-10-05T12:31:26.285917Z","iopub.status.idle":"2025-10-05T12:31:26.298758Z","shell.execute_reply.started":"2025-10-05T12:31:26.285896Z","shell.execute_reply":"2025-10-05T12:31:26.29806Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Checking Both DataSet (Before and After the Preprocessing)","metadata":{}},{"cell_type":"code","source":"def visualize_clahe_effect(df_sample: pd.DataFrame):\n    \"\"\"\n    Visualizes original and CLAHE-processed images side-by-side.\n\n    Args:\n        df_sample: A small Pandas DataFrame containing 'id_code' (full path to original)\n                   and 'diagnosis' columns.\n    \"\"\"\n    # Create subplots based on the number of samples in the DataFrame\n    fig, axes = plt.subplots(len(df_sample), 2, figsize=(10, 5 * len(df_sample)))\n    \n    # Ensure axes is at least 2D for consistent indexing, even for a single row sample\n    if len(df_sample) == 1:\n        axes = np.array([axes])\n    \n    # Iterate through each row in the sample DataFrame\n    for i, row in enumerate(df_sample.itertuples()):\n        img_path = row.id_code # Full path to original image\n        \n        # --- Load and prepare Original Image ---\n        original = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        if original is None:\n            print(f\"Warning: Could not read original image at {img_path}\")\n            continue\n\n        # Resize and convert from BGR (OpenCV default) to RGB (Matplotlib default)\n        original = cv2.resize(original, (img_size, img_size))\n        original = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)\n        \n        # --- Load and prepare Processed Image ---\n        # Construct the path to the CLAHE-processed image\n        processed_path = os.path.join(save_dir, os.path.basename(img_path))\n        processed = cv2.imread(processed_path, cv2.IMREAD_COLOR)\n\n        if processed is None:\n            print(f\"Warning: Could not read processed image at {processed_path}\")\n            # If the processed image was saved as RGB, it's read back as BGR. \n            # If it was saved using the BGR convention in the CLAHE function, this is fine.\n            continue\n            \n        # Convert from BGR (OpenCV default) to RGB (Matplotlib default)\n        processed = cv2.cvtColor(processed, cv2.COLOR_BGR2RGB)\n        \n        # --- Plotting ---\n        axes[i, 0].imshow(original)\n        axes[i, 0].set_title(f\"Original - {row.diagnosis}\")\n        axes[i, 0].axis(\"off\")\n\n        axes[i, 1].imshow(processed)\n        axes[i, 1].set_title(f\"CLAHE Processed - {row.diagnosis}\")\n        axes[i, 1].axis(\"off\")\n    \n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:32:10.757483Z","iopub.execute_input":"2025-10-05T12:32:10.7582Z","iopub.status.idle":"2025-10-05T12:32:10.764975Z","shell.execute_reply.started":"2025-10-05T12:32:10.758176Z","shell.execute_reply":"2025-10-05T12:32:10.764257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_sample = df.sample(5)\nvisualize_clahe_effect(df_sample)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:32:33.663774Z","iopub.execute_input":"2025-10-05T12:32:33.664102Z","iopub.status.idle":"2025-10-05T12:32:36.329683Z","shell.execute_reply.started":"2025-10-05T12:32:33.664082Z","shell.execute_reply":"2025-10-05T12:32:36.328878Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5.Splitting the DataSet (Training,Testing,Validation)","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, random_split\nimport math\n\ndef dataset_split_pytorch(ds: Dataset, train: float = 0.7, val: float = 0.15, test: float = 0.15):\n    # 1. Verify the proportions sum to 1 (or close to it due to floating point math)\n    if not math.isclose(train + val + test, 1.0):\n        raise ValueError(\"The sum of train, val, and test proportions must be 1.0\")\n\n    ds_size = len(ds)\n    \n    # 2. Calculate the exact sizes using integer conversion\n    train_size = int(train * ds_size)\n    val_size = int(val * ds_size)\n    \n    # The remainder goes to the test set to ensure all data is used\n    test_size = ds_size - train_size - val_size\n    \n    # 3. Use torch.utils.data.random_split\n    # This function automatically shuffles the data and creates the splits\n    train_ds, val_ds, test_ds = random_split(\n        dataset=ds, \n        lengths=[train_size, val_size, test_size]\n    )\n    \n    return train_ds, val_ds, test_ds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:43:15.548614Z","iopub.execute_input":"2025-10-05T12:43:15.548931Z","iopub.status.idle":"2025-10-05T12:43:15.554331Z","shell.execute_reply.started":"2025-10-05T12:43:15.548909Z","shell.execute_reply":"2025-10-05T12:43:15.553559Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- SETUP AND SPLIT CODE (Must be executed before DataLoaders) ---\n\n# 1. Define necessary parameters (replace with your actual values)\nbatch_size = 32\nnum_workers = 4 \ntrain_ds, val_ds, test_ds = dataset_split_pytorch(\n    pytorch_dataset, \n    train=0.7, \n    val=0.15, \n    test=0.15\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:46:58.991808Z","iopub.execute_input":"2025-10-05T12:46:58.99236Z","iopub.status.idle":"2025-10-05T12:46:59.008727Z","shell.execute_reply.started":"2025-10-05T12:46:58.992339Z","shell.execute_reply":"2025-10-05T12:46:59.008157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import DataLoader\n\n# 1. Training DataLoader (Equivalent to cache().shuffle(1000).prefetch())\ntrain_loader = DataLoader(\n    dataset=train_ds,\n    batch_size=batch_size,\n    shuffle=True,          # Equivalent to .shuffle(1000) for Subsets\n    num_workers=num_workers, # Equivalent to part of prefetch(tf.data.AUTOTUNE)\n    pin_memory=True        # Helps with prefetching/caching to GPU\n)\n\n# ---------------------------------------------------------------------\n\n# 2. Validation DataLoader (Equivalent to cache().prefetch())\nval_loader = DataLoader(\n    dataset=val_ds,\n    batch_size=batch_size,\n    shuffle=False,         # Always False for validation/test\n    num_workers=num_workers,\n    pin_memory=True\n)\n\n# ---------------------------------------------------------------------\n\n# 3. Test DataLoader (Equivalent to cache().prefetch())\ntest_loader = DataLoader(\n    dataset=test_ds,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=num_workers,\n    pin_memory=True\n)\n\nprint(f\"PyTorch DataLoaders created for training, validation, and testing.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:47:05.027461Z","iopub.execute_input":"2025-10-05T12:47:05.028034Z","iopub.status.idle":"2025-10-05T12:47:05.033536Z","shell.execute_reply.started":"2025-10-05T12:47:05.028013Z","shell.execute_reply":"2025-10-05T12:47:05.032667Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Using Conv2D Model For the Accuracy Check","metadata":{}},{"cell_type":"code","source":"# -----------------------------\n# CNN MODEL\n# -----------------------------\nclass SimpleCNN2D(nn.Module):\n    def __init__(self, input_shape=(224, 224, 3), num_classes=5):\n        super(SimpleCNN2D, self).__init__()\n        \n        in_channels = input_shape[2]  # From (H, W, C) → use C\n        \n        # Conv Block 1\n        self.conv1 = nn.Conv2d(in_channels, 32, kernel_size=3, padding=1)\n        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)\n        \n        # Conv Block 2\n        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)\n        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)\n        \n        # Conv Block 3\n        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)\n        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)\n        \n        # Flattened size after pooling (224 → 112 → 56 → 28)\n        fc_input_size = 128 * 28 * 28\n        \n        # Fully Connected Layers\n        self.fc1 = nn.Linear(fc_input_size, 256)\n        self.dropout = nn.Dropout(0.5)\n        self.fc_out = nn.Linear(256, num_classes)\n\n    def forward(self, x):\n        x = self.pool1(F.relu(self.conv1(x)))\n        x = self.pool2(F.relu(self.conv2(x)))\n        x = self.pool3(F.relu(self.conv3(x)))\n        \n        x = x.view(x.size(0), -1)  # Flatten\n        x = F.relu(self.fc1(x))\n        x = self.dropout(x)\n        return self.fc_out(x)  ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:45.852593Z","iopub.execute_input":"2025-10-05T13:09:45.853067Z","iopub.status.idle":"2025-10-05T13:09:45.861283Z","shell.execute_reply.started":"2025-10-05T13:09:45.853036Z","shell.execute_reply":"2025-10-05T13:09:45.860596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = SimpleCNN2D(input_shape=(224, 224, 3), num_classes=5)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:51.354539Z","iopub.execute_input":"2025-10-05T13:09:51.354802Z","iopub.status.idle":"2025-10-05T13:09:51.5707Z","shell.execute_reply.started":"2025-10-05T13:09:51.354785Z","shell.execute_reply":"2025-10-05T13:09:51.570115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# DEVICE + LOSS FUNCTION\n# -----------------------------\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Example class weights\ncalculated_class_weights = [1.0, 1.0, 1.0, 1.0, 1.0]  \nclass_weights_tensor = torch.tensor(\n    calculated_class_weights, dtype=torch.float, device=device\n)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:10:11.656258Z","iopub.execute_input":"2025-10-05T13:10:11.656517Z","iopub.status.idle":"2025-10-05T13:10:11.661121Z","shell.execute_reply.started":"2025-10-05T13:10:11.656497Z","shell.execute_reply":"2025-10-05T13:10:11.660454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TRAINING + EVALUATION FUNCTIONS\n# -----------------------------\ndef train_model(model, criterion, optimizer, scheduler, train_loader, val_loader, num_epochs=40):\n    device = next(model.parameters()).device\n    history = {'train_loss': [], 'val_loss': [], 'val_accuracy': []}\n    \n    for epoch in range(num_epochs):\n        model.train()\n        running_loss = 0.0\n        \n        for inputs, labels in train_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            \n            optimizer.zero_grad()\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            \n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item() * inputs.size(0)\n        \n        scheduler.step()\n        epoch_loss = running_loss / len(train_loader.dataset)\n        history['train_loss'].append(epoch_loss)\n        \n        # Validation\n        val_loss, val_acc = evaluate_model(model, criterion, val_loader, device)\n        history['val_loss'].append(val_loss)\n        history['val_accuracy'].append(val_acc)\n        \n        print(f'Epoch {epoch+1}/{num_epochs} | '\n              f'LR: {optimizer.param_groups[0][\"lr\"]:.6f} | '\n              f'Train Loss: {epoch_loss:.4f} | '\n              f'Val Loss: {val_loss:.4f} | '\n              f'Val Acc: {val_acc:.4f}')\n    \n    return history\n\n\n@torch.no_grad()\ndef evaluate_model(model, criterion, data_loader, device):\n    model.eval()\n    running_loss = 0.0\n    correct_preds = 0\n    total_samples = 0\n    \n    for inputs, labels in data_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        \n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        running_loss += loss.item() * inputs.size(0)\n        \n        _, predicted = torch.max(outputs, 1)\n        total_samples += labels.size(0)\n        correct_preds += (predicted == labels).sum().item()\n    \n    avg_loss = running_loss / len(data_loader.dataset)\n    avg_accuracy = correct_preds / total_samples\n    return avg_loss, avg_accuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:10:39.7054Z","iopub.execute_input":"2025-10-05T13:10:39.706026Z","iopub.status.idle":"2025-10-05T13:10:39.71384Z","shell.execute_reply.started":"2025-10-05T13:10:39.706001Z","shell.execute_reply":"2025-10-05T13:10:39.713146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# USAGE EXAMPLE (pseudo-code)\n# -----------------------------\nmodel = SimpleCNN2D(input_shape=(224, 224, 3), num_classes=5).to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)\ncnn_history = train_model(model, criterion, optimizer, scheduler, train_loader, val_loader, num_epochs=40)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:11:18.081509Z","iopub.execute_input":"2025-10-05T13:11:18.082055Z","iopub.status.idle":"2025-10-05T13:15:28.900537Z","shell.execute_reply.started":"2025-10-05T13:11:18.082031Z","shell.execute_reply":"2025-10-05T13:15:28.899631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef test_model(model, criterion, data_loader, device):\n    model.eval()\n    running_loss = 0.0\n    correct_preds = 0\n    total_samples = 0\n    \n    for inputs, labels in data_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        \n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        running_loss += loss.item() * inputs.size(0)\n        \n        _, predicted = torch.max(outputs, 1)\n        total_samples += labels.size(0)\n        correct_preds += (predicted == labels).sum().item()\n    \n    avg_loss = running_loss / len(data_loader.dataset)\n    avg_accuracy = correct_preds / total_samples\n    return avg_loss, avg_accuracy\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:17:14.294807Z","iopub.execute_input":"2025-10-05T13:17:14.295402Z","iopub.status.idle":"2025-10-05T13:17:14.300287Z","shell.execute_reply.started":"2025-10-05T13:17:14.295379Z","shell.execute_reply":"2025-10-05T13:17:14.299529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save\ntorch.save(model.state_dict(), \"cnn_model_1.pth\")\n\n# Evaluate\ntest_loss, test_acc = test_model(model, criterion, test_loader, device)\nprint(f\"Test Accuracy: {test_acc * 100:.2f}% | Test Loss: {test_loss:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:17:16.071579Z","iopub.execute_input":"2025-10-05T13:17:16.072257Z","iopub.status.idle":"2025-10-05T13:17:17.316059Z","shell.execute_reply.started":"2025-10-05T13:17:16.072223Z","shell.execute_reply":"2025-10-05T13:17:17.315172Z"}},"outputs":[],"execution_count":null}]}