Module 6 · Lesson 335 minIntermediate

Project 21: Image Classifier

What you'll build
Classify any photo into 1,000 categories with a pretrained neural network.

Introduction

Image classification is the hello-world of deep learning — and with MobileNetV2, a pretrained model from TensorFlow Hub, it fits in about 60 lines. Upload any photo and the app returns its top-5 guesses from 1,000 categories (dogs, guitars, coffee mugs) with confidence scores. No training, no GPUs, no dataset wrangling: transfer learning means someone else's millions of images are working for you.

This is the heavyweight sibling of the QR scanner — same upload-decode-display flow, but the "decode" step is a neural network forward pass.

Features

  • 1000-category classification — ImageNet's full label space.
  • Top-5 predictions — confidence-ranked results with progress bars.
  • Fast model — MobileNetV2 runs on CPU in under a second.
  • Cached model loading — the 14MB weights download once.
  • Confidence threshold — filter out low-confidence noise.
  • Prerequisites

  • Python 3.9+ — from python.org.
  • Dependencies:
  • code
    pip install streamlit tensorflow-cpu pillow numpy

    Step 1: Create the Script

    Save as classifier_app.py:

    code
    import streamlit as st
    import numpy as np
    from PIL import Image
    
    st.set_page_config(page_title="Image Classifier", page_icon="🖼️")
    st.title("🖼️ Image Classifier — MobileNetV2")
    
    
    @st.cache_resource
    def load_model():
        from tensorflow.keras.applications import MobileNetV2
        from tensorflow.keras.applications.mobilenet_v2 import decode_predictions
        model = MobileNetV2(weights="imagenet")
        return model, decode_predictions
    
    
    model, decode = load_model()
    
    uploaded = st.file_uploader("Upload an image", type=["png", "jpg", "jpeg"])
    min_conf = st.slider("Minimum confidence %", 0, 100, 5)
    
    if uploaded:
        img = Image.open(uploaded).convert("RGB")
        st.image(img, caption="Your image", width=350)
    
        if st.button("🔍 Classify", type="primary"):
            with st.spinner("Thinking..."):
                resized = img.resize((224, 224))
                arr = np.array(resized, dtype=np.float32)
                arr = np.expand_dims(arr, 0)
    
                preds = model.predict(arr, verbose=0)
                results = decode(preds, top=5)[0]
    
            st.subheader("Predictions")
            shown = 0
            for _, name, score in results:
                pct = score * 100
                if pct >= min_conf:
                    st.markdown(f"**{name.replace('_', ' ').title()}**")
                    st.progress(score, text=f"{pct:.1f}%")
                    shown += 1
            if shown == 0:
                st.warning("Nothing above your confidence threshold.")

    Step 2: Run the App

    code
    streamlit run classifier_app.py

    The first run downloads the weights (one time, ~14MB). Try a pet photo, a kitchen object, a screenshot of a car — then try something deliberately weird and watch the model's confidence crumble.

    How It Works

    MobileNetV2 is a convolutional network trained on ImageNet: 1.2M images across 1,000 categories. Preprocessing is minimal but non-negotiable — resize to 224×224 (its trained input size) and scale values the way it expects. Every pixel becomes a float; expand_dims adds the batch dimension because Keras models expect shape (samples, 224, 224, 3).

    The output is 1,000 logits converted to a probability distribution — one score per category summing to 1.0. decode_predictions maps indices to human names and returns the top-k. The confidence slider filters that list client-side; a 3% "sea slug" guess is rarely what the user wanted to see.

    @st.cache_resource is the performance hero: model loading takes seconds and hundreds of MB, so it must happen once per process, not once per rerun. This is different from cache_data (which caches *values*) — cache_resource caches *objects* like models and connections, the same pattern NLTK's analyzer uses in the sentiment app.

    Common Errors & Fixes

  • First prediction is very slow — weights are downloading and the graph is warming; subsequent runs are fast. Warm up at startup with a dummy predict if the delay bothers users.
  • `PIL.UnidentifiedImageError` — unsupported format (HEIC again); convert or add pillow-heif.
  • Predictions all wrong for line art / sketches — the model trained on photos; domain shift is real, not a bug.
  • Memory errors on tiny machines — use MobileNetV2(weights="imagenet", alpha=0.35) for a 4× smaller variant.
  • Key Concepts

  • Transfer learning — pretrained weights as a starting point; zero training here.
  • Input contracts — 224×224 RGB floats; models fail silently when fed surprises.
  • `cache_resource` vs `cache_data` — cache objects once, cache computed values per-input.
  • Top-k + threshold — showing ranked uncertainty beats showing one answer.
  • What to Try Next

  • Add camera input with st.camera_input for live classification.
  • Show grad-CAM heatmaps — overlay where the model looked (needs tf-keras-vis).
  • Swap in EfficientNet via one line and compare predictions.
  • Batch mode with accept_multiple_files=True and a results table, like the file organizer.
  • FAQ

    Can I classify custom categories (my own products)?

    Not with this model — it only knows ImageNet's 1,000 classes. For custom categories you'd retrain the final layer (fine-tuning) on your own labeled images; MobileNet is actually the standard base for that.

    Why CPU instead of GPU?

    MobileNet is deliberately small — CPU inference is under a second, which is plenty for an app. tensorflow-cpu also avoids a 2GB CUDA install.

    Why 224×224 specifically?

    It's the input size the architecture was designed and trained with. Feeding other sizes either errors out or forces a resize — the network's learned filters expect that resolution.

    Adapted from: Image Classifier using Python, Streamlit, and MobileNet

    Checkpoint
    Uploading a photo returns top-5 predictions with confidence bars.
    What you learned
    • Transfer learning with MobileNet
    • Input preprocessing contracts
    • cache_resource for models