Computer Vision · Whole-Image Understanding

Visual Scene ClassificationNaming the Place, Not the Parts

An object detector tells you there is a bed and a lamp. Scene classification answers a different, harder question: what kind of place is this? A bedroom, a hotel room, a furniture showroom? Same objects, three different answers. This is the art of reading the whole image at once — the gestalt — and it is the front door to robot navigation, photo search, and remote sensing.

Prerequisites: you know what a CNN roughly is and what a softmax does. If "a network outputs a probability over classes" makes sense, you're ready. That's it.
10
Chapters
9
Simulations
0
Assumed Knowledge

Chapter 0: A Robot in a Doorway

You are a delivery robot. You have just rolled through a doorway, and your camera fills with a fresh image. Before you plan another inch of motion you need to answer one question: where am I? Not your GPS coordinates — what kind of place this is. A kitchen means watch for people and hot surfaces. A server room means stay on the tile and never block an aisle. A lobby means look for the elevator. The single word you assign to this whole image will steer everything you do next.

Your first instinct is to reach for the tool everyone has: an object detector, a network that draws boxes around things and names them. You run it. It returns: chair (0.97), table (0.94), bottle (0.81), person (0.76). Useful — but it has not answered your question. Chairs and tables and bottles live in kitchens, conference rooms, cafes, break rooms, and classrooms alike. The detector named the parts. You asked about the place.

This is the gap that scene classification fills. Scene classification (also called scene recognition) assigns a single label describing the environment of the whole image — "kitchen", "beach", "operating room", "highway" — chosen from a fixed list of place categories. It is not about any one object. It is about the configuration: what is present, how it is arranged, the colors, the light, the open space versus clutter, the overall feel. Psychologists call this the gestalt: the whole that is more than the sum of its parts.

Drag the toggle below. The same room can be read two ways. The detector view scatters boxes over individual objects and never commits to what the room is. The scene view ignores the boxes and commits to one word for the entire frame. Notice that you, a human, knew the scene label before you consciously found a single object — you read the place in a glance. That glance is exactly the capability we are going to build.

Objects vs. Scene — two ways to read one image

Toggle between what an object detector sees (named parts) and what a scene classifier sees (one label for the whole place). Same pixels, two questions.

The misconception to kill on day one: "scene classification is just object detection plus a vote." It is not. A break room and a small kitchen contain almost identical objects, yet they are different scenes; a modern operating room and a clean industrial lab share steel, lights, and tile, yet they are different scenes. The information that separates them lives in the arrangement and global statistics, not in any object list. A vote over detected objects throws away exactly the signal you need.

Why should you care beyond a delivery robot? The same question — "what is this whole image?" — powers photo libraries that let you search "sunset" or "kitchen," satellite systems that label a square kilometer as "forest" or "industrial," and self-driving stacks that switch behavior between "highway", "residential street", and "parking lot". Whenever a machine must understand context before it acts, scene classification is the first move.

Over the next nine chapters we will build this capability from the ground up: define the task and its strange datasets, confront why it is genuinely hard (the accuracy numbers are humbling for a reason), build a CNN classifier by hand, discover why global context is the whole game, watch the field move from CNNs to Vision Transformers to billion-parameter foundation models, and finish with a live classifier you can break on purpose. Let's start by pinning down what a "scene" even is.

A robot's object detector reports "chair, table, bottle, person" with high confidence. Why is this not enough to decide whether the robot is in a kitchen, a cafe, or a conference room?

Chapter 1: What Exactly Is a Scene

Let us make the task precise, because precision is what lets us build a system instead of waving at one. Scene classification is a single-label image classification problem: given an image, output one category from a fixed, predefined taxonomy of places. The output is a probability distribution over K scene classes, and we read off the most probable one. The whole machine is a function that eats pixels and emits one of K words.

So what is in the taxonomy? This is where scene classification gets its character, and the answer comes from the datasets that defined the field. The big three you must know:

DatasetClassesImagesFlavor
Places365365~1.8M (Standard); ~8M (Challenge)The modern benchmark: indoor + outdoor places at scale
SUN397397~108KBroad "Scene UNderstanding" taxonomy, the classic survey set
MIT Indoor6767~15,600Indoor only — the hardest slice, because indoor scenes share objects

Places365 (from MIT's Bolei Zhou and colleagues) is the one to anchor on. It contains roughly 1.8 million images in its Standard split, hand-organized into 365 categories like airport_terminal, bamboo_forest, bedroom, operating_room, and pizzeria. The category names tell you the philosophy: these are functional places a person would name, not visual textures. A bedroom is defined by what you do there as much as by how it looks.

The model's job, then, is a 365-way softmax. Concretely: the network produces 365 real-valued logits — one raw score per class — and softmax turns them into probabilities that sum to 1. If the logit for "bedroom" is 6.0 and the logit for "hotel_room" is 5.5 and everything else is near zero, the softmax will hand most of the probability to those two and report "bedroom" as the top-1 guess. Let us do that arithmetic by hand, because the numbers will matter enormously in Chapter 2.

Softmax, worked by hand. Suppose for one image the model outputs three relevant logits: bedroom = 6.0, hotel_room = 5.5, child's_room = 4.0 (ignore the other 362, which are near −∞ in effect). Softmax exponentiates each and normalizes: e6.0 ≈ 403.4, e5.5 ≈ 244.7, e4.0 ≈ 54.6. Sum ≈ 702.7. So P(bedroom) = 403.4 / 702.7 ≈ 0.574, P(hotel_room) ≈ 244.7 / 702.7 ≈ 0.348, P(child's_room) ≈ 54.6 / 702.7 ≈ 0.078. Top-1 = bedroom at 57%. Keep that 57% in mind — it is suspiciously close to the real-world ceiling on this task.

Notice something already: the model is "right" (it says bedroom) but it is only 57% sure, and hotel_room is breathing down its neck at 35%. That is not a bug in our toy numbers. It is the signature of scene classification, and it is why the field reports a metric you may not have seen for ImageNet objects: top-5 accuracy, where a prediction counts as correct if the true label is among the model's five most probable guesses. We will earn the right to that metric in the next chapter.

Before that, build intuition for how close together scene classes can sit. The widget below places scenes in a 2D "feature space" — a flattened cartoon of the high-dimensional space a network actually learns. Pick a class and watch its nearest neighbors light up. Bedroom and hotel_room overlap heavily; beach and desert share warm open horizons; library and bookstore are nearly on top of each other. The geometry of this overlap is the difficulty of the task.

Scene feature space — who sits next to whom

Click a scene. Its nearest neighbors (the scenes most easily confused with it) glow. Notice how tight some clusters are — that tightness is the source of the low accuracy numbers ahead.

Tap a labeled dot to select it.
Common mistake: assuming more classes always means a harder, lower-accuracy task in a simple monotone way. The real driver is not the count of classes but their separability. MIT Indoor67 has only 67 classes yet is brutal, because indoor places share objects and layouts. A hypothetical 1000-class set of wildly different scenes could be easier than Indoor67. Class count is a red herring; class overlap is the truth.
For one image a scene model outputs logits: museum = 3.0, art_gallery = 2.0, others ≈ 0. Using softmax over just these two (treat the rest as negligible), what is P(museum)? (e3≈20.1, e2≈7.39)

Chapter 2: Why Scene Classification Is Genuinely Hard

Here is a fact that surprises newcomers. On ImageNet object classification (1000 classes), the best models exceed 90% top-1 accuracy. On Places365 scene classification (a mere 365 classes), the state of the art is about 61% top-1. Fewer classes, far lower accuracy. Something is fundamentally different, and understanding what is the heart of this lesson.

Three forces conspire against you, and each one is worth naming because each one shapes how we design models.

Force 1: label ambiguity. Many images legitimately belong to more than one scene. A photo of a long table with place settings is a "dining_room" and a "restaurant" and arguably a "banquet_hall". The annotators picked one. Your model picks one. When they disagree, the model is marked wrong even though its answer is reasonable. This is not noise you can train away; it is built into the world. A single ground-truth label is a lie we tell about a multi-label reality, and top-1 accuracy is the tax we pay for that lie.

Force 2: high intra-class variance. "Kitchen" spans a cramped studio galley, a gleaming commercial kitchen, a rustic farmhouse, and a mid-renovation gut job. All are kitchens; pixel-wise they could hardly be more different. The model must learn a concept abstract enough to cover all of them — which means it cannot lean on any specific object or color, because none is universal across the class.

Force 3: low inter-class similarity margin. The flip side. Bedroom vs hotel_room. Library vs bookstore. Coffee_shop vs cafeteria vs fast_food_restaurant. These pairs are more similar to each other than two random kitchens are. So the model is squeezed: it must call wildly different images "the same" (intra-class) while splitting near-identical images into "different" (inter-class). That squeeze is the whole difficulty.

The key insight that reframes the whole task: the ~61% ceiling on Places365 is not mostly a model failure — it is substantially a property of the data. Many "errors" are the model choosing a label a reasonable human would also accept. This is precisely why the field reports top-5 accuracy: it asks "is the truth among your top 5 guesses?", which forgives the unavoidable ambiguity while still punishing genuine confusion. On Places365 the original ResNet baselines reach about 85% top-5 even while top-1 sits far lower.

Play with the ambiguity directly. The widget below shows one image's true multi-label nature. As you raise the "scene ambiguity" slider, more labels become genuinely plausible, the probability mass spreads, and top-1 accuracy collapses while top-5 holds up. This is the single most important behavior to internalize: top-1 and top-5 diverge exactly because the world is multi-label.

Ambiguity collapses top-1 but spares top-5

Raise ambiguity and watch the probability mass spread across plausible labels. The top-1 bar shrinks toward a coin-flip; the top-5 "is the truth in here?" stays high. This gap is why scene benchmarks report both.

Scene ambiguity 0.30

Let us put numbers on the divergence so it is not just a feeling. Suppose the truth is "dining_room" and the model's top-5 probabilities are dining_room 0.30, restaurant 0.26, banquet_hall 0.20, kitchen 0.14, cafeteria 0.10. Top-1 prediction is dining_room — correct, barely, at 30% confidence. But it was one nudge away from "restaurant" and being marked wrong. Now ask the top-5 question: is dining_room in the set {dining_room, restaurant, banquet_hall, kitchen, cafeteria}? Yes. Top-5 correct. The very same prediction is "57% borderline-right" by top-1 and "confidently right" by top-5, and both readings are honest.

Misconception: "low top-1 accuracy means the model is bad." On scenes, a model with 55% top-1 can be excellent if its 85% top-5 shows the truth is almost always among its top guesses and its mistakes are reasonable swaps (bedroom↔hotel_room) rather than absurd ones (bedroom↔highway). Always read top-1 and top-5 together, and look at which classes get confused, before judging a scene model.
Places365 has fewer classes than ImageNet (365 vs 1000) yet much lower top-1 accuracy (~61% vs ~90%). The single best explanation is:

Chapter 3: The CNN Baseline

Time to build a classifier. The workhorse of scene recognition from 2014 to roughly 2021 was the convolutional neural network, and the recipe is short enough to hold in your head: stack convolutions to extract features, collapse the spatial map into a single vector, and run a linear classifier with softmax. Let us trace the data flow with real shapes, because the shapes reveal the design.

An input image is a tensor of shape (3, 224, 224): three color channels, 224 pixels tall and wide. A CNN backbone like ResNet passes it through blocks of convolutions, each block shrinking the spatial size and growing the channel count. After the final block you hold a feature map of shape, say, (512, 7, 7): 512 feature channels, each a tiny 7×7 spatial grid. Each of those 512 channels is a learned detector that fires where some pattern appears — "vertical edges here", "warm texture there", "repeating grid pattern over here".

Now the crucial step for scenes specifically: global average pooling (GAP). We collapse each 7×7 channel down to a single number by averaging its 49 values. The (512, 7, 7) map becomes a (512,) vector. This vector says, for each of the 512 patterns, "how much of this pattern is present anywhere in the image" — a global summary that has thrown away where each pattern was. For object detection that would be a disaster (you need location). For scene classification it is exactly right: a kitchen is a kitchen whether the stove is on the left or the right.

Finally a single fully-connected layer maps the 512-vector to K logits (512 × 365 weights), and softmax turns logits into probabilities. That is the entire classifier. Let us watch one toy example end to end.

Global average pooling, by hand. Take one channel, a 3×3 grid (we use 3×3 instead of 7×7 to keep the arithmetic visible):
[[0.2, 0.9, 0.1], [0.0, 0.8, 0.3], [0.1, 0.7, 0.2]]
GAP = average of all nine = (0.2+0.9+0.1+0.0+0.8+0.3+0.1+0.7+0.2) / 9 = 3.3 / 9 = 0.367. One channel, one number. Do this for all 512 channels and you have the 512-dim scene descriptor. The "0.367" means "this pattern is moderately present, on average, across the whole image" — location discarded on purpose.

The simulation below runs this pipeline live. You drag activity into the feature map (bright = a pattern firing), and watch GAP collapse it to a vector, the linear layer turn that into logits, and softmax produce class probabilities. Paint a "scene signature" and see what it gets classified as.

CNN pipeline: feature map → GAP → logits → softmax

Click cells in the feature grid to toggle which "patterns" are firing. Watch the global-average-pooled vector, the logits, and the final class probabilities update. This is the whole CNN classifier in one picture.

Here is the same pipeline as code — first from scratch so nothing is hidden, then the one-line library version so you see they do the same thing.

python
import numpy as np

# feature_map: (C, H, W) = output of a CNN backbone, e.g. (512, 7, 7)
def global_average_pool(feature_map):
    return feature_map.mean(axis=(1, 2))   # -> (C,)  one number per channel

def softmax(z):
    z = z - z.max()                      # subtract max for numerical stability
    e = np.exp(z)
    return e / e.sum()

# W_fc: (K, C) classifier weights; b_fc: (K,) biases; K = number of scenes
def classify_scene(feature_map, W_fc, b_fc):
    v      = global_average_pool(feature_map)   # (C,)  the scene descriptor
    logits = W_fc @ v + b_fc                     # (K,)  raw class scores
    probs  = softmax(logits)                     # (K,)  sum to 1
    return probs.argmax(), probs                 # top-1 index, full distribution
python
# The library version: torchvision gives you the whole thing, pretrained on Places365
import torch, torchvision.models as models
net = models.resnet50(num_classes=365)   # the FC layer is already (365, 2048)
# resnet internally does conv blocks -> nn.AdaptiveAvgPool2d(1) [== GAP] -> nn.Linear
logits = net(image_batch)                  # (B, 365)
probs  = logits.softmax(dim=1)            # same softmax we wrote by hand
top5   = probs.topk(5, dim=1).indices       # the five best scene guesses

See the correspondence: AdaptiveAvgPool2d(1) is our global_average_pool, nn.Linear is our W_fc @ v + b_fc, and .softmax is the function we wrote three lines of. The library is not magic; it is our by-hand pipeline with trained weights. The original Places365-ResNet152 baseline, trained exactly this way, reached about 85% top-5 on the benchmark — the number to beat for years.

Misconception: "global average pooling throws away too much — surely max pooling or keeping the spatial map is better." For scenes, averaging is usually the right aggressive move: scene identity is a holistic, distributed property, so summarizing "how much of each pattern is present overall" is the signal. Max pooling (keep the single strongest location) suits object detection, where one strong response = one object. Choosing GAP over max-pool is an engineering decision driven by the task's nature, not a default.
A CNN's final feature map is (256, 7, 7). After global average pooling, what is the shape of the scene descriptor vector, and what did pooling discard?

Chapter 4: Global Context Is the Whole Game

Chapter 3 hid a deep idea inside a pooling operation. Let us drag it into the light, because it explains the entire arc of architectures that follows. The claim: scene identity is a global property, and the central design problem of scene classification is how to let the model see the whole image at once.

Why is global context special here? Consider what tells you a room is a "library". No single object does it — a chair, a desk, and a person could be an office. What says "library" is the configuration: rows of shelves receding into the distance, the repeated vertical rhythm of book spines, the quiet uniformity, the aisle geometry. That signal is spread across the entire frame. A model that only looks at small local patches — the way early convolutions do — literally cannot see it. It needs a wide enough receptive field: the region of the input that influences a given feature.

This is the tension that drove CNN design for scenes. A single 3×3 convolution sees only a 3×3 patch. To grow the receptive field you must stack many conv layers (each layer widens it a little) or downsample aggressively (each halving doubles the effective reach). Deep ResNets for scenes are deep partly so that the final features can integrate context from across the whole image. Depth, here, is a means to the end of globality.

The unifying lens for the rest of this lesson: every architecture we meet — deep CNNs, attention-augmented CNNs, Vision Transformers, foundation models — is, for scene classification, a different answer to one question: "how do I let every part of the image inform the final decision?" CNNs answer with depth + pooling. Transformers answer with attention. Once you see the question, the architectures stop being a zoo and become a conversation.

There is a second, subtler reason global context matters: it resolves the ambiguity from Chapter 2. Bedroom vs hotel_room is decided by small global cues — a hotel has a certain anonymity, matching lamps, a particular window-and-curtain style, sometimes a desk-and-coffee setup. None of these is a single object; each is a faint, distributed signal. Only a model integrating the whole frame can pick them up. Local models confuse the pair precisely because the distinguishing evidence is non-local.

The widget below makes the receptive-field idea tangible. A small "model" only sees through a window of adjustable size. Shrink the window and it sees local texture but cannot tell a library from a warehouse (both have shelves up close). Grow the window until it spans the scene and the global structure — aisles receding, the room's proportions — suddenly disambiguates them. Watch the prediction flip as the receptive field crosses the threshold where global structure becomes visible.

Receptive field: when can the model see the scene?

The model only "sees" inside the moving window. Small window = local texture (library and warehouse look the same). Grow it until global structure appears and the classification snaps to the right answer. This is why scene models fight for receptive field.

Receptive field size 0.25

One more move the field made: when pure global pooling felt too lossy, researchers fused a second stream of information — the objects in the scene — via a separate branch, often a graph neural network over detected object regions, then combined the CNN's holistic descriptor with the object-relationship descriptor. These two-branch CNN+GNN models pushed indoor benchmarks like MIT Indoor67 above 90% by giving the model both the gestalt and the explicit object structure. The lesson generalizes: when one view of "global context" is insufficient, fuse another. But the dominant trend went a different way — toward an architecture where globality is native rather than bolted on. That architecture is the Transformer, and it is next.

Misconception: "a bigger input image automatically gives more global context." Not by itself. Feeding a 448×448 image to a network whose receptive field only spans 100 pixels just gives you more local detail, not more global integration. Context is about how far information propagates through the network (effective receptive field), not about input resolution. You can have a huge image and a myopic model.
Why do scene classification CNNs tend to be deep and to pool aggressively, more so than you might expect for "just 365 classes"?

Chapter 5: From CNN to Vision Transformer

If the central problem is "let every part of the image inform the decision," the Vision Transformer (ViT) is almost a direct answer to that prayer. Where a CNN grows global context laboriously through depth, a ViT has global context on layer one, by construction. Understanding why takes three steps: patches, tokens, and attention.

Step 1 — patches. Cut the image into a grid of small squares, say 16×16 pixels each. A 224×224 image becomes 14×14 = 196 patches. Flatten each patch and project it to a vector (a token). Now your image is a sequence of 196 tokens, plus one special extra token called [CLS] whose job is to accumulate a summary of the whole image — it will become our scene descriptor, the ViT's answer to global average pooling.

Step 2 — self-attention, the global mixer. Here is the magic. In each Transformer layer, every token looks at every other token and pulls in information weighted by relevance. A patch showing a bookshelf can directly attend to a patch showing an aisle on the far side of the image — in a single layer, at any distance. There is no receptive field to grow; the receptive field is the whole image from the start. For a task whose signal is spread across the frame, this is exactly the right inductive bias.

Let us see attention as the weighted average it really is. A token forms a query q; every token offers a key k and a value v. The attention weight from our token to token j is the softmax of the dot products q·kj. The output is the weighted sum of the values. Concretely, suppose three tokens give similarity scores (after q·k) of 2.0, 1.0, and 0.0 to our [CLS] token. Softmax: e2≈7.39, e1≈2.72, e0=1.0, sum≈11.11. Weights ≈ 0.665, 0.245, 0.090. The [CLS] output is 0.665·v1 + 0.245·v2 + 0.090·v3 — it has mixed in the most relevant patches from anywhere in the image. That mixing, stacked over layers, is how [CLS] becomes a holistic scene summary.

The [CLS] token is GAP's smarter cousin. Global average pooling gives every spatial location equal weight (1/49 each). Attention lets the model learn unequal, content-dependent weights: it can downweight the blank ceiling and upweight the row of bookshelves. GAP is the special case of attention where all weights are forced equal. The ViT didn't replace pooling — it made pooling learnable and global.

Here is that "[CLS] becomes a scene descriptor by attention" written from scratch — no library — so the weighted-average story is concrete code, not a metaphor:

python
import numpy as np

def softmax(z):
    z = z - z.max(); e = np.exp(z); return e / e.sum()

# tokens: (N, d) patch embeddings; the first row is the [CLS] token
def cls_attention(tokens, Wq, Wk, Wv):
    q   = tokens[0] @ Wq             # (d,)  the [CLS] query — "what summarizes this scene?"
    K   = tokens @ Wk                  # (N, d) one key per patch
    V   = tokens @ Wv                  # (N, d) one value per patch
    scores  = K @ q / np.sqrt(q.shape[0])   # (N,) similarity of [CLS] to every patch
    weights = softmax(scores)          # (N,) sum to 1 — the LEARNED, unequal weights
    return weights @ V                  # (d,) the scene descriptor = weighted avg of patch values

# Compare: global average pooling is this with weights forced uniform —
#   gap = tokens[1:].mean(axis=0)   # every patch weighted 1/N, no learning

Read the last comment: GAP is literally this function with weights hard-coded to 1/N. Attention earns the right to make those weights unequal and content-dependent — that is the entire upgrade. Click a patch in the widget below to make it the query. The heatmap shows where that patch attends — notice it can reach clear across the image to structurally related regions, something a shallow CNN simply cannot do. Toggle to the [CLS] token to see how the scene summary distributes its attention over the whole frame.

Self-attention: every patch sees every patch

Click any patch to make it the query; the glow shows where it attends. Long-range links appear instantly — no stacking of layers needed. Hit "[CLS]" to see how the scene-summary token gathers from across the image.

Step 3 — the cost, and why CNNs didn't simply die. Attention's "everyone sees everyone" is quadratic: 196 tokens means 196×196 pairwise comparisons per layer. Double the tokens and the cost quadruples. And ViTs are data-hungry — lacking the CNN's built-in locality prior, a ViT needs large-scale pretraining to match a CNN on the same labeled set. So the field branched: hierarchical transformers like Swin restored some locality and efficiency by attending within shifted windows, modernized CNNs like ConvNeXt borrowed Transformer ideas to stay competitive, and the strongest scene results came from very large models pretrained on enormous data — the subject of Chapter 6.

Misconception: "Transformers beat CNNs because attention is just better." On a modest labeled dataset trained from scratch, a good CNN often beats a ViT, because the CNN's locality prior is a useful head start and the ViT has to learn structure from data it doesn't have. ViTs win when there is scale — massive pretraining data — that lets attention's flexibility pay off. The right takeaway is not "attention > convolution" but "with enough data, a weaker prior + more flexibility wins."
In what sense is the ViT's [CLS] token a generalization of a CNN's global average pooling?

Chapter 6: Foundation Models & the State of the Art

By the early 2020s the recipe for the very best scene accuracy had a clear shape: take an enormous backbone, pretrain it on a vast amount of data, then fine-tune (or just linearly probe) for scenes. The headline result on Places365 is InternImage-H, a roughly one-billion-parameter vision foundation model from OpenGVLab, which reaches about 61.2% top-1 — the current state of the art and a jump over the previous best (a weakly-supervised ViT-H/14 around 60.7%).

InternImage's twist is worth a sentence because it bucks the "transformers everywhere" narrative: its core operator is deformable convolution v3 (DCNv3), a convolution whose sampling grid can bend to follow content rather than sitting on a rigid raster. This gives it a large, adaptive, content-aware receptive field — the same globality goal as attention, reached through a convolutional door. The lesson from Chapter 4 holds: it is another answer to "how do I see the whole scene."

But the more important shift is how foundation models get used, because you will rarely train a billion-parameter model yourself. Three usage modes dominate, and choosing among them is a real engineering decision:

ModeWhat you trainWhen to use it
Zero-shot (CLIP-style)Nothing — just write text promptsNo labeled data; new/changing taxonomy; quick prototype
Linear probeOnly a small linear head on frozen featuresSome labels (<~10K); want strong results cheaply & fast
Full fine-tuneThe whole backboneLots of labels; chasing the last few points of accuracy

Zero-shot deserves a closer look because it feels like a magic trick and is now a default first move. A model like CLIP was trained to put images and their text captions near each other in a shared embedding space. To classify a scene with no training at all, you embed the image, embed the sentence "a photo of a {scene}" for every candidate scene, and pick the text whose embedding is closest to the image's. The taxonomy is just a list of strings you can edit at runtime — add a class by typing it. Models like DINOv2 (self-supervised, no language) instead shine under linear probe: freeze the features, train a tiny linear layer, and reach strong scene accuracy for almost no compute.

Why does the SOTA "only" reach ~61% top-1 even at a billion parameters? Because, as Chapter 2 argued, much of the remaining error is label ambiguity baked into the data, not model weakness. You cannot scale your way past a genuine multi-label reality squeezed through a single-label metric. This is the most important number-literacy point in scene classification: a low top-1 on Places365 reflects the task as much as the model. The same giant models post far higher top-5 and excellent transfer.

Zero-shot is so useful as a first move that it is worth seeing in code. There is no training loop — just two embeddings and a dot product:

python
import torch, clip                       # OpenAI CLIP (or open_clip)
model, preprocess = clip.load("ViT-B/32")

scenes = ["bedroom", "hotel room", "kitchen", "library", "beach"]   # taxonomy = editable strings!
prompts = clip.tokenize([f"a photo of a {s}" for s in scenes])

with torch.no_grad():
    img_feat  = model.encode_image(preprocess(image).unsqueeze(0))   # (1, D)
    text_feat = model.encode_text(prompts)                            # (K, D)
    img_feat  = img_feat  / img_feat.norm(dim=-1, keepdim=True)     # unit vectors so
    text_feat = text_feat / text_feat.norm(dim=-1, keepdim=True)     # dot product = cosine sim
    sims  = (img_feat @ text_feat.T).squeeze(0)                    # (K,) similarity to each scene
    probs = sims.softmax(dim=-1)                                  # (K,) the zero-shot distribution

pred = scenes[probs.argmax()]   # classified a scene having trained NOTHING; add a class by typing it

That is the whole magic trick: scene identity becomes "which text sentence is my image closest to in a shared space." Add a 13th scene by appending a string — no retraining. Now contrast cost: full fine-tuning the backbone would mean a training loop over your labels and gradients through every weight; the zero-shot version above is a forward pass. Scaling is real but it bends. The widget below sketches the empirical pattern: accuracy rises roughly logarithmically with model/data scale — each doubling buys a little, and the curve flattens toward the ambiguity ceiling. Drag the scale and watch diminishing returns set in. This shape is why teams ask "linear-probe a frozen giant" before "train a bigger one from scratch": the cheap option captures most of the curve.

Scaling curve: accuracy vs model/data scale (and its ceiling)

Drag the model scale. Accuracy climbs logarithmically and flattens toward the ambiguity ceiling. Notice how much of the gain is captured early — the argument for frozen-feature linear probes over training ever-bigger models.

Model / data scale 0.30
ModelApproachPlaces365 top-1Note
InternImage-H~1B params, DCNv3, fine-tune~61.2%Current SOTA
SWAG (ViT-H/14)Weakly-supervised ViT, ~630M~60.7%Prior best
Places365-ResNet152From-scratch CNN baseline(~85% top-5)The original 2016 yardstick
DINOv2 / OpenCLIP ViT-GLinear probe on frozen featuresstrong transferCheap, no full training
Misconception: "to get good scene accuracy I should fine-tune the biggest model I can." Often wrong. With limited labels, full fine-tuning a giant model overfits and underperforms a simple linear probe on its frozen features, while costing far more. The competent default in 2024+: start zero-shot to scope the problem, then linear-probe a strong frozen backbone, and only full-fine-tune if you have lots of labels and need the last few points.
You have ~3,000 labeled images for a custom set of 12 indoor scenes and a tight compute budget. The best first move is usually:

Chapter 7: ShowcaseBuild & Break a Scene Classifier

Everything so far converges here. This is a working scene classifier you can drive and, more importantly, break. The model holds five candidate scenes — bedroom, hotel room, kitchen, restaurant, beach — each defined by a learned weight over five interpretable features. You set the image's features with the sliders; the model computes a descriptor, multiplies by its weight matrix to get logits, and softmaxes to a probability over the five scenes. The bars are the live distribution; the panel shows the top-1 and the full top-5 ranking.

The five features are deliberately the kind of distributed, holistic cues Chapter 4 argued for: bed-like furniture, cooking surfaces, open horizon / outdoor, dining setup, and "anonymity" (the matched-decor uniformity that signals a hotel). No single feature decides a scene; the combination does — which is the entire point of scene recognition.

Three things to try, each illustrating a chapter:

Make a confident bedroom (high bed, low everything else, low anonymity) and watch one bar dominate — this is the easy, separable case.
Now raise "anonymity" alone. Watch bedroom and hotel_room converge into a near-tie. You have manufactured the exact ambiguity of Chapter 2, live: the top-1 becomes a coin flip while the truth stays safely in the top-2.
Crank the noise slider. This injects randomness into the logits, the way a hard, cluttered, or unusual real image would. A confident prediction degrades into a flat distribution — you are watching intra-class variance and label ambiguity erode top-1 in real time.

Live scene classifier — set the features, read the distribution, break it with noise

Set the five scene features, then push the noise up to watch a confident call collapse. The right panel shows the full top-5 ranking the way a real benchmark reads it.

Bed-like furniture0.80
Cooking surfaces0.10
Open horizon / outdoor0.05
Dining setup0.10
Anonymity (hotel-ness)0.10
Noise / image difficulty0.00
What you just proved to yourself: a scene classifier is a feature descriptor → linear logits → softmax, exactly as in Chapter 3 — and its accuracy is bounded not only by the model but by how separable the scenes are given the features. When features make two scenes overlap (anonymity) or noise blurs the descriptor (hard images), top-1 collapses while top-5 endures. That is the whole story of Chapters 2–6 in one interactive panel.

If you remove this simulation, do you lose understanding? Yes — because reading "top-1 and top-5 diverge under ambiguity" is abstract until you watch the bedroom and hotel bars cross by sliding one feature. The napkin drawing of this whole lesson is exactly this: features in, distribution out, ambiguity and noise pulling the distribution flat.

Chapter 8: Evaluation — Reading the Numbers Honestly

You now understand the models; here is how to judge them without being fooled. Scene classification has its own evaluation culture, and three ideas cover most of it: the top-1/top-5 pair, the confusion matrix, and dataset bias.

Top-1 vs top-5, computed by hand. Top-1 accuracy: fraction of images where the single most probable class is the truth. Top-5 accuracy: fraction where the truth is among the five most probable. Take one image, truth = "coffee_shop", and a model that outputs (sorted): cafeteria 0.31, coffee_shop 0.27, restaurant 0.18, fast_food 0.14, bakery 0.06, … The top-1 prediction is cafeteria ≠ coffee_shop → top-1 wrong. Is coffee_shop in the top 5 {cafeteria, coffee_shop, restaurant, fast_food, bakery}? Yes → top-5 correct. One image, two verdicts, both true. Averaged over a test set, the gap between these two numbers measures how much ambiguity the model is fighting.

Top-k by hand, the algorithm. Given a probability (or logit) vector, top-k = the indices of the k largest entries. For logits [1.2, 3.5, 0.4, 2.9, 3.1, 0.1], sort descending: 3.5(idx1), 3.1(idx4), 2.9(idx3), 1.2(idx0), 0.4(idx2), 0.1(idx5). Top-1 = {idx1}. Top-5 = {idx1, idx4, idx3, idx0, idx2}. A prediction is "top-5 correct" iff the true index is in that set. No need to softmax first — ranking is unchanged by the monotonic softmax, so you can rank logits directly.

And the same two metrics in code, computed over a whole test set the way a benchmark script does it — first from scratch, then the one-liner:

python
import numpy as np

# logits: (B, K) model outputs for B images; labels: (B,) true class indices
def topk_accuracy(logits, labels, k=5):
    # argsort descending; take the k highest-scoring class indices per image
    topk = np.argsort(-logits, axis=1)[:, :k]         # (B, k)  — ranking, softmax not needed
    hits = [labels[i] in topk[i] for i in range(len(labels))]
    return np.mean(hits)

top1 = topk_accuracy(logits, labels, k=1)        # strict: the single best guess must be right
top5 = topk_accuracy(logits, labels, k=5)        # forgiving: truth among the 5 best — the ambiguity-aware metric

# PyTorch one-liner equivalent (same ranking logic):
#   correct = logits.topk(5, dim=1).indices.eq(labels[:, None]).any(dim=1).float().mean()

Notice np.argsort(-logits) ranks by raw logit — we never softmax, because softmax is monotonic and cannot change the order. The gap top5 - top1 averaged over the set is a direct readout of how much ambiguity the model faces. The confusion matrix tells you which errors. A scene model's quality is not just its accuracy but the shape of its mistakes. A confusion matrix is a grid: rows = true class, columns = predicted class, each cell = how often that true→predicted swap happened. A good scene model's confusion mass sits on reasonable, near-diagonal swaps (bedroom↔hotel_room, library↔bookstore). A broken model scatters mass into absurd cells (bedroom↔highway). Two models with identical 60% top-1 can be very different: one fails gracefully, one fails insanely. Always look at where the off-diagonal mass lands.

The interactive confusion matrix below is built from plausible scene confusions. Hover or tap a cell to read the true→predicted story. Notice the bright off-diagonal blocks clustering semantically similar scenes — that pattern is the visual fingerprint of "the model is confused by genuine ambiguity, not by being broken."

Confusion matrix — where do the errors live?

Tap a cell. Bright diagonal = correct. Bright off-diagonal = a confusion; the ones near the diagonal are semantically reasonable (bedroom↔hotel), the far ones would signal a broken model. Reading this shape is how experts judge a scene model beyond a single accuracy number.

Tap a cell to read the true → predicted story.

Dataset bias is the silent killer. Places365 and friends are scraped from the web, which skews toward certain countries, lighting, and aesthetics. A model trained on them can quietly assume "kitchen" looks like a North-American kitchen and stumble on a kitchen from elsewhere. When you deploy a scene model on a robot in a specific building or a camera in a specific region, the benchmark accuracy is an optimistic estimate; the distribution shift between web photos and your cameras is real and usually hurts. The fix is not a metric — it is collecting or fine-tuning on in-domain data.

One more honest number: calibration. A model that says "0.9" should be right about 90% of the time. Scene models, like most deep nets, are often over-confident — their 0.9 is really 0.75. If a downstream system (a robot's planner) trusts those probabilities, miscalibration causes confident mistakes. Temperature scaling (dividing logits by a learned constant before softmax) is the cheap standard fix.

Misconception: "model A has higher top-1, so model A is better." Not necessarily for deployment. Model B with slightly lower top-1 but better top-5, more reasonable confusions, and better calibration can be the safer choice on a robot — it fails gracefully and its confidences mean something. Single-number leaderboard rank is the start of evaluation, never the end.
A scene model outputs logits [2.0, 4.0, 1.0, 3.5, 3.8] for classes [A, B, C, D, E]; the true class is D. Is this prediction top-1 correct? Top-5 correct (out of these 5)?

Chapter 9: Connections & Where It's Used

Scene classification is a hub, not an island. The exact pattern you built — read a whole signal, summarize it into a descriptor, classify into a fixed taxonomy, and accept that the world is multi-label — recurs across vision, audio, robotics, and remote sensing. Naming those connections is how you turn one lesson into a mental map.

Robotics. A robot deciding "what room am I in?" is doing scene classification, and it pairs naturally with visual place recognition (is this the specific place I've seen before, for loop closure?) and semantic mapping (labeling regions of a map by scene type). Scene class steers behavior; place recognition steers localization. See Place Recognition: Bag-of-Words & Loop Closure for the retrieval cousin of this task.

Other modalities, same shape. Acoustic scene classification asks "is this audio a park, a metro, an airport?" — whole-signal classification with the same ambiguity and top-k culture, but over a spectrogram instead of an image. RGB-D scene classification adds a depth channel, which sharply helps indoor scenes by revealing layout. Remote-sensing scene classification labels satellite tiles (forest, residential, industrial) — same task, bird's-eye view, multispectral input. Each is "this lesson, new sensor."

ApproachHow it sees globallyBest whenScene strength
Deep CNN (ResNet)Depth + pooling grow receptive fieldModest labeled data, from scratchStrong, efficient baseline (~85% top-5)
CNN + GNN two-branchHolistic CNN + object-relation graphIndoor scenes rich in objects>90% on MIT Indoor67
Vision Transformer / SwinSelf-attention: global on layer 1Large pretraining data availableExcellent with scale
Foundation model (InternImage-H, DINOv2, CLIP)Huge adaptive receptive field + scaleWant SOTA, transfer, or zero-shotSOTA (~61% top-1 Places365)

Where to go next in the corpus. The machinery you used here is taught in depth elsewhere: the convolutional backbone in CNN Classification and CNN Training & Architectures (which covers ConvNeXt); the attention mechanism in Vision Transformer and The Transformer; the zero-shot trick in Contrastive CLIP; multi-modal fusion in Multimodal Foundation Models; and the broader object-vs-whole-image distinction in Image Classification and Detection & Segmentation.

The one idea to carry out the door: scene classification is the discipline of reading the whole and committing to a label in a multi-label world. The architectures are all answers to "how do I see everything at once," and the metrics (top-1 vs top-5) are honest acknowledgments that "one place, one word" is a useful fiction. Master that tension and you have mastered the task.

Cheat sheet

ThingWhat to remember
TaskOne label for the whole image, from a fixed place taxonomy (Places365: 365 classes, 1.8M imgs)
Why hardLabel ambiguity + high intra-class variance + low inter-class margin → ~61% top-1 SOTA
CNN pipelineconv backbone → global average pool (C,H,W)→(C) → linear → softmax
Core problemGlobal context: scene identity is distributed, so you need a large receptive field
ViTPatches → tokens → self-attention (global on layer 1); [CLS] = learnable, weighted GAP
SOTAInternImage-H (~1B, DCNv3) ~61.2% top-1; usage = zero-shot / linear-probe / fine-tune
MetricsReport top-1 and top-5; read the confusion matrix; watch dataset bias & calibration

"The whole is other than the sum of its parts." — Kurt Koffka, paraphrasing the Gestalt principle that is, quite literally, why scene classification needed its own toolbox.