Sagargupta1610 commited on
Commit
4bad397
·
verified ·
1 Parent(s): 587a802

Publish the model trained 2026-10-18

Browse files
Files changed (8) hide show
  1. .gitattributes +1 -0
  2. README.md +112 -0
  3. backtest.md +23 -0
  4. metrics.json +19 -0
  5. model.json +0 -0
  6. model.keras +3 -0
  7. scaler.json +38 -0
  8. weights.npz +3 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ model.keras filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ library_name: keras
4
+ pipeline_tag: tabular-regression
5
+ tags:
6
+ - tabular-regression
7
+ - leetcode
8
+ - rating-prediction
9
+ - numpy
10
+ ---
11
+
12
+ # LeetCode Rating Predictor
13
+
14
+ Predicts how much a LeetCode contest placement moves a user's rating, from the
15
+ rank and the user's contest history. It is a small Dense network
16
+ (`15 -> 128 -> 64 -> 32 -> 1`, 12,417 parameters) that runs on plain
17
+ NumPy or JavaScript; no ML framework is needed to serve it.
18
+
19
+ It powers the [web app](https://leetcode-rating-predictor.onrender.com) and the
20
+ [browser extension](https://github.com/Sagargupta16/LeetCode_Rating_Predictor#browser-extension), which loads `model.json`
21
+ from this repo and adds a Predicted column to LeetCode ranking pages.
22
+ Source, training and feature code: [GitHub](https://github.com/Sagargupta16/LeetCode_Rating_Predictor).
23
+
24
+ ## Files
25
+
26
+ | File | What |
27
+ |---|---|
28
+ | `model.json` | Network and scaler in one JSON file (what the extension loads) |
29
+ | `weights.npz`, `scaler.json` | The same network as NumPy arrays (what the API loads) |
30
+ | `model.keras` | The trained Keras model these are exported from |
31
+ | `metrics.json` | Split sizes and held-out scores of this model |
32
+ | `backtest.md` | Accuracy on recent contests of users outside the training data |
33
+
34
+ ## Results
35
+
36
+ Held-out users (818 users, 27,698 contest
37
+ records never seen in training), trained 2026-10-18:
38
+
39
+ | Metric | Value |
40
+ |---|---|
41
+ | MAE | 14.14 |
42
+ | RMSE | 22.01 |
43
+ | MAE of always predicting 0 | 27.79 |
44
+
45
+ Backtest on recent real contests of users outside the training data
46
+ (`scripts/backtest.py`, 2026-10-19):
47
+
48
+ | Metric | Value |
49
+ |---|---|
50
+ | Contests scored | 173 |
51
+ | MAE | 9.11 |
52
+ | Direction right (gain vs loss) | 90.8% |
53
+ | MAE of always predicting 0 | 29.34 |
54
+
55
+ ## Inputs
56
+
57
+ One row per contest result, in this order. `scaler` in `model.json` maps them
58
+ to the network's range (`x * scale + min`).
59
+
60
+ | | Feature |
61
+ |---|---|
62
+ | f1 | `rating_before` |
63
+ | f2 | `rank` |
64
+ | f3 | `participants` |
65
+ | f4 | `rank_percent` |
66
+ | f5 | `contest_index` |
67
+ | f6 | `avg_solve_rate` |
68
+ | f7 | `avg_finish_time` |
69
+ | f8 | `recent_solve_rate` |
70
+ | f9 | `recent_finish_time` |
71
+ | f10 | `rating_trend` |
72
+ | f11 | `max_rating` |
73
+ | f12 | `log_rank` |
74
+ | f13 | `rating_x_rank_share` |
75
+ | f14 | `solve_rate_x_rating` |
76
+ | f15 | `finish_time_share` |
77
+
78
+ `participants` is `max(rank * 1.5, 10000)`, the form the training data was
79
+ built with, not the registered count. See `app/services/features.py` on GitHub
80
+ for the exact definitions.
81
+
82
+ ## Use
83
+
84
+ ```python
85
+ import json, numpy as np
86
+ from huggingface_hub import hf_hub_download
87
+
88
+ model = json.load(open(hf_hub_download("Sagargupta1610/leetcode-rating-predictor", "model.json")))
89
+ x = np.asarray(features, dtype=float) # the 15 inputs above
90
+ x = x * np.asarray(model["scaler"]["scale"]) + np.asarray(model["scaler"]["min"])
91
+ for layer in model["layers"]:
92
+ x = x @ np.asarray(layer["kernel"]) + np.asarray(layer["bias"])
93
+ if layer["activation"] == "relu":
94
+ x = np.maximum(x, 0)
95
+ rating_change = float(x[0])
96
+ ```
97
+
98
+ ## Training
99
+
100
+ Trained on the contest records collected by `scripts/update_data.py`: 295,899 records from 8,182 users,
101
+ split by user so no user is on both sides (242,108 train,
102
+ 26,093 validation, 27,698 test), huber loss,
103
+ seed 42, 62 epochs with early stopping.
104
+
105
+ ## Limits
106
+
107
+ - A user's first few contests can move the rating by 100+ points; those are the
108
+ largest errors.
109
+ - The participant count is synthetic (see Inputs), so the model does not know
110
+ how large a contest really was.
111
+ - LeetCode can change its rating formula; the backtest is re-run weekly to
112
+ catch drift.
backtest.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backtest
2
+
3
+ Generated by `python scripts/backtest.py` on 2026-10-19. Each prediction
4
+ uses only the history a user had before that contest, built by the same feature
5
+ code the API serves, and is compared with the rating change LeetCode applied.
6
+
7
+ | Metric | Value |
8
+ |---|---|
9
+ | Contests scored | 173 |
10
+ | Users | 50 |
11
+ | MAE | **9.11** |
12
+ | RMSE | 19.85 |
13
+ | Median absolute error | 5.59 |
14
+ | Predictions within 10 points | 73.4% |
15
+ | Direction right (gain vs loss) | 90.8% |
16
+ | MAE of always predicting 0 | 29.34 |
17
+
18
+ Users were sampled with seed 7 from `data/usernames.json` past index
19
+ 12000, the slice `scripts/update_data.py` never fetches, so none of
20
+ these contests is in the training data. Up to the last 5 rated contests
21
+ per user count, if they started within 365 days of the run.
22
+ 869 sampled accounts had no contest in that window.
23
+ Model: `models/weights.npz`.
metrics.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "trained": "2026-10-18",
3
+ "split": "by user",
4
+ "loss": "huber",
5
+ "seed": 42,
6
+ "records": 295899,
7
+ "users": 8182,
8
+ "train_records": 242108,
9
+ "val_records": 26093,
10
+ "test_records": 27698,
11
+ "test_users": 818,
12
+ "epochs_run": 62,
13
+ "params": 12417,
14
+ "test": {
15
+ "mae": 14.14,
16
+ "rmse": 22.01,
17
+ "zero_baseline_mae": 27.79
18
+ }
19
+ }
model.json ADDED
The diff for this file is too large to render. See raw diff
 
model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e557df8d962cbf075e533c0692183ba5bc6372818547e1579195f3c9104c0528
3
+ size 184012
scaler.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "MinMaxScaler",
3
+ "n_features_in": 15,
4
+ "scale": [
5
+ 0.0004001544596214139,
6
+ 2.491839226533104e-05,
7
+ 1.9921112394916133e-05,
8
+ 0.015001500150015003,
9
+ 0.0015772870662460567,
10
+ 1.0,
11
+ 5.1148278860416345e-05,
12
+ 1.0,
13
+ 5.1148278860416345e-05,
14
+ 0.0017399474535869017,
15
+ 0.0004595343262951056,
16
+ 0.10093974906378382,
17
+ 0.000510551230420807,
18
+ 0.0002867677557417787,
19
+ 0.2761973153620947
20
+ ],
21
+ "min": [
22
+ -0.4302804882684717,
23
+ -2.491839226533104e-05,
24
+ -0.19921112394916132,
25
+ -0.00015001500150015003,
26
+ 0.0,
27
+ 0.0,
28
+ -0.005012531328320802,
29
+ 0.0,
30
+ -0.005012531328320802,
31
+ 0.1451672959476624,
32
+ -0.6425231926974482,
33
+ -0.06996134007610857,
34
+ -7.658268456312105e-05,
35
+ 0.0,
36
+ -0.004999171408053915
37
+ ]
38
+ }
weights.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:37600eaa49d5a96e1e6b478332b3e5d1cc94ff245259241f33d38e8fae7154a2
3
+ size 52142