Skip to content

Latest commit

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

💧 Water Segmentation — Hard Boundary Detection

Kaggle Competition: Find the Water Task: Pixel-level segmentation of water regions with precise boundary detection Training time: ~1 hour on Kaggle T4 GPU


🏆 Final Results

MetricScore
Val Dice0.9814
Val IoU0.9641
Composite Score0.9710
Best Epoch45 / 60
Optimal Threshold0.60

📈 Training Curves

Training Curves

The model converged rapidly — hitting 0.94+ composite by epoch 5 — then continued refining steadily through epoch 45 before plateauing. No overfitting was observed: val loss tracked train loss closely throughout all 60 epochs.

Key milestones:

EpochVal DiceVal IoUComposite
10.8580.7630.801
50.9630.9310.944
100.9710.9460.956
200.9780.9580.966
430.9810.9640.971 ← best
600.9780.9590.967

🔍 Sample Predictions

Sample Predictions

Each row shows: input image → ground truth mask → model prediction. The model cleanly delineates water boundaries even in complex scenes with reflections, partial occlusion, and varying lighting.


📉 Threshold Optimization

Threshold Analysis

Rather than using a fixed threshold of 0.5, the optimal binarization threshold was searched across the validation set using the actual competition metric. Higher thresholds produced sharper, more conservative boundaries — improving Boundary IoU and Contour F1 at the cost of slightly lower Region IoU.

ThresholdScoreB-IoUC-F1R-IoU
0.300.4210.1450.4290.955
0.400.4530.1860.4680.959
0.500.4780.2210.4940.962
0.600.4930.2430.5080.962

Optimal threshold: 0.60 — the scoring heavily weights boundary precision (80% of score), so a higher threshold that tightens the predicted boundary outperforms the default 0.5.


🧠 Approach

Why this problem is hard

Standard segmentation metrics (IoU, Dice) reward getting the bulk of the region right and are forgiving of sloppy edges. This competition flips that — 80% of the score comes from boundary precision:

ComponentWeightWhat it measures
Boundary IoU40%Overlap of dilated boundary pixels
Contour F140%Point-to-point contour matching within 3px
Region IoU20%Standard mask overlap

A model that perfectly segments the region but has rough edges will score poorly. Every design decision here was made with boundary sharpness in mind.


Model: U-Net + EfficientNet-B3

Input (512×640) → EfficientNet-B3 Encoder → U-Net Decoder → Binary Mask

U-Net was chosen for its skip connections, which pass full-resolution spatial features directly from encoder to decoder — preserving the fine edge detail that deep encoders compress away. This is essential when boundary precision accounts for 80% of the score.

EfficientNet-B3 as the encoder provides a strong pretrained feature extractor with a good balance of accuracy and compute efficiency.

Input resolution 512×640 (upscaled from original) gives the model more pixels to work with at boundaries, directly improving contour precision.


Loss Function: EnhancedBoundaryLoss

Standard BCE or Dice loss only cares about pixel-level region overlap — they give no special weight to boundaries. The custom loss combines four terms:

ComponentWeightRole
BCE Loss20%Baseline per-pixel supervision
Dice Loss30%Region-level overlap
Gradient Loss30%Penalizes differences in spatial gradients — directly targets boundary sharpness
Sobel Edge Loss20%MSE between predicted and ground truth edge maps via Sobel filter

The gradient and Sobel terms together account for 50% of the total loss, meaning the model is explicitly trained to match boundaries rather than just regions.


Training Setup

ParameterValueReason
OptimizerAdamWWeight decay regularization helps generalization
Learning rate1e-4Conservative LR for stable boundary learning
SchedulerCosineAnnealingWarmRestartsPeriodic LR resets help escape local minima
Batch size2 (effective 12)Gradient accumulation × 6 steps
Mixed precisionAMPFaster training, lower memory
Early stoppingPatience 20Stopped at epoch 60 (patience 15/20)

Augmentation Strategy

Augmentations were kept deliberately conservative — aggressive transforms distort boundaries and degrade the contour metric.

  • Geometric: horizontal/vertical flips, mild rotation (±15°), small scale/shift
  • Color: brightness/contrast, hue-saturation, gamma
  • Noise: Gaussian noise/blur (light)
  • Weather: rain, fog, shadow (20% probability — simulates real-world water scenes)
  • Dropout: CoarseDropout for occlusion robustness

Inference: Test-Time Augmentation (TTA)

At inference, each image is predicted 4 times and the results averaged:

  1. Original
  2. Horizontal flip → flip back
  3. Vertical flip → flip back
  4. Both flips → flip back

Averaging reduces prediction variance at boundaries, which directly improves Contour F1.


Post-Processing

  1. Noise removal — connected components smaller than 100px are discarded as false positives
  2. Morphological closing — small holes in the mask are filled with a 2×2 elliptical kernel
  3. Edge refinement — in the boundary region (dilated − eroded mask), a slightly lower threshold (0.50 instead of 0.60) is applied to recover high-confidence edge pixels that the main threshold would discard

📁 Repository Structure

├── notebook.ipynb # Full training + inference notebook
├── submission.csv # Final predictions (RLE encoded, 200 images)
├── results/
│ ├── training_curves.png # Loss & Dice/IoU over 60 epochs
│ ├── predictions_sample.png # Image | Ground truth | Prediction
│ └── threshold_analysis.png # Competition score vs threshold
├── data/
│ └── threshold_optimization.csv # Raw threshold search results
└── README.md

🔁 Reproducing

  1. Add the find-the-water dataset to your Kaggle notebook
  2. Run all cells in notebook.ipynb
  3. Outputs saved to /kaggle/working/: submission.csv, checkpoints/best_model.pth, training_history.csv

Training takes approximately 1 hour on a Kaggle T4 GPU (60 epochs × ~63s/epoch).

Releases

Packages

Contributors

Languages