...

How To Actually Fine-Tune MobileNetV2 | Classify 9 Fish Species

How To Actually Fine-Tune MobileNetV2 | Classify 9 Fish Species

🎣 Classify Fish Images Using MobileNetV2 & TensorFlow 🧠

In this hands-on video, I’ll show you how I built a deep learning model that can classify 9 different species of fish using MobileNetV2 and TensorFlow 2.10 — all trained on a real Kaggle dataset!

This Tensorflow image recognition tutorial covers from dataset splitting to live predictions with OpenCV, and demonstrate image classification pipeline.

🚀 What you’ll learn:

  • How to preprocess & split image datasets
  • How to use ImageDataGenerator for clean input pipelines
  • How to customize MobileNetV2 for your own dataset
  • How to freeze layers, fine-tune, and save your model
  • How to run predictions with OpenCV overlays!

👉 Watch the full tutorial here: https://youtu.be/9FMVlhOGDoo

You can download the full code here : https://ko-fi.com/s/44ce6c2ad8

You can download the dataset here : https://www.kaggle.com/datasets/crowww/a-large-scale-fish-dataset

You can find more tutorials, and join my newsletter here : https://eranfeit.net/


Installation :

The code is based on this components :

# ───────────────────────────── # Environment requirements  # ───────────────────────────── # pip install tensorflow==2.10         # deep-learning framework # pip install numpy                    # numerical arrays # pip install opencv-python            # image I/O + basic CV # Python version : 3.9.16

You can download the full code here : https://ko-fi.com/s/44ce6c2ad8


Part 1 : Prepare the data

This part of the code prepares the dataset for a deep learning classification model. It automates the process of:

  • Reading the raw image folders (each representing a different fish species),
  • Creating a new folder structure for training and validation,
  • Splitting the images in each category into training (85%) and validation (15%) sets,
  • Copying them into separate subdirectories for train and validate, maintaining the original category structure.
# Import required libraries import os import random import shutil   # Define the split ratio: 85% for training, 15% for validation splitsize = .85 categories = []  # Path to the raw dataset directory that contains folders for each class source_folder = "E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/Fish_Dataset"  # List all entries (files/folders) in the source directory folders = os.listdir(source_folder) print(folders)  # Collect only the folder names (classes), ignoring any files for subfolder in folders:     if os.path.isdir(source_folder + "/" + subfolder):         categories.append(subfolder)  # Sort the categories alphabetically for consistency categories.sort() print(categories)  # Define the target folder for the processed dataset target_folder = "E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model"  # Create the target directory if it doesn't exist existDataSetPath = os.path.exists(target_folder) if existDataSetPath==False:     os.mkdir(target_folder)   # ------------------------------ # Function: split_data # Purpose: split a folder of images into training and validation sets # ------------------------------ def split_data(SOURCE , TRAINING, VALIDATION, SPLIT_SIZE):     files=[]      # Loop over all files in the source folder     for filename in os.listdir(SOURCE):         file = SOURCE + filename         print(file)         # Ignore empty files (size = 0)         if os.path.getsize(file) > 0 :             files.append(filename)         else:             print(filename + " is 0 length , ignot it ....")     print(len(files))      # Randomly shuffle the list and split based on the split ratio     trainingLength = int(len(files) * SPLIT_SIZE )     shuffleSet = random.sample(files , len(files))     trainingSet = shuffleSet[0:trainingLength]     validSet = shuffleSet[trainingLength:]      # Copy the training files to the training folder     for filename in trainingSet :         thisFile = SOURCE + filename         destination = TRAINING + filename         shutil.copyfile(thisFile , destination)      # Copy the validation files to the validation folder     for filename in validSet :         thisFile = SOURCE + filename         destination = VALIDATION + filename         shutil.copyfile(thisFile , destination)          # Define paths for train and validate folders trainPath = target_folder + "/train" print(trainPath) validatePath = target_folder + "/validate"  # Create the train and validate folders if they don't exist exitsDataSetPth = os.path.exists(trainPath) print(exitsDataSetPth) if not(exitsDataSetPth):     os.mkdir(trainPath)  exitsDataSetPth = os.path.exists(validatePath) if exitsDataSetPth==False:     os.mkdir(validatePath)   # ------------------------------ # Loop over each category and prepare folders # ------------------------------ for category in categories:     # Paths where training and validation images of this category will be stored     trainDestPath = trainPath + "/" + category     validateDestPath = validatePath + "/" + category      print(trainDestPath)      # Create category subfolders if they don’t exist     if os.path.exists(trainDestPath)==False :         os.mkdir(trainDestPath)     if os.path.exists(validateDestPath)==False :         os.mkdir(validateDestPath)      # Define source and destination paths     sourePath = source_folder + "/" + category + "/"     trainDestPath = trainDestPath + "/"     validateDestPath = validateDestPath + "/"      # Log the source and destination of copying     print("Copy from : "+sourePath + " to : " + trainDestPath + " and " +validateDestPath)      # Split and copy the data     split_data(sourePath , trainDestPath , validateDestPath , splitsize)

You can download the full code here : https://ko-fi.com/s/44ce6c2ad8


Part 2 : Build The Model based on Transfer Learning

This code builds a deep learning image classification model using transfer learning with the pre-trained MobileNetV2 as a base. It adds custom fully connected layers on top, compiles the model with the Adam optimizer and a low learning rate, and then trains it on the fish dataset prepared earlier. After training, the model is saved to disk for later inference.

The approach leverages ImageNet pre-trained weights to reduce training time and improve performance on a smaller dataset by only training the new top layers.

# Import necessary Keras modules from tensorflow.keras import Model from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2 , preprocess_input from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.layers import Dense, GlobalAveragePooling2D from tensorflow.keras.optimizers import Adam  # Define paths to training and validation datasets train_path = "E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model/train" validation_path = "E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model/validate"  # ------------------------------ # Create image generators with preprocessing (MobileNet expects input preprocessed this way) # ------------------------------ trainGenerator = ImageDataGenerator(     preprocessing_function=preprocess_input ).flow_from_directory(     train_path,     target_size=(224, 224),  # Resize images to MobileNetV2 expected size     batch_size=30 )  validGenerator = ImageDataGenerator(     preprocessing_function=preprocess_input ).flow_from_directory(     validation_path,     target_size=(224, 224),     batch_size=30 )  # ------------------------------ # Build the transfer learning model # ------------------------------  # Load pre-trained MobileNetV2 without its top (classification) layers baseModel = MobileNetV2(weights='imagenet', include_top=False)  # Add custom layers on top x = baseModel.output x = GlobalAveragePooling2D()(x)         # Reduce spatial dimensions into a single vector x = Dense(512, activation='relu')(x)    # First custom fully-connected layer x = Dense(256, activation='relu')(x)    # Second custom fully-connected layer x = Dense(128, activation='relu')(x)    # Third custom fully-connected layer  # Final prediction layer (9 classes, softmax for multi-class) predictLayer = Dense(9, activation='softmax')(x)  # Create the full model from input to custom output model = Model(inputs=baseModel.input, outputs=predictLayer)  # Print model summary to visualize architecture print(model.summary())  # ------------------------------ # Freeze all the layers in base MobileNetV2 (except last 5 layers we added) # ------------------------------ for layer in model.layers[:-5]:  # Freeze everything except last layers (our custom layers)     layer.trainable = False  # ------------------------------ # Compile the model # ------------------------------ epochs = 5 optimizer = Adam(learning_rate=0.0001)  # Use a small learning rate for transfer learning  model.compile(     loss="categorical_crossentropy",  # Multi-class classification loss     optimizer=optimizer,     metrics=['accuracy'] )  # ------------------------------ # Train the model on the training data and validate on validation set # ------------------------------ model.fit(     trainGenerator,     validation_data=validGenerator,     epochs=epochs )  # ------------------------------ # Save the trained model to disk # ------------------------------ path_for_saved_model = "E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model/fishV2.h5" model.save(path_for_saved_model)

You can download the full code here : https://ko-fi.com/s/44ce6c2ad8


Part 3 : Test the model (Inference)

This is the test image :

Sea Bass test 2

This code loads the trained MobileNetV2 model and performs image classification on a test image. It:

  • Loads and resizes a new image,
  • Preprocesses it to match MobileNetV2 input format,
  • Uses the model to predict which fish species is present,
  • Maps the prediction index to the correct class name,
  • Annotates the image with the prediction label using OpenCV,
  • Saves and displays the annotated image.

This is the final step that simulates real-world prediction/inference after training.

# Import necessary libraries import os from tensorflow.keras.preprocessing import image from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2 , preprocess_input from PIL import Image import numpy as np import tensorflow as tf import cv2  # ------------------------------ # Get the list of class names (categories) used during training # ------------------------------ categories = os.listdir("E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model/train") categories.sort()  # Sort to match the training label order print(categories)  # ------------------------------ # Load the trained model from the saved .h5 file # ------------------------------ path_for_saved_model = "E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model/fishV2.h5" model = tf.keras.models.load_model(path_for_saved_model)  # Optional: Uncomment to print the model architecture # print(model.summary())  # ------------------------------ # Define a function to classify a single image # ------------------------------ def classify_image(imageFile):     x = []      # Load the image using PIL     img = Image.open(imageFile)     img.load()      # Resize image to match MobileNetV2 expected input size     img = img.resize((224,224), Image.ANTIALIAS)      # Convert the PIL image to numpy array format     x = image.img_to_array(img)      # Expand dimensions to add batch size (1, 224, 224, 3)     x = np.expand_dims(x , axis=0)      # Preprocess the image to match MobileNetV2 input scaling     x = preprocess_input(x)      # Debug: print the shape of the image input     print(x.shape)      # Run prediction     pred = model.predict(x)      # Get the index of the highest probability     categoryValue = np.argmax(pred , axis=1)     print(categoryValue)      # Extract the index as an integer     categoryValue = categoryValue[0]     print(categoryValue)         # Map prediction index to class name     result = categories[categoryValue]      return result  # ------------------------------ # Run classification on a test image # ------------------------------ imagePath = "Best-image-classification-models/Classify-Images-Transfer-Learning-MobileNet-V2/Sea-Bass-test.jpg" resultText = classify_image(imagePath)  # Perform prediction print(resultText)  # ------------------------------ # Load the original image using OpenCV and overlay the predicted label # ------------------------------ img = cv2.imread(imagePath)  # Draw the predicted label on the image img = cv2.putText(img , resultText , (50,50) , cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 2)  # Display the image with prediction in a popup window cv2.imshow("img", img)  # Save the result image to disk img = cv2.imwrite("E:/Data-sets/A Large Scale Fish Dataset/Fish_Dataset/dataset_for_model/testFish.png", img)  # Wait for a key press and close the window cv2.waitKey(0) cv2.destroyAllWindows()

You can download the full code here : https://ko-fi.com/s/44ce6c2ad8


Connect :

☕ Buy me a coffee — https://ko-fi.com/eranfeit

🖥️ Email : feitgemel@gmail.com

🌐 https://eranfeit.net

🤝 Fiverr : https://www.fiverr.com/s/mB3Pbb

Enjoy,

Eran

error: Content is protected !!
Eran Feit