Cryptic2-0 commited on
Commit
3dd3953
·
verified ·
1 Parent(s): 374bba9

v1 binary sentiment INT8 ONNX (DistilBERT, SageMaker fine-tune, Macro F1 0.939)

Browse files
Files changed (6) hide show
  1. README.md +132 -0
  2. model.onnx +3 -0
  3. special_tokens_map.json +37 -0
  4. tokenizer.json +0 -0
  5. tokenizer_config.json +60 -0
  6. vocab.txt +0 -0
README.md ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model Card — MovieSentiment DistilBERT INT8
2
+
3
+ Following the [Mitchell et al. 2019 Model Card](https://arxiv.org/abs/1810.03993) template.
4
+
5
+ ## Model details
6
+
7
+ - **Model name**: `moviesentiment-classifier`
8
+ - **Architecture**: DistilBERT-base-uncased (66M parameters), HuggingFace `distilbert-base-uncased`
9
+ - **Head**: 2-class linear classifier on `[CLS]` pooled output
10
+ - **Quantization**: ONNX dynamic INT8 (8-bit weights, FP32 activations) via `optimum.onnxruntime.ORTQuantizer` with `AutoQuantizationConfig.avx512_vnni(is_static=False)`
11
+ - **Size**: 64 MB (down from 256 MB FP32; 4× compression)
12
+ - **Framework versions**: `transformers>=4.46`, `optimum[onnxruntime]>=1.17`, `onnxruntime>=1.17`
13
+ - **Owner**: Soumya Sarkar — `github.com/Cryptic2-0`
14
+ - **License**: MIT
15
+ - **Date**: 2026-05-26 (last retrained from main)
16
+ - **Git SHA**: returned at `GET /version`
17
+
18
+ ## Intended use
19
+
20
+ **Primary use**: classify English IMDb-style movie reviews into positive/negative sentiment.
21
+
22
+ **Primary users**: this project is a portfolio MLOps demonstration. The API is publicly reachable but is not intended for downstream products.
23
+
24
+ **Out-of-scope**:
25
+ - Non-English text — see *Quantitative analyses* below for the cliff-edge accuracy drop.
26
+ - Product, food, news, or any non-movie domain — not evaluated; results unreliable.
27
+ - Long-form text > 5000 characters — truncated at 512 tokens.
28
+ - Multi-class sentiment (neutral, mixed, sarcastic) — binary output only.
29
+ - Any safety-, health-, or finance-sensitive decision.
30
+
31
+ ## Factors
32
+
33
+ | Factor | Buckets evaluated |
34
+ |---|---|
35
+ | Review length | short (<200 chars), medium (200–600), long (>600) |
36
+ | Sentiment intensity | strong (>0.9 confidence), weak (0.55–0.7) |
37
+ | Genre signal | comedy / drama / action / horror (from movie metadata, where available) |
38
+ | Named entities | reviews mentioning specific actors / directors |
39
+ | Sarcasm proxy | reviews flagged by a heuristic sarcasm detector (manual seed list) |
40
+
41
+ ## Metrics
42
+
43
+ Test set: held-out 5,000 reviews from IMDb 50K.
44
+
45
+ | Metric | DistilBERT INT8 | TF-IDF + LR (baseline) |
46
+ |---|---|---|
47
+ | Macro F1 | **0.939** | 0.904 |
48
+ | Accuracy | 0.940 | 0.905 |
49
+ | ROC AUC | 0.984 | 0.962 |
50
+ | Latency p50 | 6.8 ms | <5 ms |
51
+ | Latency p99 | 14 ms | <8 ms |
52
+ | Size on disk | 64 MB | 18 MB |
53
+
54
+ CI for F1 (bootstrap, 1000 resamples): **[0.934, 0.944]**.
55
+
56
+ ## Quantitative analyses
57
+
58
+ ### Performance by review length
59
+
60
+ | Length bucket | F1 | n |
61
+ |---|---|---|
62
+ | Short (<200 chars) | 0.917 | 1,402 |
63
+ | Medium (200–600) | 0.940 | 2,217 |
64
+ | Long (>600) | 0.951 | 1,381 |
65
+
66
+ Model is weakest on very short reviews (terse one-liners with weak signal). This matches intuition — fewer tokens, fewer keywords.
67
+
68
+ ### Performance by confidence bucket
69
+
70
+ | Confidence | Accuracy | Coverage |
71
+ |---|---|---|
72
+ | >0.95 | 0.984 | 78% of test |
73
+ | 0.85–0.95 | 0.913 | 14% |
74
+ | 0.70–0.85 | 0.760 | 6% |
75
+ | <0.70 | 0.512 | 2% |
76
+
77
+ The model is **well-calibrated above 0.85** — high-confidence predictions are reliable. The thin (~2%) low-confidence band is essentially chance-level and should be treated as "model declined to answer."
78
+
79
+ ### Sarcasm slice
80
+
81
+ Reviews flagged by the heuristic sarcasm seed list (~340 examples): **F1 0.823**, vs 0.939 overall — a 12-point absolute drop. This is the most prominent failure mode and is documented on the live demo card.
82
+
83
+ ### Non-English
84
+
85
+ Spanish translations of the test set (machine-translated via NLLB): **F1 0.51** (chance is 0.50). Treat any non-English input as untrusted. The frontend does not gate on language; this is intentional (interview material), but a production deployment should.
86
+
87
+ ### Per-genre slice (live IMDb scrape, 2026-05-29)
88
+
89
+ Evaluated on a fresh **live IMDb GraphQL scrape** of the 10 movie IDs in `params.yaml`. 2,525 reviews retrieved, sorted by `HELPFULNESS_SCORE` (IMDb's default — biases toward positive reviews). Each movie's primary genre per IMDb's listing. Source data at `data/processed/live_with_genre.parquet`; raw numbers at `metrics/per_genre_f1.json`.
90
+
91
+ | Genre | n | Pos share | Accuracy | Macro F1 | F1 (positive) |
92
+ |---|---|---|---|---|---|
93
+ | Adventure | 232 | 0.918 | 0.940 | **0.848** | 0.966 |
94
+ | Crime | 786 | 0.934 | 0.950 | **0.840** | 0.973 |
95
+ | Animation | 238 | 0.929 | 0.945 | **0.834** | 0.970 |
96
+ | Drama | 764 | 0.919 | 0.937 | **0.827** | 0.965 |
97
+ | Action | 505 | 0.949 | 0.935 | **0.779** | 0.964 |
98
+
99
+ **Overall**: n=2,525, accuracy 0.942, macro F1 **0.825**.
100
+
101
+ **Key caveat — class skew**: the live test set is **~93% positive** because IMDb's helpfulness-sort top-loads positive reviews. On the original balanced HF test set (5,000 examples, 50/50) macro F1 is 0.939; the live slice's lower macro F1 (0.825) reflects the imbalance, not a model regression. Binary F1 on the majority (positive) class stays in the 0.964–0.973 band across all genres, consistent with the original eval.
102
+
103
+ **What the slice does and doesn't tell us**: Action has the weakest macro F1 (0.779), which means the model is most likely to miss negative reviews of Action films. Adventure and Crime hold the strongest macro F1, but those buckets also have less class skew. A genre-balanced re-scrape — sort by date instead of helpfulness — would be the next step to disentangle "genre effect" from "class-balance effect". Documented as a follow-up in `docs/future_improvements.md`.
104
+
105
+ ## Ethical considerations
106
+
107
+ - **Training data is movie-only**. Generalization to other "subjective text" domains is not implied.
108
+ - **No demographic factors are inferred or stored**. The pipeline does not capture reviewer identity.
109
+ - **Reservoir-sampled production inputs** are stored under `data/production/recent.parquet` for drift detection. The current sampler is in-memory and flushed every 100 inserts; users who post personally identifying text are subject to this storage for the lifetime of the running container.
110
+ - **No human-in-the-loop**. Predictions are returned directly.
111
+
112
+ ## Caveats and recommendations
113
+
114
+ 1. Use confidence to gate downstream actions — anything below 0.70 should be surfaced as "uncertain", not classified.
115
+ 2. The model was trained on IMDb 50K which is **balanced** (50/50 positive/negative). Real-world review distributions skew positive (~70/30). Expect more false negatives at deployment time.
116
+ 3. If you need higher recall on negative reviews (e.g., for moderation), retrain with a re-weighted loss or threshold-tune via the bundled `eval/metrics.py` PR-curve helpers.
117
+ 4. Retraining loop closes when Evidently drift > 0.30 (`monitor/drift.py`) — fires a GH Actions workflow to scrape, retrain (SageMaker spot + LoRA), re-export ONNX, redeploy.
118
+
119
+ ## Environmental impact
120
+
121
+ Trained once on `ml.g4dn.xlarge` (T4 GPU) for ~25 minutes. Estimated energy: ~0.05 kWh. With CodeCarbon's default Australia grid factor (~0.66 kgCO2/kWh) that's ~33g CO2 per training run. The retraining loop runs at most weekly.
122
+
123
+ Inference: ARM Graviton Fargate, 0.25 vCPU, 1 GB RAM, single task. Steady-state ~3 W. Per-prediction CO2 cost is negligible at portfolio traffic.
124
+
125
+ ## Where to find more
126
+
127
+ - Source: `github.com/Cryptic2-0/Project`
128
+ - Live API contract: `OpenAPI /docs` on the running Fargate task
129
+ - Drift reports: `docs/drift_reports/` (HTML, generated by `python -m moviesentiment.cli drift`)
130
+ - Latency benchmarks: `docs/benchmarks.md`
131
+ - Load test report: `docs/load_test_report.html`
132
+ - Datasheet for the training data: `docs/datasheet.md`
model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b39dadeb7d7de8d44efcc647adfaa64fc3bf98dee32e23755b8ac0b5d874b9d8
3
+ size 67348137
special_tokens_map.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cls_token": {
3
+ "content": "[CLS]",
4
+ "lstrip": false,
5
+ "normalized": false,
6
+ "rstrip": false,
7
+ "single_word": false
8
+ },
9
+ "mask_token": {
10
+ "content": "[MASK]",
11
+ "lstrip": false,
12
+ "normalized": false,
13
+ "rstrip": false,
14
+ "single_word": false
15
+ },
16
+ "pad_token": {
17
+ "content": "[PAD]",
18
+ "lstrip": false,
19
+ "normalized": false,
20
+ "rstrip": false,
21
+ "single_word": false
22
+ },
23
+ "sep_token": {
24
+ "content": "[SEP]",
25
+ "lstrip": false,
26
+ "normalized": false,
27
+ "rstrip": false,
28
+ "single_word": false
29
+ },
30
+ "unk_token": {
31
+ "content": "[UNK]",
32
+ "lstrip": false,
33
+ "normalized": false,
34
+ "rstrip": false,
35
+ "single_word": false
36
+ }
37
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "[PAD]",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "100": {
12
+ "content": "[UNK]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "101": {
20
+ "content": "[CLS]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "102": {
28
+ "content": "[SEP]",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "103": {
36
+ "content": "[MASK]",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ }
43
+ },
44
+ "clean_up_tokenization_spaces": false,
45
+ "cls_token": "[CLS]",
46
+ "do_lower_case": true,
47
+ "extra_special_tokens": {},
48
+ "mask_token": "[MASK]",
49
+ "max_length": 512,
50
+ "model_max_length": 512,
51
+ "pad_token": "[PAD]",
52
+ "sep_token": "[SEP]",
53
+ "stride": 0,
54
+ "strip_accents": null,
55
+ "tokenize_chinese_chars": true,
56
+ "tokenizer_class": "DistilBertTokenizer",
57
+ "truncation_side": "right",
58
+ "truncation_strategy": "longest_first",
59
+ "unk_token": "[UNK]"
60
+ }
vocab.txt ADDED
The diff for this file is too large to render. See raw diff