aboutsummaryrefslogtreecommitdiff
path: root/cmds/negcap.go
diff options
context:
space:
mode:
authorSam Scholten2026-10-07 15:33:41 +1000
committerSam Scholten2026-10-07 15:33:41 +1000
commitc465409aaa5672e3466b6f8744b737fa84993f2b (patch)
tree4e88defa764768c7f6d104a1a902d5e092023fc0 /cmds/negcap.go
parent2f75794194209586d0b17d26e4e87d94d3e6aead (diff)
downloadfluxrec-c465409aaa5672e3466b6f8744b737fa84993f2b.tar.gz
fluxrec-c465409aaa5672e3466b6f8744b737fa84993f2b.zip
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.
Diffstat (limited to 'cmds/negcap.go')
-rw-r--r--cmds/negcap.go34
1 files changed, 34 insertions, 0 deletions
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
+}