23 — Closing the Loop: Feedback, Drift, and Continual Learning
“Every recommender trains on data its previous self created. Whether that’s a virtuous cycle or a vicious one is up to you.”
Introduction
A recommender that’s deployed becomes a closed-loop system: it influences what users see, which influences what they click, which becomes the next training set. Without care this loop amplifies bias, collapses diversity, and drifts away from real preferences. This post covers how to detect, measure, and remediate feedback loops, including exploration, randomized holdouts, drift detection, continual learning, and the right monitoring stack.
1. The Loop
flowchart LR
A[Model serves recs] --> B[Users see slate]
B --> C[Clicks / conversions]
C --> D[Logs]
D --> E[Training data]
E --> F[Next-gen model]
F --> A
G[(Bias amplification<br/>diversity collapse<br/>filter bubbles)] -.-> A
Every time you retrain on logs from the previous model, you reinforce its choices.
2. What Goes Wrong
| Pathology | Symptom |
|---|---|
| Popularity bias amplification | Long-tail share drops month over month |
| Distribution shift | Training distribution stops matching the world |
| Feedback collapse | Model converges to a tiny “winners-take-all” set of items |
| Stale freshness | New items can’t break in |
| Click-bait spiral | Engagement optimized at expense of satisfaction |
| Demographic feedback | Models reinforce stereotypes that nudge users into narrower content |
3. Detecting Loops
Catalog Coverage Over Time
Plot fraction of catalog appearing in top-K across weeks. A steady decline is a red flag.
Counterfactual Eval (Blog 17)
Use a small uniform-random traffic slice as an unbiased ground truth. Track gap to it.
Holdouts
Reserve 1–5% of users in a never-treated holdout. Compare engagement vs treated cohort across months. If treated cohort engages more but enjoys less variety, you’ve identified a tradeoff.
Distribution Drift
KS test on input features and on predictions, week-over-week.
from scipy.stats import ks_2samp
def drift(today, baseline):
return ks_2samp(today, baseline).statistic
4. Remediations
Exploration Budget
Reserve K’ slots for items the model would not otherwise have shown. Allocate via:
- Random sampling among long-tail items
- Multi-armed bandit (Blog 13)
- Quota for new items (cold start, Blog 18)
Randomized Slices for Eval
Permanent ~1% traffic with uniform random rankings. Lets you compute unbiased propensities and detect overfitting to the loop.
IPS / DR Training (Blog 17)
Counterfactual training removes the bias the loop introduces.
Diversity / Calibration Re-Ranking (Blog 19)
Force breadth at slate composition time.
Negative Feedback Channels
Explicit dislikes, “show me less of this,” topic mute. These signals are gold — they aren’t conditional on what you showed.
Continual / Incremental Learning
Daily / hourly fine-tuning keeps the model close to current distribution. Warm-start from yesterday; periodic from-scratch retrain monthly to escape local optima.
# Continual training loop pseudocode
prev_model = load_latest()
data_today = read_kafka_window(last_24h)
prev_model.fit(data_today, epochs=1, lr=1e-4)
prev_model.save_versioned()
Replay Buffers
Mix today’s data with a stratified sample of historical data so the model doesn’t forget rare cases.
5. Online Learning vs Periodic Retraining
| Approach | Pros | Cons |
|---|---|---|
| Periodic (daily/weekly) | Simple, reproducible | Day-of-event freshness lost |
| Continual (hourly) | Always-fresh | Risk of catastrophic forgetting |
| Online (per-event) | Instant adaptation | Hard to roll back; debugging nightmare |
Most production systems run a hybrid: periodic full retrains + incremental hot updates.
6. Model Monitoring Stack
flowchart LR
A[Predictions log] --> B[Calibration monitor]
A --> C[Distribution monitor]
D[Outcomes log] --> E[Live metrics<br/>CTR/CVR by cohort]
E --> F[Alerting<br/>thresholds & sequential]
G[Feature store] --> H[Feature drift dashboards]
I[Catalog stats] --> J[Coverage / Gini]
Watch:
- CTR/CVR by cohort, country, device, freshness bucket
- Catalog Gini coefficient
- Calibration ECE weekly
- Latency p95/p99 per surface
- Cold-start NDCG specifically (users <5 interactions, items <7 days)
Alert on rate of change, not absolute thresholds.
7. Concept Drift Patterns
- Sudden: news event, viral content. Rapid retrain or rollback.
- Gradual: seasonality, taste evolution. Continual learning handles this.
- Recurring: weekday vs weekend, holidays. Calendar features + cohort-aware models.
- Adversarial: spam, bots, click farms. Anomaly detection upstream.
8. Catastrophic Forgetting
Continual training can forget rare patterns:
- Elastic Weight Consolidation (EWC): penalize big changes to important weights.
- Replay: keep a buffer of historical examples.
- Distillation: new model matches old model’s predictions on a holdout — keeps continuity.
9. End-to-End: Drift Dashboard Snippet
import pandas as pd, numpy as np
from scipy.stats import ks_2samp
def daily_drift_report(baseline_df, today_df, features):
rows = []
for f in features:
stat = ks_2samp(baseline_df[f].dropna(), today_df[f].dropna()).statistic
nullrate = today_df[f].isna().mean()
rows.append({"feature": f, "ks": stat, "null_rate": nullrate})
return pd.DataFrame(rows).sort_values("ks", ascending=False)
# coverage
def catalog_gini(rec_counts):
counts = np.sort(rec_counts.values)
n = len(counts); c = counts.cumsum() / counts.sum()
return 1 - 2 * (c.sum() - 0.5) / n
10. Pros & Cons of Loop Strategies
| Strategy | Pros | Cons |
|---|---|---|
| Exploration budget | Cheap, immediate | Costs CTR |
| Randomized slice | Unbiased eval forever | Some users see random recs |
| IPS / DR | Theoretically correct | Variance, propensity logging |
| Continual learning | Fresh | Catastrophic forgetting risk |
| Replay buffers | Memory of long tail | Storage cost |
| Calibration / diversity rerank | Improves UX | Complex tuning |
11. Production Tips
- Never deploy a new ranker without holdout + counterfactual eval (Blog 17, Blog 20).
- Log everything: served items, predicted scores, propensities, features used, model version.
- Make rollback a one-command operation. You will need it.
- Define a degradation runbook: which alerts trigger which rollbacks.
- Re-derive gold labels quarterly from a randomized slice.
- Pair every retraining job with shadow scoring of yesterday’s traffic by the new candidate model.
12. Pitfalls
- Retraining on logs only — no holdout, no randomization → silent collapse.
- Treating CTR upticks as success without checking diversity or long-term retention.
- Letting fresh-content gates push out cold items entirely.
- Forgetting user-side feedback: dislikes, churn, app uninstalls are signals.
- Ignoring infra drift: a Kafka schema change can quietly corrupt features.
13. Public Datasets / References
- MovieLens with simulated retraining loops
- Yahoo R6 — randomized slices for OPE
- ZOZO Open Bandit — drift simulation supported — https://github.com/st-tech/zr-obp
- Kaggle “Drift detection” datasets
- Recsys Challenge logs — across multiple years (drift visible)
14. Further Reading
- Bottou et al., Counterfactual Reasoning and Learning Systems (JMLR 2013) — defines the loop
- Chaney, Stewart, Engelhardt, How Algorithmic Confounding in Recommendation Systems Increases Homogeneity and Decreases Utility (RecSys 2018)
- Sinha, Gleich, Ramani, Deconvolving Feedback Loops in Recommender Systems (NIPS 2016)
- Kirkpatrick et al., Overcoming Catastrophic Forgetting in Neural Networks (EWC) (PNAS 2017)
- Klabjan & Naumov, Online Learning at Scale at Facebook talk
- Lu et al., Learning under Concept Drift: A Review (TKDE 2019)