dar1bi commited on
Commit
9880dcf
·
verified ·
1 Parent(s): 1804d0d

Add fine-tuned DistilBERT with per-class thresholds

Browse files
Files changed (6) hide show
  1. README.md +106 -0
  2. config.json +44 -0
  3. inference_config.json +22 -0
  4. model.safetensors +3 -0
  5. tokenizer.json +0 -0
  6. tokenizer_config.json +15 -0
README.md ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - multilingual
4
+ - en
5
+ - pt
6
+ - fr
7
+ - es
8
+ - uk
9
+ - de
10
+ license: apache-2.0
11
+ base_model: distilbert-base-multilingual-cased
12
+ pipeline_tag: text-classification
13
+ tags:
14
+ - multi-label-classification
15
+ - customer-feedback
16
+ - churn
17
+ - cloud-gaming
18
+ ---
19
+
20
+ # Multi-label classification of cloud-gaming churn feedback
21
+
22
+ Fine-tuned `distilbert-base-multilingual-cased` that tags the **technical issue** a user complains
23
+ about in the free-text comment left when cancelling a cloud-gaming subscription. A single comment
24
+ may describe several problems at once, so this is a **multi-label** task over 6 classes.
25
+
26
+ Comments are short (median 73 characters) and multilingual — no translation step is used.
27
+
28
+ ## Labels
29
+
30
+ | label | meaning |
31
+ |---|---|
32
+ | `ping_latency` | network delay, high ping, input lag |
33
+ | `frames_drop` | low FPS, stuttering, unstable stream |
34
+ | `unable_launch` | the game or service does not start |
35
+ | `mouse_keyboard_headset` | peripherals: mouse, keyboard, headset, controller |
36
+ | `game_bug` | a defect inside the game itself |
37
+ | `failed_save` | progress is lost or not saved |
38
+
39
+ ## Usage
40
+
41
+ The model outputs 6 independent sigmoid probabilities. **Do not use a 0.5 threshold** — per-class
42
+ thresholds were tuned on the validation set and are shipped in `inference_config.json`. They give
43
+ macro-F1 0.620 on the test set versus 0.581 with a plain 0.5 threshold.
44
+
45
+ ```python
46
+ import json
47
+
48
+ import torch
49
+ from huggingface_hub import hf_hub_download
50
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
51
+
52
+ REPO = "<your-username>/<your-model-name>"
53
+
54
+ tokenizer = AutoTokenizer.from_pretrained(REPO)
55
+ model = AutoModelForSequenceClassification.from_pretrained(REPO).eval()
56
+ config = json.load(open(hf_hub_download(REPO, "inference_config.json")))
57
+
58
+ text = "Constant micro stutter and very low fps in The Finals"
59
+ encoded = tokenizer(text, truncation=True, padding="max_length",
60
+ max_length=config["max_len"], return_tensors="pt")
61
+ with torch.no_grad():
62
+ probabilities = torch.sigmoid(model(**encoded).logits).numpy()[0]
63
+
64
+ labels = [label for label, probability, threshold
65
+ in zip(config["labels"], probabilities, config["thresholds"])
66
+ if probability >= threshold]
67
+ print(labels) # ['frames_drop']
68
+ ```
69
+
70
+ ## Training
71
+
72
+ - **Base model:** `distilbert-base-multilingual-cased`
73
+ - **Objective:** `BCEWithLogitsLoss` with `pos_weight` to compensate for class imbalance
74
+ - **Hyperparameters:** learning rate 8e-5 (grid over 2e-5 / 5e-5 / 8e-5), 5 epochs with best-epoch
75
+ selection by validation macro-F1, batch size 16, max sequence length 64
76
+ - **Split:** iterative-stratified multi-label split 70 / 15 / 15 — 2760 / 584 / 612 examples
77
+ - **Hardware:** Apple MPS, ~290 s per configuration
78
+
79
+ ## Evaluation
80
+
81
+ Primary metric is **macro-F1** — it weights rare classes equally with frequent ones.
82
+
83
+ | split | macro-F1 | micro-F1 | macro ROC-AUC | mAP |
84
+ |---|---|---|---|---|
85
+ | validation | 0.681 | 0.717 | 0.828 | 0.727 |
86
+ | test | 0.620 | 0.678 | 0.817 | 0.685 |
87
+
88
+ Per-class F1 on the test set: `ping_latency` 0.78, `unable_launch` 0.70,
89
+ `mouse_keyboard_headset` 0.67, `frames_drop` 0.62, `game_bug` 0.49, `failed_save` 0.46.
90
+
91
+ The model was selected on the validation set among seven approaches, from TF-IDF with classical
92
+ classifiers to a zero-shot LLM. A most-frequent-class baseline scores 0.119 macro-F1.
93
+
94
+ ## Limitations
95
+
96
+ - **Noisy labels.** Labels are self-reported by users in the cancellation form, so they are neither
97
+ consistent nor mutually exclusive. Part of what looks like model error is actually label noise.
98
+ - **Rare classes.** `game_bug` and `failed_save` are recognised in fewer than half of real cases —
99
+ they are infrequent in the data and semantically overlap with the other classes.
100
+ - **Very short texts.** Comments under 30 characters often carry too little signal.
101
+ - **Single split, single seed.** Differences of 0.01–0.02 macro-F1 are within noise.
102
+
103
+ ## Training data
104
+
105
+ Internal user feedback of a cloud-gaming service, collected at subscription cancellation
106
+ (4 616 records, 3 956 after cleaning). The dataset is confidential and is **not published**.
config.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "activation": "gelu",
3
+ "architectures": [
4
+ "DistilBertForSequenceClassification"
5
+ ],
6
+ "attention_dropout": 0.1,
7
+ "bos_token_id": null,
8
+ "dim": 768,
9
+ "dropout": 0.1,
10
+ "dtype": "float32",
11
+ "eos_token_id": null,
12
+ "hidden_dim": 3072,
13
+ "id2label": {
14
+ "0": "LABEL_0",
15
+ "1": "LABEL_1",
16
+ "2": "LABEL_2",
17
+ "3": "LABEL_3",
18
+ "4": "LABEL_4",
19
+ "5": "LABEL_5"
20
+ },
21
+ "initializer_range": 0.02,
22
+ "label2id": {
23
+ "LABEL_0": 0,
24
+ "LABEL_1": 1,
25
+ "LABEL_2": 2,
26
+ "LABEL_3": 3,
27
+ "LABEL_4": 4,
28
+ "LABEL_5": 5
29
+ },
30
+ "max_position_embeddings": 512,
31
+ "model_type": "distilbert",
32
+ "n_heads": 12,
33
+ "n_layers": 6,
34
+ "output_past": true,
35
+ "pad_token_id": 0,
36
+ "problem_type": "multi_label_classification",
37
+ "qa_dropout": 0.1,
38
+ "seq_classif_dropout": 0.2,
39
+ "sinusoidal_pos_embds": false,
40
+ "tie_weights_": true,
41
+ "tie_word_embeddings": true,
42
+ "transformers_version": "5.14.1",
43
+ "vocab_size": 119547
44
+ }
inference_config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "labels": [
3
+ "ping_latency",
4
+ "frames_drop",
5
+ "unable_launch",
6
+ "mouse_keyboard_headset",
7
+ "game_bug",
8
+ "failed_save"
9
+ ],
10
+ "thresholds": [
11
+ 0.45000000000000007,
12
+ 0.75,
13
+ 0.7000000000000001,
14
+ 0.525,
15
+ 0.55,
16
+ 0.8
17
+ ],
18
+ "max_len": 64,
19
+ "base_checkpoint": "distilbert-base-multilingual-cased",
20
+ "learning_rate": 8e-05,
21
+ "val_macro_f1": 0.6812
22
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a69642be835b984194f25f976a26c8609a42984f590790d5562147d5f3137e82
3
+ size 541329680
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "cls_token": "[CLS]",
4
+ "do_lower_case": false,
5
+ "is_local": false,
6
+ "local_files_only": false,
7
+ "mask_token": "[MASK]",
8
+ "model_max_length": 512,
9
+ "pad_token": "[PAD]",
10
+ "sep_token": "[SEP]",
11
+ "strip_accents": null,
12
+ "tokenize_chinese_chars": true,
13
+ "tokenizer_class": "BertTokenizer",
14
+ "unk_token": "[UNK]"
15
+ }