Proposal Cluster Learning for Weakly Supervised Object Detection
Overview
Proposal Cluster Learning (PCL) is an end-to-end deep network approach for Weakly Supervised Object Detection (WSOD). It allows training object detectors using only image-level labels (e.g., "this image contains a dog") without requiring expensive bounding box annotations.
Key Innovation: Instead of treating detection as classification (like standard MIL approaches), PCL generates "proposal clusters" - groups of spatially adjacent proposals associated with the same object - and uses these clusters to iteratively refine instance classifiers.
Benefits:
- Reduces annotation cost (no bounding boxes needed)
- Detects complete objects (not just discriminative parts)
- State-of-the-art results on PASCAL VOC, ImageNet, MS-COCO
When to Use This Skill
Use this skill when:
- You have image-level labels but no bounding box annotations
- Annotation budget is limited for object detection tasks
- Building detection systems for new domains without existing annotations
- Need to rapidly prototype object detectors
- Working with large-scale datasets where box annotation is infeasible
Core Concepts
The Problem with Standard MIL
Traditional Multiple Instance Learning (MIL) for WSOD:
- Treats each image as a "bag" of region proposals
- Learns to classify based on most discriminative regions
- Problem: Often focuses on object PARTS (e.g., dog's face) not complete objects
PCL Solution: Proposal Clusters
- Group related proposals: Proposals covering the same object are spatially adjacent
- Cluster-based learning: Treat each cluster as a mini-bag
- Iterative refinement: Multiple CNN streams refine detections
Architecture Overview
Image
│
▼
┌─────────────────────────────────────────────────────────┐
│ CNN Backbone (VGG16) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Region Proposal Network (RPN) │
│ or Selective Search │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ROI Pooling Layer │
└─────────────────────────────────────────────────────────┘
│
├──────► Stream 1: MIL Network (Initial Classification)
│
├──────► Stream 2: PCL Refinement 1
│
├──────► Stream 3: PCL Refinement 2
│
└──────► Stream K: PCL Refinement K-1
Core Workflow
Phase 1: Setup and Data Preparation
Prepare Image-Level Labels:
# Dataset format: image path + list of classes present
dataset = {
"image_001.jpg": ["dog", "person"],
"image_002.jpg": ["car"],
"image_003.jpg": ["dog", "cat"],
# ...
}
Generate Region Proposals:
def generate_proposals(image, method="selective_search"):
"""
Generate region proposals for each image
Options:
- Selective Search (traditional)
- Edge Boxes
- RPN (if using two-stage approach)
"""
if method == "selective_search":
import cv2
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(image)
ss.switchToSelectiveSearchFast()
proposals = ss.process()
# Typically use top 2000 proposals
return proposals[:2000]
Phase 2: MIL Network (Stream 1)
Feature Extraction:
class MILNetwork(nn.Module):
def __init__(self, num_classes, backbone='vgg16'):
super().__init__()
self.backbone = load_pretrained_backbone(backbone)
self.roi_pool = ROIPool(output_size=(7, 7))
# Two parallel branches
self.fc_cls = nn.Linear(4096, num_classes) # Classification
self.fc_det = nn.Linear(4096, num_classes) # Detection
def forward(self, image, proposals):
# Extract features
features = self.backbone(image)
# ROI pooling for each proposal
roi_features = self.roi_pool(features, proposals)
# Classification scores (image-level)
cls_scores = F.softmax(self.fc_cls(roi_features), dim=0)
# Detection scores (proposal-level)
det_scores = F.softmax(self.fc_det(roi_features), dim=1)
# Combine: proposal score = cls * det
proposal_scores = cls_scores * det_scores
return proposal_scores
MIL Loss:
def mil_loss(proposal_scores, image_labels):
"""
Image-level classification loss:
Aggregate proposal scores to image-level prediction
"""
# Sum over proposals for each class
image_scores = proposal_scores.sum(dim=0)
# Binary cross-entropy with image labels
loss = F.binary_cross_entropy(
torch.sigmoid(image_scores),
image_labels
)
return loss
Phase 3: Proposal Clustering
Generate Proposal Clusters:
def generate_proposal_clusters(proposals, proposal_scores, iou_threshold=0.5):
"""
Group proposals into clusters based on:
1. Spatial overlap (IoU)
2. Score similarity
"""
clusters = []
# For each class
for c in range(num_classes):
class_scores = proposal_scores[:, c]
# Find high-scoring proposals
high_scoring = proposals[class_scores > 0.1]
# Cluster by spatial overlap
cluster_assignments = cluster_by_iou(
high_scoring,
iou_threshold=iou_threshold
)
for cluster_id in np.unique(cluster_assignments):
cluster_proposals = high_scoring[cluster_assignments == cluster_id]
clusters.append({
'class': c,
'proposals': cluster_proposals,
'center': compute_cluster_center(cluster_proposals)
})
return clusters
Assign Labels from Clusters:
def assign_cluster_labels(proposals, clusters):
"""
Assign pseudo-labels to proposals based on clusters:
- Proposals in object cluster → object label
- Other proposals → background
"""
labels = np.zeros(len(proposals)) # Default: background
for cluster in clusters:
for proposal in cluster['proposals']:
idx = find_proposal_index(proposal, proposals)
labels[idx] = cluster['class']
return labels
Phase 4: PCL Refinement Streams
Refinement Network:
class PCLRefinementStream(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.fc1 = nn.Linear(4096, 4096)
self.fc2 = nn.Linear(4096, num_classes + 1) # +1 for background
def forward(self, roi_features, cluster_labels):
x = F.relu(self.fc1(roi_features))
scores = self.fc2(x)
# Supervised by cluster-generated pseudo-labels
loss = F.cross_entropy(scores, cluster_labels)
return scores, loss
Iterative Refinement:
def train_pcl(images, labels, num_refinement_streams=3):
"""
Train PCL with multiple refinement streams
"""
model = PCLNetwork(num_classes, num_refinement_streams)
for epoch in range(num_epochs):
for image, label in dataloader:
# Generate proposals
proposals = generate_proposals(image)
# Extract ROI features
roi_features = model.extract_features(image, proposals)
# Stream 1: MIL
mil_scores = model.mil_stream(roi_features)
mil_loss = compute_mil_loss(mil_scores, label)
# Generate clusters from MIL output
clusters = generate_proposal_clusters(proposals, mil_scores)
# Refinement streams
total_loss = mil_loss
current_scores = mil_scores
for stream_idx in range(num_refinement_streams):
# Assign labels from clusters
pseudo_labels = assign_cluster_labels(proposals, clusters)
# Refine
refined_scores, refine_loss = model.refinement_streams[stream_idx](
roi_features, pseudo_labels
)
total_loss += refine_loss
# Update clusters for next stream
clusters = generate_proposal_clusters(proposals, refined_scores)
current_scores = refined_scores
# Backprop
total_loss.backward()
optimizer.step()
Phase 5: Inference
- Object Detection:
def detect_objects(model, image, score_threshold=0.5, nms_threshold=0.3):
"""
Run inference to detect objects
"""
proposals = generate_proposals(image)
roi_features = model.extract_features(image, proposals)
# Use final refinement stream for detection
final_scores = model.final_stream(roi_features)
# Apply NMS per class
detections = []
for c in range(num_classes):
class_scores = final_scores[:, c]
high_scoring = class_scores > score_threshold
if high_scoring.any():
boxes = proposals[high_scoring]
scores = class_scores[high_scoring]
# Non-maximum suppression
keep = nms(boxes, scores, nms_threshold)
for idx in keep:
detections.append({
'class': c,
'box': boxes[idx],
'score': scores[idx]
})
return detections
Implementation Tips
Preventing Part Detection
The key advantage of PCL is detecting complete objects, not just discriminative parts:
- Cluster-based learning: Forces network to consider entire object regions
- Multiple refinement streams: Progressively improves localization
- IoU-based clustering: Groups spatially related proposals
Hyperparameters
| Parameter |
Typical Value |
Notes |
| Proposals per image |
2000 |
Top-K from proposal method |
| Refinement streams |
3 |
More streams = better but slower |
| IoU threshold (clustering) |
0.4-0.5 |
Lower = larger clusters |
| Learning rate |
0.001 |
With decay |
| Batch size |
2 |
Limited by GPU memory |
Training Schedule
# Typical training schedule
lr_schedule = {
0: 0.001, # Initial LR
40000: 0.0001, # Decay at 40k iterations
70000: 0.00001 # Final decay
}
total_iterations = 80000
Best Practices
- Pre-training: Use ImageNet pre-trained backbone
- Proposal quality: Good proposals are crucial; use multiple methods
- Data augmentation: Standard augmentation (flip, crop, color)
- Gradual refinement: Don't skip refinement streams
- Evaluation: Use standard detection metrics (mAP)
Expected Results
Based on original paper (PASCAL VOC 2007):
| Method |
mAP |
| Standard MIL |
39.3% |
| PCL (3 streams) |
48.8% |
| PCL + Regression |
52.2% |
Dependencies
# Deep learning
pip install torch torchvision
# Image processing
pip install opencv-python pillow
# Proposals (selective search)
pip install opencv-contrib-python
# Evaluation
pip install pycocotools
Integration with Other Skills
- pytorch: Implementation framework
- torchvision: Pre-trained backbones
- exploratory-data-analysis: Analyze detection results
- matplotlib: Visualize detections
References
- Tang, P., Wang, X., Bai, S., Shen, W., Bai, X., Liu, W., & Yuille, A. PCL: Proposal Cluster Learning for Weakly Supervised Object Detection. IEEE TPAMI.
- ArXiv: https://arxiv.org/abs/1807.03342
1---2name: proposal-cluster-learning3description: Proposal Cluster Learning for Weakly Supervised Object Detection4---56# Proposal Cluster Learning for Weakly Supervised Object Detection78## Overview910Proposal Cluster Learning (PCL) is an end-to-end deep network approach for Weakly Supervised Object Detection (WSOD). It allows training object detectors using only **image-level labels** (e.g., "this image contains a dog") without requiring expensive bounding box annotations.1112**Key Innovation**: Instead of treating detection as classification (like standard MIL approaches), PCL generates "proposal clusters" - groups of spatially adjacent proposals associated with the same object - and uses these clusters to iteratively refine instance classifiers.1314**Benefits**:15- Reduces annotation cost (no bounding boxes needed)16- Detects complete objects (not just discriminative parts)17- State-of-the-art results on PASCAL VOC, ImageNet, MS-COCO1819## When to Use This Skill2021Use this skill when:22- You have image-level labels but no bounding box annotations23- Annotation budget is limited for object detection tasks24- Building detection systems for new domains without existing annotations25- Need to rapidly prototype object detectors26- Working with large-scale datasets where box annotation is infeasible2728## Core Concepts2930### The Problem with Standard MIL3132Traditional Multiple Instance Learning (MIL) for WSOD:33- Treats each image as a "bag" of region proposals34- Learns to classify based on most discriminative regions35- **Problem**: Often focuses on object PARTS (e.g., dog's face) not complete objects3637### PCL Solution: Proposal Clusters38391. **Group related proposals**: Proposals covering the same object are spatially adjacent402. **Cluster-based learning**: Treat each cluster as a mini-bag413. **Iterative refinement**: Multiple CNN streams refine detections4243## Architecture Overview4445```46Image47 │48 ▼49┌─────────────────────────────────────────────────────────┐50│ CNN Backbone (VGG16) │51└─────────────────────────────────────────────────────────┘52 │53 ▼54┌─────────────────────────────────────────────────────────┐55│ Region Proposal Network (RPN) │56│ or Selective Search │57└─────────────────────────────────────────────────────────┘58 │59 ▼60┌─────────────────────────────────────────────────────────┐61│ ROI Pooling Layer │62└─────────────────────────────────────────────────────────┘63 │64 ├──────► Stream 1: MIL Network (Initial Classification)65 │66 ├──────► Stream 2: PCL Refinement 167 │68 ├──────► Stream 3: PCL Refinement 269 │70 └──────► Stream K: PCL Refinement K-171```7273## Core Workflow7475### Phase 1: Setup and Data Preparation76771. **Prepare Image-Level Labels**:78 ```python79 # Dataset format: image path + list of classes present80 dataset = {81 "image_001.jpg": ["dog", "person"],82 "image_002.jpg": ["car"],83 "image_003.jpg": ["dog", "cat"],84 # ...85 }86 ```87882. **Generate Region Proposals**:89 ```python90 def generate_proposals(image, method="selective_search"):91 """92 Generate region proposals for each image93 Options:94 - Selective Search (traditional)95 - Edge Boxes96 - RPN (if using two-stage approach)97 """98 if method == "selective_search":99 import cv2100 ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()101 ss.setBaseImage(image)102 ss.switchToSelectiveSearchFast()103 proposals = ss.process()104 105 # Typically use top 2000 proposals106 return proposals[:2000]107 ```108109### Phase 2: MIL Network (Stream 1)1101111. **Feature Extraction**:112 ```python113 class MILNetwork(nn.Module):114 def __init__(self, num_classes, backbone='vgg16'):115 super().__init__()116 self.backbone = load_pretrained_backbone(backbone)117 self.roi_pool = ROIPool(output_size=(7, 7))118 119 # Two parallel branches120 self.fc_cls = nn.Linear(4096, num_classes) # Classification121 self.fc_det = nn.Linear(4096, num_classes) # Detection122 123 def forward(self, image, proposals):124 # Extract features125 features = self.backbone(image)126 127 # ROI pooling for each proposal128 roi_features = self.roi_pool(features, proposals)129 130 # Classification scores (image-level)131 cls_scores = F.softmax(self.fc_cls(roi_features), dim=0)132 133 # Detection scores (proposal-level)134 det_scores = F.softmax(self.fc_det(roi_features), dim=1)135 136 # Combine: proposal score = cls * det137 proposal_scores = cls_scores * det_scores138 139 return proposal_scores140 ```1411422. **MIL Loss**:143 ```python144 def mil_loss(proposal_scores, image_labels):145 """146 Image-level classification loss:147 Aggregate proposal scores to image-level prediction148 """149 # Sum over proposals for each class150 image_scores = proposal_scores.sum(dim=0)151 152 # Binary cross-entropy with image labels153 loss = F.binary_cross_entropy(154 torch.sigmoid(image_scores),155 image_labels156 )157 158 return loss159 ```160161### Phase 3: Proposal Clustering1621631. **Generate Proposal Clusters**:164 ```python165 def generate_proposal_clusters(proposals, proposal_scores, iou_threshold=0.5):166 """167 Group proposals into clusters based on:168 1. Spatial overlap (IoU)169 2. Score similarity170 """171 clusters = []172 173 # For each class174 for c in range(num_classes):175 class_scores = proposal_scores[:, c]176 177 # Find high-scoring proposals178 high_scoring = proposals[class_scores > 0.1]179 180 # Cluster by spatial overlap181 cluster_assignments = cluster_by_iou(182 high_scoring, 183 iou_threshold=iou_threshold184 )185 186 for cluster_id in np.unique(cluster_assignments):187 cluster_proposals = high_scoring[cluster_assignments == cluster_id]188 clusters.append({189 'class': c,190 'proposals': cluster_proposals,191 'center': compute_cluster_center(cluster_proposals)192 })193 194 return clusters195 ```1961972. **Assign Labels from Clusters**:198 ```python199 def assign_cluster_labels(proposals, clusters):200 """201 Assign pseudo-labels to proposals based on clusters:202 - Proposals in object cluster → object label203 - Other proposals → background204 """205 labels = np.zeros(len(proposals)) # Default: background206 207 for cluster in clusters:208 for proposal in cluster['proposals']:209 idx = find_proposal_index(proposal, proposals)210 labels[idx] = cluster['class']211 212 return labels213 ```214215### Phase 4: PCL Refinement Streams2162171. **Refinement Network**:218 ```python219 class PCLRefinementStream(nn.Module):220 def __init__(self, num_classes):221 super().__init__()222 self.fc1 = nn.Linear(4096, 4096)223 self.fc2 = nn.Linear(4096, num_classes + 1) # +1 for background224 225 def forward(self, roi_features, cluster_labels):226 x = F.relu(self.fc1(roi_features))227 scores = self.fc2(x)228 229 # Supervised by cluster-generated pseudo-labels230 loss = F.cross_entropy(scores, cluster_labels)231 232 return scores, loss233 ```2342352. **Iterative Refinement**:236 ```python237 def train_pcl(images, labels, num_refinement_streams=3):238 """239 Train PCL with multiple refinement streams240 """241 model = PCLNetwork(num_classes, num_refinement_streams)242 243 for epoch in range(num_epochs):244 for image, label in dataloader:245 # Generate proposals246 proposals = generate_proposals(image)247 248 # Extract ROI features249 roi_features = model.extract_features(image, proposals)250 251 # Stream 1: MIL252 mil_scores = model.mil_stream(roi_features)253 mil_loss = compute_mil_loss(mil_scores, label)254 255 # Generate clusters from MIL output256 clusters = generate_proposal_clusters(proposals, mil_scores)257 258 # Refinement streams259 total_loss = mil_loss260 current_scores = mil_scores261 262 for stream_idx in range(num_refinement_streams):263 # Assign labels from clusters264 pseudo_labels = assign_cluster_labels(proposals, clusters)265 266 # Refine267 refined_scores, refine_loss = model.refinement_streams[stream_idx](268 roi_features, pseudo_labels269 )270 271 total_loss += refine_loss272 273 # Update clusters for next stream274 clusters = generate_proposal_clusters(proposals, refined_scores)275 current_scores = refined_scores276 277 # Backprop278 total_loss.backward()279 optimizer.step()280 ```281282### Phase 5: Inference2832841. **Object Detection**:285 ```python286 def detect_objects(model, image, score_threshold=0.5, nms_threshold=0.3):287 """288 Run inference to detect objects289 """290 proposals = generate_proposals(image)291 roi_features = model.extract_features(image, proposals)292 293 # Use final refinement stream for detection294 final_scores = model.final_stream(roi_features)295 296 # Apply NMS per class297 detections = []298 for c in range(num_classes):299 class_scores = final_scores[:, c]300 high_scoring = class_scores > score_threshold301 302 if high_scoring.any():303 boxes = proposals[high_scoring]304 scores = class_scores[high_scoring]305 306 # Non-maximum suppression307 keep = nms(boxes, scores, nms_threshold)308 309 for idx in keep:310 detections.append({311 'class': c,312 'box': boxes[idx],313 'score': scores[idx]314 })315 316 return detections317 ```318319## Implementation Tips320321### Preventing Part Detection322323The key advantage of PCL is detecting complete objects, not just discriminative parts:3243251. **Cluster-based learning**: Forces network to consider entire object regions3262. **Multiple refinement streams**: Progressively improves localization3273. **IoU-based clustering**: Groups spatially related proposals328329### Hyperparameters330331| Parameter | Typical Value | Notes |332|-----------|--------------|-------|333| Proposals per image | 2000 | Top-K from proposal method |334| Refinement streams | 3 | More streams = better but slower |335| IoU threshold (clustering) | 0.4-0.5 | Lower = larger clusters |336| Learning rate | 0.001 | With decay |337| Batch size | 2 | Limited by GPU memory |338339### Training Schedule340341```python342# Typical training schedule343lr_schedule = {344 0: 0.001, # Initial LR345 40000: 0.0001, # Decay at 40k iterations346 70000: 0.00001 # Final decay347}348total_iterations = 80000349```350351## Best Practices3523531. **Pre-training**: Use ImageNet pre-trained backbone3542. **Proposal quality**: Good proposals are crucial; use multiple methods3553. **Data augmentation**: Standard augmentation (flip, crop, color)3564. **Gradual refinement**: Don't skip refinement streams3575. **Evaluation**: Use standard detection metrics (mAP)358359## Expected Results360361Based on original paper (PASCAL VOC 2007):362363| Method | mAP |364|--------|-----|365| Standard MIL | 39.3% |366| PCL (3 streams) | 48.8% |367| PCL + Regression | 52.2% |368369## Dependencies370371```bash372# Deep learning373pip install torch torchvision374375# Image processing376pip install opencv-python pillow377378# Proposals (selective search)379pip install opencv-contrib-python380381# Evaluation382pip install pycocotools383```384385## Integration with Other Skills386387- **pytorch**: Implementation framework388- **torchvision**: Pre-trained backbones389- **exploratory-data-analysis**: Analyze detection results390- **matplotlib**: Visualize detections391392## References393394- Tang, P., Wang, X., Bai, S., Shen, W., Bai, X., Liu, W., & Yuille, A. PCL: Proposal Cluster Learning for Weakly Supervised Object Detection. IEEE TPAMI.395- ArXiv: https://arxiv.org/abs/1807.03342