From c465409aaa5672e3466b6f8744b737fa84993f2b Mon Sep 17 00:00:00 2001 From: Sam Scholten Date: Wed, 7 Oct 2026 15:33:41 +1000 Subject: export: per-feed negative ceiling (--neg-feed-cap-pct) No feed may hold more than 5% (floor 50) of the negative target, counting existing history: uncapped, a few firehoses own the negative class because the read+unstarred pool is sized by feed volume. Enforced by skipping ceiling-full feeds mid seeded permutation walk; shortfall vs the ratio is reported, neg_capped added to the run summary. Also makes bulk mark-read of high-volume feeds safe for training data. --- cmds/negcap.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 cmds/negcap.go (limited to 'cmds/negcap.go') diff --git a/cmds/negcap.go b/cmds/negcap.go new file mode 100644 index 0000000..f531b14 --- /dev/null +++ b/cmds/negcap.go @@ -0,0 +1,34 @@ +// Per-feed negative ceiling, shared by `export` (enforced while sampling) +// and `compact-labels` (enforced against existing history). +// +// Why: negatives are sampled from the read+unstarred pool, whose size is +// dominated by high-volume feeds. Without a ceiling, a handful of firehoses +// own most of the negative class, and the model learns "wire headline" as +// its main axis instead of "headline samsc liked". The ceiling is on each +// feed's *total* negatives (existing history + this run's additions), not +// just the run's sample: a per-run cap would only slow domination, not +// bound it. +package cmds + +// negFeedCapFloor is the absolute minimum ceiling. It keeps a young, +// still-small negative pool from capping feeds at a handful of rows: +// early training needs volume more than it needs balance. +const negFeedCapFloor = 50 + +// feedNegCap returns the maximum negatives any single feed may hold in the +// pool: pct% of the target (negRatio × posTotal), floored at negFeedCapFloor +// and never above the target itself. pct <= 0 means uncapped (0). +func feedNegCap(pct, negRatio, posTotal int) int { + if pct <= 0 { + return 0 + } + target := negRatio * posTotal + cap := pct * target / 100 + if cap < negFeedCapFloor { + cap = negFeedCapFloor + } + if cap > target { + cap = target + } + return cap +} -- cgit v1.2.3