JoeStrout

Untitled

Dec 16th, 2025
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.24 KB | Science | 0 0
  1. # Plan: Hierarchical supervision + ignore “uncertain” voxels (PyTorch)
  2.  
  3. ## Goals
  4. - Train a voxel-wise segmentation network on **12 fine classes**.
  5. - Add **coarse (hierarchical) supervision** so the model learns broad categories early and provides interpretable group-level confidence.
  6. - Voxels labeled **“uncertain”** by human tracers should be **excluded from *all* losses and metrics** (no gradient contribution), while still being present in the input.
  7.  
  8. ---
  9.  
  10. ## Label set
  11.  
  12. ### Fine classes (12-way softmax)
  13. Index these as `0..11` with the following ordering:
  14. 0. extracellular space
  15. 1. tear
  16. 2. dendrite
  17. 3. axon
  18. 4. soma
  19. 5. glia
  20. 6. myelin
  21. 7. myelin inner tongue
  22. 8. myelin outer tongue
  23. 9. nucleus
  24. 10. mitochondria
  25. 11. fat globule
  26.  
  27. ### Special label
  28. - **uncertain** (void/ignore): store as `IGNORE_INDEX = -1` in the fine label tensor.
  29. - Do **not** include “uncertain” as a 13th softmax class.
  30.  
  31. ---
  32.  
  33. ## Coarse hierarchy (auxiliary heads)
  34. Use one shared backbone and multiple output heads:
  35.  
  36. ### Coarse group head (example: 5 bits)
  37. A sigmoid (multi-label) head with these groups:
  38. - **neuron_part**: {dendrite, axon, soma}
  39. - **glia**: {glia}
  40. - **myelin_related**: {myelin, myelin inner tongue, myelin outer tongue}
  41. - **organelle**: {nucleus, mitochondria, fat globule}
  42. - **non_tissue**: {extracellular space, tear}
  43.  
  44. Notes:
  45. - Coarse groups are *derived deterministically* from the fine label.
  46. - If a voxel is `IGNORE_INDEX` (uncertain), **ignore it for coarse losses too** (do not assign coarse targets).
  47.  
  48. Optional later: add additional levels/heads (e.g., split `myelin_related` into `myelin` vs `{inner, outer}`), but start with one coarse head.
  49.  
  50. ---
  51.  
  52. ## Model outputs
  53. For a batch of volumes/patches:
  54. - `logits_fine`: shape **[B, 12, D, H, W]**
  55. - `logits_coarse`: shape **[B, 5, D, H, W]** (or [B, K, ...])
  56.  
  57. ---
  58.  
  59. ## Target tensors
  60. - `y_fine`: shape **[B, D, H, W]**
  61. - values in `0..11` for known voxels
  62. - value `-1` for uncertain voxels
  63.  
  64. - `valid_mask = (y_fine != IGNORE_INDEX)`: shape **[B, D, H, W]** boolean
  65.  
  66. - `y_coarse`: derived from `y_fine` (only meaningful where `valid_mask=True`):
  67. - shape **[B, 5, D, H, W]** of {0,1}
  68.  
  69. ### Mapping fine → coarse
  70. Create a lookup table of size 12 → 5 bits, e.g.:
  71.  
  72. | Fine class | neuron_part | glia | myelin_related | organelle | non_tissue |
  73. |---|---:|---:|---:|---:|---:|
  74. | extracellular space | 0 | 0 | 0 | 0 | 1 |
  75. | tear | 0 | 0 | 0 | 0 | 1 |
  76. | dendrite | 1 | 0 | 0 | 0 | 0 |
  77. | axon | 1 | 0 | 0 | 0 | 0 |
  78. | soma | 1 | 0 | 0 | 0 | 0 |
  79. | glia | 0 | 1 | 0 | 0 | 0 |
  80. | myelin | 0 | 0 | 1 | 0 | 0 |
  81. | myelin inner tongue | 0 | 0 | 1 | 0 | 0 |
  82. | myelin outer tongue | 0 | 0 | 1 | 0 | 0 |
  83. | nucleus | 0 | 0 | 0 | 1 | 0 |
  84. | mitochondria | 0 | 0 | 0 | 1 | 0 |
  85. | fat globule | 0 | 0 | 0 | 1 | 0 |
  86.  
  87. Implementation options:
  88. - `coarse_lut = torch.tensor([
  89. # extracellular space (index 0)
  90. [0,0,0,0,1],
  91. # tear (index 1)
  92. [0,0,0,0,1],
  93. # dendrite (index 2)
  94. [1,0,0,0,0],
  95. # axon (index 3)
  96. [1,0,0,0,0],
  97. # soma (index 4)
  98. [1,0,0,0,0],
  99. # glia (index 5)
  100. [0,1,0,0,0],
  101. # myelin (index 6)
  102. [0,0,1,0,0],
  103. # myelin inner tongue (index 7)
  104. [0,0,1,0,0],
  105. # myelin outer tongue (index 8)
  106. [0,0,1,0,0],
  107. # nucleus (index 9)
  108. [0,0,0,1,0],
  109. # mitochondria (index 10)
  110. [0,0,0,1,0],
  111. # fat globule (index 11)
  112. [0,0,0,1,0],
  113. ], dtype=torch.float32)` of shape [12,5]
  114. - For valid voxels: `y_coarse = coarse_lut[y_fine_valid]` then reshape back.
  115.  
  116. ---
  117.  
  118. ## Losses (mask out “uncertain” voxels everywhere)
  119.  
  120. ### Fine loss: Cross-entropy with ignore index
  121. Use per-voxel loss, then apply `valid_mask` and normalize by number of valid voxels.
  122.  
  123. PyTorch pattern:
  124. - `ce = F.cross_entropy(logits_fine, y_fine, ignore_index=IGNORE_INDEX, reduction='none')`
  125. - `ce` has shape [B, D, H, W]
  126. - `loss_fine = (ce * valid_mask).sum() / valid_mask.sum().clamp_min(1)`
  127.  
  128. ### Coarse loss: BCEWithLogitsLoss masked by valid voxels
  129. Compute elementwise BCE, reduce across coarse channels, apply same voxel mask.
  130.  
  131. PyTorch pattern:
  132. - `bce = F.binary_cross_entropy_with_logits(logits_coarse, y_coarse, reduction='none')`
  133. - `bce` shape [B, 5, D, H, W]
  134. - reduce per voxel: `bce_voxel = bce.mean(dim=1)` → shape [B, D, H, W]
  135. - `loss_coarse = (bce_voxel * valid_mask).sum() / valid_mask.sum().clamp_min(1)`
  136.  
  137. ### Total loss
  138. - `loss = loss_fine + lambda_coarse * loss_coarse`
  139.  
  140. Suggested starting weight:
  141. - `lambda_coarse = 0.2` (tune 0.05–0.5 depending on behavior)
  142.  
  143. Critical rule:
  144. - If a voxel is labeled uncertain (`-1`), it must contribute **zero** loss to both fine and coarse terms.
  145.  
  146. ---
  147.  
  148. ## Metrics (also ignore uncertain)
  149. All evaluation (accuracy, IoU/Dice, confusion matrices) should be computed only on voxels where `valid_mask=True`.
  150.  
  151. ---
  152.  
  153. ## Training loop checklist
  154. 1. Forward pass → `logits_fine`, `logits_coarse`
  155. 2. Build `valid_mask` from `y_fine`
  156. 3. Build `y_coarse` from `y_fine` using LUT (values don’t matter where invalid, but simplest is to fill zeros then mask loss)
  157. 4. Compute `loss_fine` with ignore_index and/or explicit masking
  158. 5. Compute `loss_coarse` with explicit masking
  159. 6. Combine losses, backprop
  160. 7. Compute masked metrics for logging
  161.  
  162. ---
  163.  
  164. ## Minimal code skeleton (PyTorch)
  165. ```python
  166. import torch
  167. import torch.nn.functional as F
  168.  
  169. IGNORE_INDEX = -1
  170.  
  171. # coarse groups (K=5): neuron_part, glia, myelin_related, organelle, non_tissue
  172. coarse_lut = torch.tensor([
  173. # extracellular space (index 0)
  174. [0,0,0,0,1],
  175. # tear (index 1)
  176. [0,0,0,0,1],
  177. # dendrite (index 2)
  178. [1,0,0,0,0],
  179. # axon (index 3)
  180. [1,0,0,0,0],
  181. # soma (index 4)
  182. [1,0,0,0,0],
  183. # glia (index 5)
  184. [0,1,0,0,0],
  185. # myelin (index 6)
  186. [0,0,1,0,0],
  187. # myelin inner tongue (index 7)
  188. [0,0,1,0,0],
  189. # myelin outer tongue (index 8)
  190. [0,0,1,0,0],
  191. # nucleus (index 9)
  192. [0,0,0,1,0],
  193. # mitochondria (index 10)
  194. [0,0,0,1,0],
  195. # fat globule (index 11)
  196. [0,0,0,1,0],
  197. ], dtype=torch.float32)
  198.  
  199.  
  200. def make_coarse_targets(y_fine: torch.Tensor) -> torch.Tensor:
  201. """y_fine: [B, D, H, W] with values 0..11 or -1.
  202. Returns y_coarse: [B, K, D, H, W] float {0,1}.
  203. """
  204. B, D, H, W = y_fine.shape
  205. K = coarse_lut.shape[1]
  206.  
  207. y_coarse = torch.zeros((B, K, D, H, W), device=y_fine.device, dtype=torch.float32)
  208.  
  209. valid = (y_fine != IGNORE_INDEX)
  210. if valid.any():
  211. yv = y_fine[valid].long() # [N]
  212. cv = coarse_lut.to(y_fine.device)[yv] # [N, K]
  213. # scatter back
  214. # y_coarse[:, :, ...][valid] expects [N, K] if we permute
  215. y_coarse_perm = y_coarse.permute(0,2,3,4,1) # [B, D, H, W, K]
  216. y_coarse_perm[valid] = cv
  217. y_coarse = y_coarse_perm.permute(0,4,1,2,3) # [B, K, D, H, W]
  218.  
  219. return y_coarse
  220.  
  221.  
  222. def compute_losses(logits_fine, logits_coarse, y_fine, lambda_coarse=0.2):
  223. """logits_fine: [B, 12, D, H, W]
  224. logits_coarse: [B, 5, D, H, W]
  225. y_fine: [B, D, H, W] in 0..11 or -1
  226. """
  227. valid = (y_fine != IGNORE_INDEX).float() # [B, D, H, W]
  228. denom = valid.sum().clamp_min(1.0)
  229.  
  230. # Fine CE (ignore_index also works, but we still normalize explicitly)
  231. ce = F.cross_entropy(logits_fine, y_fine, ignore_index=IGNORE_INDEX, reduction='none')
  232. loss_fine = (ce * valid).sum() / denom
  233.  
  234. # Coarse BCE
  235. y_coarse = make_coarse_targets(y_fine)
  236. bce = F.binary_cross_entropy_with_logits(logits_coarse, y_coarse, reduction='none')
  237. bce_voxel = bce.mean(dim=1) # [B, D, H, W]
  238. loss_coarse = (bce_voxel * valid).sum() / denom
  239.  
  240. loss = loss_fine + lambda_coarse * loss_coarse
  241. return loss, loss_fine, loss_coarse
  242. ```
  243.  
  244. ---
  245.  
  246. ## Inference and interpretation
  247. - Fine prediction: `argmax(softmax(logits_fine), dim=1)`
  248. - Coarse confidence: `sigmoid(logits_coarse)` gives group-wise probabilities.
  249. - Optional “fallback”: if fine distribution is uncertain but a coarse group is confident, you can report the coarse group as “good enough”.
  250.  
  251. ---
  252.  
  253. ## Common pitfalls
  254. - **Do not** treat “uncertain” as a real class in softmax if you want it excluded from learning.
  255. - Mask out uncertain voxels in **metrics and logging**, not just loss.
  256. - If you oversample patches, ensure sampling/weights are computed from **valid voxels only**.
  257.  
Tags: Markdown ML
Advertisement
Add Comment
Please, Sign In to add comment