somatize.health
Whether what happened was healthy. An opinion, and it says so.
from somatize.health import diagnose, aboutfrom somatize.torch import Audit
t = Trainer(g, ..., auditing=True, watching=Recorder(store, run="tuesday"))diagnose(store, run="tuesday") # {"body": ["VANISHING"]}about("VANISHING") # what to do about itThree things get called observability and they are not the same: the declaration drawn, the record of what happened, and this — a judgement about that record, at thresholds somebody chose. The line between the last two is an invariant, and it is a test:
A diagnosis has to be reproducible from the stored record, without training again.
So the verdict lives in a Rust crate with no dependencies at all and this module carries a store to it. Change a bound and ask again.
| flag | what it means |
|---|---|
NAN / INF | a number stopped being one; nothing below it means anything |
VANISHING / EXPLODING | the parameter gradients are too small to train on, or too big to step on |
DEAD / SATURATED | the output is off, or pinned where the derivative is nothing |
STALLED / OVERSTEPPING | the update is tiny, or enormous, next to the weights it moves |
DEAD_CHANNELS(n) / IGNORED_CHANNELS(n) | part of the width does nothing, or does something nobody asks for |
LEAKAGE | two groups meant to stay apart carry the same information |
NARROWING | the update has collapsed into a few directions |
MISSING_NORMALISATION | the signal grows over a stretch with nothing normalising it |
LOSING_PLASTICITY | weights growing, rank falling and units going quiet, all at once |
about(flag) says what to do about each, beside the thresholds because they are
one opinion.
The same three functions read a probe — somatize.torch.probe(g, x), one
forward that was recorded and never trained — because a probe writes run/<id>/0
and nothing here ever asked what made a record.
profile(store, run=...) is the picture, because vanishing is a shape over
depth and not a property of a layer; flags(store, run=...) is the table. They
are the only figures here where colour may mean good-or-bad, because they are the
only ones drawing opinions.
Classes
Section titled “Classes”Thresholds
Section titled “Thresholds”Thresholds(**over)The bounds a verdict is taken at. The whole of the opinion, and it is data: change one and the same record answers differently, without training again.
Thresholds.as_dict
Section titled “Thresholds.as_dict”Thresholds.as_dict()What they are, as a dict — for writing them down beside a diagnosis, so that an alarm from last week can be read with the bounds it was taken at.
Functions
Section titled “Functions”alerts
Section titled “alerts”alerts(store: Store, *, run: str, thresholds: Thresholds | None = None, last: int | None = None) -> AlertsWhat is wrong, loudly. See [Alerts].
about(flag)What a flag means and what to do about it, by name.
Beside the flag and not in whoever draws it: the bounds and the advice are one opinion, and splitting them is how a dashboard ends up saying something this library never said.
diagnose
Section titled “diagnose”diagnose(store: Store, *, run: str, thresholds: Thresholds | None = None, last: int | None = None) -> dict[str, list[str]]What is wrong, as {where: [flag, ...]}. where is a node, or
node.path.to.submodule when inside= was asked to look in.
A node with nothing wrong is not in the answer. Empty would say this was
checked and is fine, and no flags does not mean that: a metric nobody
measured cannot raise one, and seen is what says what was measured.
A fetch per forward looked at, because the numbers are in the blobs.
flags(store: Store, *, run: str, thresholds: Thresholds | None = None, last: int | None = None) -> FigureWhat is wrong with each node, and what to do about it. A table and not a list, because a diagnosis without its advice is a word somebody has to look up. A node with nothing wrong is not a row: empty would read as checked and fine, and a metric nobody measured cannot raise a flag.
history
Section titled “history”history(store: Store, *, run: str, node: str, of: str = 'grad_norm', last: int | None = None) -> list[tuple[int, float]]One measurement of one node over the run, as (forward, value) pairs.
What a curve is drawn from — a gradient norm falling away, an update ratio
drifting. A fetch per forward, so last= is worth using.
overlaid
Section titled “overlaid”overlaid(graph: Graph, store: Store, *, run: str, thresholds: Thresholds | None = None, last: int | None = None, inside: Inside | None = None) -> FigureThe graph, with what is wrong marked on the nodes it is wrong in — the answer to where, which is the question a diagnosis of a distributed graph raises.
Health gets a channel of its own: the fill goes on saying where a node runs, the outline turns to the alarm colour, and the flags are a badge. Recolouring the fill would let is this unhealthy eat where does this run.
inside is what somatize.torch.architecture gives back, and passing it is
what makes a finding land on the layer it is about. Findings from a node
whose architecture was not drawn land on the node.
profile
Section titled “profile”profile(store: Store, *, run: str, of: str = 'grad_norm', thresholds: Thresholds | None = None, last: int | None = None) -> FigureOne measurement per node, across the graph — the shape over depth. of is
any number the audit wrote down. A node that raised a flag is drawn in the
alarm colour and says which on its hover. Nodes are in declaration order,
which for a chain is the order the gradient travelled, backwards.
seen(store: Store, *, run: str, last: int | None = None) -> dict[str, dict[str, float | bool]]The numbers a verdict would be taken over — the latest of each.
Keyed by node, and by node.path.to.submodule for anything inside= was
asked to look at. The dot is what lets a figure colour the node while the
detail says which layer of it: this node is unhealthy is not an answer when
the node is twenty layers deep.
For looking at what was measured rather than at what somebody thinks of it,
and for taking the verdict yourself with verdict(seen[where], bounds).
where(store: Store, *, run: str, thresholds: Thresholds | None = None, last: int | None = None, inside: Inside | None = None) -> OverlayA diagnosis folded onto the nodes of a graph: {node: [flag, ...]}.
What overlaid hands to the figure, and what to hand to graph.figure()
yourself if you are composing something else. A finding inside a node is
named on its flag — LEAKAGE in net.2 — because the box it lands on is the
node.
within
Section titled “within”within(where: str) -> strThe node a key belongs to, whether or not it names a submodule of it.