cv-per-organ-multihead-sigmoid-softmax
SkillDev toolsSingle CNN backbone with one shallow Dense neck per organ and mixed sigmoid (binary) + softmax (multi-class severity) heads, trained with a dict of losses so each organ is calibrated independently while sharing visual features
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the cv-per-organ-multihead-sigmoid-softmax skill
What this skill tells your AI
The instructions your AI receives, as published by wenmin-wu/ds-skills in skills/cv/per-organ-multihead-sigmoid-softmax/SKILL.md and read by ahel’s review.
Overview
Multi-organ trauma classification has a heterogeneous label structure: some organs are binary (injured / not), others have ordered severity grades (healthy / low / high). The naive answer — one big sigmoid head with all classes flattened — destroys the mutual exclusivity inside each grade group and trains every label to compete with every other label. The right structure is one shared backbone, a tiny per-organ "neck" Dense layer, and a head whose activation matches the label semantics: sigmoid for binary organs, softmax for severity-graded ones. Keras compile(loss={...}) accepts a dict mapping head names to losses, so each head gets its correct loss without hand-rolling.
Quick Start
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense
from tensorflow.keras.models import Model
from tensorflow.keras.losses import BinaryCrossentropy, CategoricalCrossentropy
x = GlobalAveragePooling2D()(backbone.output)
necks = {n: Dense(32, activation='silu', name=f'{n}_neck')(x)
for n in ['bowel', 'extra', 'liver', 'kidney', 'spleen']}
outs = [
Dense(1, activation='sigmoid', name='bowel')(necks['bowel']),
Dense(1, activation='sigmoid', name='extra')(necks['extra']),
Dense(3, activation='softmax', name='liver')(necks['liver']),
Dense(3, activation='softmax', name='kidney')(necks['kidney']),
Dense(3, activation='softmax', name='spleen')(necks['spleen']),
]
model = Model(backbone.inputs, outs)
model.compile(
optimizer='adam',
loss={
'bowel': BinaryCrossentropy(label_smoothing=0.05),
'extra': BinaryCrossentropy(label_smoothing=0.05),
'liver': CategoricalCrossentropy(label_smoothing=0.05),
'kidney': CategoricalCrossentropy(label_smoothing=0.05),
'spleen': CategoricalCrossentropy(label_smoothing=0.05),
},
)
Workflow
- Pick the smallest "neck" width that still trains (32 is usually plenty) — smaller necks force the backbone to do the work
- Use sigmoid heads for binary organs and softmax heads for ordered/multi-class organs in the same model
- Pass a dict to
compile(loss=...)matching the head names — Keras auto-routes per-output losses - Apply uniform
label_smoothing=0.05across all heads to prevent any one organ from collapsing onto a 0/1 saturated prediction - At inference, concatenate head outputs in the order the submission expects (use
cv-multihead-softmax-to-flat-submissionpatterns)
Key Decisions
- Per-organ neck, not shared head: a shared head forces every organ to use the same projection of backbone features and underperforms on rare classes.
- Sigmoid + softmax in the same model: forcing everything to sigmoid breaks softmax's mutual-exclusivity guarantee for severity grades; forcing to softmax breaks the binary semantics.
siluoverreluin the neck: smoother gradients on a tiny 32-unit layer; the difference is small but consistently positive.- Label smoothing = 0.05, not 0.1: medical labels are clean enough that aggressive smoothing hurts; 0.05 is the sweet spot for log-loss metrics.
- Don't share weights between necks: the whole point is per-organ specialization on a shared visual representation.
References
Signals
- GitHub stars
- 60
- Forks
- 4
- Last commit
- Apr 2026
Advanced
- Catalog kind
- skill
- Gateway key
cv-per-organ-multihead-sigmoid-softmax- Source
- github.com/wenmin-wu/ds-skills