config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_cross_attention": false,
3
+ "architectures": [
4
+ "BertForSequenceClassification"
5
+ ],
6
+ "attention_probs_dropout_prob": 0.1,
7
+ "bos_token_id": null,
8
+ "classifier_dropout": null,
9
+ "dtype": "float32",
10
+ "eos_token_id": null,
11
+ "gradient_checkpointing": false,
12
+ "hidden_act": "gelu",
13
+ "hidden_dropout_prob": 0.1,
14
+ "hidden_size": 768,
15
+ "id2label": {
16
+ "0": "LABEL_0",
17
+ "1": "LABEL_1",
18
+ "2": "LABEL_2",
19
+ "3": "LABEL_3",
20
+ "4": "LABEL_4"
21
+ },
22
+ "initializer_range": 0.02,
23
+ "intermediate_size": 3072,
24
+ "is_decoder": false,
25
+ "label2id": {
26
+ "LABEL_0": 0,
27
+ "LABEL_1": 1,
28
+ "LABEL_2": 2,
29
+ "LABEL_3": 3,
30
+ "LABEL_4": 4
31
+ },
32
+ "layer_norm_eps": 1e-12,
33
+ "max_position_embeddings": 512,
34
+ "model_type": "bert",
35
+ "num_attention_heads": 12,
36
+ "num_hidden_layers": 12,
37
+ "pad_token_id": 0,
38
+ "position_embedding_type": "absolute",
39
+ "problem_type": "single_label_classification",
40
+ "tie_word_embeddings": true,
41
+ "transformers_version": "5.0.0",
42
+ "type_vocab_size": 2,
43
+ "use_cache": false,
44
+ "vocab_size": 30522
45
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c226235c9e11bb8b1d7b3d45843783e71c75dea19495ef08e0978bff7b831353
3
+ size 437967852
test_inference.ipynb ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "intro",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Abuse Model Inference Test\n",
9
+ "\n",
10
+ "This notebook loads the saved production abuse model and its local artifacts from this folder, then runs inference on a calibrated set of example sentences. It performs inference only.\n",
11
+ "\n",
12
+ "Note: this checkpoint is a subtype classifier, not a generic toxicity detector. It predicts `Age`, `Ethnicity`, `Gender`, `Religion`, or `Not Cyberbullying`, and it responds most strongly when the text explicitly names the targeted group."
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "markdown",
17
+ "id": "load-notes",
18
+ "metadata": {},
19
+ "source": [
20
+ "## 1. Load the model artifacts\n",
21
+ "\n",
22
+ "The code below reads the local model config, tokenizer, weights, and label mapping directly from the current folder."
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": 1,
28
+ "id": "load-code",
29
+ "metadata": {},
30
+ "outputs": [
31
+ {
32
+ "data": {
33
+ "application/vnd.jupyter.widget-view+json": {
34
+ "model_id": "37774b8a0e1f4d959c6e6a3293e0e766",
35
+ "version_major": 2,
36
+ "version_minor": 0
37
+ },
38
+ "text/plain": [
39
+ "Loading weights: 0%| | 0/201 [00:00<?, ?it/s]"
40
+ ]
41
+ },
42
+ "metadata": {},
43
+ "output_type": "display_data"
44
+ },
45
+ {
46
+ "name": "stdout",
47
+ "output_type": "stream",
48
+ "text": [
49
+ "Model directory: /Users/georgijkutivadze/projects/mago-text-scoring/app/app-models/prod-model-abuse\n",
50
+ "Device: cpu\n",
51
+ "Architecture: BertForSequenceClassification\n",
52
+ "Tokenizer: BertTokenizer\n",
53
+ "Max sequence length: 256\n",
54
+ "Artifacts: ['config.json', 'model-config.yaml', 'model.safetensors', 'test_inference.ipynb', 'tokenizer.json', 'tokenizer_config.json', 'training_args.bin']\n",
55
+ "Labels: ['Age', 'Ethnicity', 'Gender', 'Not Cyberbullying', 'Religion']\n"
56
+ ]
57
+ }
58
+ ],
59
+ "source": [
60
+ "from pathlib import Path\n",
61
+ "import os\n",
62
+ "\n",
63
+ "import pandas as pd\n",
64
+ "import torch\n",
65
+ "import yaml\n",
66
+ "from transformers import AutoConfig, AutoModelForSequenceClassification, AutoTokenizer\n",
67
+ "from transformers.utils import logging as hf_logging\n",
68
+ "\n",
69
+ "os.environ[\"HF_HUB_DISABLE_PROGRESS_BARS\"] = \"1\"\n",
70
+ "hf_logging.set_verbosity_error()\n",
71
+ "\n",
72
+ "MODEL_DIR = Path(\".\").resolve()\n",
73
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
74
+ "\n",
75
+ "with (MODEL_DIR / \"model-config.yaml\").open(\"r\", encoding=\"utf-8\") as handle:\n",
76
+ " model_config = yaml.safe_load(handle)\n",
77
+ "\n",
78
+ "runtime_config = model_config.get(\"runtime\", {})\n",
79
+ "MAX_LEN = int(runtime_config.get(\"max_sequence_length\", 256))\n",
80
+ "PADDING = runtime_config.get(\"padding\", False)\n",
81
+ "\n",
82
+ "hf_config = AutoConfig.from_pretrained(MODEL_DIR, local_files_only=True)\n",
83
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR, local_files_only=True)\n",
84
+ "model = AutoModelForSequenceClassification.from_pretrained(\n",
85
+ " MODEL_DIR,\n",
86
+ " config=hf_config,\n",
87
+ " local_files_only=True,\n",
88
+ ")\n",
89
+ "model.to(device)\n",
90
+ "model.eval()\n",
91
+ "\n",
92
+ "yaml_labels = model_config.get(\"labels\", {}).get(\"classes\", [])\n",
93
+ "label_names = [\n",
94
+ " item.get(\"display_name\") or item.get(\"name\") or str(item.get(\"id\"))\n",
95
+ " for item in sorted(yaml_labels, key=lambda item: item[\"id\"])\n",
96
+ "]\n",
97
+ "if not label_names:\n",
98
+ " label_names = [hf_config.id2label[idx] for idx in sorted(hf_config.id2label)]\n",
99
+ "\n",
100
+ "artifact_files = sorted(path.name for path in MODEL_DIR.iterdir() if path.is_file())\n",
101
+ "\n",
102
+ "print(f\"Model directory: {MODEL_DIR}\")\n",
103
+ "print(f\"Device: {device}\")\n",
104
+ "print(f\"Architecture: {model.__class__.__name__}\")\n",
105
+ "print(f\"Tokenizer: {tokenizer.__class__.__name__}\")\n",
106
+ "print(f\"Max sequence length: {MAX_LEN}\")\n",
107
+ "print(f\"Artifacts: {artifact_files}\")\n",
108
+ "print(f\"Labels: {label_names}\")\n"
109
+ ]
110
+ },
111
+ {
112
+ "cell_type": "markdown",
113
+ "id": "helper-notes",
114
+ "metadata": {},
115
+ "source": [
116
+ "## 2. Define an inference helper\n",
117
+ "\n",
118
+ "This helper mirrors the production runtime settings, tokenizes input text, runs the model in evaluation mode, applies softmax, and returns the predicted label together with confidence, top-3 labels, and the full probability distribution."
119
+ ]
120
+ },
121
+ {
122
+ "cell_type": "code",
123
+ "execution_count": 2,
124
+ "id": "helper-code",
125
+ "metadata": {},
126
+ "outputs": [],
127
+ "source": [
128
+ "@torch.inference_mode()\n",
129
+ "def predict_texts(texts):\n",
130
+ " encoded = tokenizer(\n",
131
+ " texts,\n",
132
+ " return_tensors=\"pt\",\n",
133
+ " truncation=bool(runtime_config.get(\"truncation\", True)),\n",
134
+ " padding=\"max_length\" if PADDING else False,\n",
135
+ " max_length=MAX_LEN,\n",
136
+ " )\n",
137
+ " encoded = {name: tensor.to(device) for name, tensor in encoded.items()}\n",
138
+ " logits = model(**encoded).logits\n",
139
+ " probabilities = torch.softmax(logits, dim=-1).cpu().tolist()\n",
140
+ "\n",
141
+ " rows = []\n",
142
+ " for text, input_ids, probs in zip(texts, encoded[\"input_ids\"].cpu().tolist(), probabilities):\n",
143
+ " ranked_indices = sorted(range(len(probs)), key=probs.__getitem__, reverse=True)\n",
144
+ " pred_idx = ranked_indices[0]\n",
145
+ " rows.append(\n",
146
+ " {\n",
147
+ " \"text\": text,\n",
148
+ " \"predicted_label\": label_names[pred_idx],\n",
149
+ " \"confidence\": round(probs[pred_idx], 4),\n",
150
+ " \"token_count\": sum(1 for token_id in input_ids if token_id != tokenizer.pad_token_id),\n",
151
+ " \"top_3\": [\n",
152
+ " f\"{label_names[idx]} ({probs[idx]:.4f})\"\n",
153
+ " for idx in ranked_indices[:3]\n",
154
+ " ],\n",
155
+ " \"probabilities\": {\n",
156
+ " label: round(score, 4)\n",
157
+ " for label, score in zip(label_names, probs)\n",
158
+ " },\n",
159
+ " }\n",
160
+ " )\n",
161
+ "\n",
162
+ " return pd.DataFrame(rows)\n",
163
+ "\n",
164
+ "pd.set_option(\"display.max_colwidth\", 140)\n"
165
+ ]
166
+ },
167
+ {
168
+ "cell_type": "markdown",
169
+ "id": "examples-notes",
170
+ "metadata": {},
171
+ "source": [
172
+ "## 3. Run inference on calibrated example sentences\n",
173
+ "\n",
174
+ "The first notebook version used generic insults and indirect phrasing, which made the checkpoint look inconsistent. The examples below are stronger subtype-targeted probes and should exercise all five labels much more reliably."
175
+ ]
176
+ },
177
+ {
178
+ "cell_type": "code",
179
+ "execution_count": 6,
180
+ "id": "examples-code",
181
+ "metadata": {},
182
+ "outputs": [
183
+ {
184
+ "data": {
185
+ "text/html": [
186
+ "<div>\n",
187
+ "<style scoped>\n",
188
+ " .dataframe tbody tr th:only-of-type {\n",
189
+ " vertical-align: middle;\n",
190
+ " }\n",
191
+ "\n",
192
+ " .dataframe tbody tr th {\n",
193
+ " vertical-align: top;\n",
194
+ " }\n",
195
+ "\n",
196
+ " .dataframe thead th {\n",
197
+ " text-align: right;\n",
198
+ " }\n",
199
+ "</style>\n",
200
+ "<table border=\"1\" class=\"dataframe\">\n",
201
+ " <thead>\n",
202
+ " <tr style=\"text-align: right;\">\n",
203
+ " <th></th>\n",
204
+ " <th>text</th>\n",
205
+ " <th>predicted_label</th>\n",
206
+ " <th>confidence</th>\n",
207
+ " <th>token_count</th>\n",
208
+ " <th>top_3</th>\n",
209
+ " <th>probabilities</th>\n",
210
+ " </tr>\n",
211
+ " </thead>\n",
212
+ " <tbody>\n",
213
+ " <tr>\n",
214
+ " <th>0</th>\n",
215
+ " <td>go do your homework you stupid child</td>\n",
216
+ " <td>Age</td>\n",
217
+ " <td>0.9971</td>\n",
218
+ " <td>9</td>\n",
219
+ " <td>[Age (0.9971), Not Cyberbullying (0.0014), Ethnicity (0.0009)]</td>\n",
220
+ " <td>{'Age': 0.9971, 'Ethnicity': 0.0009, 'Gender': 0.0003, 'Not Cyberbullying': 0.0014, 'Religion': 0.0003}</td>\n",
221
+ " </tr>\n",
222
+ " <tr>\n",
223
+ " <th>1</th>\n",
224
+ " <td>immigrants like you ruin everything in this country</td>\n",
225
+ " <td>Ethnicity</td>\n",
226
+ " <td>0.9979</td>\n",
227
+ " <td>10</td>\n",
228
+ " <td>[Ethnicity (0.9979), Not Cyberbullying (0.0013), Religion (0.0007)]</td>\n",
229
+ " <td>{'Age': 0.0, 'Ethnicity': 0.9979, 'Gender': 0.0001, 'Not Cyberbullying': 0.0013, 'Religion': 0.0007}</td>\n",
230
+ " </tr>\n",
231
+ " <tr>\n",
232
+ " <th>2</th>\n",
233
+ " <td>women are stupid and should stay quiet</td>\n",
234
+ " <td>Gender</td>\n",
235
+ " <td>0.9999</td>\n",
236
+ " <td>9</td>\n",
237
+ " <td>[Gender (0.9999), Not Cyberbullying (0.0001), Ethnicity (0.0000)]</td>\n",
238
+ " <td>{'Age': 0.0, 'Ethnicity': 0.0, 'Gender': 0.9999, 'Not Cyberbullying': 0.0001, 'Religion': 0.0}</td>\n",
239
+ " </tr>\n",
240
+ " <tr>\n",
241
+ " <th>3</th>\n",
242
+ " <td>muslims like you are the problem with this country</td>\n",
243
+ " <td>Religion</td>\n",
244
+ " <td>1.0000</td>\n",
245
+ " <td>11</td>\n",
246
+ " <td>[Religion (1.0000), Not Cyberbullying (0.0000), Age (0.0000)]</td>\n",
247
+ " <td>{'Age': 0.0, 'Ethnicity': 0.0, 'Gender': 0.0, 'Not Cyberbullying': 0.0, 'Religion': 1.0}</td>\n",
248
+ " </tr>\n",
249
+ " <tr>\n",
250
+ " <th>4</th>\n",
251
+ " <td>the project update was sent to the team this morning</td>\n",
252
+ " <td>Not Cyberbullying</td>\n",
253
+ " <td>0.9999</td>\n",
254
+ " <td>12</td>\n",
255
+ " <td>[Not Cyberbullying (0.9999), Gender (0.0000), Religion (0.0000)]</td>\n",
256
+ " <td>{'Age': 0.0, 'Ethnicity': 0.0, 'Gender': 0.0, 'Not Cyberbullying': 0.9999, 'Religion': 0.0}</td>\n",
257
+ " </tr>\n",
258
+ " </tbody>\n",
259
+ "</table>\n",
260
+ "</div>"
261
+ ],
262
+ "text/plain": [
263
+ " text predicted_label \\\n",
264
+ "0 go do your homework you stupid child Age \n",
265
+ "1 immigrants like you ruin everything in this country Ethnicity \n",
266
+ "2 women are stupid and should stay quiet Gender \n",
267
+ "3 muslims like you are the problem with this country Religion \n",
268
+ "4 the project update was sent to the team this morning Not Cyberbullying \n",
269
+ "\n",
270
+ " confidence token_count \\\n",
271
+ "0 0.9971 9 \n",
272
+ "1 0.9979 10 \n",
273
+ "2 0.9999 9 \n",
274
+ "3 1.0000 11 \n",
275
+ "4 0.9999 12 \n",
276
+ "\n",
277
+ " top_3 \\\n",
278
+ "0 [Age (0.9971), Not Cyberbullying (0.0014), Ethnicity (0.0009)] \n",
279
+ "1 [Ethnicity (0.9979), Not Cyberbullying (0.0013), Religion (0.0007)] \n",
280
+ "2 [Gender (0.9999), Not Cyberbullying (0.0001), Ethnicity (0.0000)] \n",
281
+ "3 [Religion (1.0000), Not Cyberbullying (0.0000), Age (0.0000)] \n",
282
+ "4 [Not Cyberbullying (0.9999), Gender (0.0000), Religion (0.0000)] \n",
283
+ "\n",
284
+ " probabilities \n",
285
+ "0 {'Age': 0.9971, 'Ethnicity': 0.0009, 'Gender': 0.0003, 'Not Cyberbullying': 0.0014, 'Religion': 0.0003} \n",
286
+ "1 {'Age': 0.0, 'Ethnicity': 0.9979, 'Gender': 0.0001, 'Not Cyberbullying': 0.0013, 'Religion': 0.0007} \n",
287
+ "2 {'Age': 0.0, 'Ethnicity': 0.0, 'Gender': 0.9999, 'Not Cyberbullying': 0.0001, 'Religion': 0.0} \n",
288
+ "3 {'Age': 0.0, 'Ethnicity': 0.0, 'Gender': 0.0, 'Not Cyberbullying': 0.0, 'Religion': 1.0} \n",
289
+ "4 {'Age': 0.0, 'Ethnicity': 0.0, 'Gender': 0.0, 'Not Cyberbullying': 0.9999, 'Religion': 0.0} "
290
+ ]
291
+ },
292
+ "execution_count": 6,
293
+ "metadata": {},
294
+ "output_type": "execute_result"
295
+ }
296
+ ],
297
+ "source": [
298
+ "example_sentences = [\n",
299
+ " \"go do your homework you stupid child\",\n",
300
+ " \"immigrants like you ruin everything in this country\",\n",
301
+ " \"women are stupid and should stay quiet\",\n",
302
+ " \"muslims like you are the problem with this country\",\n",
303
+ " \"the project update was sent to the team this morning\",\n",
304
+ "]\n",
305
+ "\n",
306
+ "results = predict_texts(example_sentences)\n",
307
+ "results\n"
308
+ ]
309
+ },
310
+ {
311
+ "cell_type": "code",
312
+ "execution_count": 4,
313
+ "id": "diagnostic-code",
314
+ "metadata": {},
315
+ "outputs": [
316
+ {
317
+ "name": "stdout",
318
+ "output_type": "stream",
319
+ "text": [
320
+ "Diagnostic: sample bank produced 5 distinct labels across 5 examples.\n"
321
+ ]
322
+ }
323
+ ],
324
+ "source": [
325
+ "distinct_labels = results[\"predicted_label\"].nunique()\n",
326
+ "if distinct_labels == 1:\n",
327
+ " print(\n",
328
+ " \"Warning: all sample predictions collapsed to one label. \"\n",
329
+ " \"That usually means the sample bank is too weak for this checkpoint or the export should be re-verified.\"\n",
330
+ " )\n",
331
+ "else:\n",
332
+ " print(f\"Diagnostic: sample bank produced {distinct_labels} distinct labels across {len(results)} examples.\")\n"
333
+ ]
334
+ }
335
+ ],
336
+ "metadata": {
337
+ "kernelspec": {
338
+ "display_name": "Python 3 (ipykernel)",
339
+ "language": "python",
340
+ "name": "python3"
341
+ },
342
+ "language_info": {
343
+ "codemirror_mode": {
344
+ "name": "ipython",
345
+ "version": 3
346
+ },
347
+ "file_extension": ".py",
348
+ "mimetype": "text/x-python",
349
+ "name": "python",
350
+ "nbconvert_exporter": "python",
351
+ "pygments_lexer": "ipython3",
352
+ "version": "3.11.11"
353
+ }
354
+ },
355
+ "nbformat": 4,
356
+ "nbformat_minor": 5
357
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "cls_token": "[CLS]",
4
+ "do_lower_case": true,
5
+ "is_local": false,
6
+ "mask_token": "[MASK]",
7
+ "model_max_length": 512,
8
+ "pad_token": "[PAD]",
9
+ "sep_token": "[SEP]",
10
+ "strip_accents": null,
11
+ "tokenize_chinese_chars": true,
12
+ "tokenizer_class": "BertTokenizer",
13
+ "unk_token": "[UNK]"
14
+ }
training_args.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dc0513f2640d90a5a8eeb288dbfef0a4bc3ddb6aa614bbd1d3090e5d0875c194
3
+ size 5201