{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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"},"accelerator":"GPU","colab":{"collapsed_sections":[],"name":"chapter08_image-classification","private_outputs":false,"provenance":[],"toc_visible":true},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":3362,"databundleVersionId":31148},{"sourceType":"modelInstanceVersion","sourceId":150921,"databundleVersionId":9999787,"modelInstanceId":128152}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This is a companion notebook for the book [Deep Learning with Python, Third Edition](https://www.manning.com/books/deep-learning-with-python-third-edition). For readability, it only contains runnable code blocks and section titles, and omits everything else in the book: text paragraphs, figures, and pseudocode.\n\n\nThe book's contents are available online at [deeplearningwithpython.io](https://deeplearningwithpython.io).","metadata":{}},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"jax\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:29:29.747694Z","iopub.execute_input":"2025-08-22T14:29:29.747919Z","iopub.status.idle":"2025-08-22T14:29:29.756747Z","shell.execute_reply.started":"2025-08-22T14:29:29.747894Z","shell.execute_reply":"2025-08-22T14:29:29.756003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# @title\nimport os\nfrom IPython.core.magic import register_cell_magic\n\n@register_cell_magic\ndef backend(line, cell):\n    current, required = os.environ.get(\"KERAS_BACKEND\", \"\"), line.split()[-1]\n    if current == required:\n        get_ipython().run_cell(cell)\n    else:\n        print(\n            f\"This cell requires the {required} backend. To run it, change KERAS_BACKEND to \"\n            f\"\\\"{required}\\\" at the top of the notebook, restart the runtime, and rerun the notebook.\"\n        )","metadata":{"cellView":"form","trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:29:34.352330Z","iopub.execute_input":"2025-08-22T14:29:34.352572Z","iopub.status.idle":"2025-08-22T14:29:34.357277Z","shell.execute_reply.started":"2025-08-22T14:29:34.352552Z","shell.execute_reply":"2025-08-22T14:29:34.356512Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Image Classification  \n\n**Image classification** is the task of assigning an input image to one of several predefined categories (e.g., cat vs. dog, or digits 0–9). It is a fundamental problem in computer vision and one of the most common applications of deep learning.  \n\n### Key Steps in an Image Classification Pipeline  \n1. **Data Preparation**  \n   - Collect and organize images into labeled classes.  \n   - Split into training, validation, and test sets.  \n   - Optionally apply data augmentation (random flips, rotations, zooms) to increase robustness.  \n\n2. **Model Selection**  \n   - **From scratch:** build a CNN tailored to the dataset size and complexity.  \n   - **Transfer learning:** reuse a pretrained model (e.g., EfficientNet, ResNet) and adapt it to the new classes.  \n\n3. **Training**  \n   - Define a loss function (e.g., cross-entropy).  \n   - Optimize with an algorithm like Adam or SGD.  \n   - Monitor accuracy and validation loss to detect overfitting.  \n\n4. **Evaluation**  \n   - Measure accuracy, precision, recall, and F1-score on the test set.  \n   - Visualize confusion matrices to see which classes are often misclassified.  \n\n5. **Deployment**  \n   - Save the trained model.  \n   - Integrate into applications (e.g., mobile apps, web services) for real-time predictions.  \n\n### Summary  \nImage classification allows computers to automatically **recognize and categorize visual patterns**. With deep learning, especially convolutional neural networks and pretrained models, it has achieved human-level performance in many domains such as medical imaging, self-driving cars, and facial recognition.  ","metadata":{}},{"cell_type":"markdown","source":"### Introduction to Convnets  \n\n**Convolutional Neural Networks (Convnets or CNNs)** are a class of deep learning models designed to process data with a grid-like structure, such as images. They have become the standard approach for computer vision tasks.  \n\n### Key Ideas  \n- **Local receptive fields:** Instead of connecting every input pixel to every neuron (as in dense layers), convnets use small filters (kernels) that scan over the image, detecting local patterns like edges or textures.  \n- **Weight sharing:** The same filter is applied across the whole image, reducing the number of parameters and making the model efficient.  \n- **Hierarchical feature learning:**  \n  - Early layers learn simple features (edges, colors, corners).  \n  - Middle layers learn more complex structures (shapes, textures).  \n  - Deeper layers capture high-level concepts (objects, categories).  \n\n### Common Building Blocks  \n- **Convolution layers:** extract features using learnable filters.  \n- **Pooling layers:** reduce spatial dimensions, keeping essential information while lowering computation.  \n- **Activation functions (ReLU):** add nonlinearity, allowing the network to learn complex patterns.  \n- **Fully connected or global pooling layers:** combine extracted features to make the final prediction.  \n\n### Why Convnets?  \n- They are **parameter-efficient**, leveraging weight sharing.  \n- They achieve **translation invariance**, recognizing objects regardless of position in the image.  \n- They form the backbone of modern computer vision applications such as image classification, object detection, and segmentation.  \n\n✅ In short: Convnets revolutionized image recognition by automatically learning useful features directly from raw pixels, outperforming traditional hand-engineered approaches.  ","metadata":{}},{"cell_type":"markdown","source":"### CNN Architecture Explanation  \n\nThis model is a **Convolutional Neural Network (CNN)** for MNIST digits:  \n\n- **Input (28×28×1):** grayscale digit image.  \n- **Conv2D (64 filters, 3×3):** learns local features (edges, curves).  \n- **MaxPooling2D (2×2):** reduces spatial size, keeps main patterns.  \n- **Conv2D (128 filters, 3×3):** deeper feature extraction.  \n- **MaxPooling2D (2×2):** further downsampling.  \n- **Conv2D (256 filters, 3×3):** captures complex patterns.  \n- **GlobalAveragePooling2D:** converts feature maps into a single vector.  \n- **Dense (10, softmax):** outputs class probabilities for the 10 digits.  ","metadata":{}},{"cell_type":"code","source":"import keras\nfrom keras import layers\n\ninputs = keras.Input(shape=(28, 28, 1))\nx = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(inputs)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\nx = layers.GlobalAveragePooling2D()(x)\noutputs = layers.Dense(10, activation=\"softmax\")(x)\nmodel = keras.Model(inputs=inputs, outputs=outputs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:29:37.009321Z","iopub.execute_input":"2025-08-22T14:29:37.009910Z","iopub.status.idle":"2025-08-22T14:29:44.280055Z","shell.execute_reply.started":"2025-08-22T14:29:37.009878Z","shell.execute_reply":"2025-08-22T14:29:44.279295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary(line_length=80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:31:47.090256Z","iopub.execute_input":"2025-08-22T14:31:47.090758Z","iopub.status.idle":"2025-08-22T14:31:47.110773Z","shell.execute_reply.started":"2025-08-22T14:31:47.090733Z","shell.execute_reply":"2025-08-22T14:31:47.110136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from keras.datasets import mnist\n\n(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\ntrain_images = train_images.reshape((60000, 28, 28, 1))\ntrain_images = train_images.astype(\"float32\") / 255\ntest_images = test_images.reshape((10000, 28, 28, 1))\ntest_images = test_images.astype(\"float32\") / 255","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:31:50.846435Z","iopub.execute_input":"2025-08-22T14:31:50.846698Z","iopub.status.idle":"2025-08-22T14:32:12.674916Z","shell.execute_reply.started":"2025-08-22T14:31:50.846677Z","shell.execute_reply":"2025-08-22T14:32:12.674374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# pick first 9 training images\nplt.figure(figsize=(6,6))\nfor i in range(9):\n    plt.subplot(3,3,i+1)\n    plt.imshow(train_images[i].squeeze(), cmap=\"gray\")\n    plt.title(f\"Label: {train_labels[i]}\")\n    plt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:02:10.981816Z","iopub.execute_input":"2025-08-22T15:02:10.982122Z","iopub.status.idle":"2025-08-22T15:02:11.330912Z","shell.execute_reply.started":"2025-08-22T15:02:10.982096Z","shell.execute_reply":"2025-08-22T15:02:11.330215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.compile(\n    optimizer=\"adam\",\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"accuracy\"],\n)\nmodel.fit(train_images, train_labels, epochs=5, batch_size=64)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_acc = model.evaluate(test_images, test_labels)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:32:21.361253Z","iopub.execute_input":"2025-08-22T14:32:21.361510Z","iopub.status.idle":"2025-08-22T14:32:23.444184Z","shell.execute_reply.started":"2025-08-22T14:32:21.361490Z","shell.execute_reply":"2025-08-22T14:32:23.443603Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### CNN without MaxPooling  \n\nThis model removes **MaxPooling layers** and stacks only convolutional layers:  \n\n- **Input (28×28×1):** grayscale digit image.  \n- **Conv2D (64, 128, 256 filters, 3×3):** successive convolutions extract increasingly complex features, while preserving full spatial resolution.  \n- **GlobalAveragePooling2D:** collapses all feature maps into a single vector by averaging each map.  \n- **Dense (10, softmax):** outputs class probabilities for the 10 digits.  \n\n### Difference vs. Model with MaxPooling  \n\n- **With MaxPooling:**  \n  - Downsamples feature maps → reduces computation and memory.  \n  - Provides **translation invariance** (robust to small shifts).  \n  - Risk of losing fine-grained details.  \n\n- **Without MaxPooling:**  \n  - Keeps more spatial information across layers.  \n  - Network may learn more detailed patterns.  \n  - Higher computational cost and risk of **overfitting** without proper regularization.  Fine-tuning a pretrained model","metadata":{}},{"cell_type":"code","source":"inputs = keras.Input(shape=(28, 28, 1))\nx = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(inputs)\nx = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\nx = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\nx = layers.GlobalAveragePooling2D()(x)\noutputs = layers.Dense(10, activation=\"softmax\")(x)\nmodel_no_max_pool = keras.Model(inputs=inputs, outputs=outputs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:32:26.993564Z","iopub.execute_input":"2025-08-22T14:32:26.994196Z","iopub.status.idle":"2025-08-22T14:32:27.016198Z","shell.execute_reply.started":"2025-08-22T14:32:26.994164Z","shell.execute_reply":"2025-08-22T14:32:27.015504Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_no_max_pool.summary(line_length=80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:32:29.566748Z","iopub.execute_input":"2025-08-22T14:32:29.567042Z","iopub.status.idle":"2025-08-22T14:32:29.582388Z","shell.execute_reply.started":"2025-08-22T14:32:29.567017Z","shell.execute_reply":"2025-08-22T14:32:29.581610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_no_max_pool.compile(\n    optimizer=\"adam\",\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"accuracy\"],\n)\nmodel_no_max_pool.fit(train_images, train_labels, epochs=5, batch_size=64)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_acc = model_no_max_pool.evaluate(test_images, test_labels)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### The relevance of deep learning for small-data problems  \n\nWhen training **convolutional neural networks (ConvNets) from scratch** on small datasets, their relevance depends on how well the model is adapted to the data scale:  \n\n- **Strong Inductive Bias:** ConvNets exploit the spatial structure of images (local connectivity, weight sharing), which allows them to generalize better than fully connected networks even with limited data.  \n- **Low-Parameter Architectures:** Using smaller models with fewer layers and filters reduces the risk of overfitting when examples are scarce.  \n- **Regularization is Key:** Techniques like dropout, L2 regularization, and batch normalization help stabilize training.  \n- **Data Augmentation:** Expanding the dataset through rotations, flips, or shifts is often essential to reach good performance.  \n- **Early Stopping:** Monitoring validation loss prevents the network from memorizing the small dataset.  \n\nIn summary, ConvNets remain **relevant for small-data problems** because their architectural design inherently captures image structure. However, success usually requires careful **model simplification, heavy regularization, and augmentation** rather than the very deep or large models used in big-data settings.  ","metadata":{}},{"cell_type":"markdown","source":"#### Downloading the data","metadata":{}},{"cell_type":"code","source":"import kagglehub\n\nkagglehub.login()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:32:42.496145Z","iopub.execute_input":"2025-08-22T14:32:42.496778Z","iopub.status.idle":"2025-08-22T14:32:42.808928Z","shell.execute_reply.started":"2025-08-22T14:32:42.496755Z","shell.execute_reply":"2025-08-22T14:32:42.808259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"download_path = kagglehub.competition_download(\"dogs-vs-cats\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:04.368486Z","iopub.execute_input":"2025-08-22T14:33:04.369339Z","iopub.status.idle":"2025-08-22T14:33:06.284244Z","shell.execute_reply.started":"2025-08-22T14:33:04.369303Z","shell.execute_reply":"2025-08-22T14:33:06.283655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\n\nwith zipfile.ZipFile(download_path + \"/train.zip\", \"r\") as zip_ref:\n    zip_ref.extractall(\".\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:10.159594Z","iopub.execute_input":"2025-08-22T14:33:10.160337Z","iopub.status.idle":"2025-08-22T14:33:19.760183Z","shell.execute_reply.started":"2025-08-22T14:33:10.160312Z","shell.execute_reply":"2025-08-22T14:33:19.759529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, shutil, pathlib\n\noriginal_dir = pathlib.Path(\"train\")\nnew_base_dir = pathlib.Path(\"dogs_vs_cats_small\")\n\ndef make_subset(subset_name, start_index, end_index):\n    for category in (\"cat\", \"dog\"):\n        dir = new_base_dir / subset_name / category\n        os.makedirs(dir)\n        fnames = [f\"{category}.{i}.jpg\" for i in range(start_index, end_index)]\n        for fname in fnames:\n            shutil.copyfile(src=original_dir / fname, dst=dir / fname)\n\nmake_subset(\"train\", start_index=0, end_index=1000)\nmake_subset(\"validation\", start_index=1000, end_index=1500)\nmake_subset(\"test\", start_index=1500, end_index=2500)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:22.392127Z","iopub.execute_input":"2025-08-22T14:33:22.392355Z","iopub.status.idle":"2025-08-22T14:33:22.898406Z","shell.execute_reply.started":"2025-08-22T14:33:22.392340Z","shell.execute_reply":"2025-08-22T14:33:22.897800Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\nfrom PIL import Image\n\nbase_dir = pathlib.Path(\"dogs_vs_cats_small\")\n\nsubset = \"train\"\ncategories = [\"cat\", \"dog\"]\n\nplt.figure(figsize=(8, 4))  # 2 rows, 5 columns\n\nfor i, category in enumerate(categories):\n    files = list((base_dir / subset / category).glob(\"*.jpg\"))\n    sample_files = random.sample(files, 5)  # 5 examples per class\n    \n    for j, fname in enumerate(sample_files):\n        img = Image.open(fname)\n        plt.subplot(2, 5, i*5 + j + 1)\n        plt.imshow(img)\n        plt.title(category)\n        plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:11:10.985837Z","iopub.execute_input":"2025-08-22T15:11:10.986542Z","iopub.status.idle":"2025-08-22T15:11:11.623407Z","shell.execute_reply.started":"2025-08-22T15:11:10.986519Z","shell.execute_reply":"2025-08-22T15:11:11.622762Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Building your model","metadata":{}},{"cell_type":"markdown","source":"### Explanation of the CNN Architecture  \n\nThis code builds a **Convolutional Neural Network (CNN)** in Keras for **binary image classification** (e.g., cats vs dogs).  \n\n- **Input Layer (180×180×3):**  \n  Accepts RGB images resized to 180×180 pixels.  \n\n- **Rescaling:**  \n  Normalizes pixel values from `[0, 255]` to `[0, 1]`, making training more stable.  \n\n- **Conv2D + MaxPooling Blocks:**  \n  The network has **five convolutional stages**, each extracting more complex features:  \n  - **Conv2D (32 filters, 3×3, ReLU)** → detects simple edges and textures.  \n  - **MaxPooling (2×2)** → reduces spatial dimensions, keeping essential features.  \n  - **Conv2D (64 filters, 3×3, ReLU)** → learns more patterns.  \n  - **MaxPooling (2×2)**.  \n  - **Conv2D (128 filters, 3×3, ReLU)** → captures more detailed structures.  \n  - **MaxPooling (2×2)**.  \n  - **Conv2D (256 filters, 3×3, ReLU)** → deeper features.  \n  - **MaxPooling (2×2)**.  \n  - **Conv2D (512 filters, 3×3, ReLU)** → very high-level, abstract features.  \n\n- **GlobalAveragePooling2D:**  \n  Collapses each feature map to a single value (average), drastically reducing the number of parameters compared to a Flatten layer.  \n\n- **Dense (1, Sigmoid):**  \n  Outputs a single probability between 0 and 1 for **binary classification**.  \n\n---\n\n### Key Points  \n- The model progressively increases the number of filters (32 → 512) while reducing spatial resolution with pooling.  \n- **Global Average Pooling** replaces fully connected layers, reducing overfitting.  \n- The final **sigmoid unit** makes it suitable for tasks like **cats vs dogs**.  ","metadata":{}},{"cell_type":"code","source":"import keras\nfrom keras import layers\n\ninputs = keras.Input(shape=(180, 180, 3))\nx = layers.Rescaling(1.0 / 255)(inputs)\nx = layers.Conv2D(filters=32, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=512, kernel_size=3, activation=\"relu\")(x)\nx = layers.GlobalAveragePooling2D()(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\nmodel = keras.Model(inputs=inputs, outputs=outputs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:27.511029Z","iopub.execute_input":"2025-08-22T14:33:27.511439Z","iopub.status.idle":"2025-08-22T14:33:28.431529Z","shell.execute_reply.started":"2025-08-22T14:33:27.511419Z","shell.execute_reply":"2025-08-22T14:33:28.430973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary(line_length=80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:36.829566Z","iopub.execute_input":"2025-08-22T14:33:36.829848Z","iopub.status.idle":"2025-08-22T14:33:36.851724Z","shell.execute_reply.started":"2025-08-22T14:33:36.829828Z","shell.execute_reply":"2025-08-22T14:33:36.850940Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.compile(\n    loss=\"binary_crossentropy\",\n    optimizer=\"adam\",\n    metrics=[\"accuracy\"],\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:40.105725Z","iopub.execute_input":"2025-08-22T14:33:40.106268Z","iopub.status.idle":"2025-08-22T14:33:40.111801Z","shell.execute_reply.started":"2025-08-22T14:33:40.106245Z","shell.execute_reply":"2025-08-22T14:33:40.111154Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Data preprocessing\n\nThis code uses **`image_dataset_from_directory`** to create TensorFlow/Keras datasets directly from folders of images.\n\n- **Parameters**:  \n  - `batch_size = 64`: images are grouped into mini-batches of 64 for training.  \n  - `image_size = (180, 180)`: all images are resized to 180×180 pixels.  \n\n- **Datasets**:  \n  1. **`train_dataset`** → loads images from `new_base_dir/train`, labeled automatically by subfolders (`cat`, `dog`).  \n  2. **`validation_dataset`** → loads images from `new_base_dir/validation` to check performance during training.  \n  3. **`test_dataset`** → loads images from `new_base_dir/test` for final evaluation.  \n\n- **What `image_dataset_from_directory` does**:  \n  - Scans the directory structure. Each **subfolder = class label**.  \n  - Resizes each image to the target size `(180, 180)`.  \n  - Converts images into **tensors** and groups them into batches.  \n  - Labels are assigned automatically (`0` for the first folder, `1` for the second, etc.).  \n\n---\n\n### Summary  \nThis pipeline converts raw images stored in folders into **ready-to-train TensorFlow datasets** with batching, resizing, and labeling handled automatically.  ","metadata":{}},{"cell_type":"code","source":"from keras.utils import image_dataset_from_directory\n\nbatch_size = 64\nimage_size = (180, 180)\ntrain_dataset = image_dataset_from_directory(\n    new_base_dir / \"train\", image_size=image_size, batch_size=batch_size\n)\nvalidation_dataset = image_dataset_from_directory(\n    new_base_dir / \"validation\", image_size=image_size, batch_size=batch_size\n)\ntest_dataset = image_dataset_from_directory(\n    new_base_dir / \"test\", image_size=image_size, batch_size=batch_size\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:33:43.487824Z","iopub.execute_input":"2025-08-22T14:33:43.488491Z","iopub.status.idle":"2025-08-22T14:33:53.893283Z","shell.execute_reply.started":"2025-08-22T14:33:43.488467Z","shell.execute_reply":"2025-08-22T14:33:53.892705Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Understanding TensorFlow Dataset objects  \n\nThis example shows how a `tf.data.Dataset` can be created and transformed. First, `from_tensor_slices` converts a NumPy array of shape `(1000, 16)` into a dataset where each element is a vector `(16,)`. Then, `.batch(32)` groups 32 elements together, producing batches of shape `(32, 16)`. Finally, `.map(lambda x: tf.reshape(x, (4, 4)))` reshapes each vector `(16,)` into a matrix `(4, 4)`, applying the transformation in parallel. In summary:  \n\n- **Raw dataset:** elements `(16,)`  \n- **Batched dataset:** elements `(32, 16)`  \n- **Reshaped dataset:** elements `(4, 4)`  \n\nThis illustrates how TensorFlow Datasets allow efficient **streaming, batching, and preprocessing** before training a model.  \n\n","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\n\nrandom_numbers = np.random.normal(size=(1000, 16))\ndataset = tf.data.Dataset.from_tensor_slices(random_numbers)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:15:58.290438Z","iopub.execute_input":"2025-08-22T15:15:58.291030Z","iopub.status.idle":"2025-08-22T15:15:58.298186Z","shell.execute_reply.started":"2025-08-22T15:15:58.291007Z","shell.execute_reply":"2025-08-22T15:15:58.297561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i, element in enumerate(dataset):\n    print(element.shape)\n    if i >= 2:\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:34:06.525230Z","iopub.execute_input":"2025-08-22T14:34:06.525501Z","iopub.status.idle":"2025-08-22T14:34:06.558930Z","shell.execute_reply.started":"2025-08-22T14:34:06.525480Z","shell.execute_reply":"2025-08-22T14:34:06.558379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batched_dataset = dataset.batch(32)\nfor i, element in enumerate(batched_dataset):\n    print(element.shape)\n    if i >= 2:\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:43:32.592043Z","iopub.execute_input":"2025-08-22T14:43:32.592721Z","iopub.status.idle":"2025-08-22T14:43:32.605252Z","shell.execute_reply.started":"2025-08-22T14:43:32.592701Z","shell.execute_reply":"2025-08-22T14:43:32.604695Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"reshaped_dataset = dataset.map(\n    lambda x: tf.reshape(x, (4, 4)),\n    num_parallel_calls=8)\nfor i, element in enumerate(reshaped_dataset):\n    print(element.shape)\n    if i >= 2:\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:43:36.085062Z","iopub.execute_input":"2025-08-22T14:43:36.085774Z","iopub.status.idle":"2025-08-22T14:43:36.127866Z","shell.execute_reply.started":"2025-08-22T14:43:36.085734Z","shell.execute_reply":"2025-08-22T14:43:36.127204Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##### Fitting the model","metadata":{}},{"cell_type":"code","source":"for data_batch, labels_batch in train_dataset:\n    print(\"data batch shape:\", data_batch.shape)\n    print(\"labels batch shape:\", labels_batch.shape)\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:43:39.594617Z","iopub.execute_input":"2025-08-22T14:43:39.594884Z","iopub.status.idle":"2025-08-22T14:43:39.742799Z","shell.execute_reply.started":"2025-08-22T14:43:39.594863Z","shell.execute_reply":"2025-08-22T14:43:39.742163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    keras.callbacks.ModelCheckpoint(\n        filepath=\"convnet_from_scratch.keras\",\n        save_best_only=True,\n        monitor=\"val_loss\",\n    )\n]\nhistory = model.fit(\n    train_dataset,\n    epochs=50,\n    validation_data=validation_dataset,\n    callbacks=callbacks,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:43:58.806348Z","iopub.execute_input":"2025-08-22T14:43:58.806898Z","iopub.status.idle":"2025-08-22T14:46:52.920359Z","shell.execute_reply.started":"2025-08-22T14:43:58.806878Z","shell.execute_reply":"2025-08-22T14:46:52.919593Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\naccuracy = history.history[\"accuracy\"]\nval_accuracy = history.history[\"val_accuracy\"]\nloss = history.history[\"loss\"]\nval_loss = history.history[\"val_loss\"]\nepochs = range(1, len(accuracy) + 1)\n\nplt.plot(epochs, accuracy, \"r--\", label=\"Training accuracy\")\nplt.plot(epochs, val_accuracy, \"b\", label=\"Validation accuracy\")\nplt.title(\"Training and validation accuracy\")\nplt.legend()\nplt.figure()\n\nplt.plot(epochs, loss, \"r--\", label=\"Training loss\")\nplt.plot(epochs, val_loss, \"b\", label=\"Validation loss\")\nplt.title(\"Training and validation loss\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:49:25.614254Z","iopub.execute_input":"2025-08-22T14:49:25.614834Z","iopub.status.idle":"2025-08-22T14:49:25.993540Z","shell.execute_reply.started":"2025-08-22T14:49:25.614808Z","shell.execute_reply":"2025-08-22T14:49:25.992798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_model = keras.models.load_model(\"convnet_from_scratch.keras\")\ntest_loss, test_acc = test_model.evaluate(test_dataset)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:49:34.906989Z","iopub.execute_input":"2025-08-22T14:49:34.907272Z","iopub.status.idle":"2025-08-22T14:49:37.782674Z","shell.execute_reply.started":"2025-08-22T14:49:34.907253Z","shell.execute_reply":"2025-08-22T14:49:37.781987Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Using data augmentation\n\nThe code applies random flips, rotations, and zooms to training images using `map`. With `num_parallel_calls=8`, the augmentations run in parallel for speed. Adding  \n\n```python\n.prefetch(tf.data.AUTOTUNE)\n```\n\nmakes TensorFlow prepare the next batch while the model trains on the current one, so data loading and training happen at the same time. This keeps the GPU busy and speeds up training.","metadata":{}},{"cell_type":"code","source":"data_augmentation_layers = [\n    layers.RandomFlip(\"horizontal\"),\n    layers.RandomRotation(0.1),\n    layers.RandomZoom(0.2),\n]\n\ndef data_augmentation(images, targets):\n    for layer in data_augmentation_layers:\n        images = layer(images)\n    return images, targets\n\naugmented_train_dataset = train_dataset.map(\n    data_augmentation, num_parallel_calls=8\n)\naugmented_train_dataset = augmented_train_dataset.prefetch(tf.data.AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:21:30.066762Z","iopub.execute_input":"2025-08-22T15:21:30.067529Z","iopub.status.idle":"2025-08-22T15:21:30.578280Z","shell.execute_reply.started":"2025-08-22T15:21:30.067505Z","shell.execute_reply":"2025-08-22T15:21:30.577707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(10, 10))\nfor image_batch, _ in train_dataset.take(1):\n    image = image_batch[0]\n    for i in range(9):\n        ax = plt.subplot(3, 3, i + 1)\n        augmented_image, _ = data_augmentation(image, None)\n        augmented_image = keras.ops.convert_to_numpy(augmented_image)\n        plt.imshow(augmented_image.astype(\"uint8\"))\n        plt.axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:49:51.108197Z","iopub.execute_input":"2025-08-22T14:49:51.108843Z","iopub.status.idle":"2025-08-22T14:49:55.712360Z","shell.execute_reply.started":"2025-08-22T14:49:51.108822Z","shell.execute_reply":"2025-08-22T14:49:55.711501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inputs = keras.Input(shape=(180, 180, 3))\nx = layers.Rescaling(1.0 / 255)(inputs)\nx = layers.Conv2D(filters=32, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\nx = layers.MaxPooling2D(pool_size=2)(x)\nx = layers.Conv2D(filters=512, kernel_size=3, activation=\"relu\")(x)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dropout(0.25)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\nmodel = keras.Model(inputs=inputs, outputs=outputs)\n\nmodel.compile(\n    loss=\"binary_crossentropy\",\n    optimizer=\"adam\",\n    metrics=[\"accuracy\"],\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:50:08.875404Z","iopub.execute_input":"2025-08-22T14:50:08.876098Z","iopub.status.idle":"2025-08-22T14:50:08.903854Z","shell.execute_reply.started":"2025-08-22T14:50:08.876073Z","shell.execute_reply":"2025-08-22T14:50:08.903363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    keras.callbacks.ModelCheckpoint(\n        filepath=\"convnet_from_scratch_with_augmentation.keras\",\n        save_best_only=True,\n        monitor=\"val_loss\",\n    )\n]\nhistory = model.fit(\n    augmented_train_dataset,\n    epochs=100,\n    validation_data=validation_dataset,\n    callbacks=callbacks,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T14:50:13.185292Z","iopub.execute_input":"2025-08-22T14:50:13.185576Z","iopub.status.idle":"2025-08-22T15:02:10.980526Z","shell.execute_reply.started":"2025-08-22T14:50:13.185556Z","shell.execute_reply":"2025-08-22T15:02:10.979721Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_model = keras.models.load_model(\n    \"convnet_from_scratch_with_augmentation.keras\"\n)\ntest_loss, test_acc = test_model.evaluate(test_dataset)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:22:41.649881Z","iopub.execute_input":"2025-08-22T15:22:41.650198Z","iopub.status.idle":"2025-08-22T15:22:43.711709Z","shell.execute_reply.started":"2025-08-22T15:22:41.650174Z","shell.execute_reply":"2025-08-22T15:22:43.710660Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Using a Pretrained Model — Transfer Learning  \n\n**Transfer learning** means taking a model that was already trained on a large dataset (e.g., ImageNet) and reusing its learned features for a new task. Instead of training a network from scratch, we:  \n\n- **Reuse feature extractors:** early layers capture general patterns (edges, textures, shapes) that are useful for many vision tasks.  \n- **Adapt to the new problem:** add new layers (e.g., Dense for classification) on top of the pretrained base.  \n- **Options:**  \n  - **Feature extraction:** freeze the pretrained layers and only train the new layers.  \n  - **Fine-tuning:** unfreeze part of the pretrained model and retrain it on the new dataset for better adaptation.  \n\nThis approach works especially well when the new dataset is **small**, since the pretrained model already provides strong representations. It reduces training time and improves accuracy compared to training from scratch.  \n","metadata":{}},{"cell_type":"markdown","source":"#### Feature extraction with a pretrained model","metadata":{}},{"cell_type":"code","source":"print(keras_hub.models.Backbone.presets.keys())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:27:08.302131Z","iopub.execute_input":"2025-08-22T15:27:08.302397Z","iopub.status.idle":"2025-08-22T15:27:08.309940Z","shell.execute_reply.started":"2025-08-22T15:27:08.302379Z","shell.execute_reply":"2025-08-22T15:27:08.309242Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Using a Pretrained Backbone  \n\nThis code selects the **EfficientNet-B0** architecture, pretrained on **ImageNet** with RandAugment preprocessing, as the base model. The `from_preset` function loads it as a **backbone**, meaning only the feature extractor part of the network is included, without the final classification head. The resulting backbone can then be reused in a new model, making it possible to apply **transfer learning** by adapting pretrained features to a smaller dataset (such as cats vs dogs).  \n","metadata":{}},{"cell_type":"code","source":"base_model = \"efficientnet_b0_ra_imagenet\"\nconv_base = keras_hub.models.Backbone.from_preset(base_model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:28:39.223563Z","iopub.execute_input":"2025-08-22T15:28:39.224068Z","iopub.status.idle":"2025-08-22T15:29:05.917717Z","shell.execute_reply.started":"2025-08-22T15:28:39.224045Z","shell.execute_reply":"2025-08-22T15:29:05.917150Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Image Preprocessor from Preset  \n\nThis line creates an **image preprocessing layer** that matches the requirements of the chosen pretrained backbone (`efficientnet_b0_ra_imagenet`).  \n\n- **`ImageConverter.from_preset`**: loads the exact preprocessing operations (resizing, rescaling, normalization, etc.) used when training the backbone on ImageNet.  \n- **`base_model`**: ensures the preprocessing is consistent with the model’s expectations (EfficientNet-B0 here).  \n- **`image_size=(180, 180)`**: resizes all input images to the correct dimensions for the model.  \n\n✅ In short: this preprocessor guarantees that new images are transformed in the same way as the images used during pretraining, so the backbone can work correctly on your dataset.  \n","metadata":{}},{"cell_type":"code","source":"preprocessor = keras_hub.layers.ImageConverter.from_preset(\n    base_model,\n    image_size=(180, 180),\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:29:40.270194Z","iopub.execute_input":"2025-08-22T15:29:40.270428Z","iopub.status.idle":"2025-08-22T15:29:41.691323Z","shell.execute_reply.started":"2025-08-22T15:29:40.270412Z","shell.execute_reply":"2025-08-22T15:29:41.690730Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Extracting Features and Labels with a Pretrained Backbone  \n\nThe function `get_features_and_labels` converts a dataset of images into **feature vectors** and **labels** using a pretrained backbone.  \n\n- **Initialize lists**: empty lists store extracted features and corresponding labels.  \n- **Loop through the dataset**: for each batch of images and labels:  \n  - The images are passed through the **preprocessor** to apply resizing and normalization.  \n  - The preprocessed images are fed into the pretrained backbone (`conv_base`) to compute **feature representations** instead of raw pixels.  \n  - The resulting features and labels are collected.  \n- **Concatenate results**: batches are merged into full arrays using `np.concatenate`.  \n\nFinally, the code applies this function to the training, validation, and test datasets, producing three pairs of arrays:  \n\n- `train_features, train_labels`  \n- `val_features, val_labels`  \n- `test_features, test_labels`  \n\n✅ In summary: this process **precomputes features** from images using a pretrained model so that only a small classifier needs to be trained on top, speeding up training and reducing computational cost.  ","metadata":{}},{"cell_type":"code","source":"def get_features_and_labels(dataset):\n    all_features = []\n    all_labels = []\n    for images, labels in dataset:\n        preprocessed_images = preprocessor(images)\n        features = conv_base.predict(preprocessed_images, verbose=0)\n        all_features.append(features)\n        all_labels.append(labels)\n    return np.concatenate(all_features), np.concatenate(all_labels)\n\ntrain_features, train_labels = get_features_and_labels(train_dataset)\nval_features, val_labels = get_features_and_labels(validation_dataset)\ntest_features, test_labels = get_features_and_labels(test_dataset)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:33:28.520285Z","iopub.execute_input":"2025-08-22T15:33:28.520878Z","iopub.status.idle":"2025-08-22T15:33:34.666227Z","shell.execute_reply.started":"2025-08-22T15:33:28.520856Z","shell.execute_reply":"2025-08-22T15:33:34.665405Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_features.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:33:36.909944Z","iopub.execute_input":"2025-08-22T15:33:36.910502Z","iopub.status.idle":"2025-08-22T15:33:36.915709Z","shell.execute_reply.started":"2025-08-22T15:33:36.910460Z","shell.execute_reply":"2025-08-22T15:33:36.915005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inputs = keras.Input(shape=(6, 6, 1280))\nx = layers.GlobalAveragePooling2D()(inputs)\nx = layers.Dense(256, activation=\"relu\")(x)\nx = layers.Dropout(0.25)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\nmodel = keras.Model(inputs, outputs)\nmodel.compile(\n    loss=\"binary_crossentropy\",\n    optimizer=\"adam\",\n    metrics=[\"accuracy\"],\n)\n\ncallbacks = [\n    keras.callbacks.ModelCheckpoint(\n        filepath=\"feature_extraction.keras\",\n        save_best_only=True,\n        monitor=\"val_loss\",\n    )\n]\nhistory = model.fit(\n    train_features,\n    train_labels,\n    epochs=10,\n    validation_data=(val_features, val_labels),\n    callbacks=callbacks,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:34:05.675326Z","iopub.execute_input":"2025-08-22T15:34:05.675978Z","iopub.status.idle":"2025-08-22T15:34:11.161745Z","shell.execute_reply.started":"2025-08-22T15:34:05.675942Z","shell.execute_reply":"2025-08-22T15:34:11.161207Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nacc = history.history[\"accuracy\"]\nval_acc = history.history[\"val_accuracy\"]\nloss = history.history[\"loss\"]\nval_loss = history.history[\"val_loss\"]\nepochs = range(1, len(acc) + 1)\nplt.plot(epochs, acc, \"r--\", label=\"Training accuracy\")\nplt.plot(epochs, val_acc, \"b\", label=\"Validation accuracy\")\nplt.title(\"Training and validation accuracy\")\nplt.legend()\nplt.figure()\nplt.plot(epochs, loss, \"r--\", label=\"Training loss\")\nplt.plot(epochs, val_loss, \"b\", label=\"Validation loss\")\nplt.title(\"Training and validation loss\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:34:19.907851Z","iopub.execute_input":"2025-08-22T15:34:19.908165Z","iopub.status.idle":"2025-08-22T15:34:20.230844Z","shell.execute_reply.started":"2025-08-22T15:34:19.908146Z","shell.execute_reply":"2025-08-22T15:34:20.230167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_model = keras.models.load_model(\"feature_extraction.keras\")\ntest_loss, test_acc = test_model.evaluate(test_features, test_labels)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:34:27.613438Z","iopub.execute_input":"2025-08-22T15:34:27.614195Z","iopub.status.idle":"2025-08-22T15:34:28.324824Z","shell.execute_reply.started":"2025-08-22T15:34:27.614170Z","shell.execute_reply":"2025-08-22T15:34:28.324216Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##### Feature extraction together with data augmentation","metadata":{}},{"cell_type":"code","source":"import keras_hub\n\nconv_base = keras_hub.models.Backbone.from_preset(\n    base_model,\n    trainable=False,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:36:28.214566Z","iopub.execute_input":"2025-08-22T15:36:28.215108Z","iopub.status.idle":"2025-08-22T15:36:30.429081Z","shell.execute_reply.started":"2025-08-22T15:36:28.215085Z","shell.execute_reply":"2025-08-22T15:36:30.428482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"conv_base.trainable = True\nlen(conv_base.trainable_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:36:33.437195Z","iopub.execute_input":"2025-08-22T15:36:33.437448Z","iopub.status.idle":"2025-08-22T15:36:33.443942Z","shell.execute_reply.started":"2025-08-22T15:36:33.437431Z","shell.execute_reply":"2025-08-22T15:36:33.443417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"conv_base.trainable = False\nlen(conv_base.trainable_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:36:40.427297Z","iopub.execute_input":"2025-08-22T15:36:40.427562Z","iopub.status.idle":"2025-08-22T15:36:40.433935Z","shell.execute_reply.started":"2025-08-22T15:36:40.427529Z","shell.execute_reply":"2025-08-22T15:36:40.433151Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inputs = keras.Input(shape=(180, 180, 3))\nx = preprocessor(inputs)\nx = conv_base(x)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dense(256)(x)\nx = layers.Dropout(0.25)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\nmodel = keras.Model(inputs, outputs)\nmodel.compile(\n    loss=\"binary_crossentropy\",\n    optimizer=\"adam\",\n    metrics=[\"accuracy\"],\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:36:49.308444Z","iopub.execute_input":"2025-08-22T15:36:49.309135Z","iopub.status.idle":"2025-08-22T15:36:49.350214Z","shell.execute_reply.started":"2025-08-22T15:36:49.309110Z","shell.execute_reply":"2025-08-22T15:36:49.349483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    keras.callbacks.ModelCheckpoint(\n        filepath=\"feature_extraction_with_data_augmentation.keras\",\n        save_best_only=True,\n        monitor=\"val_loss\",\n    )\n]\nhistory = model.fit(\n    augmented_train_dataset,\n    epochs=30,\n    validation_data=validation_dataset,\n    callbacks=callbacks,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:36:57.614932Z","iopub.execute_input":"2025-08-22T15:36:57.615652Z","iopub.status.idle":"2025-08-22T15:40:56.654224Z","shell.execute_reply.started":"2025-08-22T15:36:57.615628Z","shell.execute_reply":"2025-08-22T15:40:56.653463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_model = keras.models.load_model(\n    \"feature_extraction_with_data_augmentation.keras\"\n)\ntest_loss, test_acc = test_model.evaluate(test_dataset)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:41:02.953518Z","iopub.execute_input":"2025-08-22T15:41:02.954250Z","iopub.status.idle":"2025-08-22T15:41:16.197103Z","shell.execute_reply.started":"2025-08-22T15:41:02.954215Z","shell.execute_reply":"2025-08-22T15:41:16.196353Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Fine-tuning a Pretrained Model  \n\n**Fine-tuning** is the process of taking a pretrained model (such as EfficientNet, ResNet, or BERT) and adapting not only its final layers but also some of its internal layers to a new task.  \n\nSteps usually followed:  \n\n1. **Load a pretrained backbone**: start with a model trained on a large dataset (e.g., ImageNet).  \n2. **Freeze layers**: initially keep the backbone’s weights fixed, and train only the new classification head on the target dataset. This ensures stable training and prevents catastrophic forgetting.  \n3. **Unfreeze some layers**: after the head has adapted, selectively unfreeze deeper layers of the backbone. These layers are then trained with a lower learning rate so they can adjust their weights to the new dataset.  \n4. **Train end-to-end**: the model gradually adapts both the general representations from pretraining and the specific patterns of the new dataset.  \n\n**Benefits:**  \n- Leverages powerful, general-purpose features learned from massive datasets.  \n- Requires fewer training samples compared to training from scratch.  \n- Achieves higher accuracy and faster convergence.  \n\n✅ Fine-tuning is especially effective when the target dataset is not huge, but still large enough to allow slight adjustments to the pretrained model’s weights.  ","metadata":{}},{"cell_type":"code","source":"conv_base.trainable = True\ninputs = keras.Input(shape=(180, 180, 3))\nx = preprocessor(inputs)\nx = conv_base(x)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dense(256)(x)\nx = layers.Dropout(0.25)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\nmodel = keras.Model(inputs, outputs)\nmodel.compile(\n    loss=\"binary_crossentropy\",\n    optimizer=\"adam\",\n    metrics=[\"accuracy\"],\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:50:17.811056Z","iopub.execute_input":"2025-08-22T15:50:17.811590Z","iopub.status.idle":"2025-08-22T15:50:17.857423Z","shell.execute_reply.started":"2025-08-22T15:50:17.811567Z","shell.execute_reply":"2025-08-22T15:50:17.856856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.compile(\n    loss=\"binary_crossentropy\",\n    optimizer=keras.optimizers.Adam(learning_rate=1e-5),\n    metrics=[\"accuracy\"],\n)\n\ncallbacks = [\n    keras.callbacks.ModelCheckpoint(\n        filepath=\"fine_tuning.keras\",\n        save_best_only=True,\n        monitor=\"val_loss\",\n    )\n]\nhistory = model.fit(\n    augmented_train_dataset,\n    epochs=30,\n    validation_data=validation_dataset,\n    callbacks=callbacks,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:50:40.397129Z","iopub.execute_input":"2025-08-22T15:50:40.397844Z","iopub.status.idle":"2025-08-22T15:57:04.649846Z","shell.execute_reply.started":"2025-08-22T15:50:40.397819Z","shell.execute_reply":"2025-08-22T15:57:04.649209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = keras.models.load_model(\"fine_tuning.keras\")\ntest_loss, test_acc = model.evaluate(test_dataset)\nprint(f\"Test accuracy: {test_acc:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:57:56.133826Z","iopub.execute_input":"2025-08-22T15:57:56.134593Z","iopub.status.idle":"2025-08-22T15:58:08.335715Z","shell.execute_reply.started":"2025-08-22T15:57:56.134567Z","shell.execute_reply":"2025-08-22T15:58:08.334136Z"}},"outputs":[],"execution_count":null}]}