aboutsummaryrefslogtreecommitdiff
path: root/cmds/negcap.go
blob: f531b145cb120ea9e4e6427675cbc78dfdb12aeb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
}