领域适应:标签稀缺的医学成像
您将把从 ImageNet 学到的迁移学习应用于胸部 X 光数据集,为类别不平衡的病理问题实现加权损失,并评估 AUC-ROC。
领域适应:标签稀缺的医学成像 是 CoddyKit 上的免费 Machine Learning Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Machine Learning Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Machine Learning Academy 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
The Medical Imaging Challenge
Medical imaging presents unique transfer learning challenges. Unlike natural photographs, chest X-rays, MRI scans, and histology slides look nothing like ImageNet images: they are grayscale or have domain-specific colour patterns, the features that matter (lesions, nodules, calcifications) are subtle and domain-specific, and labelled data requires expert radiologists — making large labelled datasets expensive and rare.
Despite these challenges, ImageNet pre-trained models consistently outperform training from scratch on medical imaging tasks, even when the visual appearance differs substantially. Universal low-level features (edge detectors, texture filters) transfer across domains, providing a strong initialisation that speeds convergence and improves generalisation with scarce labels.
The CheXpert Dataset: Multi-Label X-Ray Classification
CheXpert is a benchmark chest X-ray dataset with 224,316 images and 14 labels (Cardiomegaly, Pleural Effusion, Pneumonia, Atelectasis, etc.). In our scarce-label scenario, we simulate using only a small fraction — say 1% (about 2,243 images) — to mimic real-world clinical settings where annotation budget is limited.
This is a multi-label classification problem: each image can have multiple pathologies simultaneously, unlike single-label classification. The target is a vector of 14 binary values, and we use Binary Cross-Entropy with Logits (BCE) applied element-wise. Evaluation uses AUC-ROC per pathology, averaged across all 14 labels.
# Dataset setup (pseudo-code for illustration)
import torch
from torch.utils.data import Dataset
from PIL import Image
import pandas as pd
class CheXpertDataset(Dataset):
def __init__(self, csv_path, img_dir, transform=None):
self.df = pd.read_csv(csv_path)
self.img_dir = img_dir
self.transform = transform
self.labels = ['Atelectasis', 'Cardiomegaly',
'Consolidation', 'Edema', 'Pleural Effusion']
def __len__(self):
return len(self.df)
def __getitem__(self, idx):
img_path = self.df.iloc[idx]['Path']
img = Image.open(img_path).convert('RGB') # Convert grayscale to 3-ch
label = torch.tensor(self.df.iloc[idx][self.labels].values.astype(float))
if self.transform:
img = self.transform(img)
return img, labelConverting Grayscale to RGB for Pre-trained Models
Most medical images (X-rays, CT scans) are grayscale (1 channel), but ImageNet pre-trained models expect 3-channel RGB inputs. The simplest solution is Image.convert('RGB') which replicates the single channel three times, or transforms.Grayscale(num_output_channels=3) in the transform pipeline.
This is slightly wasteful — the three channels are identical — but in practice it works well because the model simply learns to weight all three channels equally. An alternative is to replace the first convolutional layer with a new Conv2d(1, 64, kernel_size=7, ...) and initialise it by averaging the three input channel weights. This is more principled but adds training complexity.
from torchvision import transforms
# Method 1: Replicate channel at loading time (simplest)
# img = Image.open(path).convert('RGB')
# Method 2: Use transforms
medical_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.Grayscale(num_output_channels=3), # 1 -> 3 channels
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# Method 3: Modify first conv layer for single-channel input
import torchvision.models as models
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
# Average the 3-channel weights into 1 channel
w = model.conv1.weight.mean(dim=1, keepdim=True)
model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
model.conv1.weight.data = wMulti-Label Loss: BCE with Logits
For multi-label classification, each label is an independent binary prediction. We use nn.BCEWithLogitsLoss() applied to a vector of 14 logits. The loss is computed element-wise and averaged across both the 14 classes and the batch size.
Imbalanced labels are a major challenge in medical imaging: only 5-10% of images have Pneumonia or Consolidation, while 40-60% have Pleural Effusion. Pass pos_weight to BCEWithLogitsLoss to upweight the rare positive class: a pos_weight of 10 makes the model pay 10× more attention to positive examples of that pathology.
import torch
import torch.nn as nn
# Multi-label BCE loss
criterion = nn.BCEWithLogitsLoss()
# With class weighting for imbalanced pathologies
# Compute pos_weight from training data frequencies
pos_counts = train_labels.sum(dim=0) # Positives per class
neg_counts = len(train_labels) - pos_counts
pos_weight = (neg_counts / pos_counts.clamp(min=1)).clamp(max=20) # Cap at 20
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
print('pos_weight per label:', pos_weight)
# High values mean that class is rare and needs more emphasisModel Architecture for Multi-Label Output
Replace the ResNet-50 classification head with a linear layer that outputs 14 logits (one per pathology), rather than the default 1000 for ImageNet. We do not apply sigmoid in the forward pass — the BCEWithLogitsLoss applies it internally for numerical stability. At inference time, apply sigmoid manually to get probabilities.
Adding a dropout layer before the final linear layer is especially important with scarce data, as regularisation prevents the small head from overfitting. A dropout probability of 0.3-0.5 is typical for medical imaging fine-tuning.
import torchvision.models as models
import torch.nn as nn
NUM_CLASSES = 14 # One per CheXpert pathology label
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
# Replace with multi-label head
model.fc = nn.Sequential(
nn.Dropout(p=0.4),
nn.Linear(model.fc.in_features, NUM_CLASSES)
)
# No sigmoid here - BCEWithLogitsLoss handles it internally
print('Model output shape for batch of 16:',
model(torch.randn(16, 3, 224, 224)).shape) # (16, 14)AUC-ROC: The Right Metric for Medical Tasks
Accuracy is useless for imbalanced medical datasets. If only 5% of patients have Pneumonia, a model that always predicts 'no pneumonia' achieves 95% accuracy while being completely useless clinically. AUC-ROC (Area Under the ROC Curve) measures discrimination ability across all thresholds.
We compute AUC-ROC for each of the 14 pathologies separately and report the mean AUC across all labels. AUC of 0.5 is random chance; 0.7 is acceptable; 0.85+ is clinical grade; 0.9+ often approaches radiologist-level performance. scikit-learn's roc_auc_score computes this efficiently.
import numpy as np
from sklearn.metrics import roc_auc_score
import torch
def evaluate_auc(model, loader, device):
model.eval()
all_logits, all_labels = [], []
with torch.no_grad():
for images, labels in loader:
logits = model(images.to(device))
all_logits.append(torch.sigmoid(logits).cpu().numpy())
all_labels.append(labels.numpy())
probs = np.vstack(all_logits) # (N, 14)
targets = np.vstack(all_labels) # (N, 14)
# AUC per class, then average
aucs = [roc_auc_score(targets[:, i], probs[:, i])
for i in range(targets.shape[1])]
return np.mean(aucs), aucsTraining with Scarce Labels: Key Techniques
When you have only hundreds or a few thousand labelled medical images, several techniques help maximally exploit the available data. Strong data augmentation is the most important: random flips, rotations, contrast and brightness jitter all help, while keeping augmentations clinically plausible (a chest X-ray should not be vertically flipped — that would never occur in clinical practice).
Progressive resizing (training initially at lower resolution, then increasing) is another effective technique. Start at 128×128 to quickly iterate, then fine-tune at 224×224 or even 320×320 for final accuracy. This is much faster than always training at full resolution and often matches full-resolution accuracy.
from torchvision import transforms
# Clinically appropriate augmentations for chest X-rays
medical_aug = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.85, 1.0)), # Mild crop
transforms.RandomHorizontalFlip(p=0.5), # OK: X-rays can be flipped
# transforms.RandomVerticalFlip(p=0.5), # NOT OK: clinically invalid
transforms.ColorJitter(brightness=0.2, contrast=0.3), # Simulate scan variation
transforms.RandomRotation(degrees=10), # Slight mis-alignment
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])Self-Supervised Pre-Training for Medical Imaging
When ImageNet features do not transfer well enough, self-supervised pre-training on unlabelled medical images is a powerful alternative. Methods like SimCLR, MoCo, and DINO learn representations by training the model to recognise that two augmented versions of the same image are similar, without any labels.
The workflow: (1) pre-train on large unlabelled medical datasets (CheXpert has 224K unlabelled images), (2) fine-tune with the small labelled set. This consistently outperforms ImageNet transfer on medical tasks because the model learns features specific to the medical domain rather than general object recognition features.
# Self-supervised pre-training concept (SimCLR-style)
# No labels needed during this phase
class SimCLRLoss(torch.nn.Module):
def __init__(self, temperature=0.07):
super().__init__()
self.temperature = temperature
def forward(self, z_i, z_j):
# z_i, z_j: augmented views of the same images
# Maximise agreement between paired views
# Minimise agreement between all other pairs in batch
z = torch.cat([z_i, z_j], dim=0)
z = torch.nn.functional.normalize(z, dim=1)
sim = torch.matmul(z, z.T) / self.temperature
# Contrastive loss computation...
return sim # Simplified illustrationUncertainty Quantification in Medical AI
In medical decision support, knowing how confident a model is is as important as the prediction itself. A model that says 'Pneumonia: 95% probability' should be more trusted than one saying '52% probability'. Standard softmax probabilities are often overconfident and do not represent true uncertainty.
Monte Carlo Dropout (MC Dropout) approximates Bayesian uncertainty by keeping dropout active at inference time and running forward pass multiple times. The variance of predictions across runs estimates uncertainty. Predictions with high variance should be flagged for human review rather than acted upon automatically.
import torch
def mc_dropout_predict(model, x, n_samples=30):
model.train() # Keep dropout active during inference
predictions = []
with torch.no_grad():
for _ in range(n_samples):
logits = model(x)
probs = torch.sigmoid(logits)
predictions.append(probs)
preds = torch.stack(predictions) # (n_samples, batch, num_classes)
mean_pred = preds.mean(dim=0) # Average prediction
uncertainty = preds.std(dim=0) # Std = uncertainty estimate
return mean_pred, uncertainty
# High uncertainty cases should be reviewed by a radiologist
MEAN_THRESHOLD = 0.5
UNCERTAINTY_THRESHOLD = 0.15Ethical Considerations in Medical ML
Deploying ML models in medical settings carries serious ethical responsibilities. Dataset bias is a critical concern: models trained predominantly on images from one hospital's scanners, patient demographics, or scanner manufacturers may fail on images from different settings — a known problem in radiology AI.
Before deployment, always evaluate model performance across demographic subgroups (age, sex, race) and across different scanning equipment. Regulatory frameworks like the FDA's AI/ML-based Software as a Medical Device (SaMD) guidelines in the US and the EU Medical Device Regulation (MDR) require rigorous clinical validation before deployment. Models should assist radiologists, not replace them — especially on high-stakes pathologies where errors have life-or-death consequences.
Quick Check
Test your understanding of domain adaptation for medical imaging from this lesson.
Lesson Recap
In this lesson you learned: medical imaging transfer learning applies ImageNet pre-trained models to domains with grayscale images and scarce labels by converting channels and replacing the classification head, multi-label BCE loss with pos_weight handles the extreme class imbalance typical of pathology datasets, and AUC-ROC is the correct evaluation metric that measures discrimination ability independent of class balance. With these transfer learning skills mastered, you are ready to explore NLP with BERT in the next course.
常见问题解答
「领域适应:标签稀缺的医学成像」课时是免费的吗?
是的 — 「领域适应:标签稀缺的医学成像」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Machine Learning Academy 课程的其余内容,请升级到 CoddyKit PRO。 Machine Learning Academy 课程共包含 4 节课。
「领域适应:标签稀缺的医学成像」这节课中我会学到什么?
您将把从 ImageNet 学到的迁移学习应用于胸部 X 光数据集,为类别不平衡的病理问题实现加权损失,并评估 AUC-ROC。 你通过在浏览器中直接运行的动手代码来练习 Machine Learning Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Machine Learning Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Machine Learning Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「领域适应:标签稀缺的医学成像」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Machine Learning Academy 课中编写并运行代码吗?
能。每节 Machine Learning Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。