In 07-01 we left a figure on the table — 82-85% accuracy for the category classifier — and a promise: to beat it while barely writing more code. This final project keeps that promise with the transfer learning of 05-03: instead of training a CNN from scratch, we take MobileNetV2 pretrained on ImageNet and adapt it to our problem in two stages (feature extraction and fine-tuning). By the end you will compare both approaches with numbers in hand, decide which one TecnoMarket deploys, and close the module with all five projects delivered.
Contents
- The brief: redo 07-01 and beat it
- Phase 1: data — resizing and the correct
preprocess_input - Phase 2: the model with a frozen MobileNetV2
- Phase 3: two-stage training (extraction → fine-tuning)
- Phase 4: comparing the curves across stages
- Phase 5: an honest final comparison against the 07-01 CNN
- Phase 6: when transfer learning pays off (the 2×2 table, revisited)
- Phase 7: final packaging and business decision
- Closing the module: the TecnoMarket portfolio
The brief: redo 07-01 and beat it
Same problem, same data, same yardstick: CIFAR-10 as the stand-in for TecnoMarket's product categories, the same frozen test set from 07-01 and the same train/validation split. Only the strategy changes: instead of learning to see from scratch with 45,000 images, we start from a network that already learned to see with 1.4 million (ImageNet) and teach it only our categories. It is the central bet of 05-03; today we measure it against a real alternative we built ourselves — the most honest comparison possible.
Quantitative goal: clearly beat the 85% of 07-01. Typical expected result: 90-93%.
Phase 1: data — resizing and the correct preprocess_input
MobileNetV2 does not accept 32×32: it was pretrained on large images and its cascade of downsamplings would leave empty feature maps. We resize to 160×160 (a compromise between fidelity and cost; 05-03). And the point that breaks the most projects: every pretrained network demands its own preprocessing. MobileNetV2 expects pixels in [-1, 1], not the /255 we used in 07-01:
import tensorflow as tf
import numpy as np
tf.random.set_seed(42)
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
x_val, y_val = x_train[45000:], y_train[45000:] # same split as 07-01
x_train, y_train = x_train[:45000], y_train[:45000]
IMG_SIZE = 160
BATCH = 64
AUTOTUNE = tf.data.AUTOTUNE
augmentation = tf.keras.Sequential([ # same augmentation as 07-01 (05-04)
tf.keras.layers.RandomFlip("horizontal"),
tf.keras.layers.RandomTranslation(0.1, 0.1),
tf.keras.layers.RandomZoom(0.1),
])
def preprocess(x, y):
x = tf.image.resize(tf.cast(x, tf.float32), (IMG_SIZE, IMG_SIZE))
x = tf.keras.applications.mobilenet_v2.preprocess_input(x) # -> [-1, 1]
return x, y
def dataset(x, y, training=False):
ds = tf.data.Dataset.from_tensor_slices((x, y)).map(preprocess, AUTOTUNE)
if training:
ds = ds.shuffle(10_000).batch(BATCH).map(
lambda a, b: (augmentation(a, training=True), b), AUTOTUNE)
else:
ds = ds.batch(BATCH)
return ds.prefetch(AUTOTUNE)
train_ds = dataset(x_train, y_train, training=True)
val_ds = dataset(x_val, y_val)
test_ds = dataset(x_test, y_test)Using the preprocess_input from the same model family is not optional: with the /255 from 07-01, MobileNetV2 would receive inputs outside the distribution it was pretrained on and would score several points lower without raising any visible error.
Phase 2: the model with a frozen MobileNetV2
The 05-03 recipe: a pretrained base without its head (include_top=False), frozen, plus a minimal new head:
from tensorflow.keras import layers, models
base = tf.keras.applications.MobileNetV2(
input_shape=(IMG_SIZE, IMG_SIZE, 3),
include_top=False, # without the 1000-class ImageNet head
weights="imagenet",
)
base.trainable = False # stage 1: fully frozen
inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))
x = base(inputs, training=False) # training=False: BN in inference mode (05-03)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.3)(x)
outputs = layers.Dense(10, activation="softmax")(x)
model = models.Model(inputs, outputs, name="tecnomarket_tl")
model.summary()
# Total: ~2.3M parameters; trainable: ~13,000 (the head only)Note the disproportion: the base contributes ~2.26M already-learned parameters; we train only ~13,000. The training=False in the call to the base is the subtle detail from 05-03: it keeps the BatchNormalization layers in inference mode even during the later fine-tuning, protecting the ImageNet statistics.
Phase 3: two-stage training (extraction → fine-tuning)
Feature extraction stage — head only, normal lr:
model.compile(optimizer=tf.keras.optimizers.Adam(1e-3),
loss="sparse_categorical_crossentropy", metrics=["accuracy"])
callbacks = [
tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=5,
restore_best_weights=True),
tf.keras.callbacks.ModelCheckpoint("models/tl_extraction.keras",
monitor="val_accuracy", save_best_only=True),
tf.keras.callbacks.TensorBoard(log_dir="logs/tl_phase1"),
]
hist1 = model.fit(train_ds, validation_data=val_ds, epochs=15,
callbacks=callbacks)
# Typical result: val_accuracy ~86-88% in 8-12 epochsAlready at this stage — training 0.6% of the parameters — we beat the CNN from 07-01. ImageNet's features (edges, textures, object shapes) transfer well to CIFAR-10.
Fine-tuning stage — unfreeze the final block, low lr:
base.trainable = True
for layer in base.layers[:100]: # MobileNetV2 has 154 layers: we release the final ~1/3
layer.trainable = False
model.compile(optimizer=tf.keras.optimizers.Adam(1e-5), # lr 100x lower
loss="sparse_categorical_crossentropy", metrics=["accuracy"])
callbacks[1] = tf.keras.callbacks.ModelCheckpoint("models/tl_finetuning.keras",
monitor="val_accuracy", save_best_only=True)
callbacks[2] = tf.keras.callbacks.TensorBoard(log_dir="logs/tl_phase2")
hist2 = model.fit(train_ds, validation_data=val_ds, epochs=15,
callbacks=callbacks)
# Typical result: val_accuracy ~90-93%The two golden rules of 05-03, applied: recompile after changing trainable (otherwise the change has no effect) and a very low learning rate (1e-5): the unfrozen layers already hold valuable knowledge; a high lr would destroy it within the first iterations (so-called catastrophic forgetting). We unfreeze only the final block because the early layers (edges, colors) are universal, while the late ones are the ones worth specializing in our categories.
Phase 4: comparing the curves across stages
Concatenate both histories and plot the validation accuracy with a vertical line at the stage change:
import matplotlib.pyplot as plt
acc = hist1.history["val_accuracy"] + hist2.history["val_accuracy"]
plt.plot(acc, label="val_accuracy")
plt.axvline(len(hist1.history["val_accuracy"]) - 1, ls="--", c="gray",
label="fine-tuning start")
plt.xlabel("epoch"); plt.legend(); plt.grid(True)A typical reading of the curve:
- Stage 1: a fast climb in the first epochs and a plateau around ~87% — the head squeezes out everything that frozen, generic features can give.
- Stage change: a visible jump of 2-4 points in the first fine-tuning epochs — the final layers adapt to CIFAR textures — and a new plateau at ~91-92%.
- If in stage 2 the accuracy drops abruptly, the lr was too high: it's the classic symptom.
Phase 5: an honest final comparison against the 07-01 CNN
Evaluate on the same frozen test set from 07-01 and put the figures side by side. Typical values (yours will vary somewhat; use the ones you actually obtain):
test_loss, test_acc = model.evaluate(test_ds)
print(f"Transfer learning test accuracy: {test_acc:.3f}")| Criterion | Our own CNN (07-01) | MobileNetV2 + fine-tuning |
|---|---|---|
| Test accuracy | ~83% | ~91% |
| Total parameters | ~1.2M | ~2.3M |
| Trained parameters | ~1.2M (all) | ~13K + ~0.9M (stage 2) |
| Epochs to the best model | 40-50 | 10 + 8 |
| Training time (GPU) | ~20 min | ~25-35 min* |
| Size on disk | ~14 MB | ~28 MB |
| Inference cost | Lower (32×32 input) | Higher (160×160 input) |
* Each transfer learning epoch is more expensive (160×160 images), but far fewer are needed.
Honest conclusions: +8 accuracy points is a huge improvement (errors fall from ~17% to ~9%: nearly halved); the price is a bigger model and more expensive inference. Repeat the 07-01 confusion matrix too: you'll see that the troublesome pairs (cat↔dog) remain the worst, but with half the confusions — with the 0.80 threshold from 03-04, typical automatic coverage rises from ~70% to ~85%.
Phase 6: when transfer learning pays off (the 2×2 table, revisited)
We revisit the 2×2 table from 05-03 and place this project in it:
| Data similar to ImageNet | Very different data | |
|---|---|---|
| Little data | Feature extraction (head only) | Careful deep fine-tuning, or look for another base |
| Lots of data | Partial fine-tuning ← our case | Consider training from scratch |
CIFAR-10 falls in "lots of data (45K), similar to ImageNet (natural objects)": the partial fine-tuning cell, and the results confirm it. The from-scratch CNN of 07-01 would only have been competitive in the bottom-right cell — for instance, medical images or spectrograms with millions of examples. For TecnoMarket's real product photos (everyday objects, tens of thousands of photos), today's recipe is almost certainly the right one.
Phase 7: final packaging and business decision
Decision: TecnoMarket deploys the transfer learning model. The accuracy improvement halves the load on the review queue (03-04), which more than makes up for the higher inference cost. The 07-01 CNN stays documented as the baseline in the repository (git, 06-04) — every model in production needs a baseline to justify itself against.
Packaging per 06-05, with the preprocessing inside the model:
raw_inputs = tf.keras.layers.Input(shape=(32, 32, 3), dtype=tf.uint8) # raw image
x = tf.cast(raw_inputs, tf.float32)
x = tf.keras.layers.Resizing(IMG_SIZE, IMG_SIZE)(x)
x = tf.keras.applications.mobilenet_v2.preprocess_input(x)
prob = model(x, training=False)
serving_model = tf.keras.Model(raw_inputs, prob)
serving_model.save("models/category_classifier_v2.keras")The resizing and preprocess_input travel inside the .keras file: the FastAPI service from 06-05 receives the raw image and cannot get the preprocessing wrong. Before replacing v1, the 06-05 protocol: validate v2 on the frozen set, compare against v1 on the same images, deploy with a rollback path, and recalibrate the confidence threshold on validation.
Closing the module: the TecnoMarket portfolio
With this fifth project, the TecnoMarket team (that is, you) delivers its complete portfolio:
| Project | Business problem | Main technique | Status |
|---|---|---|---|
| 07-01 | Automatic item listing | Our own CNN + threshold and review queue | Documented baseline |
| 07-02 | Description drafts | Character-level LSTM + temperature | Prototype with human review |
| 07-03 | Fraud detection | Autoencoder + reconstruction error | In production with periodic recalibration |
| 07-04 | Generative creatives | DCGAN with a GradientTape loop | Educational prototype; production requires diffusion |
| 07-05 | Automatic listing, v2 | MobileNetV2 transfer learning | Deployed, replaces 07-01 |
Five projects, four modalities (images, text, tabular data, generation) and one common method: clear phases, honest splits, metrics fitted to the problem, humans in the loop and a packaged delivery.
Common Mistakes and Tips
- Using
/255instead of the model family'spreprocess_input: the most common silent error in transfer learning; it costs several accuracy points without throwing a single exception. - Forgetting to recompile after changing
trainable: Keras fixes which variables get trained at compile time; without recompiling, your "fine-tuning" is still training only the head. - Fine-tuning with the stage 1 lr: it destroys the pretrained weights in a few iterations and the accuracy collapses. Drop the lr by two orders of magnitude.
- Unfreezing the whole base with little data: more free parameters than your data can govern; overfitting all but guaranteed. Unfreeze by blocks, from the end backward.
- Comparing models across different splits: the phase 5 comparison is valid only because the frozen test set and the train/val split are identical to 07-01's. Changing the data between comparisons invalidates the table.
Exercises
- Repeat the project training only the extraction stage (no fine-tuning) and extend the phase 5 table with three columns: our own CNN, extraction alone, extraction + fine-tuning. How much does each step contribute?
- Try
IMG_SIZE = 96instead of 160 and measure accuracy and time per epoch. Where does it land in the cost/benefit comparison? - Unfreeze only the last 20 layers (instead of ~54) and, separately, the whole base. Compare final accuracy and curve behavior across the three unfreezing scenarios.
Solutions
- Just skip stage 2. Typical table: our own CNN ~83%, extraction ~87%, extraction+fine-tuning ~91%. Reading: the bulk of the jump comes from pretraining (ImageNet features); fine-tuning adds the final refinement. If the compute budget is minimal, extraction alone already beats the homemade model.
- At 96×96 the time per epoch roughly halves and the typical accuracy drops 1-2 points (~89-90%): less resolution, less detail for the deep layers. It is a reasonable middle ground for fast iteration, training the final version at 160.
- With 20 layers the result stays ~1 point below the base scenario (less adaptation capacity); with the whole base and lr=1e-5 it can match or gain a few tenths, but the curves are more fragile (higher risk of overfitting and of the initial drop) and training is slower. The final third is the robust compromise: enough adaptation without putting the universal layers at risk.
Conclusion
Final project delivered and promise kept: the v2 classifier with MobileNetV2 beats our own 07-01 CNN by about 8 points, with an honest cost comparison and a justified deployment decision. More important than the figure is the judgment you take with you: the 2×2 table for deciding when to transfer, the two stages with their two learning rates, and the discipline of always comparing against a baseline on the same frozen test set. This closes module 7: five end-to-end projects in the TecnoMarket portfolio, integrating everything learned from the first dense network of 02-05 to the deployment of 06-05. But a professional isn't done when the model works: what we have built — models that classify products, generate text and images, and flag customers as suspicious — has consequences for real people. Module 8 tackles exactly that: the ethics, impact and future of Deep Learning that every practitioner must master.
Deep Learning Course
Module 1: Introduction to Deep Learning
- What is Deep Learning?
- History and evolution of Deep Learning
- Applications of Deep Learning
- Basic concepts of neural networks
- Setting up the work environment
Module 2: Neural Network Fundamentals
- Perceptron and Multilayer Perceptron
- Activation functions
- Forward and backward propagation
- Optimization and loss functions
- Your first complete neural network
Module 3: Convolutional Neural Networks (CNN)
- Introduction to CNNs
- Convolutional and pooling layers
- Popular CNN architectures
- CNN applications in image recognition
Module 4: Recurrent Neural Networks (RNN)
- Introduction to RNNs
- LSTM and GRU
- RNN applications in natural language processing
- Sequences and time series
Module 5: Advanced Deep Learning Techniques
- Generative Adversarial Networks (GAN)
- Autoencoders
- Transfer Learning
- Regularization and improvement techniques
- Attention mechanisms and Transformers
Module 6: Tools and Frameworks
- Introduction to TensorFlow
- Introduction to PyTorch
- Framework comparison
- Development environments and additional resources
- Saving, loading and deploying models
Module 7: Hands-On Projects
- Image classification with CNNs
- Text generation with RNNs
- Anomaly detection with Autoencoders
- Building a GAN for image generation
- Fine-tuning a pretrained model
