Dog vs Cat Classifier — Complete Project (Guide +
Code)
This is a ready-to-run deep learning project that classifies images as Dog or Cat using TensorFlow/Keras
with transfer learning (EfficientNetB0 by default). It includes:
• Clear folder structure
• Data loading & augmentation
• Model building (transfer learning)
• Training with callbacks
• Evaluation (metrics, confusion matrix)
• Inference script for single images & a batch
• Minimal Flask API and Streamlit app for quick demo
Python version: 3.9+ recommended
1) Project Structure
cat-dog-classifier/
├─ data/
│ ├─ train/
│ │ ├─ cat/ (e.g., [Link] ...)
│ │ └─ dog/ (e.g., [Link] ...)
│ ├─ val/
│ │ ├─ cat/
│ │ └─ dog/
│ └─ test/
│ ├─ cat/
│ └─ dog/
├─ src/
│ ├─ [Link]
│ ├─ [Link]
│ ├─ [Link]
│ ├─ [Link]
│ ├─ [Link]
│ ├─ infer_batch.py
│ ├─ app_flask.py
│ └─ app_streamlit.py
├─ [Link]
├─ [Link]
└─ .gitignore
1
You can split your dataset into train/val/test using your own script or tools; for the
popular Kaggle Dogs vs. Cats dataset, place images accordingly.
2) [Link]
tensorflow>=2.12
numpy
pandas
matplotlib
scikit-learn
Pillow
opencv-python
tqdm
flask
streamlit
3) src/[Link]
import os
# Paths
PROJECT_ROOT = [Link]([Link](__file__))
DATA_DIR = [Link](PROJECT_ROOT, 'data')
TRAIN_DIR = [Link](DATA_DIR, 'train')
VAL_DIR = [Link](DATA_DIR, 'val')
TEST_DIR = [Link](DATA_DIR, 'test')
OUTPUT_DIR = [Link](PROJECT_ROOT, 'outputs')
MODELS_DIR = [Link](OUTPUT_DIR, 'models')
LOGS_DIR = [Link](OUTPUT_DIR, 'logs')
PLOTS_DIR = [Link](OUTPUT_DIR, 'plots')
# Create dirs if missing
for d in [OUTPUT_DIR, MODELS_DIR, LOGS_DIR, PLOTS_DIR]:
[Link](d, exist_ok=True)
# Data settings
IMG_SIZE = (224, 224)
BATCH_SIZE = 32
NUM_CLASSES = 2
CLASS_NAMES = ['cat', 'dog'] # Ensure subfolders match these
# Training settings
2
EPOCHS = 20
BASE_LR = 1e-3
PATIENCE_EARLYSTOP = 5
PATIENCE_LR = 3
MODEL_NAME = 'efficientnet_b0_cat_dog.h5'
RANDOM_SEED = 42
4) src/[Link]
import os
import numpy as np
import [Link] as plt
from [Link] import confusion_matrix, classification_report
import itertools
def plot_training(history, out_path=None):
acc = [Link]('accuracy', [])
val_acc = [Link]('val_accuracy', [])
loss = [Link]('loss', [])
val_loss = [Link]('val_loss', [])
[Link]()
[Link](acc, label='train_acc')
[Link](val_acc, label='val_acc')
[Link]('Epoch')
[Link]('Accuracy')
[Link]()
if out_path:
[Link](out_path.replace('.png', '_acc.png'), bbox_inches='tight')
[Link]()
[Link]()
[Link](loss, label='train_loss')
[Link](val_loss, label='val_loss')
[Link]('Epoch')
[Link]('Loss')
[Link]()
if out_path:
[Link](out_path.replace('.png', '_loss.png'), bbox_inches='tight')
[Link]()
def plot_confusion_matrix(cm, classes, normalize=False, title='Confusion
3
matrix', out_path=None):
if normalize:
cm = [Link]('float') / [Link](axis=1)[:, [Link]]
[Link]()
[Link](cm, interpolation='nearest', cmap=[Link])
[Link](title)
[Link]()
tick_marks = [Link](len(classes))
[Link](tick_marks, classes, rotation=45)
[Link](tick_marks, classes)
fmt = '.2f' if normalize else 'd'
thresh = [Link]() / 2.
for i, j in [Link](range([Link][0]), range([Link][1])):
[Link](j, i, format(cm[i, j], fmt),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
[Link]('True label')
[Link]('Predicted label')
plt.tight_layout()
if out_path:
[Link](out_path, bbox_inches='tight')
[Link]()
def print_classification_report(y_true, y_pred, target_names):
print(classification_report(y_true, y_pred, target_names=target_names))
5) src/[Link]
import os
import random
import numpy as np
import tensorflow as tf
from tensorflow import keras
from [Link] import layers
from [Link] import image_dataset_from_directory
from config import (TRAIN_DIR, VAL_DIR, MODELS_DIR, LOGS_DIR, PLOTS_DIR,
IMG_SIZE, BATCH_SIZE, EPOCHS, BASE_LR, PATIENCE_EARLYSTOP,
PATIENCE_LR, MODEL_NAME, RANDOM_SEED)
from utils import plot_training
4
# Reproducibility
[Link]['PYTHONHASHSEED'] = str(RANDOM_SEED)
[Link](RANDOM_SEED)
[Link](RANDOM_SEED)
[Link].set_seed(RANDOM_SEED)
# 1) Load datasets
train_ds = image_dataset_from_directory(
TRAIN_DIR,
validation_split=None,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE,
label_mode='int',
shuffle=True,
)
val_ds = image_dataset_from_directory(
VAL_DIR,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE,
label_mode='int',
shuffle=False,
)
# 2) Performance tweaks
AUTOTUNE = [Link]
train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)
# 3) Data augmentation pipeline
augment = [Link]([
[Link]('horizontal'),
[Link](0.1),
[Link](0.1),
], name='augmentation')
# 4) Build model with transfer learning (EfficientNetB0)
base_model = [Link].EfficientNetB0(
include_top=False,
input_shape=IMG_SIZE + (3,),
weights='imagenet'
)
base_model.trainable = False # first stage: freeze
inputs = [Link](shape=IMG_SIZE + (3,))
x = augment(inputs)
x = [Link].preprocess_input(x)
5
x = base_model(x, training=False)
x = layers.GlobalAveragePooling2D()(x)
x = [Link](0.3)(x)
outputs = [Link](1, activation='sigmoid')(x) # binary
model = [Link](inputs, outputs, name='cat_dog_efficientnetb0')
[Link](
optimizer=[Link](learning_rate=BASE_LR),
loss='binary_crossentropy',
metrics=['accuracy']
)
callbacks = [
[Link](
filepath=[Link](MODELS_DIR, MODEL_NAME),
save_best_only=True,
monitor='val_accuracy',
mode='max'
),
[Link](
monitor='val_accuracy',
patience=PATIENCE_EARLYSTOP,
restore_best_weights=True
),
[Link](
monitor='val_loss',
factor=0.5,
patience=PATIENCE_LR
),
[Link](log_dir=LOGS_DIR)
]
history = [Link](
train_ds,
epochs=EPOCHS,
validation_data=val_ds,
callbacks=callbacks
)
# Optional fine-tuning: unfreeze top layers
base_model.trainable = True
for layer in base_model.layers[:-20]: # fine-tune last ~20 layers
[Link] = False
[Link](
optimizer=[Link](learning_rate=BASE_LR * 0.1),
loss='binary_crossentropy',
metrics=['accuracy']
6
)
history_ft = [Link](
train_ds,
epochs=max(5, EPOCHS // 2),
validation_data=val_ds,
callbacks=callbacks
)
# Save final model
[Link]([Link](MODELS_DIR, MODEL_NAME))
# Plot training curves
plot_training(history, out_path=[Link](PLOTS_DIR, 'training_curves.png'))
plot_training(history_ft, out_path=[Link](PLOTS_DIR,
'training_curves_ft.png'))
print('Training complete. Best model saved to:', [Link](MODELS_DIR,
MODEL_NAME))
6) src/[Link]
import os
import numpy as np
import tensorflow as tf
from [Link] import image_dataset_from_directory
from [Link] import confusion_matrix
from config import (TEST_DIR, MODELS_DIR, IMG_SIZE, BATCH_SIZE, CLASS_NAMES,
MODEL_NAME, PLOTS_DIR)
from utils import plot_confusion_matrix, print_classification_report
# 1) Load test dataset
test_ds = image_dataset_from_directory(
TEST_DIR,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE,
label_mode='int',
shuffle=False
)
# 2) Load trained model
model_path = [Link](MODELS_DIR, MODEL_NAME)
7
model = [Link].load_model(model_path)
# 3) Predict
probs = [Link](test_ds)
preds = ([Link]() >= 0.5).astype(int)
# 4) Collect ground-truth labels
true_labels = []
for _, y in test_ds.unbatch():
true_labels.append(int([Link]()))
true_labels = [Link](true_labels)
# 5) Metrics & confusion matrix
cm = confusion_matrix(true_labels, preds)
plot_confusion_matrix(cm, classes=CLASS_NAMES, normalize=False,
title='Confusion Matrix',
out_path=[Link](PLOTS_DIR, 'confusion_matrix.png'))
print_classification_report(true_labels, preds, target_names=CLASS_NAMES)
acc = (true_labels == preds).mean()
print(f"Test Accuracy: {acc:.4f}")
7) src/[Link] (single image inference)
import os
import sys
import numpy as np
import tensorflow as tf
from PIL import Image
from config import IMG_SIZE, MODELS_DIR, MODEL_NAME, CLASS_NAMES
model = [Link].load_model([Link](MODELS_DIR, MODEL_NAME))
def load_image(path):
img = [Link](path).convert('RGB').resize(IMG_SIZE)
arr = [Link](img)
arr = [Link].preprocess_input(arr)
return np.expand_dims(arr, 0)
if __name__ == '__main__':
8
if len([Link]) < 2:
print('Usage: python [Link] path/to/[Link]')
[Link](1)
img_path = [Link][1]
x = load_image(img_path)
prob = float([Link](x)[0][0])
label = CLASS_NAMES[1] if prob >= 0.5 else CLASS_NAMES[0]
confidence = prob if prob >= 0.5 else 1 - prob
print(f'Prediction: {label} (confidence={confidence:.3f})')
8) src/infer_batch.py (batch inference on a folder)
import os
import glob
import csv
import numpy as np
import tensorflow as tf
from PIL import Image
from config import IMG_SIZE, MODELS_DIR, MODEL_NAME, CLASS_NAMES, OUTPUT_DIR
model = [Link].load_model([Link](MODELS_DIR, MODEL_NAME))
def load_image(path):
img = [Link](path).convert('RGB').resize(IMG_SIZE)
arr = [Link](img)
arr = [Link].preprocess_input(arr)
return np.expand_dims(arr, 0)
def predict_folder(folder):
results = []
for fp in [Link]([Link](folder, '*')):
if [Link]().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
x = load_image(fp)
prob = float([Link](x, verbose=0)[0][0])
label_idx = 1 if prob >= 0.5 else 0
conf = prob if prob >= 0.5 else 1 - prob
[Link](([Link](fp), CLASS_NAMES[label_idx], conf))
return results
9
if __name__ == '__main__':
import sys
if len([Link]) < 2:
print('Usage: python infer_batch.py path/to/folder_of_images')
[Link](1)
folder = [Link][1]
rows = predict_folder(folder)
out_csv = [Link](OUTPUT_DIR, 'batch_predictions.csv')
with open(out_csv, 'w', newline='') as f:
writer = [Link](f)
[Link](['filename', 'prediction', 'confidence'])
[Link](rows)
print('Saved predictions to', out_csv)
9) src/app_flask.py (simple REST API)
from flask import Flask, request, jsonify
import io
from PIL import Image
import numpy as np
import tensorflow as tf
from config import IMG_SIZE, MODELS_DIR, MODEL_NAME, CLASS_NAMES
app = Flask(__name__)
model = [Link].load_model([Link](MODELS_DIR, MODEL_NAME))
def preprocess(img: [Link]):
img = [Link]('RGB').resize(IMG_SIZE)
arr = [Link](img)
arr = [Link].preprocess_input(arr)
return np.expand_dims(arr, 0)
@[Link]('/predict', methods=['POST'])
def predict():
if 'file' not in [Link]:
return jsonify({'error': 'no file uploaded'}), 400
file = [Link]['file']
img = [Link]([Link]([Link]()))
10
x = preprocess(img)
prob = float([Link](x)[0][0])
idx = 1 if prob >= 0.5 else 0
conf = prob if prob >= 0.5 else 1 - prob
return jsonify({'prediction': CLASS_NAMES[idx], 'confidence': conf})
if __name__ == '__main__':
[Link](host='[Link]', port=5000, debug=True)
10) src/app_streamlit.py (quick UI demo)
import streamlit as st
import numpy as np
import tensorflow as tf
from PIL import Image
import os
from config import IMG_SIZE, MODELS_DIR, MODEL_NAME, CLASS_NAMES
st.set_page_config(page_title='Dog vs Cat Classifier', page_icon='🐶🐱')
@st.cache_resource
def load_model():
return [Link].load_model([Link](MODELS_DIR, MODEL_NAME))
model = load_model()
[Link]('🐶🐱 Dog vs Cat Classifier')
file = st.file_uploader('Upload an image', type=['jpg', 'jpeg', 'png'])
if file:
img = [Link](file).convert('RGB').resize(IMG_SIZE)
[Link](img, caption='Uploaded Image', use_column_width=True)
arr = [Link](img)
arr = [Link].preprocess_input(arr)
x = np.expand_dims(arr, 0)
prob = float([Link](x)[0][0])
label_idx = 1 if prob >= 0.5 else 0
conf = prob if prob >= 0.5 else 1 - prob
11
[Link](f'Prediction: **{CLASS_NAMES[label_idx].title()}**')
[Link](f'Confidence: {conf:.3f}')
11) [Link]
# Dog vs Cat Classifier
A TensorFlow/Keras-based image classifier for dogs vs. cats using transfer
learning (EfficientNetB0).
## Setup
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r [Link]
Data
Place images under data/train , data/val , and data/test with subfolders cat and dog .
Example:
data/
train/
cat/ [Link] ...
dog/ [Link] ...
val/
cat/
dog/
test/
cat/
dog/
You can create val and test splits using any splitter tool or a quick Python script.
Train
python -m [Link]
12
Evaluate
python -m [Link]
Predict (single image)
python -m [Link] path/to/[Link]
Batch Inference (folder)
python -m src.infer_batch path/to/folder
Run Flask API
python -m src.app_flask
# POST an image to [Link]
Run Streamlit App
streamlit run src/app_streamlit.py
Notes
• Default image size is 224x224; adjust in [Link] if needed.
• Fine-tuning is enabled after initial training; change layer freezing for speed/accuracy trade-offs.
• Swap backbones easily (e.g., ResNet50, MobileNetV2) — see below.
---
## 12) Swapping the Backbone (e.g., ResNet50)
In `src/[Link]`, replace EfficientNetB0 with ResNet50:
```python
base_model = [Link].ResNet50(
include_top=False,
input_shape=IMG_SIZE + (3,),
weights='imagenet'
)
13
preprocess = [Link].preprocess_input
...
x = preprocess(x)
...
Keep the rest identical. Adjust fine-tuning slices (e.g., [:-30] ).
13) Tips for Best Results
• Balanced data: Keep cat/dog counts similar.
• Augmentation: Increase if overfitting; decrease if underfitting.
• Batch size: 16–64 depending on GPU RAM.
• Learning rate: Start 1e-3; reduce on plateau.
• Early stopping: Prevents overfitting; monitor val_accuracy .
• Confusion matrix: Inspect which class is misclassified more often.
• Mixed precision (if on GPU):
[Link].mixed_precision.set_global_policy("mixed_float16") for speed.
14) (Optional) Quick Split Script
If you only have a single folder with cat.* and dog.* images, this script creates train/val/test
splits.
# save as tools/split_dataset.py, run: python tools/split_dataset.py /path/to/
images
import os, shutil, random, sys
[Link](42)
src = [Link][1]
out = [Link]([Link](src), 'data')
for split in ['train', 'val', 'test']:
for cls in ['cat', 'dog']:
[Link]([Link](out, split, cls), exist_ok=True)
cats = [f for f in [Link](src) if [Link]('cat')]
dogs = [f for f in [Link](src) if [Link]('dog')]
for label, files in [('cat', cats), ('dog', dogs)]:
[Link](files)
n = len(files)
train_n = int(0.7*n)
val_n = int(0.15*n)
14
splits = [
('train', files[:train_n]),
('val', files[train_n:train_n+val_n]),
('test', files[train_n+val_n:])
]
for split, names in splits:
for name in names:
[Link]([Link](src, name), [Link](out, split, label,
name))
print('Done. Created train/val/test under', out)
15) Explanation Summary
• Why transfer learning? Pretrained models learned generic visual features on ImageNet; they adapt
quickly to cats vs. dogs with fewer images and less compute.
• Pipeline: Load data → Augment → Preprocess → Feature extractor (frozen) → Classifier head →
Fine-tune top layers → Evaluate → Deploy.
• Metrics: Accuracy + classification report to capture precision/recall per class.
This template is production-friendly, easy to extend, and suitable for academic reports and demos.
15