Skip to content

Medical AI · Deep learning & imaging · Lesson 2 of 5

CNNs for chest X-ray and retinal images

Reviewed by Dr. Ram Kumar G, MBBS, MD (Internal Medicine), MPH (USA), on · Last updated

  • Intermediate
  • Technical focus
  • About 35 minutes
  • Includes exercise

Before this lesson: Neural-network basics

In short

Convolutional neural networks (CNNs) scan an image with small learned filters, so a feature such as a lesion's edge is recognised wherever it appears. This makes them far better than pixel-by-pixel models for medical images, and CNNs underpin many published systems for diabetic retinopathy, skin lesions and chest X-rays. They also learn whatever separates the training labels, including scanner markers or hospital labels, so a model can look excellent at its own hospital and fail at another.

You will learn to

  • Explain convolution, pooling and why CNNs suit images
  • Train a small CNN on synthetic scans in PyTorch
  • Compare it with a pixel-based logistic regression
  • Demonstrate shortcut learning and how to test for it

How a CNN sees an image

CNNs drove the early landmark results in medical imaging, such as detecting referable diabetic retinopathy in retinal photographs (Gulshan et al., 2016) and classifying skin lesions (Esteva et al., 2017). Later architectures such as ResNets, and more recently vision transformers, build on the same ideas.

  • Convolution: a small filter (for example 3 × 3 pixels) slides across the image, producing a map of where a pattern occurs. The network learns the filters.
  • Weight sharing: the same filter is used everywhere, so a lesion is recognised in any position, with far fewer parameters than a fully connected network.
  • Pooling: downsampling keeps the strongest responses, so deeper layers see larger regions and more abstract features.
  • Depth: early layers detect edges and spots; later layers combine them into shapes and structures.

Training a small CNN

The synthetic 'scans' below are 28 × 28 pixels of noise; half contain a faint round lesion at a random position. Lesion images can also carry a bright corner marker, used later to show shortcut learning.

python
import numpy as np
import torch
from torch import nn

def make_images(n, seed, marker_with_lesion=0.0):
    """Synthetic 28x28 'scans': noisy background; half contain a faint round lesion at a random place.
    marker_with_lesion: chance that lesion images also carry a bright corner marker (a shortcut)."""
    rng = np.random.default_rng(seed)
    yy, xx = np.mgrid[:28, :28]
    images = rng.normal(0, 1, (n, 28, 28))
    labels = rng.integers(0, 2, n)
    for i in np.where(labels == 1)[0]:
        cy, cx, r = rng.integers(6, 22), rng.integers(6, 22), rng.uniform(2.5, 4.0)
        images[i] += 1.0 * ((yy - cy) ** 2 + (xx - cx) ** 2 < r ** 2)
        if rng.random() < marker_with_lesion:
            images[i, :3, :3] += 4.0                     # e.g. a portable-scanner label
    return torch.tensor(images, dtype=torch.float32).unsqueeze(1), torch.tensor(labels)

class SmallCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 8, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),      # 28 -> 14
            nn.Conv2d(8, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),     # 14 -> 7
        )
        self.head = nn.Sequential(nn.Flatten(), nn.Linear(16 * 7 * 7, 2))
    def forward(self, x):
        return self.head(self.features(x))

def train(model, X, y, epochs=8, seed=0):
    torch.manual_seed(seed)
    opt = torch.optim.Adam(model.parameters(), lr=1e-3)
    for _ in range(epochs):
        for idx in torch.randperm(len(X)).split(64):
            opt.zero_grad()
            nn.functional.cross_entropy(model(X[idx]), y[idx]).backward()
            opt.step()
    return model

def accuracy(model, X, y):
    with torch.no_grad():
        return (model(X).argmax(1) == y).float().mean().item()

X_train, y_train = make_images(4000, seed=1)
X_test, y_test = make_images(1000, seed=2)
torch.manual_seed(0)
cnn = train(SmallCNN(), X_train, y_train)
print(f"CNN test accuracy: {accuracy(cnn, X_test, y_test):.3f}")
print(f"Parameters: {sum(p.numel() for p in cnn.parameters()):,}")

# CNN test accuracy: 0.946
# Parameters: 2,818
Synthetic scans, a two-layer CNN and its test accuracy. Takes a few seconds on a CPU.

About the numbers

  • The code uses fixed random seeds, PyTorch 2.8 and a laptop CPU. Other versions or hardware can give slightly different numbers; the pattern of results is what matters.
  • Images in this track are synthetic, so they run anywhere in seconds. The exercises point to MedMNIST, an openly licensed collection of small medical-image datasets, for real images.

Why convolution matters

python
from sklearn.linear_model import LogisticRegression

flat = LogisticRegression(max_iter=2000).fit(X_train.reshape(len(X_train), -1).numpy(), y_train.numpy())
print(f"Logistic regression on raw pixels: "
      f"{flat.score(X_test.reshape(len(X_test), -1).numpy(), y_test.numpy()):.3f}")

# Logistic regression on raw pixels: 0.738
Continuing from the previous block: logistic regression on the raw pixels, for comparison.
  • Logistic regression must learn a separate weight for every pixel and cannot recognise that a lesion in the corner is the same as one in the centre. The CNN, with only 2,818 parameters, scores 94.6% against 73.8%.

Shortcut learning

At the training hospital in this simulation, 90% of lesion images come from a scanner that stamps a bright marker in the corner, a pattern like the portable-scanner labels found in real chest X-ray datasets. The model is then tested at the same hospital and at one where the marker never appears.

python
X_dev, y_dev = make_images(4000, seed=3, marker_with_lesion=0.9)
torch.manual_seed(0)
shortcut_cnn = train(SmallCNN(), X_dev, y_dev)
X_same, y_same = make_images(1000, seed=4, marker_with_lesion=0.9)
X_new, y_new = make_images(1000, seed=5, marker_with_lesion=0.0)
print(f"Same hospital (marker present): {accuracy(shortcut_cnn, X_same, y_same):.3f}")
print(f"New hospital (no marker):       {accuracy(shortcut_cnn, X_new, y_new):.3f}")

# Same hospital (marker present): 0.984
# New hospital (no marker):       0.813
Continuing from the first block: train where the marker goes with lesions, test with and without it.
  • At its own hospital the shortcut model looks better than the honest one (98.4% against 94.6%), because the marker is an easy cue. Without the marker it falls to 81.3%, below the model trained without the shortcut.
  • Real examples include pneumonia models that recognised which hospital an X-ray came from (Zech et al., 2018) and COVID-19 models that relied on text labels and image borders rather than the lungs (DeGrave et al., 2021).

Testing for shortcuts

  • Test on data from other hospitals, scanners and time periods, as in the external validation lesson.
  • Check whether a model can predict the site, scanner or view from the image; if it can, those features are available as shortcuts.
  • Look at saliency or attribution maps as a prompt for questions, not as proof: they can highlight markers, but they are themselves unreliable.
  • Mask or crop out known artefacts (text labels, borders) and see whether performance drops.
  • Report results by acquisition type, such as portable versus fixed X-ray, and by patient group.

Exercise

Train CNNs on synthetic and real medical images, and test them for shortcut learning.

Data: Synthetic scans in the notebook; MedMNIST PneumoniaMNIST and RetinaMNIST (optional download) (Generated in the notebook; MedMNIST under CC BY 4.0)

  1. Reproduce the CNN, pixel baseline and shortcut experiments, and vary the marker rate from 0.5 to 1.0.
  2. Mask the top-left corner of every image before training the shortcut model; how much does new-hospital accuracy recover?
  3. Train the same CNN on PneumoniaMNIST and report AUROC, sensitivity and specificity on its test set.
  4. Add a third convolutional layer and data augmentation (random flips and shifts) and compare.

Open the notebook in ColabView on GitHubWorked solution coming soon

Key takeaways

  • CNNs use shared filters, so features are recognised wherever they appear.
  • They far outperform pixel-based models on images with few parameters.
  • CNNs learn any cue that separates labels, including scanner markers.
  • Test across sites and acquisition types to expose shortcuts.

Terms used: Convolutional neural network (CNN), External validation, Shortcut learning

Related: Image segmentation with U-Net, Multimodal models: shortcuts, External validation and dataset shift

FAQ

What is a convolutional neural network?

A neural network that applies small learned filters across an image, so it can recognise patterns such as edges or lesions wherever they occur. It is the classic architecture for image analysis.

Why are CNNs good for medical images?

Because features like lesions can appear anywhere, and CNNs reuse the same filters across the whole image. This makes them accurate and efficient compared with models that treat each pixel separately.

What is shortcut learning in medical imaging?

When a model relies on a feature linked to the label in the training data, such as a hospital marker or scanner type, instead of the disease itself. It fails when that link changes.

Do I need thousands of images to train a CNN?

Training from scratch usually needs many images. Transfer learning from pre-trained models, covered later in this track, reduces the number needed.

Are CNNs still used now that transformers exist?

Yes. CNNs remain widely used and effective in medical imaging, and many newer models combine convolutional and transformer components.

Educational content only. This material teaches methods; it is not clinical advice. Code, models and examples here are not validated medical devices and must not be used to make decisions about real patients without proper clinical validation, governance and any required regulatory approval. Examples use synthetic or openly licensed de-identified data only. Never paste identifiable patient information into public AI tools. Mentions of models, tools or vendors are for identification, not endorsement.