agentleFS
topprismdatatopprismdata/cultivating-ml-agent/skills/examples/per-category-modeling-backfire/SKILL.md

per-category-modeling-backfire

Skill5 starsChanged 35 days agoMITView on GitHub

Per-category/per-family models can produce WORSE leaderboard scores than a single global model, even when per-category CV is better. Use when: (1) Considering training separate models per product category/family/store in tabular competitions, (2) Per-category CV improves but LB degrades, (3) Each category has <100K rows from a larger dataset. Covers data volume thresholds, hybrid fallback strategies, and when per-category modeling is appropriate vs counterproductive.

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Test predictions from per-category models have shifted mean (higher or lower than expected)

    line 32
  2. Distinctive:

    No principled way to calibrate between the two prediction sources

    line 70
  3. Distinctive:

    Before committing to per-category modeling:

    line 83
  4. Distinctive:

    If each category has <100K rows, the global model's data advantage outweighs the specialization benefit.

    line 148

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Per-Category Modeling Backfire: When Splitting Data Hurts

Problem

A common "advanced" technique in Kaggle competitions is to train separate models for each category (e.g., per product family, per store, per region). While this seems intuitively correct—different categories have different patterns—it can backfire spectacularly when individual categories lack sufficient data volume.

Context / Trigger Conditions

Use when considering:

  • Training separate GBDT models per product family, store, or region
  • Per-category CV improves but leaderboard score degrades
  • Each category has <100K rows but the full dataset has >1M rows
  • Categories have highly imbalanced data volumes (some huge, some tiny)
  • The global model already handles category differences via categorical features

Specific symptoms:

  • Per-category CV: 0.367 (better) vs Global CV: 0.375 → Per-category LB: 2.10 vs Global LB: 1.86
  • Small categories show high CV variance (>0.05 std across folds)
  • Test predictions from per-category models have shifted mean (higher or lower than expected)

Solution

The Data Volume Threshold

Per-category modeling only works when each category has enough data to train a robust model.

Rows per categoryPer-category modelingRecommendation
<50KHarmful — overfitting, high varianceUse global model
50K-200KRisky — depends on problem complexityTest both, compare LB
200K-1MUsually beneficialPer-category if CV confirms
>1MAlmost always beneficialPer-category recommended

Why It Fails With Small Categories

  1. Overfitting: 70K rows × 5-fold CV = 14K validation rows. Easy to overfit.
  2. High variance: Small categories (BOOKS, HARDWARE) have CV std >0.05, meaning the model is unstable across folds.
  3. Feature instability: Lag/rolling features with limited history don't generalize.
  4. Loss of cross-category signal: Global model learns that "weekend → more sales" applies across ALL categories, amplifying this signal. Per-category models each must re-learn this from limited data.

Hybrid Approach Also Fails

A natural fallback—"use per-category for large categories, global for small"—also didn't work in practice:

Global model: LB = 1.86
Per-family (33 models): LB = 2.10 (worse)
Hybrid (15 per-family + global): LB = 2.13 (even worse!)

The hybrid fails because:

  1. Predictions from per-family and global models have different scales/biases
  2. No principled way to calibrate between the two prediction sources
  3. The boundary (large vs small threshold) introduces discontinuities

When Per-Category DOES Work

Per-category modeling is appropriate when:

  1. Categories have genuinely different data-generating processes
  2. Each category has >200K rows
  3. The global model cannot capture category-specific patterns (no categorical features)
  4. Categories have different feature importance rankings

Verification

Before committing to per-category modeling:

# 1. Check data volume per category
for cat in categories:
    n = len(train[train['category'] == cat])
    if n < 100000:
        print(f"WARNING: {cat} has only {n} rows - per-category model may overfit")

# 2. Check CV stability per category
for cat in categories:
    cv_scores = cross_validate(cat_data)
    if np.std(cv_scores) > 0.03:
        print(f"WARNING: {cat} has unstable CV (std={np.std(cv_scores):.4f})")

# 3. Compare global vs per-category on HOLDOUT (not OOF)
global_pred = global_model.predict(test)
per_cat_pred = per_category_models.predict(test)
# If mean(per_cat_pred) differs significantly from mean(global_pred), per-category is suspect

Example

Kaggle Store Sales Time Series Forecasting — 54 stores × 33 families:

Dataset: 2.35M clean rows, 1782 store-family combinations
Per-family: 33 families × ~71K rows each

Results:
├─ Global LightGBM:    CV=0.375, LB=1.859 ✅ Best
├─ Per-family (33):    CV=0.367, LB=2.102 ❌ 13% worse
└─ Hybrid (15+global): CV=—,    LB=2.129 ❌ 15% worse

Analysis:
- Top 5 families (GROCERY I, BEVERAGES, PRODUCE, CLEANING, DAIRY):
  CV 0.108-0.245, each >100K rows worth of sales → per-family helps
- Bottom 5 families (LINGERIE, CELEBRATION, GROCERY II, HARDWARE, AUTOMOTIVE):
  CV 0.450-0.605, each with tiny sales → per-family hurts badly
- The bad families dragged the overall LB down more than the good families helped

Root cause: 71K rows per family is below the threshold for robust GBDT training.
The global model's 2.35M rows provide much more stable estimates.

Notes

Related to Other Skills

  • ml-sweet-spot: "More is not always better" — per-category is "more models" but not always better
  • small-dataset-optimization-limits: Per-category creates small datasets from a large one
  • ensemble-model-correlation-trap: Hybrid approach combines correlated predictions poorly
  • ts-lag-nan-cascade-bug: The ffill fix that made the global model work well

Why TOP Solutions Use Per-Family

Many top Kaggle solutions DO use per-family models successfully. The difference:

  1. They use linear models (Ridge/Lasso) for small families, not GBDT
  2. They have more sophisticated features per family
  3. They use recursive prediction to handle lag features properly
  4. Their per-family models are often simpler (fewer trees, more regularization)

Key Insight

Per-category modeling is a sophistication that requires sufficient data per category. If each category has <100K rows, the global model's data advantage outweighs the specialization benefit.

References

  • Kaggle Store Sales Competition: Global model LB=1.86, Per-family LB=2.10
  • Web search confirms per-family is common winning strategy but typically with larger per-category datasets or simpler models (linear regression)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.