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
}
|