# Transfer Learning with MobileNetV2 in Keras: A Practical Guide

> Source: <https://pub.towardsai.net/transfer-learning-with-mobilenetv2-in-keras-a-practical-guide-f5cc4935108a?source=rss----98111c9905da---4>
> Published: 2026-09-22 19:01:01+00:00

Here’s a number: **3,670**.

That’s how many flower photos I had to teach a neural network the difference between roses, daisies, dandelions, sunflowers and tulips.

For deep learning, that’s tiny. The famous ImageNet dataset has about **1.28 million** training images. Train a deep convolutional network from scratch on 3,670 photos and it will do what small datasets make networks do: **memorize** the training set and fall apart on new images.

So I didn’t train from scratch. I used **transfer learning**.

By the end of this article you’ll know how it works, how to build it in about 20 lines of Keras, and the mistakes that silently wreck most transfer learning projects.

*Don’t train a brain from scratch. Borrow one that has already learned how to see.*

Transfer learning is a machine learning technique where a model trained on one task is reused as the starting point for a different task.

Think of a radiologist who has studied thousands of scans. If you asked them to start reading a new type of scan, they wouldn’t begin from zero. They already know what edges, shapes, textures and abnormalities look like. They only need to learn what’s specific to the new scan.

Neural networks work the same way:

```
Early layers   →  edges, corners, coloursMiddle layers  →  textures, patterns, simple shapesLater layers   →  task-specific parts (e.g. "dog ear", "car wheel")Final layer    →  the actual decision (1,000 ImageNet classes)
```

The early and middle layers learn things that are useful for almost any image task. The final layer is the part that’s specific to the original task. So we keep the first part and replace the last.

```
Pretrained base (frozen)  ──►  Global average pooling  ──►  New Dense(5)     "how to see"                  "summarise"              "which flower?"
```

People often mix these up, so here’s the difference:

Start with feature extraction. Fine-tune only if you need more.

Before building anything, I wanted to see what the pretrained model could already do. I loaded a MobileNetV2 classifier from TensorFlow Hub, resized a goldfish photo to 224×224, scaled it to the 0–1 range, and asked for a prediction:

python

``` python
import numpy as npimport PIL.Image as Imageimport tensorflow_hub as hubimport tf_keras
img_size = (224, 224)classifier = tf_keras.Sequential([    hub.KerasLayer(        "https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/4",        input_shape=img_size + (3,)    )])img = Image.open("goldfish.jpg").resize(img_size)x = np.array(img) / 255.0result = classifier.predict(x[np.newaxis, ...])label_index = np.argmax(result[0], axis=-1)
```

Then I matched the predicted index against the ImageNet label file. The model recognised the goldfish without a single line of training from me.

**Why did I use** **tf_keras?** With TensorFlow 2.16+ and Keras 3, hub.KerasLayer needs the legacy Keras package. That's a very common error in Colab, and importing tf_keras is the fix.

But this classifier can only choose among **ImageNet’s 1,000 categories**. The label list has “daisy”, but no rose, tulip, sunflower or dandelion class. To classify my flowers I needed the model’s *knowledge*, not its *final answer*.

I used the flower photos dataset from TensorFlow: 3,670 images in five folders.

``` python
import pathlib, cv2, numpy as np, tensorflow as tffrom sklearn.model_selection import train_test_split
url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"data_dir = pathlib.Path(    tf.keras.utils.get_file("flower_photos", origin=url, cache_dir=".", untar=True))classes = ["roses", "daisy", "dandelion", "sunflowers", "tulips"]X, y = [], []for label, name in enumerate(classes):    for path in data_dir.glob(f"**/{name}/*"):        img = cv2.imread(str(path))        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)   # OpenCV loads BGR        X.append(cv2.resize(img, (224, 224)))        y.append(label)X, y = np.array(X), np.array(y)X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
```

Two details matter here:

```
base = tf.keras.applications.MobileNetV2(    input_shape=(224, 224, 3),    include_top=False,        # remove the ImageNet classifier    weights="imagenet"        # keep the pretrained knowledge)base.trainable = False        # feature extraction: freeze everything
model = tf.keras.Sequential([    base,    tf.keras.layers.GlobalAveragePooling2D(),    tf.keras.layers.Dense(5)   # one output per flower class])model.summary()
```

Let’s break this down:

Now look at model.summary(). The base has about **2.26 million** parameters, all frozen. The new head has only **6,405** trainable parameters (1,280 × 5 weights plus 5 biases).

*I’m training roughly 6,000 parameters instead of 2,000,000. That’s why a small dataset is enough.*

MobileNetV2 in Keras expects pixel values in the range **[-1, 1]**, which preprocess_input handles:

``` python
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
X_train_p = preprocess_input(X_train.astype("float32"))X_test_p  = preprocess_input(X_test.astype("float32"))model.compile(    optimizer="adam",    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),    metrics=["accuracy"])model.fit(X_train_p, y_train, epochs=5)model.evaluate(X_test_p, y_test)
```

from_logits=True is there because the last layer has no softmax. It outputs raw scores, and the loss function handles the conversion.

Even in the middle of the very first epoch (batch 45 of 86), the running training accuracy was already around **70%** [UPDATE with your final numbers].

After training, my accuracy on the **untouched test set** was **[YOUR TEST ACCURACY]%**, after only **[N] epochs** [ADD your own numbers, and a training curve screenshot if you can].

For a network that had never seen these flower classes before, that’s the power of borrowed knowledge.

If you want more accuracy, unfreeze the last few layers and train with a **much smaller learning rate**:

```
base.trainable = Truefor layer in base.layers[:-30]:      # keep early layers frozen    layer.trainable = False
model.compile(    optimizer=tf.keras.optimizers.Adam(1e-5),   # tiny learning rate    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),    metrics=["accuracy"])model.fit(X_train_p, y_train, epochs=5)
```

You must **recompile** after changing trainable, or the change won't take effect. Also read the Keras fine-tuning guide's advice about BatchNorm layers before unfreezing a large part of the network.

**1. Forgetting to freeze the base.** Without base.trainable = False, you're training all 2.26 million parameters with a large learning rate. That can wipe out the pretrained knowledge you came for.

**2. Wrong preprocessing.** The TF Hub MobileNetV2 model expects inputs in **[0, 1]**. The Keras applications version expects **[-1, 1]**. Same architecture, different scaling. Use the right one for your source.

**3. RGB vs BGR.** OpenCV loads images as BGR, but pretrained models were trained on RGB. Convert with cv2.cvtColor or the colours are wrong.

**4. Off-by-one labels.** The TF Hub classifier outputs 1,001 scores, and index 0 is a “background” class. Match it against a label file with the same 1,001 entries.

**5. Leaky evaluation.** Split into train and test *before* any augmentation or tuning, and touch the test set once. I covered this in detail in my article on [data augmentation](https://medium.com/@fahadrehmann07/data-augmentation-the-simple-trick-that-can-make-your-machine-learning-model-more-robust-098a1399c1aa).

Transfer learning isn’t magic. Think twice when:

Even then, it’s worth a quick experiment. A frozen-base baseline takes minutes.

**What is transfer learning in deep learning?**

 It’s reusing a model trained on a large dataset as the starting point for a new task, so you need far less data and training time.

**What’s the difference between feature extraction and fine-tuning?**

 Feature extraction freezes the pretrained layers and trains only a new head. Fine-tuning also unfreezes some pretrained layers and trains them gently.

**Why freeze the pretrained layers?**

 To protect what they learned from ImageNet while your new head is still random. Updating them early can damage them.

**Which pretrained model should I choose?**

 MobileNetV2 is small and fast, so it’s a great starting point. Larger models such as ResNet, EfficientNet or ConvNeXt can be more accurate but cost more to run.

The biggest lesson from this project is that **you don’t always need more data or a bigger model. Sometimes you need better starting knowledge.**

Transfer learning turns a 3,670-photo dataset into a working image classifier, and the same idea powers modern NLP and computer vision systems far beyond this example.

**If this helped you, clap 👏 (you can clap up to 50 times), follow for more practical deep learning, and tell me in the comments: which pretrained model would you try next, and on what data?**

The full notebook is here: [GITHUB](https://github.com/FahadUrRehman07/FloraVision-Flower-Species-Classification-Using-MobileNetV2-Transfer-Learning)

[Transfer Learning with MobileNetV2 in Keras: A Practical Guide](https://pub.towardsai.net/transfer-learning-with-mobilenetv2-in-keras-a-practical-guide-f5cc4935108a) was originally published in [Towards AI](https://pub.towardsai.net) on Medium, where people are continuing the conversation by highlighting and responding to this story.
