← Back to the problem

USA USAAIO 2026 · Correction by IOAI Training

Ordering CNN Feature Maps

This page gives the full approach. Read it once you have searched.

Metric
written answer (10 points)
Evaluated on
written answers
Our score
(2),(1),(4),(3),(5); checked on ResNet
Expected level
correct layer for each map, with reasoning

Expected level reached

We solved the problem ourselves: downloaded the data, ran the experiments and measured the score below. The statement and data belong to their organisers.

The problem in brief

A photograph (a city street with a cartoon character dancing on a pedestrian crossing) is passed through a pretrained convolutional network. The organisers show five feature maps, taken from five different layers numbered 0 (closest to the input) to 4 (deepest), but in a shuffled order labelled (1) to (5). You must say which layer produced each map and justify it. It is Problem 6 of the 2026 USA AI Olympiad Round 1. The network is not named, so the answer must rest on general properties of CNNs. We then check them by running a real pretrained network, ResNet-18, on the same image.

How you are scored

10 points, reasoning required. No official answer is published with the problem PDF; the answer below is our own.

Answer: map (2) is from layer 0, (1) from layer 1, (4) from layer 2, (3) from layer 3 and (5) from layer 4. In depth order: (2), (1), (4), (3), (5).

Question by question

What changes from one layer to the next

Two facts about a standard CNN do almost all the work.

  1. Resolution falls with depth. Strided convolutions and pooling halve the height and width again and again: in a ResNet, 224→112→56→28→14→7224 \to 112 \to 56 \to 28 \to 14 \to 7. The heatmaps in the figure are drawn at the same size, so a deep layer shows up as a few large, smooth blobs (each cell, upsampled, covers a large patch of the image), and a shallow layer as fine detail.
  2. The receptive field grows and the features become more abstract. A unit in the first layer sees a 7×77\times7 patch, so it can only respond to local colour and edges, and its map looks like a filtered version of the photo. A unit in the last layer sees essentially the whole image and responds to "is there an object of the kind I detect here?", so its map no longer looks like the photo at all. It looks like a heat spot on the main object.

Placing the five maps

  • (2): layer 0. It is the only map in which you can still recognise the photograph: the outline of the character, the street and the building edges are visible, with smooth regions in between. That is what a first-layer filter (a colour or brightness or edge detector at full resolution) produces.
  • (1): layer 1. It is still high-resolution, with fine, dense texture everywhere, but the scene is no longer recognisable: these are combinations of edges (textures, small motifs) computed over slightly larger patches. The fine grid-like pattern is the typical trace of a strided layer at high resolution.
  • (4): layer 2. The detail has turned into many medium-sized blobs scattered over the image. The resolution is lower (bigger cells) and the units respond to parts or motifs, not edges.
  • (3): layer 3. Only a few large blobs remain, mostly dark elsewhere, and they sit near the lower middle where the character and the car are. The resolution is coarser still and the responses are sparse and semantic.
  • (5): layer 4. One smooth bright spot in the centre on a uniform background: the coarsest grid (in ResNet, 7×77\times 7) upsampled, and a unit that fires where the main object is. This is the last convolutional stage, the one whose global average feeds the classifier.

The ordering is consistent on both criteria at once. Going (2), (1), (4), (3), (5), the blob size grows and the resemblance to the photo disappears.

Check with a real network

ResNet-18 activations on the problem's image, averaged over channels

We extracted the image from the PDF and fed it to an ImageNet-pretrained ResNet-18 (solution.py). We then averaged the activations over channels after the stem (layer 0) and after each residual stage (layers 1 to 4). The pattern is the same as in the problem:

layergridchannelscorrelation with the image's edge map
0 (stem)112 x 11264+0.76
156 x 5664+0.42
228 x 28128+0.12
314 x 14256-0.13
47 x 7512+0.55 (one central blob on the character)

Layer 0 is a recognisable edge picture of the scene, layer 1 is dense texture, layers 2 and 3 are increasingly coarse scattered blobs, and layer 4 is a single central hot spot on the character. The last correlation is positive again only because the character is both the most textured region and the object the network attends to. The map itself is just a 7×77\times7 blob.

What to remember

  • Depth in a CNN means lower resolution, larger receptive field, more abstract features: edges, then textures, then parts, then objects.
  • When maps are shown at the same display size, judge the depth by the size of the smallest structures (pixel size) and by how much the map still looks like the input.
  • The last convolutional stage gives coarse "where is the object" maps. This is the basis of class activation maps (CAM, Grad-CAM) used to explain CNN decisions.
Full code (solution.py)
"""USAAIO 2026 Round 1, Problem 6: which CNN layer produced which feature map?

Check of the reasoning: push the problem's own image through a pretrained ResNet-18
(torchvision weights, converted to safetensors) and look at the channel-averaged activation
after the stem and after each of the 4 residual stages. We print two things that order
the layers: spatial resolution, and how "image-like" the map is (correlation with the image's
edge map).  Inputs: work/pikachu.png (extracted from the PDF), work/resnet18.safetensors.  Writes figures/resnet_maps.png.
"""
import numpy as np, torch, torch.nn.functional as F, matplotlib
matplotlib.use('Agg'); import matplotlib.pyplot as plt
from PIL import Image
from safetensors.torch import load_file
from torchvision.models import resnet18
torch.set_num_threads(2)

net = resnet18(); net.load_state_dict(load_file('work/resnet18.safetensors')); net.eval()
img = Image.open('work/pikachu.png').convert('RGB').resize((224, 224))
x = torch.tensor(np.array(img) / 255.0, dtype=torch.float32).permute(2, 0, 1)[None]
x = (x - torch.tensor([0.485, 0.456, 0.406])[:, None, None]) / torch.tensor([0.229, 0.224, 0.225])[:, None, None]

maps, names = [], ['layer 0: stem (conv1+bn+relu)', 'layer 1', 'layer 2', 'layer 3', 'layer 4']
with torch.no_grad():
    h = net.relu(net.bn1(net.conv1(x))); maps.append(h)
    h = net.maxpool(h)
    for stage in [net.layer1, net.layer2, net.layer3, net.layer4]:
        h = stage(h); maps.append(h)

gray = torch.tensor(np.array(img.convert('L')) / 255.0, dtype=torch.float32)[None, None]
edges = (F.conv2d(gray, torch.tensor([[[[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]]], dtype=torch.float32), padding=1) ** 2
         + F.conv2d(gray, torch.tensor([[[[-1, -2, -1], [0, 0, 0], [1, 2, 1]]]], dtype=torch.float32), padding=1) ** 2).sqrt()

fig, ax = plt.subplots(1, 6, figsize=(17, 3.4))
ax[0].imshow(img); ax[0].set_title('input'); ax[0].axis('off')
for i, (m, n) in enumerate(zip(maps, names)):
    a = m.mean(1)[0]                                            # average over channels
    e = F.adaptive_avg_pool2d(edges, a.shape)[0, 0]
    corr = np.corrcoef(a.ravel(), e.ravel())[0, 1]
    print(f'{n:30s} shape {tuple(m.shape[1:])}  corr(map, edges) = {corr:+.2f}')
    ax[i + 1].imshow(a, cmap='viridis'); ax[i + 1].axis('off')
    ax[i + 1].set_title(f'{n.split(":")[0]}\n{m.shape[2]}x{m.shape[3]}, {m.shape[1]} ch', fontsize=10)
plt.tight_layout(); plt.savefig('figures/resnet_maps.png', dpi=150, facecolor='white')