{"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":"none","dataSources":[{"sourceId":35760,"databundleVersionId":3441104,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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-10-19T23:36:46.126611Z","iopub.execute_input":"2025-10-19T23:36:46.126849Z","iopub.status.idle":"2025-10-19T23:36:46.132394Z","shell.execute_reply.started":"2025-10-19T23:36:46.126828Z","shell.execute_reply":"2025-10-19T23:36:46.131178Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":" 1. Setup and Imports\n\n","metadata":{}},{"cell_type":"code","source":"# Package and library imports\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import VGG16\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\n\n# Data manipulation and analysis\nimport numpy as np\nimport pandas as pd\nimport os\nimport glob\nfrom pathlib import Path\n\n# Visualization\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom PIL import Image\n\n# Utilities\nimport random\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Set random seeds for reproducibility\nSEED = 42\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\nrandom.seed(SEED)\n\n# Configure matplotlib for better plots\nplt.style.use('default')\nplt.rcParams['figure.figsize'] = (12, 8)\nplt.rcParams['font.size'] = 12\n\n#Confirmation for myself to make sure everying worked\nprint(\"All libraries imported successfully.\")\nprint(f\"TensorFlow version: {tf.__version__}\")\nprint(f\"GPU Available: {len(tf.config.experimental.list_physical_devices('GPU')) > 0}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:37:59.851162Z","iopub.execute_input":"2025-10-19T23:37:59.851468Z","iopub.status.idle":"2025-10-19T23:38:18.782535Z","shell.execute_reply.started":"2025-10-19T23:37:59.851442Z","shell.execute_reply":"2025-10-19T23:38:18.781546Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"2. Data Analysis and Characteristics","metadata":{}},{"cell_type":"code","source":"# Define paths to dataset\nBASE_DIR = \"/kaggle/input/cat-and-dog-classification-harper2022\"\nTRAIN_CATS_DIR = os.path.join(BASE_DIR, \"Cat\")\nTRAIN_DOGS_DIR = os.path.join(BASE_DIR, \"Dog\") \nTEST_DIR = os.path.join(BASE_DIR, \"Test\")\n\n# Count images in each directory\ndef count_images(directory):\n    \"\"\"Count the number of image files in a directory.\"\"\"\n    if os.path.exists(directory):\n        return len([f for f in os.listdir(directory) if f.lower().endswith(('.jpg', '.jpeg', '.png'))])\n    return 0\n\nnum_cats = count_images(TRAIN_CATS_DIR)\nnum_dogs = count_images(TRAIN_DOGS_DIR)\nnum_test = count_images(TEST_DIR)\n\nprint(\"Dataset Overview:\")\nprint(f\"Cat images: {num_cats:,}\")\nprint(f\"Dog images: {num_dogs:,}\")\nprint(f\"Test images: {num_test:,}\")\nprint(f\"Total training images: {num_cats + num_dogs:,}\")\nprint(f\"Class balance: {num_cats/(num_cats + num_dogs)*100:.1f}% cats, {num_dogs/(num_cats + num_dogs)*100:.1f}% dogs\")\n\n# Visualize class distribution\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n\n# Bar chart\nclasses = ['Cats', 'Dogs']\ncounts = [num_cats, num_dogs]\ncolors = ['orange', 'skyblue']\nax1.bar(classes, counts, color=colors, alpha=0.7)\nax1.set_title('Class Distribution in Training Set', fontsize=14, fontweight='bold')\nax1.set_ylabel('Number of Images')\nfor i, count in enumerate(counts):\n    ax1.text(i, count + 100, f'{count:,}', ha='center', fontweight='bold')\n\n# Pie chart\nax2.pie(counts, labels=classes, colors=colors, autopct='%1.1f%%', startangle=90)\nax2.set_title('Class Distribution (Percentage)', fontsize=14, fontweight='bold')\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Sample image characteristics\ndef display_sample_images(cat_dir, dog_dir, num_samples=8):\n    \"\"\"Display sample images from both classes to understand the data.\"\"\"\n    \n    # Getting random sample of images\n    cat_images = random.sample(os.listdir(cat_dir), num_samples//2)\n    dog_images = random.sample(os.listdir(dog_dir), num_samples//2)\n    \n    fig, axes = plt.subplots(2, num_samples//2, figsize=(20, 8))\n    fig.suptitle('Sample Images from Our Dataset', fontsize=16, fontweight='bold')\n    \n    # Display cat images\n    for i, img_name in enumerate(cat_images):\n        img_path = os.path.join(cat_dir, img_name)\n        img = Image.open(img_path)\n        axes[0, i].imshow(img)\n        axes[0, i].set_title(f'Cat\\nSize: {img.size}', fontsize=10)\n        axes[0, i].axis('off')\n    \n    # Display dog images  \n    for i, img_name in enumerate(dog_images):\n        img_path = os.path.join(dog_dir, img_name)\n        img = Image.open(img_path)\n        axes[1, i].imshow(img)\n        axes[1, i].set_title(f'Dog\\nSize: {img.size}', fontsize=10)\n        axes[1, i].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Display sample images\ndisplay_sample_images(TRAIN_CATS_DIR, TRAIN_DOGS_DIR)\n\n# Analyze image dimensions\ndef analyze_image_dimensions(directory, sample_size=100):\n    \"\"\"Analyze the dimensions of images in a directory.\"\"\"\n    files = os.listdir(directory)\n    sample_files = random.sample(files, min(sample_size, len(files)))\n    \n    dimensions = []\n    for file in sample_files:\n        try:\n            img = Image.open(os.path.join(directory, file))\n            dimensions.append(img.size)  # (width, height)\n        except Exception:\n            continue\n    \n    return dimensions\n\nprint(\"Analyzing image dimensions...\")\ncat_dims = analyze_image_dimensions(TRAIN_CATS_DIR)\ndog_dims = analyze_image_dimensions(TRAIN_DOGS_DIR)\n\nall_widths = [dim[0] for dim in cat_dims + dog_dims]\nall_heights = [dim[1] for dim in cat_dims + dog_dims]\n\nprint(f\"Image Dimension Analysis (sample of 200 images):\")\nprint(f\"Width  - Min: {min(all_widths)}, Max: {max(all_widths)}, Mean: {np.mean(all_widths):.0f}\")\nprint(f\"Height - Min: {min(all_heights)}, Max: {max(all_heights)}, Mean: {np.mean(all_heights):.0f}\")\n\n# Plot dimension distribution\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n\nax1.hist(all_widths, bins=30, alpha=0.7, color='blue', edgecolor='black')\nax1.set_title('Distribution of Image Widths')\nax1.set_xlabel('Width (pixels)')\nax1.set_ylabel('Frequency')\n\nax2.hist(all_heights, bins=30, alpha=0.7, color='green', edgecolor='black')\nax2.set_title('Distribution of Image Heights')\nax2.set_xlabel('Height (pixels)')\nax2.set_ylabel('Frequency')\n\nplt.tight_layout()\nplt.show()\n\n#Images have varying dimensios, so resizing is necessary for training the CNN model. \n#Fortunately though, the dataset is very diverse, so the model will generalize better\n#to new images.\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:39:16.839827Z","iopub.execute_input":"2025-10-19T23:39:16.840212Z","iopub.status.idle":"2025-10-19T23:39:19.717449Z","shell.execute_reply.started":"2025-10-19T23:39:16.840186Z","shell.execute_reply":"2025-10-19T23:39:19.716528Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Data Preprocessing and Augmentation","metadata":{}},{"cell_type":"code","source":"# Image parameters\nIMG_WIDTH = 150  # Width to resize images to\nIMG_HEIGHT = 150  # Height to resize images to\nBATCH_SIZE = 32   # Number of images to process at once\nEPOCHS = 25       # Number of training iterations\n\nprint(f\"Image Configuration:\")\nprint(f\"Target size: {IMG_WIDTH}x{IMG_HEIGHT} pixels\")\nprint(f\"Batch size: {BATCH_SIZE}\")\nprint(f\"Training epochs: {EPOCHS}\")\n\n# Create data generators with augmentation for training\n# ImageDataGenerator automatically handles loading, resizing, and augmentation\ntrain_datagen = ImageDataGenerator(\n    rescale=1.0/255.0,          # Normalize pixel values to [0,1]\n    rotation_range=20,          # Randomly rotate images by up to 20 degrees\n    width_shift_range=0.2,      # Randomly shift images horizontally by up to 20%\n    height_shift_range=0.2,     # Randomly shift images vertically by up to 20%\n    shear_range=0.2,           # Apply shearing transformation\n    zoom_range=0.2,            # Randomly zoom in/out by up to 20%\n    horizontal_flip=True,       # Randomly flip images horizontally\n    fill_mode='nearest',        # Fill pixels after transformation\n    validation_split=0.2        # Use 20% of training data for validation\n)\n\n# For test data, rescale (no augmentation for evaluation)\ntest_datagen = ImageDataGenerator(rescale=1.0/255.0)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:43:15.784402Z","iopub.execute_input":"2025-10-19T23:43:15.784933Z","iopub.status.idle":"2025-10-19T23:43:15.791983Z","shell.execute_reply.started":"2025-10-19T23:43:15.784906Z","shell.execute_reply":"2025-10-19T23:43:15.791006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n       # Reorganization of cat/dog files into folders\n       import shutil\n\n       # Create temporary directory structure\n       TEMP_DIR = \"temp_data\"\n       TEMP_TRAIN_DIR = os.path.join(TEMP_DIR, \"train\")\n       TEMP_TRAIN_CATS = os.path.join(TEMP_TRAIN_DIR, \"cat\")\n       TEMP_TRAIN_DOGS = os.path.join(TEMP_TRAIN_DIR, \"dog\")\n\n       # Create directories if they don't exist\n       os.makedirs(TEMP_TRAIN_CATS, exist_ok=True)\n       os.makedirs(TEMP_TRAIN_DOGS, exist_ok=True)\n\n\n       # Create symbolic links to avoid copying large files\n       def create_symlinks(source_dir, target_dir, max_files=None):\n           \"\"\"Create symbolic links from source to target directory.\"\"\"\n           files = os.listdir(source_dir)\n           if max_files:\n               files = files[:max_files]\n\n           for file in files:\n               source_path = os.path.join(source_dir, file)\n               target_path = os.path.join(target_dir, file)\n\n               # Remove existing symlink if it exists\n               if os.path.islink(target_path):\n                   os.unlink(target_path)\n\n               # Create new symlink\n               try:\n                   os.symlink(os.path.abspath(source_path), target_path)\n               except FileExistsError:\n                   pass  # Link already exists\n\n       #Subset of the data because I want to see the results a little more quickly \n       SUBSET_SIZE = 2000  \n\n#Confirmation below for myself to ensure smooth running:\n       print(f\"Creating symlinks for {SUBSET_SIZE} images per class...\")\n       create_symlinks(TRAIN_CATS_DIR, TEMP_TRAIN_CATS, SUBSET_SIZE)\n       create_symlinks(TRAIN_DOGS_DIR, TEMP_TRAIN_DOGS, SUBSET_SIZE)\n\n       print(f\"Temporary structure created with {SUBSET_SIZE} images per class\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:43:38.151706Z","iopub.execute_input":"2025-10-19T23:43:38.152087Z","iopub.status.idle":"2025-10-19T23:43:38.481567Z","shell.execute_reply.started":"2025-10-19T23:43:38.152062Z","shell.execute_reply":"2025-10-19T23:43:38.480509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n       # flow_from_directory automatically assigns labels based on subfolder names\n\n       # Training data generator (with augmentation)\n       train_generator = train_datagen.flow_from_directory(\n           TEMP_TRAIN_DIR,\n           target_size=(IMG_WIDTH, IMG_HEIGHT),\n           batch_size=BATCH_SIZE,\n           class_mode='binary',  # Binary classification (cat=0, dog=1)\n           subset='training',    # Use training subset (80%)\n           shuffle=True,         # Shuffle the data\n           seed=SEED            # For reproducibility\n       )\n\n       # Validation data generator (with same preprocessing, no augmentation)\n       validation_generator = train_datagen.flow_from_directory(\n           TEMP_TRAIN_DIR,\n           target_size=(IMG_WIDTH, IMG_HEIGHT),\n           batch_size=BATCH_SIZE,\n           class_mode='binary',\n           subset='validation',  # Use validation subset (20%)\n           shuffle=True,\n           seed=SEED\n       )\n\n       print(f\"Training samples: {train_generator.samples}\")\n       print(f\"Validation samples: {validation_generator.samples}\")\n       print(f\"Class indices: {train_generator.class_indices}\")\n       print(f\"Steps per epoch: {train_generator.samples // BATCH_SIZE}\")\n       print(f\"Validation steps: {validation_generator.samples // BATCH_SIZE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:43:46.306789Z","iopub.execute_input":"2025-10-19T23:43:46.307151Z","iopub.status.idle":"2025-10-19T23:43:51.017548Z","shell.execute_reply.started":"2025-10-19T23:43:46.307126Z","shell.execute_reply":"2025-10-19T23:43:51.016726Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"4. CNN Architecture Development\n","metadata":{}},{"cell_type":"code","source":"       def create_cnn_model(input_shape=(IMG_WIDTH, IMG_HEIGHT, 3)):\n           \"\"\"Create a CNN model for binary classification.\"\"\"\n\n           model = keras.Sequential([\n               # First Convolutional Block\n               # 32 filters of size 3x3, ReLU activation\n               layers.Conv2D(32, (3, 3), activation='relu', input_shape=input_shape, name='conv2d_1'),\n               layers.MaxPooling2D(2, 2, name='maxpool_1'),  # Reduce spatial dimensions by half\n\n               # Second Convolutional Block  \n               # Increase filters to 64 to detect more complex features\n               layers.Conv2D(64, (3, 3), activation='relu', name='conv2d_2'),\n               layers.MaxPooling2D(2, 2, name='maxpool_2'),\n\n               # Third Convolutional Block\n               # Further increase to 128 filters\n               layers.Conv2D(128, (3, 3), activation='relu', name='conv2d_3'),\n               layers.MaxPooling2D(2, 2, name='maxpool_3'),\n\n               # Fourth Convolutional Block\n               # 128 filters again to capture high-level features\n               layers.Conv2D(128, (3, 3), activation='relu', name='conv2d_4'),\n               layers.MaxPooling2D(2, 2, name='maxpool_4'),\n\n               # Flatten the 2D feature maps to 1D vector\n               layers.Flatten(name='flatten'),\n\n               # I am gonna add dropout for regularization (prevents overfitting)\n               layers.Dropout(0.5, name='dropout_1'),\n\n               # Dense layer with 512 neurons\n               layers.Dense(512, activation='relu', name='dense_1'),\n\n               # Another dropout layer\n               layers.Dropout(0.5, name='dropout_2'),\n\n               # Output layer - 1 neuron with sigmoid for binary classification\n               # Sigmoid outputs probability between 0 and 1\n               layers.Dense(1, activation='sigmoid', name='output')\n           ])\n\n           return model\n\n       # Create the model (finallyyy)\n       model = create_cnn_model()\n\n    \n       print(\"Model Summary:\")\n       model.summary()\n\n       print(\"Model Statistics:\")\n       total_params = model.count_params()\n       print(f\"Total parameters: {total_params:,}\")\n       print(f\"Model input shape: {model.input_shape}\")\n       print(f\"Model output shape: {model.output_shape}\")\n\n# Some notes for myself:\n#1. The convolutional 2D layers detect features, like edges, textures and patterns\n#2. MaxPooling will reduce spational dimensions and computational load\n#3. The filter count will double until reaching 128 images.\n#4. The 50% dropout will prevent overfitting the model (thanks Alex the Analyst)\n#5. The dense layers in the CNN perform the final classification for the cats and dogs model\n#6. Sigmoid function will output the probability, which is that less than 50% of the images\n#are cats, and more than 50% of the images are dogs.\n       ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:45:14.549274Z","iopub.execute_input":"2025-10-19T23:45:14.549534Z","iopub.status.idle":"2025-10-19T23:45:14.655276Z","shell.execute_reply.started":"2025-10-19T23:45:14.549513Z","shell.execute_reply":"2025-10-19T23:45:14.654320Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"       # Compiling the model - specify optimizer, loss function, and metrics\n       model.compile(\n           optimizer='adam',              # Adam optimizer - adaptive learning rate\n           loss='binary_crossentropy',    # Binary crossentropy for binary classification\n           metrics=['accuracy']           # Track accuracy during training\n       )\n\n\n\n       # Below, I decided to set up callbacks for training\n       # Extra for myslelf:\n#Callbacks are functions called during training to monitor and control the process\n\n       callbacks = [\n           # Save the best model based on validation accuracy\n           ModelCheckpoint(\n               'best_cat_dog_model.h5',\n               monitor='val_accuracy',\n               save_best_only=True,\n               mode='max',\n               verbose=1\n           ),\n\n           # Stop training early if validation accuracy stops improving\n           EarlyStopping(\n               monitor='val_accuracy',\n               patience=5,  # Wait 5 epochs before stopping\n               mode='max',\n               verbose=1,\n               restore_best_weights=True\n           ),\n\n           # Reduce learning rate when validation accuracy plateaus\n           ReduceLROnPlateau(\n               monitor='val_accuracy',\n               factor=0.2,  # Reduce LR by factor of 5\n               patience=3,  # Wait 3 epochs before reducing\n               min_lr=0.0001,\n               verbose=1\n           )\n       ]\n\n      ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:45:37.818833Z","iopub.execute_input":"2025-10-19T23:45:37.819165Z","iopub.status.idle":"2025-10-19T23:45:37.830990Z","shell.execute_reply.started":"2025-10-19T23:45:37.819142Z","shell.execute_reply":"2025-10-19T23:45:37.829997Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"5. Training the Model","metadata":{}},{"cell_type":"code","source":"       # Train the model\n       print(f\"Training for {EPOCHS} epochs with {train_generator.samples} training samples\")\n       print(f\"Validating on {validation_generator.samples} validation samples\")\n       print(\"\\n\" + \"=\"*50)\n\n       # Calculate steps per epoch\n       steps_per_epoch = train_generator.samples // BATCH_SIZE\n       validation_steps = validation_generator.samples // BATCH_SIZE\n\n       print(f\"Steps per epoch: {steps_per_epoch}\")\n       print(f\"Validation steps: {validation_steps}\")\n       print(\"\\nStarting training...\\n\")\n\n       # Train the model\n       history = model.fit(\n           train_generator,\n           steps_per_epoch=steps_per_epoch,\n           epochs=EPOCHS,\n           validation_data=validation_generator,\n           validation_steps=validation_steps,\n           callbacks=callbacks,\n           verbose=1  # Useful call that shows progress bar (I will start implementing)\n       )\n\n       print(f\"Final training accuracy: {history.history['accuracy'][-1]:.4f}\")\n       print(f\"Final validation accuracy: {history.history['val_accuracy'][-1]:.4f}\")\n       print(f\"Final training loss: {history.history['loss'][-1]:.4f}\")\n       print(f\"Final validation loss: {history.history['val_loss'][-1]:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-19T23:46:12.827724Z","iopub.execute_input":"2025-10-19T23:46:12.828080Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"       def plot_training_history(history):\n           \"\"\"Plot training and validation accuracy and loss.\"\"\"\n\n           # Extract metrics from history\n           acc = history.history['accuracy']\n           val_acc = history.history['val_accuracy']\n           loss = history.history['loss']\n           val_loss = history.history['val_loss']\n           epochs_range = range(len(acc))\n\n           # Create subplots\n           fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))\n           fig.suptitle('Training Progress', fontsize=16, fontweight='bold')\n\n           # Plot accuracy\n           ax1.plot(epochs_range, acc, 'b-', label='Training Accuracy', linewidth=2)\n           ax1.plot(epochs_range, val_acc, 'r-', label='Validation Accuracy', linewidth=2)\n           ax1.set_title('Model Accuracy', fontsize=14)\n           ax1.set_xlabel('Epoch')\n           ax1.set_ylabel('Accuracy')\n           ax1.legend()\n           ax1.grid(True, alpha=0.3)\n\n           # Plot loss\n           ax2.plot(epochs_range, loss, 'b-', label='Training Loss', linewidth=2)\n           ax2.plot(epochs_range, val_loss, 'r-', label='Validation Loss', linewidth=2)\n           ax2.set_title('Model Loss', fontsize=14)\n           ax2.set_xlabel('Epoch')\n           ax2.set_ylabel('Loss')\n           ax2.legend()\n           ax2.grid(True, alpha=0.3)\n\n           plt.tight_layout()\n           plt.show()\n\n           # Analyze the results\n           best_val_acc = max(val_acc)\n           best_epoch = val_acc.index(best_val_acc) + 1\n           final_gap = acc[-1] - val_acc[-1]\n\n           print(f\"Training Analysis:\")\n           print(f\"• Best validation accuracy: {best_val_acc:.4f} (epoch {best_epoch})\")\n           print(f\"• Final accuracy gap: {final_gap:.4f} (training - validation)\")\n\n           if final_gap > 0.1:\n               print(\"Large gap suggests overfitting - consider more regularization\")\n           elif final_gap < 0.05:\n               print(\"Good balance between training and validation performance\")\n           else:\n               print(\"Moderate gap - model performance is acceptable\")\n\n       # Plot the training history\n       plot_training_history(history)\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Evaluating the Model and the Predictions","metadata":{}},{"cell_type":"code","source":"       # Loading the best saved model\n       best_model = keras.models.load_model('best_cat_dog_model.h5')\n#Confirmation for myself\n       print(\"Best model loaded successfully!\")\n\n       # Function to make predictions on individual images\n       def predict_image(model, img_path, target_size=(IMG_WIDTH, IMG_HEIGHT)):\n           \"\"\"Make a prediction on a single image.\"\"\"\n           # Load and preprocess the image\n           img = Image.open(img_path)\n           img = img.resize(target_size)\n           img_array = np.array(img) / 255.0  # Normalize\n           img_array = np.expand_dims(img_array, axis=0)  # Add batch dimension\n\n           # Make prediction\n           prediction = model.predict(img_array, verbose=0)[0][0]\n\n           # Interpret the prediction\n           if prediction > 0.5:\n               label = \"Dog\"\n               confidence = prediction\n           else:\n               label = \"Cat\"\n               confidence = 1 - prediction\n\n           return label, confidence, prediction\n\n       # Function to display predictions with images\n       def show_predictions(model, image_paths, true_labels=None):\n           \"\"\"Display images with their predictions.\"\"\"\n           num_images = len(image_paths)\n           cols = 4\n           rows = (num_images + cols - 1) // cols\n\n           fig, axes = plt.subplots(rows, cols, figsize=(16, 4*rows))\n           if rows == 1:\n               axes = axes.reshape(1, -1)\n\n           fig.suptitle('Model Predictions', fontsize=16, fontweight='bold')\n\n           for i, img_path in enumerate(image_paths):\n               row = i // cols\n               col = i % cols\n\n               # Make prediction\n               label, confidence, raw_prediction = predict_image(model, img_path)\n\n               # Load and display image\n               img = Image.open(img_path)\n               axes[row, col].imshow(img)\n\n               # Create title with prediction info\n               confidence_icon = \"fire\" if confidence > 0.9 else \"check\" if confidence > 0.7 else \"danger\"\n               title = f\"{confidence_icon} {label}\\nConfidence: {confidence:.2f}\"\n\n               # Add true label if provided\n               if true_labels and i < len(true_labels):\n                   true_label = true_labels[i]\n                   is_correct = (label.lower() == true_label.lower())\n                   title += f\"\\nTrue: {true_label} {'✓' if is_correct else '✗'}\"\n\n               axes[row, col].set_title(title, fontsize=10)\n               axes[row, col].axis('off')\n\n           # Hide empty subplots\n           for i in range(num_images, rows * cols):\n               row = i // cols\n               col = i % cols\n               axes[row, col].axis('off')\n\n           plt.tight_layout()\n           plt.show()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-17T23:01:51.113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"       # Test predictions on sample images from our training set\n\n       # Get random sample images from each class\n       cat_samples = random.sample(os.listdir(TRAIN_CATS_DIR), 4)\n       dog_samples = random.sample(os.listdir(TRAIN_DOGS_DIR), 4)\n\n       # Create full paths\n       cat_paths = [os.path.join(TRAIN_CATS_DIR, img) for img in cat_samples]\n       dog_paths = [os.path.join(TRAIN_DOGS_DIR, img) for img in dog_samples]\n\n       # Combine paths and labels\n       test_paths = cat_paths + dog_paths\n       true_labels = ['Cat'] * 4 + ['Dog'] * 4\n\n       print(f\"Testing on {len(test_paths)} images...\")\n       show_predictions(best_model, test_paths, true_labels)\n\n       # Calculate accuracy on this sample\n       correct = 0\n       for i, path in enumerate(test_paths):\n           label, confidence, raw_pred = predict_image(best_model, path)\n           if label.lower() == true_labels[i].lower():\n               correct += 1\n\n       accuracy = correct / len(test_paths)\n       print(f\"\\nSample accuracy: {accuracy:.2%} ({correct}/{len(test_paths)})\")\n\n       ","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-17T23:01:51.113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"       # The lines below clean up temporary files\n\n       # Remove temporary directory structure\n       import shutil\n       if os.path.exists(TEMP_DIR):\n           shutil.rmtree(TEMP_DIR)\n           print(\"Temporary directory removed\")\n\n      \n\n       print(\"\\nYou CNN Model Stats:\")\n       print(f\"• Architecture: Custom 4-layer CNN\")\n       print(f\"• Parameters: {model.count_params():,}\")\n       print(f\"• Input size: {IMG_WIDTH}x{IMG_HEIGHT}x3\")\n       print(f\"• Training data: {SUBSET_SIZE*2:,} images\")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-17T23:01:51.113Z"}},"outputs":[],"execution_count":null}]}